@opengeni/react 3.7.0-canary.2 → 3.7.0-canary.4

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.
@@ -0,0 +1,13 @@
1
+ import {
2
+ ModelPolicyPickerMenu,
3
+ ModelPolicyPickerPopover
4
+ } from "./chunk-KWEZ4MPF.js";
5
+ import "./chunk-JT33KZZN.js";
6
+ import "./chunk-VZTQ73XT.js";
7
+ import "./chunk-WMGF4Z7X.js";
8
+ import "./chunk-22KP6LR5.js";
9
+ export {
10
+ ModelPolicyPickerMenu,
11
+ ModelPolicyPickerPopover
12
+ };
13
+ //# sourceMappingURL=model-policy-picker-menu-R6WXNWJL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/realtime.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  PickerAnimatedPage,
4
4
  PickerBackHeader,
5
5
  PickerNavRow
6
- } from "./chunk-4JFUT22W.js";
6
+ } from "./chunk-JT33KZZN.js";
7
7
  import {
8
8
  usePortalTokenSource,
9
9
  usePortalTokenStyle
@@ -13,12 +13,12 @@ import {
13
13
  sessionChromeGoalPillLabel,
14
14
  sessionChromeGoalPillState,
15
15
  userMessageLikelyNeedsDisclosure
16
- } from "./chunk-WZIMM3GX.js";
16
+ } from "./chunk-LUYB4SRW.js";
17
17
  import "./chunk-W3OUAH6M.js";
18
18
  import "./chunk-PAJU5KKL.js";
19
19
  import "./chunk-JVYQRRC4.js";
20
20
  import "./chunk-3IY6UZI6.js";
21
- import "./chunk-4JFUT22W.js";
21
+ import "./chunk-JT33KZZN.js";
22
22
  import "./chunk-VZTQ73XT.js";
23
23
  import {
24
24
  createOlderHistoryLoadReceipt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "3.7.0-canary.2",
3
+ "version": "3.7.0-canary.4",
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": {
@@ -120,7 +120,7 @@
120
120
  "@dnd-kit/utilities": "3.2.2",
121
121
  "@fontsource-variable/noto-sans-arabic": "5.2.10",
122
122
  "@fontsource-variable/noto-sans-jp": "5.2.10",
123
- "@opengeni/sdk": "^3.7.0-canary.2",
123
+ "@opengeni/sdk": "^3.7.0-canary.4",
124
124
  "clsx": "^2.1.1",
125
125
  "dequal": "2.0.3",
126
126
  "lucide-react": "^1.8.0",
@@ -3677,7 +3677,7 @@ function NoticeRow({ item }: { item: NoticeItem }) {
3677
3677
  {item.details ? (
3678
3678
  <details className="mt-2 text-og-control">
3679
3679
  <summary className="cursor-pointer font-medium">{item.details.label}</summary>
3680
- <pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-all rounded-og-sm bg-black/5 p-2 font-mono dark:bg-white/5">
3680
+ <pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-all rounded-og-sm bg-og-fg/5 p-2 font-mono">
3681
3681
  {JSON.stringify(item.details.value, null, 2)}
3682
3682
  </pre>
3683
3683
  </details>
@@ -0,0 +1,261 @@
1
+ import type { ClientModel, ReasoningEffort } from "@opengeni/sdk";
2
+ import { CheckIcon, SearchIcon, ZapIcon } from "lucide-react";
3
+ import { useRef, useState, type RefObject, type CSSProperties } from "react";
4
+ import { Popover } from "radix-ui";
5
+ import { cn } from "../lib/cn";
6
+ import {
7
+ coerceReasoningEffortForModel,
8
+ effortOptionsForModel,
9
+ findPickerRow,
10
+ groupPickerRowsByBillingClass,
11
+ labelReasoningEffort,
12
+ payerSummaryForModel,
13
+ runnableLatencyModesForModel,
14
+ type PickerModelRow,
15
+ } from "../model-policy";
16
+ import {
17
+ BillingClassMark,
18
+ defaultModelPolicyPickerMessages,
19
+ effectiveRows,
20
+ PickerNavRow,
21
+ type ModelPolicyPickerProps,
22
+ } from "./model-policy-picker";
23
+ type ClientPickerModelRow = PickerModelRow<ClientModel>;
24
+
25
+ /** Model selection stays flat; reasoning never becomes a navigation destination. */
26
+ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
27
+ const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
28
+ const [query, setQuery] = useState("");
29
+ const rows = effectiveRows(props);
30
+ const selected = findPickerRow(rows, props.model);
31
+ const words = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
32
+ const filtered = rows.filter((row) => {
33
+ const text =
34
+ `${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${payerSummaryForModel(row.catalog)}`.toLowerCase();
35
+ return words.every((word) => text.includes(word));
36
+ });
37
+ const groups = groupPickerRowsByBillingClass(filtered)
38
+ .map((group) => ({
39
+ ...group,
40
+ rows: group.rows.filter((row) => words.length > 0 || row.id !== selected?.id),
41
+ }))
42
+ .filter((group) => group.rows.length > 0);
43
+ const choose = (row: ClientPickerModelRow) => {
44
+ if (!row.selectable || props.disabled) return;
45
+ if (row.id !== props.model) props.onModelChange(row.id);
46
+ const effort = coerceReasoningEffortForModel(row.catalog, props.effort);
47
+ // Hosts may commit the combined model/effort draft through this callback.
48
+ props.onEffortChange(effort);
49
+ if (
50
+ props.latencyMode !== "standard" &&
51
+ !runnableLatencyModesForModel(row.catalog).includes(props.latencyMode)
52
+ ) {
53
+ props.onLatencyModeChange("standard");
54
+ }
55
+ props.onOpenChange?.(false);
56
+ };
57
+ const modelRow = (row: ClientPickerModelRow) => (
58
+ <PickerNavRow
59
+ key={row.id}
60
+ label={row.label}
61
+ hint={`${props.messages?.billingHints?.[row.billingClass] ?? payerSummaryForModel(row.catalog)}${row.unavailableReason ? ` · ${row.unavailableReason}` : ""}`}
62
+ disabled={props.disabled || !row.selectable}
63
+ title={row.unavailableReason ?? undefined}
64
+ active={row.id === props.model}
65
+ showChevron={false}
66
+ trailing={
67
+ row.id === props.model ? (
68
+ <CheckIcon className="size-3.5" aria-label={messages.selected} />
69
+ ) : null
70
+ }
71
+ testId={`model-picker-choice-${row.id}`}
72
+ onClick={() => choose(row)}
73
+ />
74
+ );
75
+ return (
76
+ <div
77
+ data-testid="model-picker-menu"
78
+ className="flex min-h-0 flex-col"
79
+ onKeyDown={(event) => {
80
+ if (
81
+ !(event.target instanceof HTMLButtonElement) ||
82
+ !["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)
83
+ )
84
+ return;
85
+ const buttons = Array.from(
86
+ event.currentTarget.querySelectorAll<HTMLButtonElement>(
87
+ '[data-testid^="model-picker-choice-"]:not(:disabled)',
88
+ ),
89
+ );
90
+ const index = buttons.indexOf(event.target);
91
+ if (index < 0) return;
92
+ event.preventDefault();
93
+ const next =
94
+ event.key === "Home"
95
+ ? 0
96
+ : event.key === "End"
97
+ ? buttons.length - 1
98
+ : (index + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
99
+ buttons[next]?.focus();
100
+ }}
101
+ >
102
+ <div className="flex items-center gap-2 border-b border-og-border px-2 py-2">
103
+ <SearchIcon className="size-4 shrink-0 text-og-fg-subtle" aria-hidden />
104
+ <input
105
+ aria-label={messages.searchLabel}
106
+ placeholder={messages.searchPlaceholder}
107
+ value={query}
108
+ onChange={(event) => setQuery(event.target.value)}
109
+ onKeyDown={(event) => {
110
+ if (event.key !== "Escape" && event.key !== "Tab") event.stopPropagation();
111
+ if (event.key === "ArrowDown") {
112
+ event.preventDefault();
113
+ event.currentTarget
114
+ .closest('[data-testid="model-picker-menu"]')
115
+ ?.querySelector<HTMLButtonElement>("button:not(:disabled)")
116
+ ?.focus();
117
+ }
118
+ }}
119
+ className="h-8 min-w-0 flex-1 bg-transparent text-og-menu text-og-fg outline-hidden placeholder:text-og-fg-subtle"
120
+ />
121
+ </div>
122
+ {props.error ? (
123
+ <p className="px-2 py-2 text-og-control text-og-status-failed" role="alert">
124
+ {props.error}
125
+ </p>
126
+ ) : null}
127
+ <div
128
+ className="min-h-0 flex-1 overflow-y-auto overscroll-contain py-1"
129
+ data-testid="model-picker-models"
130
+ >
131
+ {props.loading ? (
132
+ <p className="px-2 py-3 text-og-control text-og-fg-subtle">{messages.loading}</p>
133
+ ) : (
134
+ <>
135
+ {selected && words.length === 0 ? (
136
+ <div className="border-b border-og-border/60 pb-1 mb-1">
137
+ <p className="px-2.5 py-1.5 text-og-control text-og-fg-subtle">
138
+ {messages.currentModel}
139
+ </p>
140
+ {modelRow(selected)}
141
+ </div>
142
+ ) : null}
143
+ {groups.map((group) => (
144
+ <section key={group.billingClass} aria-label={group.label} className="py-1">
145
+ <div className="flex items-center gap-2 px-2.5 py-1.5 text-og-control font-medium text-og-fg-subtle">
146
+ <BillingClassMark billingClass={group.billingClass} aria-label="" />
147
+ {group.label}
148
+ </div>
149
+ {group.rows.map(modelRow)}
150
+ </section>
151
+ ))}
152
+ {filtered.length === 0 ? (
153
+ <p className="px-2 py-4 text-og-control text-og-fg-subtle">
154
+ {words.length ? messages.noMatches : messages.noModels}
155
+ </p>
156
+ ) : null}
157
+ </>
158
+ )}
159
+ </div>
160
+ {selected ? (
161
+ <div className="border-t border-og-border px-2 py-2">
162
+ {selected.catalog.capabilities?.inputModalities.includes("image") === false ? (
163
+ <p className="pb-1.5 text-og-control leading-relaxed text-og-fg-subtle">
164
+ {messages.unsupportedAttachments}
165
+ </p>
166
+ ) : null}
167
+ <ModelThinkingControls {...props} />
168
+ </div>
169
+ ) : null}
170
+ </div>
171
+ );
172
+ }
173
+
174
+ function ModelThinkingControls(props: ModelPolicyPickerProps) {
175
+ const selected = findPickerRow(effectiveRows(props), props.model);
176
+ if (!selected) return null;
177
+ const efforts = effortOptionsForModel(selected.catalog);
178
+ const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
179
+ const supportsFast = runnableLatencyModesForModel(selected.catalog).includes("fast");
180
+ return (
181
+ <div
182
+ className="flex items-center gap-2 text-og-control text-og-fg-muted"
183
+ data-testid="model-picker-reasoning"
184
+ >
185
+ <label className="flex min-h-9 items-center gap-2">
186
+ {messages.thinking}
187
+ <select
188
+ aria-label={messages.thinkingEffort}
189
+ value={coerceReasoningEffortForModel(selected.catalog, props.effort)}
190
+ disabled={props.disabled || !selected.selectable || efforts.length < 2}
191
+ onChange={(event) => props.onEffortChange(event.target.value as ReasoningEffort)}
192
+ className="rounded-og-sm bg-og-surface-2 px-2 py-1.5 text-og-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:opacity-50"
193
+ >
194
+ {efforts.map((effort) => (
195
+ <option key={effort} value={effort}>
196
+ {labelReasoningEffort(effort)}
197
+ </option>
198
+ ))}
199
+ </select>
200
+ </label>
201
+ {supportsFast && props.allowLatencyMode !== false ? (
202
+ <button
203
+ type="button"
204
+ data-testid="model-picker-fast"
205
+ disabled={props.disabled || !selected.selectable}
206
+ aria-pressed={props.latencyMode === "fast"}
207
+ title={`${messages.fast} · ${messages.fastRateHint}`}
208
+ onClick={() =>
209
+ props.onLatencyModeChange(props.latencyMode === "fast" ? "standard" : "fast")
210
+ }
211
+ className="ml-auto flex min-h-9 items-center gap-1.5 rounded-og-sm px-2 hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:opacity-50"
212
+ >
213
+ <ZapIcon className={cn("size-3.5", props.latencyMode === "fast" && "fill-current")} />
214
+ {messages.fast} <span className="text-og-fg-subtle">{messages.fastRateHint}</span>
215
+ </button>
216
+ ) : null}
217
+ </div>
218
+ );
219
+ }
220
+
221
+ export function ModelPolicyPickerPopover(
222
+ props: ModelPolicyPickerProps & {
223
+ anchor: RefObject<HTMLButtonElement | null>;
224
+ contentId: string;
225
+ portalStyle: CSSProperties;
226
+ },
227
+ ) {
228
+ const outside = useRef(false);
229
+ const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
230
+ return (
231
+ <Popover.Root open onOpenChange={(open) => props.onOpenChange?.(open)}>
232
+ <Popover.Anchor virtualRef={props.anchor} />
233
+ <Popover.Portal>
234
+ <Popover.Content
235
+ id={props.contentId}
236
+ aria-label={messages.label}
237
+ align="start"
238
+ side={props.menuSide ?? "bottom"}
239
+ sideOffset={8}
240
+ collisionPadding={12}
241
+ onInteractOutside={(event) => {
242
+ if (props.anchor.current?.contains(event.target as Node)) event.preventDefault();
243
+ else outside.current = true;
244
+ }}
245
+ onCloseAutoFocus={(event) => {
246
+ event.preventDefault();
247
+ if (!outside.current) props.anchor.current?.focus();
248
+ }}
249
+ className={cn(
250
+ "og-root og-model-policy-menu z-50 flex max-h-[min(32rem,var(--radix-popover-content-available-height))] w-[22rem] max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-[var(--og-model-picker-menu-padding)] text-og-fg shadow-og-lg",
251
+ props.contentClassName,
252
+ )}
253
+ style={{ ...props.portalStyle, ...props.contentStyle }}
254
+ data-testid="model-picker-content"
255
+ >
256
+ <ModelPolicyPickerMenu {...props} />
257
+ </Popover.Content>
258
+ </Popover.Portal>
259
+ </Popover.Root>
260
+ );
261
+ }