@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.
- package/README.md +15 -0
- package/dist/{chunk-4JFUT22W.js → chunk-JT33KZZN.js} +48 -245
- package/dist/chunk-JT33KZZN.js.map +1 -0
- package/dist/chunk-KWEZ4MPF.js +220 -0
- package/dist/chunk-KWEZ4MPF.js.map +1 -0
- package/dist/{chunk-WZIMM3GX.js → chunk-LUYB4SRW.js} +7 -4
- package/dist/{chunk-WZIMM3GX.js.map → chunk-LUYB4SRW.js.map} +1 -1
- package/dist/components/model-policy-picker-menu.d.ts +9 -0
- package/dist/components/model-policy-picker.d.ts +10 -15
- package/dist/composer.d.ts +2 -1
- package/dist/composer.js +4 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/model-policy-picker-menu-R6WXNWJL.js +13 -0
- package/dist/model-policy-picker-menu-R6WXNWJL.js.map +1 -0
- package/dist/realtime.js +1 -1
- package/dist/session-ui.js +2 -2
- package/package.json +2 -2
- package/src/components/message-timeline.tsx +1 -1
- package/src/components/model-policy-picker-menu.tsx +261 -0
- package/src/components/model-policy-picker.tsx +84 -346
- package/src/components/session-conversation.tsx +4 -1
- package/src/composer.ts +2 -1
- package/src/index.ts +1 -1
- package/styles/compiled.css +30 -19
- package/styles/responsive.css +1 -1
- package/dist/chunk-4JFUT22W.js.map +0 -1
|
@@ -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
package/dist/session-ui.js
CHANGED
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
sessionChromeGoalPillLabel,
|
|
14
14
|
sessionChromeGoalPillState,
|
|
15
15
|
userMessageLikelyNeedsDisclosure
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
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.
|
|
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.
|
|
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-
|
|
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
|
+
}
|