@opengeni/react 6.0.0-canary.1 → 6.1.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 +17 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
- package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
- package/dist/{chunk-IHQII5WO.js → chunk-G5JFM6ZE.js} +29 -4
- package/dist/chunk-G5JFM6ZE.js.map +1 -0
- package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
- package/dist/chunk-HJ5VJ4JB.js.map +1 -0
- package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
- package/dist/{chunk-CC7AVRIJ.js → chunk-KZ4QDBGM.js} +87 -14
- package/dist/chunk-KZ4QDBGM.js.map +1 -0
- package/dist/{chunk-PZ27MCZP.js → chunk-L5VJYZIW.js} +34 -4
- package/dist/chunk-L5VJYZIW.js.map +1 -0
- package/dist/{chunk-45XEUVTK.js → chunk-UWVY3OXF.js} +12 -5
- package/dist/chunk-UWVY3OXF.js.map +1 -0
- package/dist/components/message-timeline.d.ts +2 -2
- package/dist/composer.js +4 -4
- package/dist/connect-panel.d.ts +2 -1
- package/dist/connect-setup.d.ts +3 -1
- package/dist/connect.d.ts +1 -0
- package/dist/connect.js +620 -424
- package/dist/connect.js.map +1 -1
- package/dist/connection-catalog.d.ts +6 -1
- package/dist/connection-installed.d.ts +1 -0
- package/dist/connection-logo.d.ts +4 -2
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/hooks/use-workspace-sessions.d.ts +2 -0
- package/dist/index.js +16 -8
- package/dist/index.js.map +1 -1
- package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
- package/dist/model-policy.d.ts +6 -2
- package/dist/model-policy.js +1 -1
- package/dist/plugin-discovery.d.ts +4 -0
- package/dist/realtime.js +2 -2
- package/dist/session-ui.js +6 -6
- package/dist/session.js +1 -1
- package/dist/skill-discovery.d.ts +11 -0
- package/dist/timeline/projection.d.ts +3 -1
- package/dist/timeline/turn-summary.d.ts +2 -2
- package/package.json +3 -3
- package/src/capability-catalog-row.tsx +94 -0
- package/src/components/markdown.tsx +32 -3
- package/src/components/message-timeline.tsx +88 -4
- package/src/components/model-picker.tsx +11 -3
- package/src/components/model-policy-picker-menu.tsx +10 -3
- package/src/components/session-conversation.tsx +1 -3
- package/src/components/session-status.tsx +2 -2
- package/src/connect-panel.tsx +19 -8
- package/src/connect-setup.tsx +49 -14
- package/src/connect.ts +6 -0
- package/src/connection-catalog.tsx +61 -39
- package/src/connection-installed.tsx +2 -0
- package/src/connection-logo.tsx +14 -10
- package/src/discovery-cache.ts +42 -0
- package/src/hooks/use-session-events.ts +24 -2
- package/src/hooks/use-workspace-sessions.ts +10 -0
- package/src/model-policy.ts +21 -0
- package/src/plugin-details.tsx +8 -5
- package/src/plugin-discovery.tsx +87 -71
- package/src/skill-discovery.tsx +101 -56
- package/src/timeline/projection.ts +31 -1
- package/src/timeline/turn-summary.tsx +3 -3
- package/styles/compiled.css +27 -0
- package/styles/connect.css +56 -15
- package/dist/chunk-45XEUVTK.js.map +0 -1
- package/dist/chunk-CC7AVRIJ.js.map +0 -1
- package/dist/chunk-IHQII5WO.js.map +0 -1
- package/dist/chunk-PZ27MCZP.js.map +0 -1
- package/dist/chunk-UMECRJRV.js.map +0 -1
- /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ModelPolicyPickerMenu,
|
|
3
3
|
ModelPolicyPickerPopover
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-HJ5VJ4JB.js";
|
|
5
|
+
import "./chunk-IP4BUBKH.js";
|
|
6
6
|
import "./chunk-VZTQ73XT.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-5GAL23FF.js";
|
|
8
8
|
import "./chunk-22KP6LR5.js";
|
|
9
9
|
export {
|
|
10
10
|
ModelPolicyPickerMenu,
|
|
11
11
|
ModelPolicyPickerPopover
|
|
12
12
|
};
|
|
13
|
-
//# sourceMappingURL=model-policy-picker-menu-
|
|
13
|
+
//# sourceMappingURL=model-policy-picker-menu-RVOQ4ZZG.js.map
|
package/dist/model-policy.d.ts
CHANGED
|
@@ -33,12 +33,16 @@ export declare function projectPickerRows(models: WorkspaceModelCatalogModel[]):
|
|
|
33
33
|
export declare function projectClientModelRows(models: ClientModel[]): PickerModelRow<ClientModel>[];
|
|
34
34
|
export declare function sortPickerRows<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[]): PickerModelRow<TCatalog>[];
|
|
35
35
|
export declare function findPickerRow<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[], modelId: string): PickerModelRow<TCatalog> | null;
|
|
36
|
-
export declare function groupPickerRowsByBillingClass(rows: PickerModelRow[]
|
|
36
|
+
export declare function groupPickerRowsByBillingClass(rows: PickerModelRow[], options?: {
|
|
37
|
+
codexOnly?: boolean;
|
|
38
|
+
}): Array<{
|
|
37
39
|
billingClass: PickerBillingClass;
|
|
38
40
|
label: string;
|
|
39
41
|
rows: PickerModelRow[];
|
|
40
42
|
}>;
|
|
41
|
-
export declare function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[]
|
|
43
|
+
export declare function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[], options?: {
|
|
44
|
+
codexOnly?: boolean;
|
|
45
|
+
}): Array<{
|
|
42
46
|
billingClass: PickerBillingClass;
|
|
43
47
|
label: string;
|
|
44
48
|
rows: PickerModelRow<TCatalog>[];
|
package/dist/model-policy.js
CHANGED
|
@@ -9,6 +9,10 @@ export type PluginDiscoveryProps = {
|
|
|
9
9
|
};
|
|
10
10
|
workspaceId: string;
|
|
11
11
|
query: string;
|
|
12
|
+
/** Discovery identities already installed in the current workspace. */
|
|
13
|
+
installedIds?: ReadonlySet<string>;
|
|
14
|
+
resultLimit?: number;
|
|
15
|
+
onShowMore?: () => void;
|
|
12
16
|
onOpen: (item: PluginDiscoveryItem) => void;
|
|
13
17
|
};
|
|
14
18
|
export declare function PluginDiscovery(props: PluginDiscoveryProps): import("react").JSX.Element;
|
package/dist/realtime.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
PickerAnimatedPage,
|
|
4
4
|
PickerBackHeader,
|
|
5
5
|
PickerNavRow
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-IP4BUBKH.js";
|
|
7
7
|
import {
|
|
8
8
|
usePortalTokenSource,
|
|
9
9
|
usePortalTokenStyle
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
useEmbeddedRealtimeSession
|
|
13
13
|
} from "./chunk-NEXLLTP4.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-5GAL23FF.js";
|
|
15
15
|
import {
|
|
16
16
|
cn
|
|
17
17
|
} from "./chunk-22KP6LR5.js";
|
package/dist/session-ui.js
CHANGED
|
@@ -18,27 +18,27 @@ import {
|
|
|
18
18
|
setStartupDetails,
|
|
19
19
|
useStartupDetails,
|
|
20
20
|
userMessageLikelyNeedsDisclosure
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KZ4QDBGM.js";
|
|
22
22
|
import "./chunk-YBM6CQRU.js";
|
|
23
23
|
import "./chunk-5M6VTFJ5.js";
|
|
24
24
|
import "./chunk-ZYRGEKWS.js";
|
|
25
25
|
import "./chunk-NOSYBS2D.js";
|
|
26
26
|
import "./chunk-PAJU5KKL.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-G5JFM6ZE.js";
|
|
28
28
|
import "./chunk-JVYQRRC4.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-UWVY3OXF.js";
|
|
30
30
|
import "./chunk-HQV2I5HV.js";
|
|
31
31
|
import "./chunk-WYWRYUNQ.js";
|
|
32
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-IP4BUBKH.js";
|
|
33
33
|
import "./chunk-VZTQ73XT.js";
|
|
34
34
|
import {
|
|
35
35
|
createOlderHistoryLoadReceipt
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-L5VJYZIW.js";
|
|
37
37
|
import "./chunk-NYESIADM.js";
|
|
38
38
|
import "./chunk-PLYRL5ER.js";
|
|
39
39
|
import "./chunk-KZ73RL76.js";
|
|
40
40
|
import "./chunk-NEXLLTP4.js";
|
|
41
|
-
import "./chunk-
|
|
41
|
+
import "./chunk-5GAL23FF.js";
|
|
42
42
|
import "./chunk-22KP6LR5.js";
|
|
43
43
|
export {
|
|
44
44
|
ApprovalSurface,
|
package/dist/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CapabilityCatalogStatus } from "./capability-catalog-row.js";
|
|
1
2
|
export type SkillDiscoveryItem = {
|
|
2
3
|
id: string;
|
|
3
4
|
name: string;
|
|
@@ -23,6 +24,16 @@ export type SkillDiscoveryProps = {
|
|
|
23
24
|
query: string;
|
|
24
25
|
onImport: (url: string) => void;
|
|
25
26
|
canManage: boolean;
|
|
27
|
+
localSkills?: readonly {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
status?: CapabilityCatalogStatus;
|
|
32
|
+
statusLabel?: string;
|
|
33
|
+
onOpen: () => void;
|
|
34
|
+
}[];
|
|
35
|
+
resultLimit?: number;
|
|
36
|
+
onShowMore?: () => void;
|
|
26
37
|
onSearch?: (() => void) | undefined;
|
|
27
38
|
};
|
|
28
39
|
/** Host-owned query and import flow; browse presentation is reusable independently. */
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type SessionEvent, type SessionStatus } from "@opengeni/sdk";
|
|
2
2
|
import type { TimelineGroup, TimelineItem } from "./types.js";
|
|
3
3
|
export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name.js";
|
|
4
|
-
export declare function buildTimeline(events: SessionEvent[]
|
|
4
|
+
export declare function buildTimeline(events: SessionEvent[], options?: {
|
|
5
|
+
partialStart?: boolean;
|
|
6
|
+
}): TimelineItem[];
|
|
5
7
|
/**
|
|
6
8
|
* Codex subscription web search can return private citation handles without
|
|
7
9
|
* the URL annotation table that would make them resolvable. Keep the canonical
|
|
@@ -65,8 +65,8 @@ export type TurnSummaryProps = {
|
|
|
65
65
|
items: ActivityItem[];
|
|
66
66
|
/**
|
|
67
67
|
* The settled verdict — or absent for a completed CLUSTER of a still-running
|
|
68
|
-
* turn, which folds neutrally
|
|
69
|
-
*
|
|
68
|
+
* turn, which folds neutrally. Only unfinished activity inside this summary
|
|
69
|
+
* animates; the parent turn may still be composing a response elsewhere.
|
|
70
70
|
*/
|
|
71
71
|
outcome?: TurnOutcome | undefined;
|
|
72
72
|
/** A short failure reason shown inline on a failed chip (never hidden). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0-canary.1",
|
|
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-canary.
|
|
137
|
-
"@opengeni/sdk": "^6.
|
|
136
|
+
"@opengeni/connect": "^0.2.0-canary.12",
|
|
137
|
+
"@opengeni/sdk": "^6.1.0-canary.1",
|
|
138
138
|
"clsx": "^2.1.1",
|
|
139
139
|
"dequal": "2.0.3",
|
|
140
140
|
"lucide-react": "^1.8.0",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { AlertTriangleIcon, CheckIcon, Loader2Icon, PlusIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export type CapabilityCatalogStatus =
|
|
5
|
+
| "available"
|
|
6
|
+
| "added"
|
|
7
|
+
| "attention"
|
|
8
|
+
| "unavailable"
|
|
9
|
+
| "loading";
|
|
10
|
+
|
|
11
|
+
export type CapabilityCatalogRowProps = Omit<
|
|
12
|
+
ComponentPropsWithoutRef<"button">,
|
|
13
|
+
"children" | "onClick" | "name"
|
|
14
|
+
> & {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string | undefined;
|
|
17
|
+
icon?: ReactNode;
|
|
18
|
+
status?: CapabilityCatalogStatus;
|
|
19
|
+
statusLabel?: string | undefined;
|
|
20
|
+
/** Preserve opaque host status text when adopting this row without typed health. */
|
|
21
|
+
showStatusLabel?: boolean;
|
|
22
|
+
onOpen: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const labels: Record<CapabilityCatalogStatus, string> = {
|
|
26
|
+
available: "Available to add",
|
|
27
|
+
added: "Added",
|
|
28
|
+
attention: "Needs attention",
|
|
29
|
+
unavailable: "Unavailable",
|
|
30
|
+
loading: "Working",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A display-only state. The entire catalog row is the action, never this glyph. */
|
|
34
|
+
export function CapabilityCatalogIndicator({
|
|
35
|
+
status = "available",
|
|
36
|
+
label,
|
|
37
|
+
showLabel = false,
|
|
38
|
+
}: {
|
|
39
|
+
status?: CapabilityCatalogStatus;
|
|
40
|
+
label?: string | undefined;
|
|
41
|
+
showLabel?: boolean;
|
|
42
|
+
}) {
|
|
43
|
+
const Icon =
|
|
44
|
+
status === "added"
|
|
45
|
+
? CheckIcon
|
|
46
|
+
: status === "attention" || status === "unavailable"
|
|
47
|
+
? AlertTriangleIcon
|
|
48
|
+
: status === "loading"
|
|
49
|
+
? Loader2Icon
|
|
50
|
+
: PlusIcon;
|
|
51
|
+
return (
|
|
52
|
+
<span className="og-capability-catalog-indicator" data-status={status}>
|
|
53
|
+
<Icon aria-hidden="true" />
|
|
54
|
+
<span
|
|
55
|
+
className={
|
|
56
|
+
showLabel || status === "attention" || status === "unavailable"
|
|
57
|
+
? "og-capability-catalog-notice"
|
|
58
|
+
: "og-capability-catalog-sr-only"
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
{label ?? labels[status]}
|
|
62
|
+
</span>
|
|
63
|
+
</span>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Shared by connections, skills, and plugins: one row, one focus target, one action. */
|
|
68
|
+
export function CapabilityCatalogRow({
|
|
69
|
+
name,
|
|
70
|
+
description,
|
|
71
|
+
icon,
|
|
72
|
+
status = "available",
|
|
73
|
+
statusLabel,
|
|
74
|
+
showStatusLabel = false,
|
|
75
|
+
onOpen,
|
|
76
|
+
className = "",
|
|
77
|
+
...props
|
|
78
|
+
}: CapabilityCatalogRowProps) {
|
|
79
|
+
return (
|
|
80
|
+
<button
|
|
81
|
+
{...props}
|
|
82
|
+
type="button"
|
|
83
|
+
className={`og-capability-catalog-row ${className}`}
|
|
84
|
+
onClick={onOpen}
|
|
85
|
+
>
|
|
86
|
+
{icon ? <span className="og-capability-catalog-icon">{icon}</span> : null}
|
|
87
|
+
<span className="og-capability-catalog-copy">
|
|
88
|
+
<strong>{name}</strong>
|
|
89
|
+
{description ? <span>{description}</span> : null}
|
|
90
|
+
</span>
|
|
91
|
+
<CapabilityCatalogIndicator status={status} label={statusLabel} showLabel={showStatusLabel} />
|
|
92
|
+
</button>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -19,6 +19,7 @@ import ReactMarkdown, {
|
|
|
19
19
|
type UrlTransform,
|
|
20
20
|
} from "react-markdown";
|
|
21
21
|
import remarkGfm from "remark-gfm";
|
|
22
|
+
import { LoaderCircleIcon } from "lucide-react";
|
|
22
23
|
import { cn } from "../lib/cn";
|
|
23
24
|
import { tableElementToTsv } from "../lib/clipboard";
|
|
24
25
|
import { prefersReducedMotion } from "../lib/motion";
|
|
@@ -46,6 +47,7 @@ import { TooltipProvider } from "./tooltip";
|
|
|
46
47
|
export type MarkdownInteractiveBlock = { kind: "html" | "site"; content: string };
|
|
47
48
|
const InteractiveContext = createContext<{
|
|
48
49
|
source: string;
|
|
50
|
+
streaming?: boolean | undefined;
|
|
49
51
|
render?: (block: MarkdownInteractiveBlock) => ReactNode;
|
|
50
52
|
renderImage?: (image: { src: string; alt: string }) => ReactNode;
|
|
51
53
|
}>({ source: "" });
|
|
@@ -406,7 +408,7 @@ function fenceLanguage(children: ReactNode): string | null {
|
|
|
406
408
|
}
|
|
407
409
|
|
|
408
410
|
function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"> & ExtraProps) {
|
|
409
|
-
const { source, render } = useContext(InteractiveContext);
|
|
411
|
+
const { source, render, streaming } = useContext(InteractiveContext);
|
|
410
412
|
const language = fenceLanguage(children);
|
|
411
413
|
if (render && (language === "opengeni-html" || language === "opengeni-site")) {
|
|
412
414
|
const start = node?.position?.start.offset;
|
|
@@ -428,7 +430,33 @@ function InteractiveCodeBlock({ children, node }: ComponentPropsWithoutRef<"pre"
|
|
|
428
430
|
closing &&
|
|
429
431
|
closing.length >= opening[1]!.length &&
|
|
430
432
|
[...closing].every((c) => c === opening[1]![0]);
|
|
431
|
-
if (!complete)
|
|
433
|
+
if (!complete) {
|
|
434
|
+
return (
|
|
435
|
+
<div
|
|
436
|
+
role="status"
|
|
437
|
+
aria-live="polite"
|
|
438
|
+
aria-busy={streaming === true}
|
|
439
|
+
className="my-3 flex min-h-32 items-center gap-3 rounded-og-md bg-og-surface-1/70 px-4 py-5"
|
|
440
|
+
>
|
|
441
|
+
{streaming ? (
|
|
442
|
+
<LoaderCircleIcon
|
|
443
|
+
aria-hidden
|
|
444
|
+
className="size-5 shrink-0 text-og-fg-muted motion-safe:animate-spin"
|
|
445
|
+
/>
|
|
446
|
+
) : null}
|
|
447
|
+
<div>
|
|
448
|
+
<div className="text-og-sm font-medium text-og-fg">
|
|
449
|
+
{streaming ? "Preparing preview…" : "Preview incomplete"}
|
|
450
|
+
</div>
|
|
451
|
+
<div className="mt-1 text-og-sm text-og-fg-muted">
|
|
452
|
+
{streaming
|
|
453
|
+
? "Generating the interactive content. It will appear here when ready."
|
|
454
|
+
: "Generation stopped before the preview was ready."}
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
);
|
|
459
|
+
}
|
|
432
460
|
return (
|
|
433
461
|
<>
|
|
434
462
|
{render({
|
|
@@ -622,10 +650,11 @@ function MarkdownImpl({
|
|
|
622
650
|
const interactiveContext = useMemo(
|
|
623
651
|
() => ({
|
|
624
652
|
source: children,
|
|
653
|
+
streaming,
|
|
625
654
|
...(renderInteractiveBlock ? { render: renderInteractiveBlock } : {}),
|
|
626
655
|
...(renderImage ? { renderImage } : {}),
|
|
627
656
|
}),
|
|
628
|
-
[children, renderInteractiveBlock, renderImage],
|
|
657
|
+
[children, streaming, renderInteractiveBlock, renderImage],
|
|
629
658
|
);
|
|
630
659
|
|
|
631
660
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
@@ -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}
|
|
@@ -59,7 +59,14 @@ function providerGroups(models: ClientModel[], codexOnly: boolean) {
|
|
|
59
59
|
optionDisabled: codexOnly && !isCodexClientModel(model),
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
const groups = [...byProvider.values()];
|
|
63
|
+
if (codexOnly) {
|
|
64
|
+
const codexIndex = groups.findIndex((group) =>
|
|
65
|
+
group.models.some((model) => !model.optionDisabled && isCodexClientModel(model)),
|
|
66
|
+
);
|
|
67
|
+
if (codexIndex > 0) groups.unshift(...groups.splice(codexIndex, 1));
|
|
68
|
+
}
|
|
69
|
+
return groups;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
export function ModelPicker({
|
|
@@ -78,8 +85,9 @@ export function ModelPicker({
|
|
|
78
85
|
[rows, codexOnly],
|
|
79
86
|
);
|
|
80
87
|
const billingGroups = useMemo(
|
|
81
|
-
() =>
|
|
82
|
-
|
|
88
|
+
() =>
|
|
89
|
+
effectiveRows.length > 0 ? groupPickerRowsByBillingClass(effectiveRows, { codexOnly }) : [],
|
|
90
|
+
[effectiveRows, codexOnly],
|
|
83
91
|
);
|
|
84
92
|
const providerGroupList = useMemo(() => providerGroups(models, codexOnly), [models, codexOnly]);
|
|
85
93
|
const useBillingGroups = Boolean(effectiveRows.length);
|
|
@@ -34,7 +34,10 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
34
34
|
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${payerSummaryForModel(row.catalog)}`.toLowerCase();
|
|
35
35
|
return words.every((word) => text.includes(word));
|
|
36
36
|
});
|
|
37
|
-
const
|
|
37
|
+
const matchingIds = new Set(filtered.map((row) => row.id));
|
|
38
|
+
const groups = groupPickerRowsByBillingClass(rows, { codexOnly: props.codexOnly === true })
|
|
39
|
+
.map((group) => ({ ...group, rows: group.rows.filter((row) => matchingIds.has(row.id)) }))
|
|
40
|
+
.filter((group) => group.rows.length > 0);
|
|
38
41
|
const choose = (row: ClientPickerModelRow) => {
|
|
39
42
|
if (!row.selectable || props.disabled) return;
|
|
40
43
|
if (row.id !== props.model) props.onModelChange(row.id);
|
|
@@ -139,8 +142,12 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
139
142
|
) : (
|
|
140
143
|
<>
|
|
141
144
|
{groups.map((group) => (
|
|
142
|
-
<section
|
|
143
|
-
|
|
145
|
+
<section
|
|
146
|
+
key={group.billingClass}
|
|
147
|
+
aria-label={group.label}
|
|
148
|
+
className="py-2.5 first:pt-1 [&+section]:border-t [&+section]:border-og-border"
|
|
149
|
+
>
|
|
150
|
+
<div className="flex items-center gap-2 px-2.5 py-1.5 text-og-control font-semibold text-og-fg">
|
|
144
151
|
<BillingClassMark billingClass={group.billingClass} aria-label="" />
|
|
145
152
|
{group.label}
|
|
146
153
|
</div>
|
|
@@ -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
|
}}
|
|
@@ -12,10 +12,10 @@ export type SessionStatusMeta = {
|
|
|
12
12
|
|
|
13
13
|
export const SESSION_STATUS_META: Record<SessionStatusValue, SessionStatusMeta> = {
|
|
14
14
|
queued: {
|
|
15
|
-
label: "
|
|
15
|
+
label: "Queued",
|
|
16
16
|
dotClassName: "bg-og-status-queued",
|
|
17
17
|
badgeClassName: "text-og-fg-muted border-og-border bg-og-status-queued/10",
|
|
18
|
-
pulse:
|
|
18
|
+
pulse: false,
|
|
19
19
|
},
|
|
20
20
|
running: {
|
|
21
21
|
label: "Running",
|
package/src/connect-panel.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ConnectAccounts } from "./connect-accounts";
|
|
2
2
|
import { ConnectChooser } from "./connect-chooser";
|
|
3
3
|
import { ConnectSetup, type ConnectSetupProps } from "./connect-setup";
|
|
4
|
+
import { useConnect } from "./connect";
|
|
4
5
|
|
|
5
6
|
export type ConnectPanelProps = ConnectSetupProps & {
|
|
6
7
|
returnUrl: string;
|
|
7
|
-
|
|
8
|
+
/** Null omits the heading when the host already supplies one. */
|
|
9
|
+
title?: string | null;
|
|
8
10
|
showAccounts?: boolean;
|
|
9
11
|
presentation?: "select" | "catalog";
|
|
10
12
|
};
|
|
@@ -20,16 +22,25 @@ export function ConnectPanel({
|
|
|
20
22
|
className,
|
|
21
23
|
...setup
|
|
22
24
|
}: ConnectPanelProps) {
|
|
25
|
+
const { attempt } = useConnect(setup.controller);
|
|
26
|
+
// Unknown/failed outcomes still need reconciliation or cancellation, not a
|
|
27
|
+
// competing new acquisition. Unmounting observers never cancels controller work.
|
|
28
|
+
const activeSetup =
|
|
29
|
+
attempt !== null && !["complete", "cancelled", "expired"].includes(attempt.state);
|
|
23
30
|
return (
|
|
24
31
|
<div className={["og-connect", className].filter(Boolean).join(" ")}>
|
|
25
|
-
<h2>{title}</h2>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
{title !== null && <h2>{title}</h2>}
|
|
33
|
+
{!activeSetup && (
|
|
34
|
+
<ConnectChooser
|
|
35
|
+
presentation={presentation}
|
|
36
|
+
controller={setup.controller}
|
|
37
|
+
returnUrl={returnUrl}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
31
40
|
<ConnectSetup {...setup} />
|
|
32
|
-
{showAccounts &&
|
|
41
|
+
{showAccounts && !activeSetup && (
|
|
42
|
+
<ConnectAccounts controller={setup.controller} returnUrl={returnUrl} />
|
|
43
|
+
)}
|
|
33
44
|
</div>
|
|
34
45
|
);
|
|
35
46
|
}
|