@opengeni/react 3.8.0-canary.0 → 3.8.0-canary.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/README.md +6 -0
- package/dist/chat.js +1 -1
- package/dist/chunk-5M6VTFJ5.js +171 -0
- package/dist/chunk-5M6VTFJ5.js.map +1 -0
- package/dist/{chunk-IOLVN74D.js → chunk-6J56T3MG.js} +15 -1
- package/dist/chunk-6J56T3MG.js.map +1 -0
- package/dist/{chunk-BQGIXRKV.js → chunk-7BWFU6H4.js} +22 -4
- package/dist/chunk-7BWFU6H4.js.map +1 -0
- package/dist/{chunk-3HIF5IFC.js → chunk-7W6PVVTK.js} +63 -18
- package/dist/chunk-7W6PVVTK.js.map +1 -0
- package/dist/{chunk-ZFOFY5ST.js → chunk-NLTS7JHI.js} +16 -31
- package/dist/chunk-NLTS7JHI.js.map +1 -0
- package/dist/chunk-NOSYBS2D.js +208 -0
- package/dist/chunk-NOSYBS2D.js.map +1 -0
- package/dist/{chunk-F2CFHN3Y.js → chunk-YAIPFO5D.js} +2 -1
- package/dist/chunk-YAIPFO5D.js.map +1 -0
- package/dist/chunk-YBM6CQRU.js +292 -0
- package/dist/chunk-YBM6CQRU.js.map +1 -0
- package/dist/chunk-ZYRGEKWS.js +216 -0
- package/dist/chunk-ZYRGEKWS.js.map +1 -0
- package/dist/components/composer.d.ts +4 -2
- package/dist/components/message-timeline.d.ts +9 -2
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +1 -1
- package/dist/components/timeline-annotations.d.ts +8 -2
- package/dist/composer.js +2 -2
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +6 -6
- package/dist/machines.js +1 -1
- package/dist/{queue-surface-implementation-XL2A6PBM.js → queue-surface-implementation-LK3S4BIW.js} +5 -2
- package/dist/{queue-surface-implementation-XL2A6PBM.js.map → queue-surface-implementation-LK3S4BIW.js.map} +1 -1
- package/dist/session-ui.js +8 -5
- package/dist/session.js +1 -1
- package/dist/timeline-annotation-markers-DRSTLEM6.js +165 -0
- package/dist/timeline-annotation-markers-DRSTLEM6.js.map +1 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js +236 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js.map +1 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js +262 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js.map +1 -0
- package/package.json +2 -2
- package/src/components/chat-composer.tsx +5 -2
- package/src/components/composer.tsx +40 -5
- package/src/components/copy-button.tsx +1 -0
- package/src/components/machines-dashboard.tsx +1 -16
- package/src/components/message-timeline.tsx +357 -297
- package/src/components/timeline-annotation-chrome.tsx +183 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +99 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +172 -128
- package/src/components/timeline-annotations.tsx +178 -45
- package/src/hooks/use-composer.ts +17 -0
- package/styles/compiled.css +86 -22
- package/dist/chunk-3HIF5IFC.js.map +0 -1
- package/dist/chunk-BQGIXRKV.js.map +0 -1
- package/dist/chunk-F2CFHN3Y.js.map +0 -1
- package/dist/chunk-IOLVN74D.js.map +0 -1
- package/dist/chunk-W3OUAH6M.js +0 -170
- package/dist/chunk-W3OUAH6M.js.map +0 -1
- package/dist/chunk-ZFOFY5ST.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
|
@@ -106,6 +106,7 @@ import {
|
|
|
106
106
|
useTurnSettleOpen,
|
|
107
107
|
} from "../timeline";
|
|
108
108
|
import { CopyHoverFrame } from "./copy-button";
|
|
109
|
+
import { TimelineAnnotationSourceRootContext } from "./timeline-annotation-reveal-context";
|
|
109
110
|
import { GeneratedVideoPlayer } from "./generated-video-player";
|
|
110
111
|
import {
|
|
111
112
|
MACHINE_INPUT_META,
|
|
@@ -118,10 +119,11 @@ import { SESSION_STATUS_META, StatusDot } from "./session-status";
|
|
|
118
119
|
import { TimelineComputeLabelProvider } from "../timeline/compute-label";
|
|
119
120
|
import { EntranceAnimationProvider, useEntranceAnimation } from "../timeline/entrance";
|
|
120
121
|
import { SeenActivityIdsProvider } from "../timeline/seen-activity-ids";
|
|
121
|
-
import {
|
|
122
|
+
import { TimelineAnnotationCards } from "./timeline-annotations";
|
|
122
123
|
import { TooltipProvider } from "./tooltip";
|
|
123
124
|
|
|
124
125
|
const TimelineAnnotationSelection = lazy(() => import("./timeline-annotation-selection"));
|
|
126
|
+
const TimelineAnnotationMarkers = lazy(() => import("./timeline-annotation-markers"));
|
|
125
127
|
|
|
126
128
|
export type MessageTimelineProps = {
|
|
127
129
|
/** Raw session events (projected internally) … */
|
|
@@ -130,6 +132,8 @@ export type MessageTimelineProps = {
|
|
|
130
132
|
items?: TimelineItem[] | undefined;
|
|
131
133
|
/** Current session status (reserved; tip "Working…" chrome removed for now). */
|
|
132
134
|
status?: SessionStatus | null | undefined;
|
|
135
|
+
/** Host-owned controls beside Copy and the timestamp on settled message rows. */
|
|
136
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
133
137
|
/** Plug a markdown renderer for message bodies (e.g. streamdown). */
|
|
134
138
|
renderMessageText?:
|
|
135
139
|
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
@@ -190,6 +194,10 @@ export type MessageTimelineProps = {
|
|
|
190
194
|
autoFollow?: boolean | undefined;
|
|
191
195
|
/** Capture a same-row text selection into the host's canonical composer draft. */
|
|
192
196
|
onAnnotate?: ((annotation: DraftTimelineAnnotation) => void) | undefined;
|
|
197
|
+
/** Composer draft quotes currently attached to the next send. */
|
|
198
|
+
draftAnnotations?: readonly DraftTimelineAnnotation[] | undefined;
|
|
199
|
+
/** Open the composer review list for one numbered draft badge. */
|
|
200
|
+
onDraftAnnotationSelect?: ((id: string) => void) | undefined;
|
|
193
201
|
/** Older durable history exists above the current window (see useSessionEvents). */
|
|
194
202
|
hasOlder?: boolean | undefined;
|
|
195
203
|
/** An older window is being fetched; shows the quiet top shimmer. */
|
|
@@ -389,6 +397,7 @@ export function MessageTimeline({
|
|
|
389
397
|
events,
|
|
390
398
|
items,
|
|
391
399
|
status: _status,
|
|
400
|
+
renderMessageActions,
|
|
392
401
|
renderMessageText,
|
|
393
402
|
onOpenSession,
|
|
394
403
|
onMemoryClick,
|
|
@@ -403,6 +412,8 @@ export function MessageTimeline({
|
|
|
403
412
|
turnSummary,
|
|
404
413
|
autoFollow = true,
|
|
405
414
|
onAnnotate,
|
|
415
|
+
draftAnnotations,
|
|
416
|
+
onDraftAnnotationSelect,
|
|
406
417
|
hasOlder = false,
|
|
407
418
|
loadingOlder = false,
|
|
408
419
|
onLoadOlder,
|
|
@@ -889,6 +900,7 @@ export function MessageTimeline({
|
|
|
889
900
|
() => ({
|
|
890
901
|
userMessageDisclosureContext,
|
|
891
902
|
behavior: {
|
|
903
|
+
renderMessageActions,
|
|
892
904
|
renderMessageText,
|
|
893
905
|
onOpenSession,
|
|
894
906
|
onMemoryClick,
|
|
@@ -908,6 +920,7 @@ export function MessageTimeline({
|
|
|
908
920
|
onMemoryClick,
|
|
909
921
|
onOpenSession,
|
|
910
922
|
onReconnect,
|
|
923
|
+
renderMessageActions,
|
|
911
924
|
renderMessageText,
|
|
912
925
|
resolveProviderLogo,
|
|
913
926
|
toolRegistry,
|
|
@@ -1785,303 +1798,316 @@ export function MessageTimeline({
|
|
|
1785
1798
|
<TimelineComputeLabelProvider value={computeLabel ?? null}>
|
|
1786
1799
|
<EntranceAnimationProvider value={false}>
|
|
1787
1800
|
<TooltipProvider delayDuration={400}>
|
|
1788
|
-
<
|
|
1789
|
-
{
|
|
1790
|
-
|
|
1791
|
-
<
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1801
|
+
<TimelineAnnotationSourceRootContext.Provider value={scrollRef}>
|
|
1802
|
+
<div className={cn("og-root relative flex min-h-0 flex-col", className)}>
|
|
1803
|
+
{onAnnotate ? (
|
|
1804
|
+
<Suspense fallback={null}>
|
|
1805
|
+
<TimelineAnnotationSelection
|
|
1806
|
+
rootRef={scrollRef}
|
|
1807
|
+
sources={annotationSources}
|
|
1808
|
+
onAnnotate={onAnnotate}
|
|
1809
|
+
/>
|
|
1810
|
+
</Suspense>
|
|
1811
|
+
) : null}
|
|
1812
|
+
{/* Pinned: anchoring off so the tip-follow camera owns the motion.
|
|
1799
1813
|
Unpinned: native scroll anchoring holds the reader's place. */}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
: null;
|
|
1813
|
-
}}
|
|
1814
|
-
onTouchMove={(event) => {
|
|
1815
|
-
const touch = event.touches[0];
|
|
1816
|
-
const previous = touchPositionRef.current;
|
|
1817
|
-
if (!touch || !previous || event.touches.length !== 1) {
|
|
1818
|
-
touchPositionRef.current = null;
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
const deltaX = previous.x - touch.clientX;
|
|
1822
|
-
const deltaY = previous.y - touch.clientY;
|
|
1823
|
-
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
1824
|
-
touchPositionRef.current = { x: touch.clientX, y: touch.clientY };
|
|
1825
|
-
onWheel({
|
|
1826
|
-
deltaX,
|
|
1827
|
-
deltaY,
|
|
1828
|
-
target: event.target,
|
|
1829
|
-
currentTarget: event.currentTarget,
|
|
1830
|
-
});
|
|
1831
|
-
}}
|
|
1832
|
-
onTouchEnd={() => {
|
|
1833
|
-
touchPositionRef.current = null;
|
|
1834
|
-
}}
|
|
1835
|
-
onTouchCancel={() => {
|
|
1836
|
-
touchPositionRef.current = null;
|
|
1837
|
-
}}
|
|
1838
|
-
onClickCapture={(event) => {
|
|
1839
|
-
const target =
|
|
1840
|
-
event.target instanceof Element
|
|
1841
|
-
? event.target.closest("button[aria-expanded]")
|
|
1814
|
+
<div
|
|
1815
|
+
ref={scrollRef}
|
|
1816
|
+
data-og-timeline-scroller=""
|
|
1817
|
+
data-og-bottom-follow={autoFollow && pinned && !hasNewer ? "true" : "false"}
|
|
1818
|
+
tabIndex={-1}
|
|
1819
|
+
onScroll={onScroll}
|
|
1820
|
+
onScrollEnd={onScrollEnd}
|
|
1821
|
+
onWheel={onWheel}
|
|
1822
|
+
onTouchStart={(event) => {
|
|
1823
|
+
const touch = event.touches.length === 1 ? event.touches[0] : undefined;
|
|
1824
|
+
touchPositionRef.current = touch
|
|
1825
|
+
? { x: touch.clientX, y: touch.clientY }
|
|
1842
1826
|
: null;
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1827
|
+
}}
|
|
1828
|
+
onTouchMove={(event) => {
|
|
1829
|
+
const touch = event.touches[0];
|
|
1830
|
+
const previous = touchPositionRef.current;
|
|
1831
|
+
if (!touch || !previous || event.touches.length !== 1) {
|
|
1832
|
+
touchPositionRef.current = null;
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
const deltaX = previous.x - touch.clientX;
|
|
1836
|
+
const deltaY = previous.y - touch.clientY;
|
|
1837
|
+
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
1838
|
+
touchPositionRef.current = { x: touch.clientX, y: touch.clientY };
|
|
1839
|
+
onWheel({
|
|
1840
|
+
deltaX,
|
|
1841
|
+
deltaY,
|
|
1842
|
+
target: event.target,
|
|
1843
|
+
currentTarget: event.currentTarget,
|
|
1844
|
+
});
|
|
1845
|
+
}}
|
|
1846
|
+
onTouchEnd={() => {
|
|
1847
|
+
touchPositionRef.current = null;
|
|
1848
|
+
}}
|
|
1849
|
+
onTouchCancel={() => {
|
|
1850
|
+
touchPositionRef.current = null;
|
|
1851
|
+
}}
|
|
1852
|
+
onClickCapture={(event) => {
|
|
1853
|
+
const target =
|
|
1854
|
+
event.target instanceof Element
|
|
1855
|
+
? event.target.closest("button[aria-expanded]")
|
|
1865
1856
|
: null;
|
|
1857
|
+
if (target) {
|
|
1858
|
+
releasePinFromReader();
|
|
1859
|
+
disclosureKeepsUnpinnedRef.current = true;
|
|
1860
|
+
}
|
|
1866
1861
|
}}
|
|
1862
|
+
onPointerDown={onPointerDown}
|
|
1863
|
+
onKeyDown={onKeyDown}
|
|
1864
|
+
style={groups.length > 0 && !revealed ? { visibility: "hidden" } : undefined}
|
|
1865
|
+
className={cn(
|
|
1866
|
+
// tabIndex=-1 is programmatic only — never paint a focus ring on
|
|
1867
|
+
// the whole scroller (click + Shift used to flash a blue outline).
|
|
1868
|
+
"min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 pt-16 pb-6 sm:px-6 outline-hidden",
|
|
1869
|
+
autoFollow && pinned && !hasNewer
|
|
1870
|
+
? "[overflow-anchor:none]"
|
|
1871
|
+
: "[overflow-anchor:auto]",
|
|
1872
|
+
)}
|
|
1867
1873
|
>
|
|
1868
|
-
<
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1874
|
+
<TimelineBeforeLayout
|
|
1875
|
+
capture={() => {
|
|
1876
|
+
readingAnchorRef.current =
|
|
1877
|
+
!pinnedRef.current && scrollRef.current
|
|
1878
|
+
? captureTimelineAnchor(scrollRef.current)
|
|
1879
|
+
: null;
|
|
1880
|
+
}}
|
|
1881
|
+
>
|
|
1882
|
+
<div className="relative mx-auto flex w-full max-w-3xl flex-col gap-5">
|
|
1883
|
+
<AnimatePresence>
|
|
1884
|
+
{loadingOlder ||
|
|
1885
|
+
loadingOldest ||
|
|
1886
|
+
(hasOlder && onJumpToStart && olderPrefetchArmed) ||
|
|
1887
|
+
underfillRetryReady ? (
|
|
1888
|
+
// Reserved top gutter: controls scroll with history and cannot
|
|
1889
|
+
// cover a disclosure. Visibility never changes content height.
|
|
1890
|
+
<motion.div
|
|
1891
|
+
initial={{ opacity: 0, y: -6 }}
|
|
1892
|
+
animate={{ opacity: 1, y: 0 }}
|
|
1893
|
+
exit={{ opacity: 0, y: -6 }}
|
|
1894
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
1895
|
+
data-og-loading-older=""
|
|
1896
|
+
aria-live="polite"
|
|
1897
|
+
className="pointer-events-none absolute inset-x-0 -top-11 z-10 flex justify-center"
|
|
1898
|
+
>
|
|
1899
|
+
{loadingOlder || loadingOldest ? (
|
|
1900
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
1901
|
+
<span className="og-shimmer-text">
|
|
1902
|
+
{loadingOldest
|
|
1903
|
+
? "Jumping to start…"
|
|
1904
|
+
: "Loading earlier activity…"}
|
|
1905
|
+
</span>
|
|
1891
1906
|
</span>
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
requestOlderIfUnderfilled(node, underfillSettledAttempt!);
|
|
1910
|
-
}
|
|
1911
|
-
return;
|
|
1912
|
-
}
|
|
1913
|
-
applyPinned(false);
|
|
1914
|
-
pendingJumpToStartRef.current = true;
|
|
1915
|
-
const seq = ++jumpToStartSeqRef.current;
|
|
1916
|
-
void Promise.resolve(onJumpToStart!()).then(
|
|
1917
|
-
() => {
|
|
1918
|
-
// The commit that swaps in the oldest window consumes
|
|
1919
|
-
// the flag against the new DOM; this write covers the
|
|
1920
|
-
// already-committed order and the no-window-change
|
|
1921
|
-
// case (jumping within the current window).
|
|
1922
|
-
const scroller = scrollRef.current ?? node;
|
|
1923
|
-
if (scroller) {
|
|
1924
|
-
scroller.scrollTop = 0;
|
|
1907
|
+
) : null}
|
|
1908
|
+
{hasOlder &&
|
|
1909
|
+
!loadingOlder &&
|
|
1910
|
+
!loadingOldest &&
|
|
1911
|
+
(underfillRetryReady || onJumpToStart) ? (
|
|
1912
|
+
<button
|
|
1913
|
+
type="button"
|
|
1914
|
+
data-og-retry={underfillRetryReady || undefined}
|
|
1915
|
+
data-og-jump-to-start={!underfillRetryReady || undefined}
|
|
1916
|
+
onClick={() => {
|
|
1917
|
+
const node = scrollRef.current;
|
|
1918
|
+
if (underfillRetryReady) {
|
|
1919
|
+
if (node && underfillRetryReadyRef.current) {
|
|
1920
|
+
// AnimatePresence retains this handler during
|
|
1921
|
+
// exit. Current authorization plus the exact
|
|
1922
|
+
// attempt check prevent stale dispatch.
|
|
1923
|
+
requestOlderIfUnderfilled(node, underfillSettledAttempt!);
|
|
1925
1924
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
applyPinned(false);
|
|
1928
|
+
pendingJumpToStartRef.current = true;
|
|
1929
|
+
const seq = ++jumpToStartSeqRef.current;
|
|
1930
|
+
void Promise.resolve(onJumpToStart!()).then(
|
|
1931
|
+
() => {
|
|
1932
|
+
// The commit that swaps in the oldest window consumes
|
|
1933
|
+
// the flag against the new DOM; this write covers the
|
|
1934
|
+
// already-committed order and the no-window-change
|
|
1935
|
+
// case (jumping within the current window).
|
|
1936
|
+
const scroller = scrollRef.current ?? node;
|
|
1937
|
+
if (scroller) {
|
|
1938
|
+
scroller.scrollTop = 0;
|
|
1939
|
+
}
|
|
1940
|
+
// A host may resolve without ever changing the
|
|
1941
|
+
// window (already on the oldest page). Any swap
|
|
1942
|
+
// commit runs its layout effect before the next
|
|
1943
|
+
// frame, so a flag still armed by then is the
|
|
1944
|
+
// no-change case — clear it, or a LATER prepend
|
|
1945
|
+
// would spuriously jump the reader to the top.
|
|
1946
|
+
requestFrame(() => {
|
|
1947
|
+
if (jumpToStartSeqRef.current === seq) {
|
|
1948
|
+
pendingJumpToStartRef.current = false;
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
},
|
|
1952
|
+
() => {
|
|
1933
1953
|
if (jumpToStartSeqRef.current === seq) {
|
|
1934
1954
|
pendingJumpToStartRef.current = false;
|
|
1935
1955
|
}
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1956
|
+
},
|
|
1957
|
+
);
|
|
1958
|
+
}}
|
|
1959
|
+
className="pointer-events-auto rounded-full border border-og-border px-3 py-1.5 text-og-control"
|
|
1960
|
+
>
|
|
1961
|
+
{underfillRetryReady
|
|
1962
|
+
? "Retry earlier activity"
|
|
1963
|
+
: "Jump to start"}
|
|
1964
|
+
</button>
|
|
1965
|
+
) : null}
|
|
1966
|
+
</motion.div>
|
|
1967
|
+
) : null}
|
|
1968
|
+
</AnimatePresence>
|
|
1969
|
+
{!groups.length
|
|
1970
|
+
? (emptyState ?? (
|
|
1971
|
+
<p className="py-10 text-center text-og-menu text-og-fg-subtle">
|
|
1972
|
+
No activity yet.
|
|
1973
|
+
</p>
|
|
1974
|
+
))
|
|
1975
|
+
: null}
|
|
1976
|
+
{hasOlder && olderPrefetchArmed ? (
|
|
1977
|
+
// Overlaid, not a layout row: mounting/unmounting the sentinel
|
|
1978
|
+
// must never shift content (that shift was itself a wobble).
|
|
1979
|
+
// End at the scroll origin above the pt-16 gutter so the
|
|
1980
|
+
// observer and scrollTop cooldown share the same 400px band.
|
|
1981
|
+
<div
|
|
1982
|
+
ref={topSentinelRef}
|
|
1983
|
+
data-og-top-sentinel=""
|
|
1984
|
+
data-og-timeline-chrome=""
|
|
1985
|
+
aria-hidden="true"
|
|
1986
|
+
className="pointer-events-none absolute inset-x-0 -top-16 h-px -translate-y-full"
|
|
1987
|
+
/>
|
|
1951
1988
|
) : null}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
data-og-timeline-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
groupKey={key}
|
|
1978
|
-
group={group}
|
|
1979
|
-
nextGroup={groups[index + 1]?.group}
|
|
1980
|
-
entranceEnabled={entranceEnabled}
|
|
1981
|
-
liveEntranceEnabled={
|
|
1982
|
-
group.kind === "activity" ? !bulkRender : undefined
|
|
1983
|
-
}
|
|
1984
|
-
context={timelineGroupEntryContext}
|
|
1989
|
+
{groups.map(({ group, key, entranceEnabled }, index) => {
|
|
1990
|
+
return (
|
|
1991
|
+
<TimelineGroupEntry
|
|
1992
|
+
key={key}
|
|
1993
|
+
groupKey={key}
|
|
1994
|
+
group={group}
|
|
1995
|
+
nextGroup={groups[index + 1]?.group}
|
|
1996
|
+
entranceEnabled={entranceEnabled}
|
|
1997
|
+
liveEntranceEnabled={
|
|
1998
|
+
group.kind === "activity" ? !bulkRender : undefined
|
|
1999
|
+
}
|
|
2000
|
+
context={timelineGroupEntryContext}
|
|
2001
|
+
/>
|
|
2002
|
+
);
|
|
2003
|
+
})}
|
|
2004
|
+
{groups.length > 0 && trailingState ? (
|
|
2005
|
+
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
2006
|
+
) : null}
|
|
2007
|
+
{hasNewer ? (
|
|
2008
|
+
<div
|
|
2009
|
+
ref={bottomSentinelRef}
|
|
2010
|
+
data-og-bottom-sentinel=""
|
|
2011
|
+
data-og-timeline-chrome=""
|
|
2012
|
+
aria-hidden="true"
|
|
2013
|
+
className="h-px w-full"
|
|
1985
2014
|
/>
|
|
1986
|
-
)
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
/>
|
|
1999
|
-
) : null}
|
|
2000
|
-
</div>
|
|
2001
|
-
</TimelineBeforeLayout>
|
|
2002
|
-
</div>
|
|
2003
|
-
|
|
2004
|
-
<AnimatePresence>
|
|
2005
|
-
{loadingNewer ? (
|
|
2006
|
-
<motion.div
|
|
2007
|
-
initial={{ opacity: 0, y: 6 }}
|
|
2008
|
-
animate={{ opacity: 1, y: 0 }}
|
|
2009
|
-
exit={{ opacity: 0, y: 6 }}
|
|
2010
|
-
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2011
|
-
data-og-loading-newer=""
|
|
2012
|
-
aria-live="polite"
|
|
2013
|
-
className="pointer-events-none absolute inset-x-0 bottom-14 z-10 flex justify-center"
|
|
2014
|
-
>
|
|
2015
|
-
<span className={LOADING_CHIP_CLASS}>
|
|
2016
|
-
<span className="og-shimmer-text">Loading later activity…</span>
|
|
2017
|
-
</span>
|
|
2018
|
-
</motion.div>
|
|
2015
|
+
) : null}
|
|
2016
|
+
</div>
|
|
2017
|
+
</TimelineBeforeLayout>
|
|
2018
|
+
</div>
|
|
2019
|
+
{draftAnnotations && draftAnnotations.length > 0 ? (
|
|
2020
|
+
<Suspense fallback={null}>
|
|
2021
|
+
<TimelineAnnotationMarkers
|
|
2022
|
+
rootRef={scrollRef}
|
|
2023
|
+
annotations={draftAnnotations}
|
|
2024
|
+
onSelect={onDraftAnnotationSelect}
|
|
2025
|
+
/>
|
|
2026
|
+
</Suspense>
|
|
2019
2027
|
) : null}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2028
|
+
|
|
2029
|
+
<AnimatePresence>
|
|
2030
|
+
{loadingNewer ? (
|
|
2031
|
+
<motion.div
|
|
2032
|
+
initial={{ opacity: 0, y: 6 }}
|
|
2033
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2034
|
+
exit={{ opacity: 0, y: 6 }}
|
|
2035
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2036
|
+
data-og-loading-newer=""
|
|
2037
|
+
aria-live="polite"
|
|
2038
|
+
className="pointer-events-none absolute inset-x-0 bottom-14 z-10 flex justify-center"
|
|
2039
|
+
>
|
|
2040
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
2041
|
+
<span className="og-shimmer-text">Loading later activity…</span>
|
|
2042
|
+
</span>
|
|
2043
|
+
</motion.div>
|
|
2044
|
+
) : null}
|
|
2045
|
+
</AnimatePresence>
|
|
2046
|
+
<AnimatePresence>
|
|
2047
|
+
{((!pinned && autoFollow) || hasNewer || canSkipTipCatchup) && autoFollow ? (
|
|
2048
|
+
<motion.button
|
|
2049
|
+
type="button"
|
|
2050
|
+
data-og-jump-to-latest=""
|
|
2051
|
+
initial={{ opacity: 0, y: 8 }}
|
|
2052
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2053
|
+
exit={{ opacity: 0, y: 8 }}
|
|
2054
|
+
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2055
|
+
onClick={() => {
|
|
2056
|
+
disclosureKeepsUnpinnedRef.current = false;
|
|
2057
|
+
if (hasNewer) {
|
|
2058
|
+
// Do not pin against the current history page — its bottom
|
|
2059
|
+
// is not the tip. The pin + snap run when the tip window
|
|
2060
|
+
// actually lands (`hasNewer` flips false).
|
|
2061
|
+
wantPinRef.current = true;
|
|
2062
|
+
const node = scrollRef.current;
|
|
2063
|
+
if (onJumpToLatest) {
|
|
2064
|
+
void Promise.resolve(onJumpToLatest()).then(
|
|
2065
|
+
() => {
|
|
2066
|
+
// Covers a host that flipped hasNewer before
|
|
2067
|
+
// resolving; otherwise the tip-window commit
|
|
2068
|
+
// consumes the flag.
|
|
2069
|
+
const current = scrollRef.current;
|
|
2070
|
+
if (current && wantPinRef.current && !hasNewerRef.current) {
|
|
2071
|
+
wantPinRef.current = false;
|
|
2072
|
+
applyPinned(true);
|
|
2073
|
+
snapToBottom(current);
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
() => {
|
|
2077
|
+
// The tip reload failed (ordinary network error):
|
|
2078
|
+
// an armed latch would fire a surprise snap when
|
|
2079
|
+
// the reader later pages to the tip themselves.
|
|
2046
2080
|
wantPinRef.current = false;
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
// window so the newer sentinel can page forward; the
|
|
2061
|
-
// latch pins if the tip window eventually lands.
|
|
2081
|
+
},
|
|
2082
|
+
);
|
|
2083
|
+
} else if (node) {
|
|
2084
|
+
// No tip reload available: jump within the in-memory
|
|
2085
|
+
// window so the newer sentinel can page forward; the
|
|
2086
|
+
// latch pins if the tip window eventually lands.
|
|
2087
|
+
snapToBottom(node);
|
|
2088
|
+
}
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
const node = scrollRef.current;
|
|
2092
|
+
if (node) {
|
|
2093
|
+
applyPinned(true);
|
|
2062
2094
|
snapToBottom(node);
|
|
2063
2095
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
<ArrowDownIcon className="size-3.5" />
|
|
2080
|
-
Jump to latest
|
|
2081
|
-
</motion.button>
|
|
2082
|
-
) : null}
|
|
2083
|
-
</AnimatePresence>
|
|
2084
|
-
</div>
|
|
2096
|
+
}}
|
|
2097
|
+
className={cn(
|
|
2098
|
+
"absolute inset-x-0 bottom-4 mx-auto w-fit",
|
|
2099
|
+
"inline-flex items-center gap-1.5 rounded-full border border-og-border bg-og-surface-3/90 px-3 py-1.5",
|
|
2100
|
+
"text-og-control font-medium text-og-fg shadow-og-md backdrop-blur",
|
|
2101
|
+
"hover:border-og-border-strong",
|
|
2102
|
+
)}
|
|
2103
|
+
>
|
|
2104
|
+
<ArrowDownIcon className="size-3.5" />
|
|
2105
|
+
Jump to latest
|
|
2106
|
+
</motion.button>
|
|
2107
|
+
) : null}
|
|
2108
|
+
</AnimatePresence>
|
|
2109
|
+
</div>
|
|
2110
|
+
</TimelineAnnotationSourceRootContext.Provider>
|
|
2085
2111
|
</TooltipProvider>
|
|
2086
2112
|
</EntranceAnimationProvider>
|
|
2087
2113
|
</TimelineComputeLabelProvider>
|
|
@@ -2269,6 +2295,7 @@ type TimelineGroupEntryContext = {
|
|
|
2269
2295
|
};
|
|
2270
2296
|
|
|
2271
2297
|
type TimelineGroupBehaviorProps = {
|
|
2298
|
+
renderMessageActions: MessageTimelineProps["renderMessageActions"];
|
|
2272
2299
|
renderMessageText: MessageTimelineProps["renderMessageText"];
|
|
2273
2300
|
onOpenSession: MessageTimelineProps["onOpenSession"];
|
|
2274
2301
|
onMemoryClick: MessageTimelineProps["onMemoryClick"];
|
|
@@ -2332,6 +2359,7 @@ const TimelineGroupEntry = memo(function TimelineGroupEntry({
|
|
|
2332
2359
|
// streaming and host updates still invalidate immediately.
|
|
2333
2360
|
const TimelineGroupView = memo(function TimelineGroupView({
|
|
2334
2361
|
group,
|
|
2362
|
+
renderMessageActions,
|
|
2335
2363
|
renderMessageText,
|
|
2336
2364
|
onOpenSession,
|
|
2337
2365
|
onMemoryClick,
|
|
@@ -2349,6 +2377,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2349
2377
|
contextCompactionCount,
|
|
2350
2378
|
}: {
|
|
2351
2379
|
group: TimelineGroup;
|
|
2380
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
2352
2381
|
renderMessageText?:
|
|
2353
2382
|
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
2354
2383
|
| undefined;
|
|
@@ -2507,6 +2536,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2507
2536
|
key={key}
|
|
2508
2537
|
resetKeys={[
|
|
2509
2538
|
child,
|
|
2539
|
+
renderMessageActions,
|
|
2510
2540
|
renderMessageText,
|
|
2511
2541
|
onOpenSession,
|
|
2512
2542
|
onMemoryClick,
|
|
@@ -2521,6 +2551,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2521
2551
|
>
|
|
2522
2552
|
<TimelineGroupView
|
|
2523
2553
|
group={child}
|
|
2554
|
+
renderMessageActions={renderMessageActions}
|
|
2524
2555
|
renderMessageText={renderMessageText}
|
|
2525
2556
|
onOpenSession={onOpenSession}
|
|
2526
2557
|
onMemoryClick={onMemoryClick}
|
|
@@ -2567,6 +2598,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
|
|
|
2567
2598
|
return (
|
|
2568
2599
|
<TimelineRow
|
|
2569
2600
|
item={group.item}
|
|
2601
|
+
renderMessageActions={renderMessageActions}
|
|
2570
2602
|
renderMessageText={renderMessageText}
|
|
2571
2603
|
onReconnect={onReconnect}
|
|
2572
2604
|
resolveProviderLogo={resolveProviderLogo}
|
|
@@ -2818,6 +2850,7 @@ function durationBetween(startedAt: string, endedAt: string): number | undefined
|
|
|
2818
2850
|
*/
|
|
2819
2851
|
export function TimelineRow({
|
|
2820
2852
|
item,
|
|
2853
|
+
renderMessageActions,
|
|
2821
2854
|
renderMessageText,
|
|
2822
2855
|
onReconnect,
|
|
2823
2856
|
resolveProviderLogo,
|
|
@@ -2825,6 +2858,7 @@ export function TimelineRow({
|
|
|
2825
2858
|
loadVideoArtifactPlayback,
|
|
2826
2859
|
}: {
|
|
2827
2860
|
item: TimelineItem;
|
|
2861
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
2828
2862
|
renderMessageText?:
|
|
2829
2863
|
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
2830
2864
|
| undefined;
|
|
@@ -2835,11 +2869,23 @@ export function TimelineRow({
|
|
|
2835
2869
|
}) {
|
|
2836
2870
|
switch (item.kind) {
|
|
2837
2871
|
case "user-message":
|
|
2838
|
-
return
|
|
2872
|
+
return (
|
|
2873
|
+
<UserMessageRow
|
|
2874
|
+
item={item}
|
|
2875
|
+
renderMessageActions={renderMessageActions}
|
|
2876
|
+
renderMessageText={renderMessageText}
|
|
2877
|
+
/>
|
|
2878
|
+
);
|
|
2839
2879
|
case "human-input":
|
|
2840
2880
|
return <HumanInputConversationRow item={item} />;
|
|
2841
2881
|
case "agent-message":
|
|
2842
|
-
return
|
|
2882
|
+
return (
|
|
2883
|
+
<AgentMessageRow
|
|
2884
|
+
item={item}
|
|
2885
|
+
renderMessageActions={renderMessageActions}
|
|
2886
|
+
renderMessageText={renderMessageText}
|
|
2887
|
+
/>
|
|
2888
|
+
);
|
|
2843
2889
|
case "worker-completion":
|
|
2844
2890
|
return <WorkerCompletionRow item={item} onOpenSession={onOpenSession} />;
|
|
2845
2891
|
case "session-status":
|
|
@@ -2965,9 +3011,11 @@ function MessageFooterTime({ occurredAt }: { occurredAt: string }) {
|
|
|
2965
3011
|
|
|
2966
3012
|
function UserMessageRow({
|
|
2967
3013
|
item,
|
|
3014
|
+
renderMessageActions,
|
|
2968
3015
|
renderMessageText,
|
|
2969
3016
|
}: {
|
|
2970
3017
|
item: UserMessageItem;
|
|
3018
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
2971
3019
|
renderMessageText?:
|
|
2972
3020
|
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
2973
3021
|
| undefined;
|
|
@@ -2986,7 +3034,12 @@ function UserMessageRow({
|
|
|
2986
3034
|
}
|
|
2987
3035
|
label="Copy message"
|
|
2988
3036
|
className="w-fit max-w-full min-w-0"
|
|
2989
|
-
trailing={
|
|
3037
|
+
trailing={
|
|
3038
|
+
<>
|
|
3039
|
+
{renderMessageActions?.(item)}
|
|
3040
|
+
<MessageFooterTime occurredAt={item.occurredAt} />
|
|
3041
|
+
</>
|
|
3042
|
+
}
|
|
2990
3043
|
>
|
|
2991
3044
|
<div className={MESSAGE_BUBBLE_CLASS}>
|
|
2992
3045
|
{item.text ? (
|
|
@@ -3001,7 +3054,7 @@ function UserMessageRow({
|
|
|
3001
3054
|
</div>
|
|
3002
3055
|
) : null}
|
|
3003
3056
|
{(item.annotations?.length ?? 0) > 0 ? (
|
|
3004
|
-
<
|
|
3057
|
+
<TimelineAnnotationCards
|
|
3005
3058
|
annotations={item.annotations ?? []}
|
|
3006
3059
|
className={item.text ? "mt-2" : undefined}
|
|
3007
3060
|
/>
|
|
@@ -3177,9 +3230,11 @@ function humanInputConversationCopyText(item: HumanInputItem, settledLabel: stri
|
|
|
3177
3230
|
|
|
3178
3231
|
function AgentMessageRow({
|
|
3179
3232
|
item,
|
|
3233
|
+
renderMessageActions,
|
|
3180
3234
|
renderMessageText,
|
|
3181
3235
|
}: {
|
|
3182
3236
|
item: AgentMessageItem;
|
|
3237
|
+
renderMessageActions?: ((item: AgentMessageItem | UserMessageItem) => ReactNode) | undefined;
|
|
3183
3238
|
renderMessageText?:
|
|
3184
3239
|
| ((text: string, item: AgentMessageItem | UserMessageItem) => ReactNode)
|
|
3185
3240
|
| undefined;
|
|
@@ -3195,17 +3250,22 @@ function AgentMessageRow({
|
|
|
3195
3250
|
// While streaming, copy is still useful (current text) but keep chrome calm —
|
|
3196
3251
|
// stamp only after the message finishes (occurredAt tracks completion).
|
|
3197
3252
|
return (
|
|
3198
|
-
<
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3253
|
+
<CopyHoverFrame
|
|
3254
|
+
copyText={item.text}
|
|
3255
|
+
label="Copy message"
|
|
3256
|
+
align="start"
|
|
3257
|
+
className={cn(enter && "animate-og-enter", "min-w-0 text-og-md leading-7 text-og-fg")}
|
|
3258
|
+
trailing={
|
|
3259
|
+
item.streaming ? null : (
|
|
3260
|
+
<>
|
|
3261
|
+
{renderMessageActions?.(item)}
|
|
3262
|
+
<MessageFooterTime occurredAt={item.occurredAt} />
|
|
3263
|
+
</>
|
|
3264
|
+
)
|
|
3265
|
+
}
|
|
3266
|
+
>
|
|
3267
|
+
<div data-og-annotation-source-key={item.annotationSource?.eventId}>{body}</div>
|
|
3268
|
+
</CopyHoverFrame>
|
|
3209
3269
|
);
|
|
3210
3270
|
}
|
|
3211
3271
|
|