@opengeni/react 7.6.1 → 7.7.0-canary.37111190284001
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 +47 -2
- package/dist/{browser-viewer-5X47I6T2.js → browser-viewer-63TXMINB.js} +6 -5
- package/dist/{chunk-5EXGU7UF.js → chunk-55DDDVKU.js} +3 -3
- package/dist/{chunk-UINDK6C3.js → chunk-7CYYXWHU.js} +5 -8
- package/dist/chunk-7CYYXWHU.js.map +1 -0
- package/dist/{chunk-LZ3KHA55.js → chunk-A2L5N3EI.js} +332 -339
- package/dist/chunk-A2L5N3EI.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-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-BI57CQUV.js → chunk-I7IWRQLN.js} +2 -2
- package/dist/{chunk-XA6R7DJQ.js → chunk-IQUCRAT7.js} +3 -3
- package/dist/{chunk-CHPKSLPN.js → chunk-NBZ64YY7.js} +4 -2
- package/dist/chunk-NBZ64YY7.js.map +1 -0
- 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-YRYF2GLG.js → chunk-WYVLAAJA.js} +39 -31
- package/dist/chunk-WYVLAAJA.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/client.d.ts +4 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/components/session-conversation.d.ts +3 -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-GN2LTB7Z.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/model-policy-picker.tsx +2 -15
- 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 +10 -3
- 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 +6 -1
- 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-UINDK6C3.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-5EXGU7UF.js.map → chunk-55DDDVKU.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-GN2LTB7Z.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>
|
|
@@ -161,19 +161,6 @@ export type ModelPolicyPickerProps = {
|
|
|
161
161
|
|
|
162
162
|
const SLIDE_EASE = [0.22, 1, 0.36, 1] as const;
|
|
163
163
|
|
|
164
|
-
// Solid facets keep the brand legible at the picker’s 14px icon size.
|
|
165
|
-
function OpenGeniMark(props: SVGProps<SVGSVGElement>) {
|
|
166
|
-
return (
|
|
167
|
-
<svg viewBox="0 0 140 133" fill="currentColor" aria-hidden="true" {...props}>
|
|
168
|
-
<g transform="translate(-17.5,-20.999893188476562) scale(1.75)">
|
|
169
|
-
<g transform="translate(0,-952.36218)">
|
|
170
|
-
<path d="m 60.7828,964.36215 27.1809,0.8834 -27.1809,25.9958 z m -1.9745,1.4513 0,26.7845 -25.2681,0 c 8.6166,-8.7334 16.8796,-17.8103 25.2681,-26.7845 z m 27.7053,3.628 3.4864,1.1989 -12.5877,7.4768 z m -68.1835,2.9656 5.5226,0 12.8654,14.0705 -5.9854,6.1204 -12.4026,0 c 9e-4,-6.7347 0,-13.4597 0,-20.1909 z m -1.9746,1.2304 0,5.8364 -6.3555,0 z m 3.363,20.9796 38.627,0 -10.7675,29.43465 z m 39.0898,4.54286 0,41.20229 -12.5878,-6.8775 c 4.1972,-11.443 8.3886,-22.879 12.5878,-34.32479 z" />
|
|
171
|
-
</g>
|
|
172
|
-
</g>
|
|
173
|
-
</svg>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
164
|
function ChatGptMark(props: SVGProps<SVGSVGElement>) {
|
|
178
165
|
return (
|
|
179
166
|
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" {...props}>
|
|
@@ -189,7 +176,7 @@ export function BillingClassMark(props: {
|
|
|
189
176
|
"aria-label"?: string | undefined;
|
|
190
177
|
}) {
|
|
191
178
|
const labels: Record<PickerBillingClass, string> = {
|
|
192
|
-
opengeni_credits: "
|
|
179
|
+
opengeni_credits: "Models",
|
|
193
180
|
external: "External provider",
|
|
194
181
|
codex_subscription: "Codex",
|
|
195
182
|
supergrok_subscription: "SuperGrok",
|
|
@@ -217,7 +204,7 @@ export function BillingClassMark(props: {
|
|
|
217
204
|
{props.presentation?.icon !== undefined ? (
|
|
218
205
|
props.presentation.icon
|
|
219
206
|
) : props.billingClass === "opengeni_credits" ? (
|
|
220
|
-
<
|
|
207
|
+
<SparklesIcon className={mark} aria-hidden />
|
|
221
208
|
) : props.billingClass === "external" ? (
|
|
222
209
|
<Globe2Icon className={mark} aria-hidden />
|
|
223
210
|
) : props.billingClass === "codex_subscription" ? (
|
|
@@ -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
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import type { SiteSnapshotClient } from "./artifacts/chat-interactive-block";
|
|
13
13
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
14
14
|
import { useWorkspaceModelCatalog } from "../hooks/use-available-models";
|
|
15
|
-
import { ModelPolicyPicker } from "./model-policy-picker";
|
|
15
|
+
import { ModelPolicyPicker, type ModelPolicyPickerProps } from "./model-policy-picker";
|
|
16
16
|
import { useSessionEvents } from "../hooks/use-session-events";
|
|
17
17
|
import { useSession } from "../hooks/use-session";
|
|
18
18
|
import { useTurnQueue } from "../hooks/use-turn-queue";
|
|
@@ -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;
|
|
@@ -80,6 +81,8 @@ export type SessionConversationProps = ClientOverride & {
|
|
|
80
81
|
* reports `modelSelection: false` (a host proxy that fixes the model policy).
|
|
81
82
|
*/
|
|
82
83
|
modelPicker?: boolean | undefined;
|
|
84
|
+
/** Model-picker appearance only; visibility, policy and delivery remain owned here. */
|
|
85
|
+
modelPickerProps?: Pick<ModelPolicyPickerProps, "groupPresentation" | "messages"> | undefined;
|
|
83
86
|
/** Localized actions for already-sent user-message disclosure. */
|
|
84
87
|
userMessageDisclosureLabels?: UserMessageDisclosureLabels | undefined;
|
|
85
88
|
loadSkillReview?: HumanInputSurfaceProps["loadSkillReview"];
|
|
@@ -110,6 +113,7 @@ function Conversation({
|
|
|
110
113
|
allowanceExhaustedLabels,
|
|
111
114
|
attachments: attachmentsRequested = true,
|
|
112
115
|
modelPicker,
|
|
116
|
+
modelPickerProps,
|
|
113
117
|
userMessageDisclosureLabels,
|
|
114
118
|
loadSkillReview,
|
|
115
119
|
client,
|
|
@@ -120,6 +124,7 @@ function Conversation({
|
|
|
120
124
|
}: SessionConversationProps) {
|
|
121
125
|
const scope = { client, workspaceId };
|
|
122
126
|
const context = useOpenGeni(scope);
|
|
127
|
+
const formatError = useErrorMessage();
|
|
123
128
|
const config = useClientConfigFlags(context.client);
|
|
124
129
|
const showModelPicker = modelPicker ?? config.modelSelection;
|
|
125
130
|
const catalog = useWorkspaceModelCatalog({
|
|
@@ -203,7 +208,7 @@ function Conversation({
|
|
|
203
208
|
style={{ height }}
|
|
204
209
|
data-og-conversation=""
|
|
205
210
|
>
|
|
206
|
-
{error && <p role="alert">{error
|
|
211
|
+
{error && <p role="alert">{formatError(error)}</p>}
|
|
207
212
|
<MessageTimeline
|
|
208
213
|
renderMessageText={renderMessageText}
|
|
209
214
|
resolveLink={links}
|
|
@@ -265,7 +270,7 @@ function Conversation({
|
|
|
265
270
|
await human.respond(id, response);
|
|
266
271
|
}}
|
|
267
272
|
respondingRequestId={human.respondingRequestId}
|
|
268
|
-
error={human.mutationError
|
|
273
|
+
error={human.mutationError ? formatError(human.mutationError) : null}
|
|
269
274
|
autoFocus={false}
|
|
270
275
|
/>
|
|
271
276
|
{terminal ? (
|
|
@@ -291,6 +296,8 @@ function Conversation({
|
|
|
291
296
|
composerProps?.controlsStart ??
|
|
292
297
|
(showModelPicker && composer.policy && (
|
|
293
298
|
<ModelPolicyPicker
|
|
299
|
+
groupPresentation={modelPickerProps?.groupPresentation}
|
|
300
|
+
messages={modelPickerProps?.messages}
|
|
294
301
|
rows={catalog.rows}
|
|
295
302
|
model={composer.policy.model}
|
|
296
303
|
effort={composer.policy.reasoningEffort}
|