@opengeni/react 7.6.1 → 7.7.0-canary.37090756480001
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 +19 -2
- package/dist/{browser-viewer-5X47I6T2.js → browser-viewer-63TXMINB.js} +6 -5
- package/dist/{chunk-UINDK6C3.js → chunk-2QUSQVF2.js} +3 -3
- package/dist/{chunk-CHPKSLPN.js → chunk-6ZSWSH4I.js} +3 -1
- package/dist/chunk-6ZSWSH4I.js.map +1 -0
- package/dist/{chunk-KGENSZKS.js → chunk-AAG4R3QN.js} +4 -3
- package/dist/chunk-AAG4R3QN.js.map +1 -0
- package/dist/{chunk-Z3DISEQN.js → chunk-CWWKTNKK.js} +35 -18
- package/dist/chunk-CWWKTNKK.js.map +1 -0
- package/dist/{chunk-5EXGU7UF.js → chunk-DOJWJQ3F.js} +3 -3
- package/dist/{chunk-WZ55DVGK.js → chunk-ELCE5SXV.js} +8 -4
- package/dist/chunk-ELCE5SXV.js.map +1 -0
- package/dist/{chunk-YSMMDOOE.js → chunk-GISEVGS6.js} +10 -2
- package/dist/chunk-GISEVGS6.js.map +1 -0
- package/dist/{chunk-YRYF2GLG.js → chunk-HNFCAQ5R.js} +39 -31
- package/dist/chunk-HNFCAQ5R.js.map +1 -0
- package/dist/{chunk-BI57CQUV.js → chunk-I7IWRQLN.js} +2 -2
- package/dist/{chunk-XA6R7DJQ.js → chunk-IQUCRAT7.js} +3 -3
- package/dist/{chunk-DV2DBNPQ.js → chunk-Q5QBEZIQ.js} +48 -29
- package/dist/chunk-Q5QBEZIQ.js.map +1 -0
- package/dist/{chunk-FGBCYQOT.js → chunk-RDKBXMWX.js} +120 -74
- package/dist/chunk-RDKBXMWX.js.map +1 -0
- package/dist/{chunk-45GE35O7.js → chunk-TKB4SB44.js} +10 -2
- package/dist/chunk-TKB4SB44.js.map +1 -0
- package/dist/{chunk-YCGBREUV.js → chunk-TOAH2BKA.js} +6 -2
- package/dist/{chunk-YCGBREUV.js.map → chunk-TOAH2BKA.js.map} +1 -1
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/{chunk-UJRYEWZI.js → chunk-UTRTG6LH.js} +48 -8
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/{chunk-VGIKFIO2.js → chunk-XOAMRDDK.js} +6 -3
- package/dist/chunk-XOAMRDDK.js.map +1 -0
- package/dist/{chunk-6NEZPF53.js → chunk-YN7I3STW.js} +153 -32
- package/dist/chunk-YN7I3STW.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/{chunk-LZ3KHA55.js → chunk-ZYALTAEQ.js} +329 -339
- package/dist/chunk-ZYALTAEQ.js.map +1 -0
- package/dist/client.d.ts +4 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/composer.js +8 -7
- package/dist/{computer-viewer-7GF35SNY.js → computer-viewer-X2MBRZY4.js} +5 -4
- package/dist/connect.js +3 -3
- package/dist/hooks/use-browser-session.d.ts +4 -0
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/hooks/use-session-events.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +15 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -21
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +6 -5
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-3BJGKQQ6.js → latest-question-CNEZ5KAN.js} +3 -3
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +27 -3
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.js +5 -4
- package/dist/{model-policy-picker-menu-PUTA5JOB.js → model-policy-picker-menu-JSA64TNN.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/{queue-surface-implementation-2CAQAIHM.js → queue-surface-implementation-HENWYFWA.js} +3 -2
- package/dist/{queue-surface-implementation-2CAQAIHM.js.map → queue-surface-implementation-HENWYFWA.js.map} +1 -1
- package/dist/realtime.js +2 -2
- package/dist/{sandbox-files-AEJTWGT4.js → sandbox-files-BBC47SLD.js} +4 -3
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +13 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +7 -6
- package/dist/usage.js +2 -1
- package/dist/usage.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +5 -1
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/approval-surface.tsx +166 -42
- package/src/components/browser-viewer.tsx +38 -9
- package/src/components/composer.tsx +9 -2
- package/src/components/computer-viewer.tsx +160 -30
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/human-input-form.tsx +18 -5
- package/src/components/message-timeline.tsx +120 -168
- package/src/components/open-geni-chat.tsx +11 -4
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/session-chrome.tsx +5 -2
- package/src/components/session-commands-panel.tsx +3 -1
- package/src/components/session-conversation.tsx +4 -2
- package/src/components/session-list.tsx +7 -5
- package/src/components/session-mcp-capability-card.tsx +11 -1
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/use-browser-session.ts +115 -79
- package/src/hooks/use-composer.ts +70 -31
- package/src/hooks/use-computer-session.ts +57 -6
- package/src/hooks/use-file-attachments.ts +16 -3
- package/src/hooks/use-goal.ts +2 -1
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-events.ts +15 -8
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-workspace-sessions.ts +40 -7
- package/src/index.ts +7 -2
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +73 -9
- package/src/lib/interaction-errors.ts +15 -0
- package/src/model-policy.ts +5 -0
- package/src/provider.tsx +12 -4
- package/src/session-ui.ts +1 -1
- package/src/timeline/projection.ts +12 -1
- package/styles/compiled.css +14 -30
- package/dist/chunk-45GE35O7.js.map +0 -1
- package/dist/chunk-6NEZPF53.js.map +0 -1
- package/dist/chunk-CHPKSLPN.js.map +0 -1
- package/dist/chunk-DV2DBNPQ.js.map +0 -1
- package/dist/chunk-FGBCYQOT.js.map +0 -1
- package/dist/chunk-KGENSZKS.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LZ3KHA55.js.map +0 -1
- package/dist/chunk-UJRYEWZI.js.map +0 -1
- package/dist/chunk-VGIKFIO2.js.map +0 -1
- package/dist/chunk-WZ55DVGK.js.map +0 -1
- package/dist/chunk-YRYF2GLG.js.map +0 -1
- package/dist/chunk-YSMMDOOE.js.map +0 -1
- package/dist/chunk-Z3DISEQN.js.map +0 -1
- package/dist/components/timeline-pill-geometry.d.ts +0 -8
- package/dist/components/timeline-pill-placement.d.ts +0 -24
- package/dist/timeline-pill-geometry-Z23V243M.js +0 -110
- package/dist/timeline-pill-geometry-Z23V243M.js.map +0 -1
- package/src/components/timeline-pill-geometry.ts +0 -169
- package/src/components/timeline-pill-placement.ts +0 -99
- /package/dist/{browser-viewer-5X47I6T2.js.map → browser-viewer-63TXMINB.js.map} +0 -0
- /package/dist/{chunk-UINDK6C3.js.map → chunk-2QUSQVF2.js.map} +0 -0
- /package/dist/{chunk-5EXGU7UF.js.map → chunk-DOJWJQ3F.js.map} +0 -0
- /package/dist/{chunk-BI57CQUV.js.map → chunk-I7IWRQLN.js.map} +0 -0
- /package/dist/{chunk-XA6R7DJQ.js.map → chunk-IQUCRAT7.js.map} +0 -0
- /package/dist/{computer-viewer-7GF35SNY.js.map → computer-viewer-X2MBRZY4.js.map} +0 -0
- /package/dist/{latest-question-3BJGKQQ6.js.map → latest-question-CNEZ5KAN.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-PUTA5JOB.js.map → model-policy-picker-menu-JSA64TNN.js.map} +0 -0
- /package/dist/{sandbox-files-AEJTWGT4.js.map → sandbox-files-BBC47SLD.js.map} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RollingActivity } from "../timeline/rolling-activity";
|
|
2
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
2
3
|
import { formatElapsed } from "../timeline/turn-summary";
|
|
3
4
|
import { ActivityNoteTextContext } from "../timeline/activity-rail";
|
|
4
5
|
import { timelineGroupContainsPresentedImage } from "../timeline/presented-image";
|
|
@@ -73,7 +74,6 @@ import {
|
|
|
73
74
|
type TimelineAnchor,
|
|
74
75
|
} from "./timeline-anchor";
|
|
75
76
|
import { useReadingProgress } from "./timeline-reading-progress";
|
|
76
|
-
import { type TimelinePillPlacement, useTimelinePillPlacement } from "./timeline-pill-placement";
|
|
77
77
|
import {
|
|
78
78
|
animateTimelineSettlement,
|
|
79
79
|
captureTimelineSettlement,
|
|
@@ -332,20 +332,20 @@ const PIN_THRESHOLD_PX = 48;
|
|
|
332
332
|
const JUMP_TO_LATEST_CATCHUP_DEBT_PX = 240;
|
|
333
333
|
/** Breathing room above the question when following stops at an answer. */
|
|
334
334
|
const QUESTION_NAV_MARGIN_PX = 12;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
};
|
|
335
|
+
/**
|
|
336
|
+
* Floating timeline navigation (Back to your message, Jump to latest) shares one
|
|
337
|
+
* fixed anchor: centered at the bottom of the conversation, above the composer.
|
|
338
|
+
* The pills never measure or dodge the content beneath them, so they cannot
|
|
339
|
+
* wander sideways or drift vertically as rows stream in or the host resizes.
|
|
340
|
+
*/
|
|
341
|
+
const NAV_PILL_CLASS =
|
|
342
|
+
"pointer-events-auto inline-flex max-w-full items-center gap-1.5 whitespace-nowrap rounded-full border border-og-border bg-og-surface-3/90 px-3 py-1.5 text-og-control font-medium text-og-fg shadow-og-md backdrop-blur hover:border-og-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-og-accent pointer-coarse:min-h-11";
|
|
343
|
+
const NAV_PILL_MOTION = {
|
|
344
|
+
initial: { opacity: 0, y: 8 },
|
|
345
|
+
animate: { opacity: 1, y: 0 },
|
|
346
|
+
exit: { opacity: 0, y: 8 },
|
|
347
|
+
transition: { duration: 0.15, ease: "easeOut" },
|
|
348
|
+
} as const;
|
|
349
349
|
/**
|
|
350
350
|
* Prefetch older history when the top sentinel is this far from the viewport.
|
|
351
351
|
* After a page loads we stay cool until the reader leaves this band (scrolls
|
|
@@ -562,6 +562,7 @@ export function MessageTimeline({
|
|
|
562
562
|
emptyState,
|
|
563
563
|
className,
|
|
564
564
|
}: MessageTimelineProps) {
|
|
565
|
+
const formatError = useErrorMessage();
|
|
565
566
|
const resolvedItems = useMemo(() => {
|
|
566
567
|
const projectedItems = items ?? buildTimeline(events ?? []);
|
|
567
568
|
if (!shouldRenderAuthNeeded) {
|
|
@@ -672,12 +673,12 @@ export function MessageTimeline({
|
|
|
672
673
|
setNewerRetryPending(false);
|
|
673
674
|
setNewerFailure({
|
|
674
675
|
key: newerBoundaryKey,
|
|
675
|
-
message:
|
|
676
|
+
message: formatError(reason),
|
|
676
677
|
});
|
|
677
678
|
},
|
|
678
679
|
);
|
|
679
680
|
},
|
|
680
|
-
[onLoadNewer, loadingNewer, newerBoundaryKey],
|
|
681
|
+
[onLoadNewer, loadingNewer, newerBoundaryKey, formatError],
|
|
681
682
|
);
|
|
682
683
|
const previousSourceIdsRef = useRef(new Set<string>());
|
|
683
684
|
const previousSourceBoundaryRef = useRef<string | undefined>(undefined);
|
|
@@ -745,22 +746,7 @@ export function MessageTimeline({
|
|
|
745
746
|
const resizeFollowRafRef = useRef<number | null>(null);
|
|
746
747
|
const questionNavFrameRef = useRef<number | null>(null);
|
|
747
748
|
const [questionNav, setQuestionNav] = useState<QuestionNav | null>(null);
|
|
748
|
-
const jumpPillRef = useRef<HTMLButtonElement | null>(null);
|
|
749
|
-
const questionPillRef = useRef<HTMLDivElement | null>(null);
|
|
750
749
|
const jumpPillShown = autoFollow && (!pinned || hasNewer || canSkipTipCatchup);
|
|
751
|
-
const jumpPillPlacement = useTimelinePillPlacement({
|
|
752
|
-
pillRef: jumpPillRef,
|
|
753
|
-
scrollerRef: scrollRef,
|
|
754
|
-
active: jumpPillShown,
|
|
755
|
-
preference: JUMP_PILL_PLACEMENTS,
|
|
756
|
-
});
|
|
757
|
-
const questionPillPlacement = useTimelinePillPlacement({
|
|
758
|
-
pillRef: questionPillRef,
|
|
759
|
-
scrollerRef: scrollRef,
|
|
760
|
-
active: questionNav !== null,
|
|
761
|
-
preference: QUESTION_PILL_PLACEMENTS,
|
|
762
|
-
allowDownwardFallback: true,
|
|
763
|
-
});
|
|
764
750
|
const firstGroupKey = allGroups[0] ? timelineGroupKey(allGroups[0]) : null;
|
|
765
751
|
// Content stays invisible until the tip is hard-parked across a short
|
|
766
752
|
// post-commit settle (two rAFs). That absorbs sync late layout while hidden
|
|
@@ -2264,8 +2250,7 @@ export function MessageTimeline({
|
|
|
2264
2250
|
// (pt-16). Subtract exactly that padding so an expanded work
|
|
2265
2251
|
// header pins flush to the scrollport. Pinning it lower left a
|
|
2266
2252
|
// band of scrolling rows visible above the header, which then
|
|
2267
|
-
// looked like it floated over the middle of the timeline.
|
|
2268
|
-
// floating question action sits below this strip instead.
|
|
2253
|
+
// looked like it floated over the middle of the timeline.
|
|
2269
2254
|
style={{ "--og-work-header-top": "-4rem" } as CSSProperties}
|
|
2270
2255
|
>
|
|
2271
2256
|
{onAnnotate ? (
|
|
@@ -2394,14 +2379,7 @@ export function MessageTimeline({
|
|
|
2394
2379
|
transition={{ duration: 0.15, ease: "easeOut" }}
|
|
2395
2380
|
data-og-loading-older=""
|
|
2396
2381
|
aria-live="polite"
|
|
2397
|
-
className=
|
|
2398
|
-
"pointer-events-none absolute inset-x-0 -top-11 z-10 flex",
|
|
2399
|
-
// The scrolling history control and fixed question
|
|
2400
|
-
// action must never share the same pointer region.
|
|
2401
|
-
questionNav
|
|
2402
|
-
? "max-w-[calc(50%-0.5rem)] justify-start"
|
|
2403
|
-
: "justify-center",
|
|
2404
|
-
)}
|
|
2382
|
+
className="pointer-events-none absolute inset-x-0 -top-11 z-10 flex justify-center"
|
|
2405
2383
|
>
|
|
2406
2384
|
{loadingOlder || loadingOldest ? (
|
|
2407
2385
|
<span className={LOADING_CHIP_CLASS}>
|
|
@@ -2576,139 +2554,113 @@ export function MessageTimeline({
|
|
|
2576
2554
|
</Suspense>
|
|
2577
2555
|
) : null}
|
|
2578
2556
|
|
|
2579
|
-
<
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
// pointers), never over it: the header stays a full-width target.
|
|
2590
|
-
data-og-pill-placement={questionPillPlacement.placement}
|
|
2591
|
-
style={{ marginTop: questionPillPlacement.offsetY }}
|
|
2592
|
-
className={cn(
|
|
2593
|
-
"pointer-events-none absolute inset-x-0 top-11 z-10 flex px-4 sm:px-6 pointer-coarse:top-14",
|
|
2594
|
-
QUESTION_PILL_PLACEMENT_CLASS[questionPillPlacement.placement],
|
|
2595
|
-
)}
|
|
2596
|
-
>
|
|
2557
|
+
<div
|
|
2558
|
+
data-og-timeline-nav=""
|
|
2559
|
+
// One fixed anchor for every floating navigation control: a
|
|
2560
|
+
// centered column at the bottom of the conversation. Newest
|
|
2561
|
+
// stays nearest the composer; the up-pointing action and the
|
|
2562
|
+
// later-activity status stack above it.
|
|
2563
|
+
className="pointer-events-none absolute inset-x-0 bottom-4 z-10 flex flex-col items-center gap-2 px-4 sm:px-6"
|
|
2564
|
+
>
|
|
2565
|
+
<AnimatePresence mode="popLayout">
|
|
2566
|
+
{loadingNewer ? (
|
|
2597
2567
|
<motion.div
|
|
2598
|
-
|
|
2568
|
+
key="loading-newer"
|
|
2599
2569
|
layout="position"
|
|
2600
|
-
|
|
2601
|
-
|
|
2570
|
+
{...NAV_PILL_MOTION}
|
|
2571
|
+
data-og-loading-newer=""
|
|
2572
|
+
aria-live="polite"
|
|
2573
|
+
className="flex max-w-full justify-center"
|
|
2602
2574
|
>
|
|
2603
|
-
<
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
onClick={() => jumpToQuestion(questionNav.key)}
|
|
2607
|
-
className="inline-flex min-w-0 items-center gap-1.5 whitespace-nowrap px-3 py-1.5 hover:text-og-fg pointer-coarse:min-h-11"
|
|
2608
|
-
>
|
|
2609
|
-
<ArrowUpIcon aria-hidden className="size-3.5" />
|
|
2610
|
-
Back to your message
|
|
2611
|
-
</button>
|
|
2575
|
+
<span className={LOADING_CHIP_CLASS}>
|
|
2576
|
+
<span className="og-shimmer-text">Loading later activity…</span>
|
|
2577
|
+
</span>
|
|
2612
2578
|
</motion.div>
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2579
|
+
) : null}
|
|
2580
|
+
{questionNav ? (
|
|
2581
|
+
<motion.button
|
|
2582
|
+
key="question-nav"
|
|
2583
|
+
layout="position"
|
|
2584
|
+
{...NAV_PILL_MOTION}
|
|
2585
|
+
type="button"
|
|
2586
|
+
data-og-question-nav=""
|
|
2587
|
+
data-og-jump-to-question=""
|
|
2588
|
+
onClick={() => jumpToQuestion(questionNav.key)}
|
|
2589
|
+
className={NAV_PILL_CLASS}
|
|
2590
|
+
>
|
|
2591
|
+
<ArrowUpIcon aria-hidden className="size-3.5 shrink-0" />
|
|
2592
|
+
Back to your message
|
|
2593
|
+
</motion.button>
|
|
2594
|
+
) : null}
|
|
2595
|
+
{jumpPillShown ? (
|
|
2596
|
+
<motion.button
|
|
2597
|
+
key="jump-to-latest"
|
|
2598
|
+
layout="position"
|
|
2599
|
+
{...NAV_PILL_MOTION}
|
|
2600
|
+
type="button"
|
|
2601
|
+
data-og-jump-to-latest=""
|
|
2602
|
+
onClick={() => {
|
|
2603
|
+
// Returning to the tip explicitly releases reader-owned
|
|
2604
|
+
// prose. Clear only this timeline's selection before the
|
|
2605
|
+
// synchronous ownership check on the next commit.
|
|
2606
|
+
const viewport = scrollRef.current;
|
|
2607
|
+
const selection = viewport?.ownerDocument.getSelection();
|
|
2608
|
+
if (
|
|
2609
|
+
selection &&
|
|
2610
|
+
!selection.isCollapsed &&
|
|
2611
|
+
(viewport?.contains(selection.anchorNode) ||
|
|
2612
|
+
viewport?.contains(selection.focusNode))
|
|
2613
|
+
)
|
|
2614
|
+
selection.removeAllRanges();
|
|
2615
|
+
disclosureKeepsUnpinnedRef.current = false;
|
|
2616
|
+
if (hasNewer) {
|
|
2617
|
+
// Do not pin against the current history page — its bottom
|
|
2618
|
+
// is not the tip. The pin + snap run when the tip window
|
|
2619
|
+
// actually lands (`hasNewer` flips false).
|
|
2620
|
+
wantPinRef.current = true;
|
|
2621
|
+
const node = scrollRef.current;
|
|
2622
|
+
if (onJumpToLatest) {
|
|
2623
|
+
void Promise.resolve(onJumpToLatest()).then(
|
|
2624
|
+
() => {
|
|
2625
|
+
// Covers a host that flipped hasNewer before
|
|
2626
|
+
// resolving; otherwise the tip-window commit
|
|
2627
|
+
// consumes the flag.
|
|
2628
|
+
const current = scrollRef.current;
|
|
2629
|
+
if (current && wantPinRef.current && !hasNewerRef.current) {
|
|
2630
|
+
wantPinRef.current = false;
|
|
2631
|
+
applyPinned(true);
|
|
2632
|
+
snapToBottom(current);
|
|
2633
|
+
}
|
|
2634
|
+
},
|
|
2635
|
+
() => {
|
|
2636
|
+
// The tip reload failed (ordinary network error):
|
|
2637
|
+
// an armed latch would fire a surprise snap when
|
|
2638
|
+
// the reader later pages to the tip themselves.
|
|
2673
2639
|
wantPinRef.current = false;
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
// window so the newer sentinel can page forward; the
|
|
2688
|
-
// latch pins if the tip window eventually lands.
|
|
2640
|
+
},
|
|
2641
|
+
);
|
|
2642
|
+
} else if (node) {
|
|
2643
|
+
// No tip reload available: jump within the in-memory
|
|
2644
|
+
// window so the newer sentinel can page forward; the
|
|
2645
|
+
// latch pins if the tip window eventually lands.
|
|
2646
|
+
snapToBottom(node);
|
|
2647
|
+
}
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
const node = scrollRef.current;
|
|
2651
|
+
if (node) {
|
|
2652
|
+
applyPinned(true);
|
|
2689
2653
|
snapToBottom(node);
|
|
2690
2654
|
}
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
"absolute bottom-4 w-fit",
|
|
2701
|
-
JUMP_PILL_PLACEMENT_CLASS[jumpPillPlacement.placement],
|
|
2702
|
-
"inline-flex items-center gap-1.5 rounded-full border border-og-border bg-og-surface-3/90 px-3 py-1.5",
|
|
2703
|
-
"text-og-control font-medium text-og-fg shadow-og-md backdrop-blur",
|
|
2704
|
-
"hover:border-og-border-strong",
|
|
2705
|
-
)}
|
|
2706
|
-
>
|
|
2707
|
-
<ArrowDownIcon className="size-3.5" />
|
|
2708
|
-
Jump to latest
|
|
2709
|
-
</motion.button>
|
|
2710
|
-
) : null}
|
|
2711
|
-
</AnimatePresence>
|
|
2655
|
+
}}
|
|
2656
|
+
className={NAV_PILL_CLASS}
|
|
2657
|
+
>
|
|
2658
|
+
<ArrowDownIcon aria-hidden className="size-3.5 shrink-0" />
|
|
2659
|
+
Jump to latest
|
|
2660
|
+
</motion.button>
|
|
2661
|
+
) : null}
|
|
2662
|
+
</AnimatePresence>
|
|
2663
|
+
</div>
|
|
2712
2664
|
</div>
|
|
2713
2665
|
</TimelineAnnotationSourceRootContext.Provider>
|
|
2714
2666
|
</TooltipProvider>
|
|
@@ -2,6 +2,7 @@ import type { OpenGeniClient } from "@opengeni/sdk";
|
|
|
2
2
|
import { MenuIcon, SendIcon, XIcon } from "lucide-react";
|
|
3
3
|
import { useCallback, useRef, useState, type CSSProperties, type FormEvent } from "react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
5
6
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
6
7
|
import { SessionConversation, type SessionConversationProps } from "./session-conversation";
|
|
7
8
|
import { SessionList, type SessionListLabels, type SessionListProps } from "./session-list";
|
|
@@ -198,7 +199,8 @@ function NewChat({
|
|
|
198
199
|
}) {
|
|
199
200
|
const [text, setText] = useState("");
|
|
200
201
|
const [sending, setSending] = useState(false);
|
|
201
|
-
const [error, setError] = useState<
|
|
202
|
+
const [error, setError] = useState<{ cause: unknown } | null>(null);
|
|
203
|
+
const formatError = useErrorMessage();
|
|
202
204
|
// One key per draft, so a retried send returns the same chat.
|
|
203
205
|
const idempotencyKey = useRef(crypto.randomUUID());
|
|
204
206
|
|
|
@@ -214,7 +216,7 @@ function NewChat({
|
|
|
214
216
|
setText("");
|
|
215
217
|
onCreated(id);
|
|
216
218
|
} catch (cause) {
|
|
217
|
-
setError(
|
|
219
|
+
setError({ cause });
|
|
218
220
|
} finally {
|
|
219
221
|
setSending(false);
|
|
220
222
|
}
|
|
@@ -223,12 +225,17 @@ function NewChat({
|
|
|
223
225
|
return (
|
|
224
226
|
<form
|
|
225
227
|
onSubmit={(event) => void submit(event)}
|
|
226
|
-
className="flex h-full flex-col justify-end gap-2 p-3"
|
|
228
|
+
className="box-border flex h-full flex-col justify-end gap-2 p-3"
|
|
227
229
|
data-og-new-chat-composer=""
|
|
228
230
|
>
|
|
229
231
|
{error ? (
|
|
230
232
|
<p role="alert" className="text-og-xs text-og-status-failed">
|
|
231
|
-
{
|
|
233
|
+
{formatError(
|
|
234
|
+
error.cause,
|
|
235
|
+
error.cause instanceof Error && error.cause.message === labels.newChatUnavailable
|
|
236
|
+
? labels.newChatUnavailable
|
|
237
|
+
: undefined,
|
|
238
|
+
)}
|
|
232
239
|
</p>
|
|
233
240
|
) : null}
|
|
234
241
|
<div className="flex items-end gap-2 rounded-lg border border-og-border bg-og-surface-1 p-2">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Loader2Icon, RotateCwIcon } from "lucide-react";
|
|
2
2
|
|
|
3
3
|
import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
|
|
4
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
4
5
|
|
|
5
6
|
type QueueStateProps = {
|
|
6
7
|
queue: UseTurnQueueResult;
|
|
@@ -60,6 +61,7 @@ export function QueueErrorAlert({
|
|
|
60
61
|
queue,
|
|
61
62
|
dividerBefore = false,
|
|
62
63
|
}: QueueStateProps & { dividerBefore?: boolean }) {
|
|
64
|
+
const formatError = useErrorMessage();
|
|
63
65
|
return (
|
|
64
66
|
<div className={`${dividerBefore ? "border-t border-border" : ""} p-2`}>
|
|
65
67
|
<div
|
|
@@ -74,7 +76,7 @@ export function QueueErrorAlert({
|
|
|
74
76
|
data-testid="queue-error-message"
|
|
75
77
|
dir="auto"
|
|
76
78
|
>
|
|
77
|
-
{(queue.mutationError ?? queue.error)
|
|
79
|
+
{formatError(queue.mutationError ?? queue.error)}
|
|
78
80
|
</span>
|
|
79
81
|
<button
|
|
80
82
|
type="button"
|
|
@@ -73,6 +73,7 @@ import type { ComposerOptimisticMessage, ComposerState } from "../hooks/use-comp
|
|
|
73
73
|
import type { UseGoalResult } from "../hooks/use-goal";
|
|
74
74
|
import type { UseTurnQueueResult } from "../hooks/use-turn-queue";
|
|
75
75
|
import { cn } from "../lib/cn";
|
|
76
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
76
77
|
import { formatClockTime } from "../lib/format";
|
|
77
78
|
import { requestQueueDraftEdit } from "./queue-draft-policy";
|
|
78
79
|
import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content";
|
|
@@ -453,6 +454,7 @@ export function SessionChrome({
|
|
|
453
454
|
defaultActive = null,
|
|
454
455
|
onActiveChange,
|
|
455
456
|
}: SessionChromeProps) {
|
|
457
|
+
const formatError = useErrorMessage();
|
|
456
458
|
const [activityRequested, setActivityOpen] = useState(
|
|
457
459
|
Boolean(defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)),
|
|
458
460
|
);
|
|
@@ -1192,7 +1194,7 @@ export function SessionChrome({
|
|
|
1192
1194
|
</AnimatePresence>
|
|
1193
1195
|
{goal?.mutationError ? (
|
|
1194
1196
|
<p role="alert" className="px-3 pb-2 text-og-xs text-og-danger">
|
|
1195
|
-
Goal action not confirmed. {goal.mutationError
|
|
1197
|
+
Goal action not confirmed. {formatError(goal.mutationError)}
|
|
1196
1198
|
</p>
|
|
1197
1199
|
) : null}
|
|
1198
1200
|
<div
|
|
@@ -1667,6 +1669,7 @@ function GoalPanel({
|
|
|
1667
1669
|
elapsed: string | null;
|
|
1668
1670
|
readOnly: boolean;
|
|
1669
1671
|
}) {
|
|
1672
|
+
const formatError = useErrorMessage();
|
|
1670
1673
|
const record = goal.goal;
|
|
1671
1674
|
if (!record) return null;
|
|
1672
1675
|
const canToggle = !readOnly && (record.status === "active" || record.status === "paused");
|
|
@@ -1699,7 +1702,7 @@ function GoalPanel({
|
|
|
1699
1702
|
) : null}
|
|
1700
1703
|
{record.continuation?.lastError ? (
|
|
1701
1704
|
<p className="rounded-og-sm bg-og-status-waiting/10 px-1.5 py-1 text-og-xs leading-4 text-og-status-waiting">
|
|
1702
|
-
{record.continuation.lastError}
|
|
1705
|
+
{formatError(record.continuation.lastError)}
|
|
1703
1706
|
</p>
|
|
1704
1707
|
) : null}
|
|
1705
1708
|
<div className="flex flex-wrap items-center justify-between gap-1.5 pt-0.5">
|
|
@@ -2,6 +2,7 @@ import { ChevronRightIcon, Loader2Icon, SquareIcon } from "lucide-react";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import type { UseSessionBackgroundCommandsResult } from "../hooks/use-session-background-commands";
|
|
4
4
|
import { formatClockTime } from "../lib/format";
|
|
5
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
5
6
|
|
|
6
7
|
/** The current session's live commands. Settled commands belong to the timeline. */
|
|
7
8
|
export function SessionCommandsPanel({
|
|
@@ -11,6 +12,7 @@ export function SessionCommandsPanel({
|
|
|
11
12
|
commands: UseSessionBackgroundCommandsResult;
|
|
12
13
|
readOnly?: boolean;
|
|
13
14
|
}) {
|
|
15
|
+
const formatError = useErrorMessage();
|
|
14
16
|
const [pending, setPending] = useState<string | null>(null);
|
|
15
17
|
const [error, setError] = useState<string | null>(null);
|
|
16
18
|
const active = state.commands.filter(
|
|
@@ -22,7 +24,7 @@ export function SessionCommandsPanel({
|
|
|
22
24
|
try {
|
|
23
25
|
await state.cancel(id);
|
|
24
26
|
} catch (cause) {
|
|
25
|
-
setError(cause
|
|
27
|
+
setError(formatError(cause));
|
|
26
28
|
} finally {
|
|
27
29
|
setPending(null);
|
|
28
30
|
}
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import type { UserMessageDisclosureLabels } from "./user-message-body";
|
|
38
38
|
import { conversationTimeline } from "../conversation-timeline";
|
|
39
39
|
import { cn } from "../lib/cn";
|
|
40
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
40
41
|
|
|
41
42
|
export type SessionConversationProps = ClientOverride & {
|
|
42
43
|
sessionId: string;
|
|
@@ -120,6 +121,7 @@ function Conversation({
|
|
|
120
121
|
}: SessionConversationProps) {
|
|
121
122
|
const scope = { client, workspaceId };
|
|
122
123
|
const context = useOpenGeni(scope);
|
|
124
|
+
const formatError = useErrorMessage();
|
|
123
125
|
const config = useClientConfigFlags(context.client);
|
|
124
126
|
const showModelPicker = modelPicker ?? config.modelSelection;
|
|
125
127
|
const catalog = useWorkspaceModelCatalog({
|
|
@@ -203,7 +205,7 @@ function Conversation({
|
|
|
203
205
|
style={{ height }}
|
|
204
206
|
data-og-conversation=""
|
|
205
207
|
>
|
|
206
|
-
{error && <p role="alert">{error
|
|
208
|
+
{error && <p role="alert">{formatError(error)}</p>}
|
|
207
209
|
<MessageTimeline
|
|
208
210
|
renderMessageText={renderMessageText}
|
|
209
211
|
resolveLink={links}
|
|
@@ -265,7 +267,7 @@ function Conversation({
|
|
|
265
267
|
await human.respond(id, response);
|
|
266
268
|
}}
|
|
267
269
|
respondingRequestId={human.respondingRequestId}
|
|
268
|
-
error={human.mutationError
|
|
270
|
+
error={human.mutationError ? formatError(human.mutationError) : null}
|
|
269
271
|
autoFocus={false}
|
|
270
272
|
/>
|
|
271
273
|
{terminal ? (
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState, type FormEvent } from "react";
|
|
|
4
4
|
import { useWorkspaceSessions } from "../hooks/use-workspace-sessions";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
6
|
import { formatRelativeTime } from "../lib/format";
|
|
7
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
7
8
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
8
9
|
|
|
9
10
|
export type SessionListLabels = {
|
|
@@ -90,7 +91,8 @@ export function SessionList({
|
|
|
90
91
|
}, [refresh, refreshKey]);
|
|
91
92
|
const [editing, setEditing] = useState<{ id: string; title: string } | null>(null);
|
|
92
93
|
const [busy, setBusy] = useState<string | null>(null);
|
|
93
|
-
const [error, setError] = useState<
|
|
94
|
+
const [error, setError] = useState<{ cause: unknown } | null>(null);
|
|
95
|
+
const formatError = useErrorMessage();
|
|
94
96
|
const archiveClient = context.client as unknown as ArchiveClient;
|
|
95
97
|
const canArchive = archive && typeof archiveClient.updateSessionArchive === "function";
|
|
96
98
|
|
|
@@ -101,7 +103,7 @@ export function SessionList({
|
|
|
101
103
|
await action();
|
|
102
104
|
await list.refresh();
|
|
103
105
|
} catch (cause) {
|
|
104
|
-
setError(
|
|
106
|
+
setError({ cause });
|
|
105
107
|
} finally {
|
|
106
108
|
setBusy(null);
|
|
107
109
|
}
|
|
@@ -148,16 +150,16 @@ export function SessionList({
|
|
|
148
150
|
</button>
|
|
149
151
|
) : null}
|
|
150
152
|
</div>
|
|
151
|
-
{error ? (
|
|
153
|
+
{error || list.error ? (
|
|
152
154
|
<p role="alert" className="px-2 text-og-xs text-og-status-failed">
|
|
153
|
-
{error}
|
|
155
|
+
{formatError(error ? error.cause : list.error)}
|
|
154
156
|
</p>
|
|
155
157
|
) : null}
|
|
156
158
|
<ul className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-1 pb-2">
|
|
157
159
|
{list.loading && sessions.length === 0 ? (
|
|
158
160
|
<li className="px-2 py-2 text-og-xs text-og-fg-subtle">{labels.loading}</li>
|
|
159
161
|
) : null}
|
|
160
|
-
{!list.loading && sessions.length === 0 ? (
|
|
162
|
+
{!list.loading && !list.error && sessions.length === 0 ? (
|
|
161
163
|
<li className="px-2 py-2 text-og-xs text-og-fg-subtle">{labels.empty}</li>
|
|
162
164
|
) : null}
|
|
163
165
|
{sessions.map((session) => {
|
|
@@ -127,7 +127,17 @@ function ScopedCard({
|
|
|
127
127
|
]);
|
|
128
128
|
if (!invocation || invocation.signal.aborted || lifetime.current !== invocation || !active())
|
|
129
129
|
return null;
|
|
130
|
-
|
|
130
|
+
let resolved = catalog.items.find((entry) => entry.id === capabilityId);
|
|
131
|
+
// Custom catalog entries may not declare authentication. Discover it from
|
|
132
|
+
// the live catalog's endpoint, never from recommendation/host copy.
|
|
133
|
+
if (resolved?.kind === "mcp" && !resolved.enabled && !resolved.authKind) {
|
|
134
|
+
const endpoint = resolved.mcpUrl ?? resolved.endpointUrl;
|
|
135
|
+
if (endpoint) {
|
|
136
|
+
const inspected = await client.inspectMcpAuthentication(workspaceId, endpoint);
|
|
137
|
+
if (invocation.signal.aborted || lifetime.current !== invocation || !active()) return null;
|
|
138
|
+
if (inspected.kind === "oauth2") resolved = { ...resolved, authKind: "oauth2" };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
131
141
|
if (!resolved || resolved.kind !== "mcp" || resolved.authKind !== "oauth2")
|
|
132
142
|
throw new Error(
|
|
133
143
|
"This recommendation is not an available OAuth MCP integration. Review the current connection catalog.",
|
package/src/hooks/internal.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SessionEvent } from "@opengeni/sdk";
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import type { EmbeddedSessionEventClientLike } from "../client";
|
|
4
|
+
import { normalizeError } from "../lib/error-message";
|
|
4
5
|
|
|
5
6
|
const HIDDEN_STREAM_GRACE_MS = 2_000;
|
|
6
7
|
|
|
@@ -116,7 +117,7 @@ export function usePolledValue<T>(
|
|
|
116
117
|
activeLoadRef.current === load &&
|
|
117
118
|
!requestAbort.signal.aborted
|
|
118
119
|
) {
|
|
119
|
-
setError(
|
|
120
|
+
setError(normalizeError(cause));
|
|
120
121
|
setLoading(false);
|
|
121
122
|
}
|
|
122
123
|
} finally {
|
|
@@ -258,7 +259,7 @@ export function useMutationRunner(identity: unknown = undefined): MutationState
|
|
|
258
259
|
generation.current === ownedGeneration &&
|
|
259
260
|
Object.is(identityRef.current, ownedIdentity)
|
|
260
261
|
) {
|
|
261
|
-
setMutationError(
|
|
262
|
+
setMutationError(normalizeError(cause));
|
|
262
263
|
}
|
|
263
264
|
return null;
|
|
264
265
|
} finally {
|