@opengeni/react 6.0.0-canary.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/{chunk-PZ27MCZP.js → chunk-L2NBTKRX.js} +14 -2
- package/dist/chunk-L2NBTKRX.js.map +1 -0
- package/dist/{chunk-CC7AVRIJ.js → chunk-TDSEILUH.js} +80 -7
- package/dist/chunk-TDSEILUH.js.map +1 -0
- package/dist/components/message-timeline.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/session-ui.js +2 -2
- package/dist/session.js +1 -1
- package/package.json +3 -3
- package/src/components/message-timeline.tsx +88 -4
- package/src/components/session-conversation.tsx +1 -3
- package/src/hooks/use-session-events.ts +17 -1
- package/styles/compiled.css +3 -0
- package/dist/chunk-CC7AVRIJ.js.map +0 -1
- package/dist/chunk-PZ27MCZP.js.map +0 -1
|
@@ -98,8 +98,8 @@ export type MessageTimelineProps = {
|
|
|
98
98
|
hasNewer?: boolean | undefined;
|
|
99
99
|
/** A newer history page is being fetched. */
|
|
100
100
|
loadingNewer?: boolean | undefined;
|
|
101
|
-
/** Page forward through history
|
|
102
|
-
onLoadNewer?: (() =>
|
|
101
|
+
/** Page forward through history. Return the request promise to enable inline error/retry. */
|
|
102
|
+
onLoadNewer?: (() => unknown) | undefined;
|
|
103
103
|
/**
|
|
104
104
|
* Reload the live tip window. When omitted, Jump to latest only re-pins and
|
|
105
105
|
* scrolls the in-memory window.
|
package/dist/index.js
CHANGED
|
@@ -108,7 +108,7 @@ import {
|
|
|
108
108
|
useWorkspaceModelCatalog,
|
|
109
109
|
userMessageLikelyNeedsDisclosure,
|
|
110
110
|
v4aToGitFileDiff
|
|
111
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-TDSEILUH.js";
|
|
112
112
|
import "./chunk-YBM6CQRU.js";
|
|
113
113
|
import "./chunk-5M6VTFJ5.js";
|
|
114
114
|
import "./chunk-ZYRGEKWS.js";
|
|
@@ -219,7 +219,7 @@ import {
|
|
|
219
219
|
useSession,
|
|
220
220
|
useSessionEvents,
|
|
221
221
|
useTurnQueue
|
|
222
|
-
} from "./chunk-
|
|
222
|
+
} from "./chunk-L2NBTKRX.js";
|
|
223
223
|
import {
|
|
224
224
|
FILE_ONLY_MESSAGE_TEXT,
|
|
225
225
|
composeSendInput,
|
package/dist/session-ui.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
setStartupDetails,
|
|
19
19
|
useStartupDetails,
|
|
20
20
|
userMessageLikelyNeedsDisclosure
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-TDSEILUH.js";
|
|
22
22
|
import "./chunk-YBM6CQRU.js";
|
|
23
23
|
import "./chunk-5M6VTFJ5.js";
|
|
24
24
|
import "./chunk-ZYRGEKWS.js";
|
|
@@ -33,7 +33,7 @@ import "./chunk-R4KRSFGA.js";
|
|
|
33
33
|
import "./chunk-VZTQ73XT.js";
|
|
34
34
|
import {
|
|
35
35
|
createOlderHistoryLoadReceipt
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-L2NBTKRX.js";
|
|
37
37
|
import "./chunk-NYESIADM.js";
|
|
38
38
|
import "./chunk-PLYRL5ER.js";
|
|
39
39
|
import "./chunk-KZ73RL76.js";
|
package/dist/session.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
"@dnd-kit/utilities": "3.2.2",
|
|
134
134
|
"@fontsource-variable/noto-sans-arabic": "5.2.10",
|
|
135
135
|
"@fontsource-variable/noto-sans-jp": "5.2.10",
|
|
136
|
-
"@opengeni/connect": "^0.2.0
|
|
137
|
-
"@opengeni/sdk": "^6.
|
|
136
|
+
"@opengeni/connect": "^0.2.0",
|
|
137
|
+
"@opengeni/sdk": "^6.1.0",
|
|
138
138
|
"clsx": "^2.1.1",
|
|
139
139
|
"dequal": "2.0.3",
|
|
140
140
|
"lucide-react": "^1.8.0",
|
|
@@ -229,8 +229,8 @@ export type MessageTimelineProps = {
|
|
|
229
229
|
hasNewer?: boolean | undefined;
|
|
230
230
|
/** A newer history page is being fetched. */
|
|
231
231
|
loadingNewer?: boolean | undefined;
|
|
232
|
-
/** Page forward through history
|
|
233
|
-
onLoadNewer?: (() =>
|
|
232
|
+
/** Page forward through history. Return the request promise to enable inline error/retry. */
|
|
233
|
+
onLoadNewer?: (() => unknown) | undefined;
|
|
234
234
|
/**
|
|
235
235
|
* Reload the live tip window. When omitted, Jump to latest only re-pins and
|
|
236
236
|
* scrolls the in-memory window.
|
|
@@ -461,6 +461,67 @@ export function MessageTimeline({
|
|
|
461
461
|
// can acquire a different first-delta id when older text arrives).
|
|
462
462
|
const sourceItems = events ?? items;
|
|
463
463
|
const olderBoundaryKey = sourceItems?.[0]?.id;
|
|
464
|
+
const newerBoundaryKey = `${events?.[0]?.sessionId ?? ""}:${olderBoundaryKey ?? ""}`;
|
|
465
|
+
const newerScopeRef = useRef(newerBoundaryKey);
|
|
466
|
+
newerScopeRef.current = newerBoundaryKey;
|
|
467
|
+
const newerAttemptRef = useRef<{ pending: boolean } | null>(null);
|
|
468
|
+
const newerRetryButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
469
|
+
const [newerRetryPending, setNewerRetryPending] = useState(false);
|
|
470
|
+
const [newerFailure, setNewerFailure] = useState<{ key: string; message: string } | null>(null);
|
|
471
|
+
useEffect(() => {
|
|
472
|
+
newerScopeRef.current = newerBoundaryKey;
|
|
473
|
+
newerAttemptRef.current = null;
|
|
474
|
+
setNewerFailure(null);
|
|
475
|
+
setNewerRetryPending(false);
|
|
476
|
+
return () => {
|
|
477
|
+
newerScopeRef.current = "";
|
|
478
|
+
newerAttemptRef.current = null;
|
|
479
|
+
};
|
|
480
|
+
}, [newerBoundaryKey]);
|
|
481
|
+
const requestNewer = useCallback(
|
|
482
|
+
(explicitRetry = false) => {
|
|
483
|
+
if (
|
|
484
|
+
newerScopeRef.current !== newerBoundaryKey ||
|
|
485
|
+
!onLoadNewer ||
|
|
486
|
+
loadingNewer ||
|
|
487
|
+
(newerAttemptRef.current && (!explicitRetry || newerAttemptRef.current.pending))
|
|
488
|
+
) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const attempt = { pending: true };
|
|
492
|
+
newerAttemptRef.current = attempt;
|
|
493
|
+
if (explicitRetry) setNewerRetryPending(true);
|
|
494
|
+
const isCurrent = () =>
|
|
495
|
+
newerAttemptRef.current === attempt && newerScopeRef.current === newerBoundaryKey;
|
|
496
|
+
// Both synchronous host errors and rejected promises belong to this
|
|
497
|
+
// boundary. Retain the failed attempt so observers cannot hot-retry it.
|
|
498
|
+
void Promise.resolve()
|
|
499
|
+
.then(() => (isCurrent() ? onLoadNewer() : undefined))
|
|
500
|
+
.then(
|
|
501
|
+
() => {
|
|
502
|
+
if (!isCurrent()) return;
|
|
503
|
+
newerAttemptRef.current = null;
|
|
504
|
+
// The successful page removes the recovery control. Return focus
|
|
505
|
+
// to the reading surface without moving the reader's viewport.
|
|
506
|
+
if (document.activeElement === newerRetryButtonRef.current) {
|
|
507
|
+
scrollRef.current?.focus({ preventScroll: true });
|
|
508
|
+
}
|
|
509
|
+
setNewerFailure(null);
|
|
510
|
+
setNewerRetryPending(false);
|
|
511
|
+
},
|
|
512
|
+
(reason: unknown) => {
|
|
513
|
+
if (!isCurrent()) return;
|
|
514
|
+
attempt.pending = false;
|
|
515
|
+
setNewerRetryPending(false);
|
|
516
|
+
setNewerFailure({
|
|
517
|
+
key: newerBoundaryKey,
|
|
518
|
+
message: reason instanceof Error ? reason.message : String(reason),
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
},
|
|
523
|
+
[onLoadNewer, loadingNewer, newerBoundaryKey],
|
|
524
|
+
);
|
|
464
525
|
const previousSourceIdsRef = useRef(new Set<string>());
|
|
465
526
|
const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
|
|
466
527
|
const readingAnchorRef = useRef<TimelineAnchor | null>(null);
|
|
@@ -1586,14 +1647,14 @@ export function MessageTimeline({
|
|
|
1586
1647
|
// An underfilled history window has both sentinels visible. Advancing
|
|
1587
1648
|
// it automatically would undo an explicit older-page navigation.
|
|
1588
1649
|
if (maxScrollOf(root) > 1 && entries.some((entry) => entry.isIntersecting)) {
|
|
1589
|
-
|
|
1650
|
+
requestNewer();
|
|
1590
1651
|
}
|
|
1591
1652
|
},
|
|
1592
1653
|
{ root, rootMargin: "0px 0px 1200px 0px" },
|
|
1593
1654
|
);
|
|
1594
1655
|
observer.observe(target);
|
|
1595
1656
|
return () => observer.disconnect();
|
|
1596
|
-
}, [hasNewer, loadingNewer, onLoadNewer, firstGroupKey]);
|
|
1657
|
+
}, [hasNewer, loadingNewer, onLoadNewer, requestNewer, firstGroupKey]);
|
|
1597
1658
|
|
|
1598
1659
|
// Late layout that React commits cannot see (images decoding, fonts, code
|
|
1599
1660
|
// blocks) grows content without a commit. While pinned, soft-follow the tip;
|
|
@@ -2061,6 +2122,29 @@ export function MessageTimeline({
|
|
|
2061
2122
|
{groups.length > 0 && trailingState ? (
|
|
2062
2123
|
<div data-og-timeline-trailing-state="">{trailingState}</div>
|
|
2063
2124
|
) : null}
|
|
2125
|
+
{hasNewer && newerFailure?.key === newerBoundaryKey ? (
|
|
2126
|
+
<div
|
|
2127
|
+
data-og-newer-error=""
|
|
2128
|
+
className="flex flex-col items-center gap-2 px-4 py-3 text-center text-og-menu text-og-fg-muted"
|
|
2129
|
+
>
|
|
2130
|
+
<p role="status" className="max-w-prose [overflow-wrap:anywhere]">
|
|
2131
|
+
Couldn’t load later activity. {newerFailure.message}
|
|
2132
|
+
</p>
|
|
2133
|
+
<button
|
|
2134
|
+
ref={newerRetryButtonRef}
|
|
2135
|
+
type="button"
|
|
2136
|
+
data-og-retry-newer=""
|
|
2137
|
+
className="min-h-11 rounded-og-md border border-og-border px-3 py-2 text-og-fg hover:bg-og-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-og-accent"
|
|
2138
|
+
aria-disabled={loadingNewer || newerRetryPending}
|
|
2139
|
+
aria-busy={newerRetryPending}
|
|
2140
|
+
onClick={() => requestNewer(true)}
|
|
2141
|
+
>
|
|
2142
|
+
{newerRetryPending
|
|
2143
|
+
? "Retrying later activity…"
|
|
2144
|
+
: "Retry later activity"}
|
|
2145
|
+
</button>
|
|
2146
|
+
</div>
|
|
2147
|
+
) : null}
|
|
2064
2148
|
{hasNewer ? (
|
|
2065
2149
|
<div
|
|
2066
2150
|
ref={bottomSentinelRef}
|
|
@@ -84,9 +84,7 @@ function Conversation({
|
|
|
84
84
|
onLoadOlder={feed.loadOlder}
|
|
85
85
|
hasNewer={feed.hasNewer}
|
|
86
86
|
loadingNewer={feed.loadingNewer}
|
|
87
|
-
onLoadNewer={
|
|
88
|
-
void feed.loadNewer();
|
|
89
|
-
}}
|
|
87
|
+
onLoadNewer={feed.loadNewer}
|
|
90
88
|
onJumpToStart={async () => {
|
|
91
89
|
await feed.loadOldest();
|
|
92
90
|
}}
|
|
@@ -140,6 +140,7 @@ export function useSessionEvents(
|
|
|
140
140
|
const [eventWindow, setEventWindow] = useState<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
|
|
141
141
|
const [connectionState, setConnectionState] = useState<SessionEventsConnectionState>("idle");
|
|
142
142
|
const [error, setError] = useState<Error | null>(null);
|
|
143
|
+
const [newerError, setNewerError] = useState<Error | null>(null);
|
|
143
144
|
const [hasOlder, setHasOlder] = useState(false);
|
|
144
145
|
const [hasNewer, setHasNewer] = useState(false);
|
|
145
146
|
const [sessionStatusProjection, setSessionStatusProjection] = useState<SessionStatus | null>(
|
|
@@ -186,6 +187,7 @@ export function useSessionEvents(
|
|
|
186
187
|
// Navigation belongs to the session/client lifetime, not the transport.
|
|
187
188
|
useEffect(() => {
|
|
188
189
|
navigationGenerationRef.current += 1;
|
|
190
|
+
setNewerError(null);
|
|
189
191
|
loadingOlderRef.current = false;
|
|
190
192
|
loadingNewerRef.current = false;
|
|
191
193
|
loadingOldestRef.current = false;
|
|
@@ -691,6 +693,7 @@ export function useSessionEvents(
|
|
|
691
693
|
initialWindowLoadedRef.current = true;
|
|
692
694
|
viewModeRef.current = "history";
|
|
693
695
|
loadingOldestRef.current = false;
|
|
696
|
+
setNewerError(null);
|
|
694
697
|
startTransition(() => {
|
|
695
698
|
if (status !== undefined) {
|
|
696
699
|
setSessionStatusProjection(status);
|
|
@@ -736,6 +739,7 @@ export function useSessionEvents(
|
|
|
736
739
|
return false;
|
|
737
740
|
}
|
|
738
741
|
if (window.events.length === 0) {
|
|
742
|
+
setNewerError(null);
|
|
739
743
|
hasNewerRef.current = false;
|
|
740
744
|
setHasNewer(false);
|
|
741
745
|
// Caught up with durable history — resume the live tip stream.
|
|
@@ -788,6 +792,7 @@ export function useSessionEvents(
|
|
|
788
792
|
resumeLive = true;
|
|
789
793
|
}
|
|
790
794
|
loadingNewerRef.current = false;
|
|
795
|
+
setNewerError(null);
|
|
791
796
|
startTransition(() => {
|
|
792
797
|
if (status !== undefined) {
|
|
793
798
|
setSessionStatusProjection(status);
|
|
@@ -803,6 +808,16 @@ export function useSessionEvents(
|
|
|
803
808
|
});
|
|
804
809
|
published = true;
|
|
805
810
|
return newer;
|
|
811
|
+
} catch (reason) {
|
|
812
|
+
// A settled request from an old navigation lifetime is not a failure of
|
|
813
|
+
// the current session. Preserve current loading/error state as well.
|
|
814
|
+
if (navigationGenerationRef.current !== generation) {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
setNewerError(reason instanceof Error ? reason : new Error(String(reason)));
|
|
818
|
+
// Keep authorization and integrity failures actionable for callers;
|
|
819
|
+
// timeline-owned invocations attach their own explicit recovery UI.
|
|
820
|
+
throw reason;
|
|
806
821
|
} finally {
|
|
807
822
|
if (!published && navigationGenerationRef.current === generation) {
|
|
808
823
|
loadingNewerRef.current = false;
|
|
@@ -817,6 +832,7 @@ export function useSessionEvents(
|
|
|
817
832
|
}
|
|
818
833
|
loadingLatestRef.current = true;
|
|
819
834
|
setLoadingLatest(true);
|
|
835
|
+
setNewerError(null);
|
|
820
836
|
let published = false;
|
|
821
837
|
try {
|
|
822
838
|
streamAbortRef.current?.abort();
|
|
@@ -873,7 +889,7 @@ export function useSessionEvents(
|
|
|
873
889
|
loadOldest,
|
|
874
890
|
loadingLatest: !identityMatches ? false : loadingLatest,
|
|
875
891
|
jumpToLatest,
|
|
876
|
-
error: identityMatches ? error : null,
|
|
892
|
+
error: identityMatches ? (newerError ?? error) : null,
|
|
877
893
|
};
|
|
878
894
|
}
|
|
879
895
|
|
package/styles/compiled.css
CHANGED
|
@@ -1785,6 +1785,9 @@
|
|
|
1785
1785
|
:where(.og-root).max-w-md,:where(.og-root) .max-w-md {
|
|
1786
1786
|
max-width: var(--container-md);
|
|
1787
1787
|
}
|
|
1788
|
+
:where(.og-root).max-w-prose,:where(.og-root) .max-w-prose {
|
|
1789
|
+
max-width: 65ch;
|
|
1790
|
+
}
|
|
1788
1791
|
:where(.og-root).max-w-sm,:where(.og-root) .max-w-sm {
|
|
1789
1792
|
max-width: var(--container-sm);
|
|
1790
1793
|
}
|