@opengeni/react 0.37.0 → 0.40.0
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 +88 -2
- package/dist/{chunk-7CJOAW3V.js → chunk-EB67J347.js} +1 -135
- package/dist/chunk-EB67J347.js.map +1 -0
- package/dist/chunk-EKZH6IDG.js +141 -0
- package/dist/chunk-EKZH6IDG.js.map +1 -0
- package/dist/{chunk-FSPDND3P.js → chunk-JALF5FI3.js} +8 -6
- package/dist/{chunk-FSPDND3P.js.map → chunk-JALF5FI3.js.map} +1 -1
- package/dist/{chunk-UCZPNXV3.js → chunk-KR2SK5GJ.js} +1292 -711
- package/dist/chunk-KR2SK5GJ.js.map +1 -0
- package/dist/{chunk-IP22SLO6.js → chunk-OMCFRWHL.js} +28 -441
- package/dist/chunk-OMCFRWHL.js.map +1 -0
- package/dist/{chunk-HJ4OQVGW.js → chunk-Q2NCKWTK.js} +10 -5
- package/dist/chunk-Q2NCKWTK.js.map +1 -0
- package/dist/{chunk-ALA3UGWB.js → chunk-SXIQK54Z.js} +8 -6
- package/dist/{chunk-ALA3UGWB.js.map → chunk-SXIQK54Z.js.map} +1 -1
- package/dist/chunk-WZT5G5OR.js +433 -0
- package/dist/chunk-WZT5G5OR.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/components/chat-composer.d.ts +5 -1
- package/dist/components/human-input-form.d.ts +16 -6
- package/dist/components/human-input-surface.d.ts +19 -0
- package/dist/components/machine-input-display.d.ts +12 -0
- package/dist/components/message-timeline.d.ts +6 -1
- package/dist/components/session-chrome.d.ts +1 -0
- package/dist/components/tip-follow.d.ts +30 -0
- package/dist/composer.js +14 -11
- package/dist/hooks/use-composer.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2246 -2236
- package/dist/index.js.map +1 -1
- package/dist/machines.js +4 -3
- package/dist/realtime/dropdown-menu.d.ts +16 -0
- package/dist/realtime/realtime-control.d.ts +126 -0
- package/dist/realtime.d.ts +11 -0
- package/dist/realtime.js +1023 -0
- package/dist/realtime.js.map +1 -0
- package/dist/session-context.d.ts +7 -1
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +5 -3
- package/dist/session.js +4 -3
- package/dist/timeline/compute-label.d.ts +2 -0
- package/dist/timeline/index.d.ts +1 -0
- package/package.json +7 -2
- package/src/client.ts +13 -0
- package/src/components/chat-composer.tsx +11 -2
- package/src/components/composer-transcription-control.tsx +2 -2
- package/src/components/composer.tsx +14 -5
- package/src/components/human-input-form.tsx +506 -186
- package/src/components/human-input-surface.tsx +80 -0
- package/src/components/machine-input-display.ts +83 -0
- package/src/components/message-timeline.tsx +348 -308
- package/src/components/model-policy-picker.tsx +8 -3
- package/src/components/sandbox-files.tsx +1 -1
- package/src/components/session-chrome.tsx +3 -1
- package/src/components/tip-follow.ts +127 -6
- package/src/hooks/use-composer.ts +16 -13
- package/src/index.ts +4 -0
- package/src/realtime/dropdown-menu.tsx +123 -0
- package/src/realtime/realtime-control.tsx +1178 -0
- package/src/realtime.ts +27 -0
- package/src/session-context.ts +16 -0
- package/src/session-ui.ts +2 -0
- package/src/timeline/compute-label.tsx +18 -0
- package/src/timeline/index.ts +3 -0
- package/src/timeline/tool-renderers.tsx +232 -43
- package/styles/index.css +61 -0
- package/styles/tokens.css +2 -0
- package/dist/chunk-7CJOAW3V.js.map +0 -1
- package/dist/chunk-HJ4OQVGW.js.map +0 -1
- package/dist/chunk-IP22SLO6.js.map +0 -1
- package/dist/chunk-UCZPNXV3.js.map +0 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import {
|
|
2
|
+
coerceReasoningEffortForModel,
|
|
3
|
+
effortOptionsForModel,
|
|
4
|
+
findPickerRow,
|
|
5
|
+
groupPickerRowsByBillingClass,
|
|
6
|
+
labelReasoningEffort,
|
|
7
|
+
projectClientModelRows,
|
|
8
|
+
runnableLatencyModesForModel
|
|
9
|
+
} from "./chunk-SJKT4TKW.js";
|
|
10
|
+
import {
|
|
11
|
+
cn
|
|
12
|
+
} from "./chunk-TK7G6XLT.js";
|
|
13
|
+
|
|
14
|
+
// src/components/model-policy-picker.tsx
|
|
15
|
+
import {
|
|
16
|
+
CheckIcon,
|
|
17
|
+
ChevronDownIcon,
|
|
18
|
+
ChevronLeftIcon,
|
|
19
|
+
ChevronRightIcon,
|
|
20
|
+
KeyRoundIcon,
|
|
21
|
+
ZapIcon
|
|
22
|
+
} from "lucide-react";
|
|
23
|
+
import { motion, useReducedMotion } from "motion/react";
|
|
24
|
+
import { DropdownMenu } from "radix-ui";
|
|
25
|
+
import { useEffect, useMemo, useState } from "react";
|
|
26
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
var defaultModelPolicyPickerMessages = {
|
|
28
|
+
label: "Model and effort",
|
|
29
|
+
loading: "Loading model catalog\u2026",
|
|
30
|
+
noModels: "No models available.",
|
|
31
|
+
thinking: "Thinking",
|
|
32
|
+
fast: "Fast",
|
|
33
|
+
fastRateHint: "2\xD7 rate",
|
|
34
|
+
codexOnly: "Codex-only session",
|
|
35
|
+
billingHints: {
|
|
36
|
+
opengeni_credits: "Will use credits",
|
|
37
|
+
codex_subscription: "ChatGPT / Codex plan",
|
|
38
|
+
byok: "Billed to your AI Gateway"
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var SLIDE_EASE = [0.22, 1, 0.36, 1];
|
|
42
|
+
function OpenGeniMark(props) {
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
"svg",
|
|
45
|
+
{
|
|
46
|
+
viewBox: "0 0 140 133",
|
|
47
|
+
fill: "none",
|
|
48
|
+
stroke: "currentColor",
|
|
49
|
+
strokeWidth: 1.2,
|
|
50
|
+
strokeLinejoin: "round",
|
|
51
|
+
"aria-hidden": "true",
|
|
52
|
+
...props,
|
|
53
|
+
children: /* @__PURE__ */ jsx("g", { transform: "translate(-17.5,-20.999893188476562) scale(1.75)", children: /* @__PURE__ */ jsx("g", { transform: "translate(0,-952.36218)", children: /* @__PURE__ */ jsx("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" }) }) })
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function ChatGptMark(props) {
|
|
58
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", ...props, children: /* @__PURE__ */ jsx("path", { d: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" }) });
|
|
59
|
+
}
|
|
60
|
+
function BillingClassMark(props) {
|
|
61
|
+
const labels = {
|
|
62
|
+
opengeni_credits: "OpenGeni",
|
|
63
|
+
codex_subscription: "Codex",
|
|
64
|
+
byok: "Bring your own key"
|
|
65
|
+
};
|
|
66
|
+
const label = props["aria-label"] ?? labels[props.billingClass];
|
|
67
|
+
const accessibility = label.length === 0 ? { "aria-hidden": true } : { role: "img", "aria-label": label };
|
|
68
|
+
const shell = cn(
|
|
69
|
+
"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle",
|
|
70
|
+
props.className
|
|
71
|
+
);
|
|
72
|
+
const mark = "size-3.5";
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
"span",
|
|
75
|
+
{
|
|
76
|
+
className: shell,
|
|
77
|
+
"data-testid": `billing-class-icon-${props.billingClass}`,
|
|
78
|
+
...accessibility,
|
|
79
|
+
children: props.billingClass === "opengeni_credits" ? /* @__PURE__ */ jsx(OpenGeniMark, { className: mark }) : props.billingClass === "codex_subscription" ? /* @__PURE__ */ jsx(ChatGptMark, { className: mark }) : /* @__PURE__ */ jsx(KeyRoundIcon, { className: mark, "aria-hidden": true })
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function isCodexModel(model) {
|
|
84
|
+
return model.id.startsWith("codex/") || model.source === "codex";
|
|
85
|
+
}
|
|
86
|
+
function applyCodexOnly(rows, codexOnly, unavailableReason) {
|
|
87
|
+
if (!codexOnly) return rows;
|
|
88
|
+
return rows.map(
|
|
89
|
+
(row) => isCodexModel(row.catalog) ? row : {
|
|
90
|
+
...row,
|
|
91
|
+
selectable: false,
|
|
92
|
+
unavailableReason: row.unavailableReason ?? unavailableReason
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function effectiveRows(props) {
|
|
97
|
+
const rows = props.rows !== void 0 ? props.rows : projectClientModelRows(props.models ?? []);
|
|
98
|
+
const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
|
|
99
|
+
return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly);
|
|
100
|
+
}
|
|
101
|
+
function defaultNavState(rows, modelId) {
|
|
102
|
+
const selected = findPickerRow(rows, modelId);
|
|
103
|
+
if (!selected) {
|
|
104
|
+
const rails = new Set(rows.map((row) => row.billingClass));
|
|
105
|
+
const onlyRail = rails.size === 1 ? rows[0]?.billingClass : null;
|
|
106
|
+
return onlyRail ? { level: "models", rail: onlyRail, modelId: null } : { level: "providers", rail: null, modelId: null };
|
|
107
|
+
}
|
|
108
|
+
return { level: "thinking", rail: selected.billingClass, modelId: selected.id };
|
|
109
|
+
}
|
|
110
|
+
function usePickerNavState(sessionKey, rows, model) {
|
|
111
|
+
const [state, setState] = useState(() => defaultNavState(rows, model));
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
setState(defaultNavState(rows, model));
|
|
114
|
+
}, [sessionKey]);
|
|
115
|
+
return [state, setState];
|
|
116
|
+
}
|
|
117
|
+
function PickerNavRow(props) {
|
|
118
|
+
return /* @__PURE__ */ jsxs(
|
|
119
|
+
"button",
|
|
120
|
+
{
|
|
121
|
+
type: "button",
|
|
122
|
+
disabled: props.disabled,
|
|
123
|
+
title: props.title,
|
|
124
|
+
onClick: props.onClick,
|
|
125
|
+
"data-testid": props.testId,
|
|
126
|
+
className: cn(
|
|
127
|
+
"flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-2.5 py-2 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
|
|
128
|
+
props.disabled && "cursor-not-allowed opacity-50"
|
|
129
|
+
),
|
|
130
|
+
children: [
|
|
131
|
+
props.icon,
|
|
132
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
133
|
+
/* @__PURE__ */ jsx("span", { className: cn("block truncate text-sm", props.active && "font-medium"), children: props.label }),
|
|
134
|
+
props.hint ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-xs text-og-fg-subtle", children: props.hint }) : null
|
|
135
|
+
] }),
|
|
136
|
+
props.trailing ? /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0", children: props.trailing }) : null,
|
|
137
|
+
props.showChevron === false ? null : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle" })
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function PickerBackHeader(props) {
|
|
143
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-0.5 border-b border-og-border/70 px-0.5 pb-1.5", children: [
|
|
144
|
+
/* @__PURE__ */ jsxs(
|
|
145
|
+
"button",
|
|
146
|
+
{
|
|
147
|
+
type: "button",
|
|
148
|
+
onClick: props.onBack,
|
|
149
|
+
"data-testid": "model-picker-back",
|
|
150
|
+
className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle" }),
|
|
153
|
+
props.icon,
|
|
154
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: props.label })
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
props.trailing ? /* @__PURE__ */ jsx("div", { className: "relative z-10 shrink-0", children: props.trailing }) : null
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
function PickerAnimatedPage(props) {
|
|
162
|
+
const reduceMotion = useReducedMotion();
|
|
163
|
+
return /* @__PURE__ */ jsx(
|
|
164
|
+
motion.div,
|
|
165
|
+
{
|
|
166
|
+
initial: reduceMotion ? false : { x: props.direction * 14, opacity: 0 },
|
|
167
|
+
animate: { x: 0, opacity: 1 },
|
|
168
|
+
transition: { duration: reduceMotion ? 0 : 0.18, ease: SLIDE_EASE },
|
|
169
|
+
className: "w-full",
|
|
170
|
+
children: props.children
|
|
171
|
+
},
|
|
172
|
+
props.pageKey
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
function ModelPolicyPickerMenu(props) {
|
|
176
|
+
const messages = useMemo(
|
|
177
|
+
() => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),
|
|
178
|
+
[props.messages]
|
|
179
|
+
);
|
|
180
|
+
const rows = effectiveRows(props);
|
|
181
|
+
const groups = useMemo(() => groupPickerRowsByBillingClass(rows), [rows]);
|
|
182
|
+
const [localNav, setLocalNav] = usePickerNavState(
|
|
183
|
+
props.nav === void 0 ? props.sessionKey : void 0,
|
|
184
|
+
rows,
|
|
185
|
+
props.model
|
|
186
|
+
);
|
|
187
|
+
const nav = props.nav ?? localNav;
|
|
188
|
+
const setNav = props.onNavChange ?? setLocalNav;
|
|
189
|
+
const [direction, setDirection] = useState(1);
|
|
190
|
+
const effectiveNav = nav.level === "providers" && groups.length === 1 ? { level: "models", rail: groups[0].billingClass, modelId: null } : nav;
|
|
191
|
+
const activeGroup = effectiveNav.rail === null ? void 0 : groups.find((group) => group.billingClass === effectiveNav.rail);
|
|
192
|
+
const focusModel = effectiveNav.modelId === null ? void 0 : findPickerRow(rows, effectiveNav.modelId);
|
|
193
|
+
const selectedRow = findPickerRow(rows, props.model);
|
|
194
|
+
const { latencyMode, loading, onLatencyModeChange } = props;
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (loading || !selectedRow?.selectable) return;
|
|
197
|
+
const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(
|
|
198
|
+
latencyMode
|
|
199
|
+
);
|
|
200
|
+
if (!supportsCurrentMode && latencyMode !== "standard") {
|
|
201
|
+
onLatencyModeChange("standard");
|
|
202
|
+
}
|
|
203
|
+
}, [latencyMode, loading, onLatencyModeChange, selectedRow]);
|
|
204
|
+
const go = (next, nextDirection) => {
|
|
205
|
+
setDirection(nextDirection);
|
|
206
|
+
setNav(next);
|
|
207
|
+
};
|
|
208
|
+
const selectEffort = (row, effort) => {
|
|
209
|
+
if (!row.selectable) return;
|
|
210
|
+
if (row.id !== props.model) props.onModelChange(row.id);
|
|
211
|
+
props.onEffortChange(coerceReasoningEffortForModel(row.catalog, effort));
|
|
212
|
+
};
|
|
213
|
+
const pageKey = effectiveNav.level === "providers" ? "providers" : effectiveNav.level === "models" ? `models:${effectiveNav.rail ?? ""}` : `thinking:${effectiveNav.modelId ?? ""}`;
|
|
214
|
+
let body;
|
|
215
|
+
if (props.loading) {
|
|
216
|
+
body = /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-og-fg-subtle", children: messages.loading });
|
|
217
|
+
} else if (groups.length === 0) {
|
|
218
|
+
body = /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-og-fg-subtle", children: messages.noModels });
|
|
219
|
+
} else if (effectiveNav.level === "providers") {
|
|
220
|
+
body = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "model-picker-providers", children: groups.map((group) => /* @__PURE__ */ jsx(
|
|
221
|
+
PickerNavRow,
|
|
222
|
+
{
|
|
223
|
+
label: group.label,
|
|
224
|
+
hint: messages.billingHints[group.billingClass],
|
|
225
|
+
icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: group.billingClass }),
|
|
226
|
+
active: selectedRow?.billingClass === group.billingClass,
|
|
227
|
+
testId: `model-picker-rail-${group.billingClass}`,
|
|
228
|
+
onClick: () => go({ level: "models", rail: group.billingClass, modelId: null }, 1)
|
|
229
|
+
},
|
|
230
|
+
group.billingClass
|
|
231
|
+
)) });
|
|
232
|
+
} else if (effectiveNav.level === "models" && activeGroup) {
|
|
233
|
+
body = /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-models", children: [
|
|
234
|
+
groups.length > 1 ? /* @__PURE__ */ jsx(
|
|
235
|
+
PickerBackHeader,
|
|
236
|
+
{
|
|
237
|
+
label: activeGroup.label,
|
|
238
|
+
icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: activeGroup.billingClass }),
|
|
239
|
+
onBack: () => go({ level: "providers", rail: null, modelId: null }, -1)
|
|
240
|
+
}
|
|
241
|
+
) : null,
|
|
242
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: activeGroup.rows.map((row) => /* @__PURE__ */ jsx(
|
|
243
|
+
PickerNavRow,
|
|
244
|
+
{
|
|
245
|
+
label: row.label,
|
|
246
|
+
hint: row.unavailableReason ?? void 0,
|
|
247
|
+
disabled: !row.selectable,
|
|
248
|
+
title: row.unavailableReason ?? void 0,
|
|
249
|
+
active: row.id === props.model,
|
|
250
|
+
testId: `model-picker-choice-${row.id}`,
|
|
251
|
+
onClick: () => {
|
|
252
|
+
if (row.selectable) {
|
|
253
|
+
go({ level: "thinking", rail: row.billingClass, modelId: row.id }, 1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
`${row.billingClass}:${row.id}`
|
|
258
|
+
)) })
|
|
259
|
+
] });
|
|
260
|
+
} else if (effectiveNav.level === "thinking" && focusModel) {
|
|
261
|
+
const activeModel = focusModel.id === props.model;
|
|
262
|
+
const supportsFast = runnableLatencyModesForModel(focusModel.catalog).includes("fast");
|
|
263
|
+
body = /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-reasoning", children: [
|
|
264
|
+
/* @__PURE__ */ jsx(
|
|
265
|
+
PickerBackHeader,
|
|
266
|
+
{
|
|
267
|
+
label: focusModel.label,
|
|
268
|
+
icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: focusModel.billingClass }),
|
|
269
|
+
onBack: () => go({ level: "models", rail: focusModel.billingClass, modelId: null }, -1),
|
|
270
|
+
trailing: supportsFast ? /* @__PURE__ */ jsx(
|
|
271
|
+
"button",
|
|
272
|
+
{
|
|
273
|
+
type: "button",
|
|
274
|
+
disabled: !focusModel.selectable,
|
|
275
|
+
"aria-pressed": activeModel && props.latencyMode === "fast",
|
|
276
|
+
"aria-label": activeModel && props.latencyMode === "fast" ? `Disable ${messages.fast}` : `Enable ${messages.fast}`,
|
|
277
|
+
title: `${messages.fast} \xB7 ${messages.fastRateHint}`,
|
|
278
|
+
"data-testid": "model-picker-fast",
|
|
279
|
+
onClick: () => {
|
|
280
|
+
if (!activeModel) {
|
|
281
|
+
props.onModelChange(focusModel.id);
|
|
282
|
+
props.onEffortChange(
|
|
283
|
+
coerceReasoningEffortForModel(focusModel.catalog, props.effort)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
props.onLatencyModeChange(
|
|
287
|
+
activeModel && props.latencyMode === "fast" ? "standard" : "fast"
|
|
288
|
+
);
|
|
289
|
+
},
|
|
290
|
+
className: cn(
|
|
291
|
+
"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
|
|
292
|
+
activeModel && props.latencyMode === "fast" && "text-og-fg"
|
|
293
|
+
),
|
|
294
|
+
children: /* @__PURE__ */ jsx(
|
|
295
|
+
ZapIcon,
|
|
296
|
+
{
|
|
297
|
+
className: cn(
|
|
298
|
+
"size-3.5",
|
|
299
|
+
activeModel && props.latencyMode === "fast" && "fill-current"
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
) : null
|
|
305
|
+
}
|
|
306
|
+
),
|
|
307
|
+
/* @__PURE__ */ jsx("p", { className: "px-2.5 pt-1 pb-1 text-xs font-medium tracking-wide text-og-fg-subtle uppercase", children: messages.thinking }),
|
|
308
|
+
focusModel.catalog.capabilities?.inputModalities.includes("image") === false ? /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-1.5 text-xs leading-relaxed text-og-fg-subtle", children: "Unsupported attachments stay in the session but are hidden from this model." }) : null,
|
|
309
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: effortOptionsForModel(focusModel.catalog).map((effort) => {
|
|
310
|
+
const selected = activeModel && effort === props.effort;
|
|
311
|
+
return /* @__PURE__ */ jsxs(
|
|
312
|
+
"button",
|
|
313
|
+
{
|
|
314
|
+
type: "button",
|
|
315
|
+
disabled: !focusModel.selectable,
|
|
316
|
+
onClick: () => selectEffort(focusModel, effort),
|
|
317
|
+
className: cn(
|
|
318
|
+
"flex h-8 w-full cursor-pointer items-center rounded-og-sm px-2.5 text-left text-sm text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
|
|
319
|
+
selected && "bg-og-surface-2"
|
|
320
|
+
),
|
|
321
|
+
children: [
|
|
322
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: labelReasoningEffort(effort) }),
|
|
323
|
+
selected ? /* @__PURE__ */ jsx(
|
|
324
|
+
CheckIcon,
|
|
325
|
+
{
|
|
326
|
+
className: "ml-auto size-3.5 shrink-0",
|
|
327
|
+
"data-testid": "model-picker-effort-check"
|
|
328
|
+
}
|
|
329
|
+
) : null
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
effort
|
|
333
|
+
);
|
|
334
|
+
}) })
|
|
335
|
+
] });
|
|
336
|
+
} else {
|
|
337
|
+
body = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "model-picker-providers", children: groups.map((group) => /* @__PURE__ */ jsx(
|
|
338
|
+
PickerNavRow,
|
|
339
|
+
{
|
|
340
|
+
label: group.label,
|
|
341
|
+
hint: messages.billingHints[group.billingClass],
|
|
342
|
+
icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: group.billingClass }),
|
|
343
|
+
testId: `model-picker-rail-${group.billingClass}`,
|
|
344
|
+
onClick: () => go({ level: "models", rail: group.billingClass, modelId: null }, 1)
|
|
345
|
+
},
|
|
346
|
+
group.billingClass
|
|
347
|
+
)) });
|
|
348
|
+
}
|
|
349
|
+
return /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-menu", className: "relative overflow-hidden", children: [
|
|
350
|
+
props.error ? /* @__PURE__ */ jsx("p", { className: "px-2 py-1 text-xs text-og-status-failed", role: "alert", children: props.error }) : null,
|
|
351
|
+
/* @__PURE__ */ jsx(PickerAnimatedPage, { pageKey, direction, children: body })
|
|
352
|
+
] });
|
|
353
|
+
}
|
|
354
|
+
function ModelPolicyPicker(props) {
|
|
355
|
+
const [open, setOpen] = useState(false);
|
|
356
|
+
const messages = useMemo(
|
|
357
|
+
() => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),
|
|
358
|
+
[props.messages]
|
|
359
|
+
);
|
|
360
|
+
const rows = effectiveRows(props);
|
|
361
|
+
const [nav, setNav] = usePickerNavState(props.sessionKey, rows, props.model);
|
|
362
|
+
const selected = findPickerRow(rows, props.model);
|
|
363
|
+
if (props.loading) {
|
|
364
|
+
return /* @__PURE__ */ jsx(
|
|
365
|
+
"span",
|
|
366
|
+
{
|
|
367
|
+
className: cn(
|
|
368
|
+
"inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2",
|
|
369
|
+
props.className
|
|
370
|
+
),
|
|
371
|
+
"aria-label": messages.loading,
|
|
372
|
+
"data-testid": "model-picker-loading"
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Root, { open, onOpenChange: setOpen, children: [
|
|
377
|
+
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
378
|
+
"button",
|
|
379
|
+
{
|
|
380
|
+
type: "button",
|
|
381
|
+
disabled: props.disabled,
|
|
382
|
+
"aria-label": messages.label,
|
|
383
|
+
className: cn(
|
|
384
|
+
"inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-7 max-sm:max-w-[9rem] max-sm:px-2",
|
|
385
|
+
props.className
|
|
386
|
+
),
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ jsx(
|
|
389
|
+
BillingClassMark,
|
|
390
|
+
{
|
|
391
|
+
billingClass: selected?.billingClass ?? (props.model.startsWith("codex/") ? "codex_subscription" : "opengeni_credits"),
|
|
392
|
+
className: "text-og-fg"
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-og-fg", children: selected?.label ?? props.model }),
|
|
396
|
+
/* @__PURE__ */ jsx("span", { className: "max-sm:hidden", children: labelReasoningEffort(props.effort) }),
|
|
397
|
+
props.latencyMode === "fast" ? /* @__PURE__ */ jsx(
|
|
398
|
+
ZapIcon,
|
|
399
|
+
{
|
|
400
|
+
className: "size-3.5 shrink-0 fill-current stroke-current text-og-fg",
|
|
401
|
+
"aria-label": messages.fast,
|
|
402
|
+
"data-testid": "model-picker-fast-icon"
|
|
403
|
+
}
|
|
404
|
+
) : null,
|
|
405
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0" })
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
) }),
|
|
409
|
+
/* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx(
|
|
410
|
+
DropdownMenu.Content,
|
|
411
|
+
{
|
|
412
|
+
align: "start",
|
|
413
|
+
side: props.menuSide ?? "bottom",
|
|
414
|
+
sideOffset: 8,
|
|
415
|
+
collisionPadding: 12,
|
|
416
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
417
|
+
className: "z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-72 max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg",
|
|
418
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: /* @__PURE__ */ jsx(ModelPolicyPickerMenu, { ...props, nav, onNavChange: setNav }) })
|
|
419
|
+
}
|
|
420
|
+
) })
|
|
421
|
+
] });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export {
|
|
425
|
+
defaultModelPolicyPickerMessages,
|
|
426
|
+
BillingClassMark,
|
|
427
|
+
PickerNavRow,
|
|
428
|
+
PickerBackHeader,
|
|
429
|
+
PickerAnimatedPage,
|
|
430
|
+
ModelPolicyPickerMenu,
|
|
431
|
+
ModelPolicyPicker
|
|
432
|
+
};
|
|
433
|
+
//# sourceMappingURL=chunk-WZT5G5OR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/model-policy-picker.tsx"],"sourcesContent":["import type { ClientModel, LatencyMode, ReasoningEffort } from \"@opengeni/sdk\";\nimport {\n CheckIcon,\n ChevronDownIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n KeyRoundIcon,\n ZapIcon,\n} from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { DropdownMenu } from \"radix-ui\";\nimport { useEffect, useMemo, useState, type ReactNode, type SVGProps } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n coerceReasoningEffortForModel,\n effortOptionsForModel,\n findPickerRow,\n groupPickerRowsByBillingClass,\n labelReasoningEffort,\n projectClientModelRows,\n runnableLatencyModesForModel,\n type PickerBillingClass,\n type PickerModelRow,\n} from \"../model-policy\";\n\ntype ClientPickerModelRow = PickerModelRow<ClientModel>;\n\ntype NavLevel = \"providers\" | \"models\" | \"thinking\";\n\ntype PickerNavState = {\n level: NavLevel;\n rail: PickerBillingClass | null;\n modelId: string | null;\n};\n\ntype NavUpdater = PickerNavState | ((previous: PickerNavState) => PickerNavState);\n\nexport type ModelPolicyPickerMessages = {\n label: string;\n loading: string;\n noModels: string;\n thinking: string;\n fast: string;\n fastRateHint: string;\n codexOnly: string;\n billingHints: Record<PickerBillingClass, string>;\n};\n\nexport const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {\n label: \"Model and effort\",\n loading: \"Loading model catalog…\",\n noModels: \"No models available.\",\n thinking: \"Thinking\",\n fast: \"Fast\",\n fastRateHint: \"2× rate\",\n codexOnly: \"Codex-only session\",\n billingHints: {\n opengeni_credits: \"Will use credits\",\n codex_subscription: \"ChatGPT / Codex plan\",\n byok: \"Billed to your AI Gateway\",\n },\n};\n\nexport type ModelPolicyPickerProps = {\n /** Lightweight deployment models. Catalog rows take precedence when supplied. */\n models?: ClientModel[] | undefined;\n /** Catalog-backed rows with availability and billing-class truth. */\n rows?: PickerModelRow[] | undefined;\n model: string;\n effort: ReasoningEffort;\n latencyMode: LatencyMode;\n disabled?: boolean | undefined;\n loading?: boolean | undefined;\n error?: string | null | undefined;\n /** Non-Codex models stay visible but cannot be selected. */\n codexOnly?: boolean | undefined;\n /** Resets drill-down navigation when the session scope changes. */\n sessionKey?: string | undefined;\n /** Prefer bottom on new-chat surfaces and top for bottom-docked composers. */\n menuSide?: \"top\" | \"bottom\" | undefined;\n className?: string | undefined;\n messages?: Partial<ModelPolicyPickerMessages> | undefined;\n onModelChange: (modelId: string) => void;\n onEffortChange: (effort: ReasoningEffort) => void;\n onLatencyModeChange: (latencyMode: LatencyMode) => void;\n};\n\nconst SLIDE_EASE = [0.22, 1, 0.36, 1] as const;\n\nfunction OpenGeniMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg\n viewBox=\"0 0 140 133\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.2}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n {...props}\n >\n <g transform=\"translate(-17.5,-20.999893188476562) scale(1.75)\">\n <g transform=\"translate(0,-952.36218)\">\n <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\" />\n </g>\n </g>\n </svg>\n );\n}\n\nfunction ChatGptMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n <path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z\" />\n </svg>\n );\n}\n\nexport function BillingClassMark(props: {\n billingClass: PickerBillingClass;\n className?: string | undefined;\n \"aria-label\"?: string | undefined;\n}) {\n const labels: Record<PickerBillingClass, string> = {\n opengeni_credits: \"OpenGeni\",\n codex_subscription: \"Codex\",\n byok: \"Bring your own key\",\n };\n const label = props[\"aria-label\"] ?? labels[props.billingClass];\n const accessibility =\n label.length === 0\n ? { \"aria-hidden\": true as const }\n : { role: \"img\" as const, \"aria-label\": label };\n const shell = cn(\n \"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle\",\n props.className,\n );\n const mark = \"size-3.5\";\n return (\n <span\n className={shell}\n data-testid={`billing-class-icon-${props.billingClass}`}\n {...accessibility}\n >\n {props.billingClass === \"opengeni_credits\" ? (\n <OpenGeniMark className={mark} />\n ) : props.billingClass === \"codex_subscription\" ? (\n <ChatGptMark className={mark} />\n ) : (\n <KeyRoundIcon className={mark} aria-hidden />\n )}\n </span>\n );\n}\n\nfunction isCodexModel(model: ClientModel): boolean {\n return model.id.startsWith(\"codex/\") || model.source === \"codex\";\n}\n\nfunction applyCodexOnly(\n rows: ClientPickerModelRow[],\n codexOnly: boolean,\n unavailableReason: string,\n): ClientPickerModelRow[] {\n if (!codexOnly) return rows;\n return rows.map((row) =>\n isCodexModel(row.catalog)\n ? row\n : {\n ...row,\n selectable: false,\n unavailableReason: row.unavailableReason ?? unavailableReason,\n },\n );\n}\n\nfunction effectiveRows(props: ModelPolicyPickerProps): ClientPickerModelRow[] {\n const rows = props.rows !== undefined ? props.rows : projectClientModelRows(props.models ?? []);\n const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };\n return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly);\n}\n\nfunction defaultNavState(rows: ClientPickerModelRow[], modelId: string): PickerNavState {\n const selected = findPickerRow(rows, modelId);\n if (!selected) {\n const rails = new Set(rows.map((row) => row.billingClass));\n const onlyRail = rails.size === 1 ? rows[0]?.billingClass : null;\n return onlyRail\n ? { level: \"models\", rail: onlyRail, modelId: null }\n : { level: \"providers\", rail: null, modelId: null };\n }\n return { level: \"thinking\", rail: selected.billingClass, modelId: selected.id };\n}\n\nfunction usePickerNavState(\n sessionKey: string | undefined,\n rows: ClientPickerModelRow[],\n model: string,\n): [PickerNavState, (next: NavUpdater) => void] {\n const [state, setState] = useState<PickerNavState>(() => defaultNavState(rows, model));\n useEffect(() => {\n setState(defaultNavState(rows, model));\n // The menu deliberately preserves its drill-down while the same session is mounted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sessionKey]);\n return [state, setState];\n}\n\nexport function PickerNavRow(props: {\n label: string;\n hint?: string | undefined;\n icon?: ReactNode;\n trailing?: ReactNode;\n showChevron?: boolean | undefined;\n disabled?: boolean | undefined;\n title?: string | undefined;\n active?: boolean | undefined;\n testId?: string | undefined;\n onClick: () => void;\n}) {\n return (\n <button\n type=\"button\"\n disabled={props.disabled}\n title={props.title}\n onClick={props.onClick}\n data-testid={props.testId}\n className={cn(\n \"flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-2.5 py-2 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\",\n props.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {props.icon}\n <span className=\"min-w-0 flex-1\">\n <span className={cn(\"block truncate text-sm\", props.active && \"font-medium\")}>\n {props.label}\n </span>\n {props.hint ? (\n <span className=\"mt-0.5 block truncate text-xs text-og-fg-subtle\">{props.hint}</span>\n ) : null}\n </span>\n {props.trailing ? <span className=\"ml-auto shrink-0\">{props.trailing}</span> : null}\n {props.showChevron === false ? null : (\n <ChevronRightIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n )}\n </button>\n );\n}\n\nexport function PickerBackHeader(props: {\n label: string;\n icon?: ReactNode;\n onBack: () => void;\n trailing?: ReactNode;\n}) {\n return (\n <div className=\"mb-1 flex items-center gap-0.5 border-b border-og-border/70 px-0.5 pb-1.5\">\n <button\n type=\"button\"\n onClick={props.onBack}\n data-testid=\"model-picker-back\"\n className=\"flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\"\n >\n <ChevronLeftIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n {props.icon}\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">{props.label}</span>\n </button>\n {props.trailing ? <div className=\"relative z-10 shrink-0\">{props.trailing}</div> : null}\n </div>\n );\n}\n\nexport function PickerAnimatedPage(props: {\n pageKey: string;\n direction: 1 | -1;\n children: ReactNode;\n}) {\n const reduceMotion = useReducedMotion();\n return (\n <motion.div\n key={props.pageKey}\n initial={reduceMotion ? false : { x: props.direction * 14, opacity: 0 }}\n animate={{ x: 0, opacity: 1 }}\n transition={{ duration: reduceMotion ? 0 : 0.18, ease: SLIDE_EASE }}\n className=\"w-full\"\n >\n {props.children}\n </motion.div>\n );\n}\n\nexport function ModelPolicyPickerMenu(\n props: ModelPolicyPickerProps & {\n nav?: PickerNavState | undefined;\n onNavChange?: ((next: NavUpdater) => void) | undefined;\n },\n) {\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const groups = useMemo(() => groupPickerRowsByBillingClass(rows), [rows]);\n const [localNav, setLocalNav] = usePickerNavState(\n props.nav === undefined ? props.sessionKey : undefined,\n rows,\n props.model,\n );\n const nav = props.nav ?? localNav;\n const setNav = props.onNavChange ?? setLocalNav;\n const [direction, setDirection] = useState<1 | -1>(1);\n\n const effectiveNav =\n nav.level === \"providers\" && groups.length === 1\n ? { level: \"models\" as const, rail: groups[0]!.billingClass, modelId: null }\n : nav;\n const activeGroup =\n effectiveNav.rail === null\n ? undefined\n : groups.find((group) => group.billingClass === effectiveNav.rail);\n const focusModel =\n effectiveNav.modelId === null ? undefined : findPickerRow(rows, effectiveNav.modelId);\n const selectedRow = findPickerRow(rows, props.model);\n const { latencyMode, loading, onLatencyModeChange } = props;\n\n useEffect(() => {\n if (loading || !selectedRow?.selectable) return;\n const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(\n latencyMode,\n );\n if (!supportsCurrentMode && latencyMode !== \"standard\") {\n onLatencyModeChange(\"standard\");\n }\n }, [latencyMode, loading, onLatencyModeChange, selectedRow]);\n\n const go = (next: PickerNavState, nextDirection: 1 | -1) => {\n setDirection(nextDirection);\n setNav(next);\n };\n\n const selectEffort = (row: ClientPickerModelRow, effort: ReasoningEffort) => {\n if (!row.selectable) return;\n if (row.id !== props.model) props.onModelChange(row.id);\n props.onEffortChange(coerceReasoningEffortForModel(row.catalog, effort));\n };\n\n const pageKey =\n effectiveNav.level === \"providers\"\n ? \"providers\"\n : effectiveNav.level === \"models\"\n ? `models:${effectiveNav.rail ?? \"\"}`\n : `thinking:${effectiveNav.modelId ?? \"\"}`;\n\n let body: ReactNode;\n if (props.loading) {\n body = <p className=\"px-2 py-3 text-xs text-og-fg-subtle\">{messages.loading}</p>;\n } else if (groups.length === 0) {\n body = <p className=\"px-2 py-3 text-xs text-og-fg-subtle\">{messages.noModels}</p>;\n } else if (effectiveNav.level === \"providers\") {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n active={selectedRow?.billingClass === group.billingClass}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n } else if (effectiveNav.level === \"models\" && activeGroup) {\n body = (\n <div data-testid=\"model-picker-models\">\n {groups.length > 1 ? (\n <PickerBackHeader\n label={activeGroup.label}\n icon={<BillingClassMark billingClass={activeGroup.billingClass} />}\n onBack={() => go({ level: \"providers\", rail: null, modelId: null }, -1)}\n />\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {activeGroup.rows.map((row) => (\n <PickerNavRow\n key={`${row.billingClass}:${row.id}`}\n label={row.label}\n hint={row.unavailableReason ?? undefined}\n disabled={!row.selectable}\n title={row.unavailableReason ?? undefined}\n active={row.id === props.model}\n testId={`model-picker-choice-${row.id}`}\n onClick={() => {\n if (row.selectable) {\n go({ level: \"thinking\", rail: row.billingClass, modelId: row.id }, 1);\n }\n }}\n />\n ))}\n </div>\n </div>\n );\n } else if (effectiveNav.level === \"thinking\" && focusModel) {\n const activeModel = focusModel.id === props.model;\n const supportsFast = runnableLatencyModesForModel(focusModel.catalog).includes(\"fast\");\n body = (\n <div data-testid=\"model-picker-reasoning\">\n <PickerBackHeader\n label={focusModel.label}\n icon={<BillingClassMark billingClass={focusModel.billingClass} />}\n onBack={() => go({ level: \"models\", rail: focusModel.billingClass, modelId: null }, -1)}\n trailing={\n supportsFast ? (\n <button\n type=\"button\"\n disabled={!focusModel.selectable}\n aria-pressed={activeModel && props.latencyMode === \"fast\"}\n aria-label={\n activeModel && props.latencyMode === \"fast\"\n ? `Disable ${messages.fast}`\n : `Enable ${messages.fast}`\n }\n title={`${messages.fast} · ${messages.fastRateHint}`}\n data-testid=\"model-picker-fast\"\n onClick={() => {\n if (!activeModel) {\n props.onModelChange(focusModel.id);\n props.onEffortChange(\n coerceReasoningEffortForModel(focusModel.catalog, props.effort),\n );\n }\n props.onLatencyModeChange(\n activeModel && props.latencyMode === \"fast\" ? \"standard\" : \"fast\",\n );\n }}\n className={cn(\n \"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n activeModel && props.latencyMode === \"fast\" && \"text-og-fg\",\n )}\n >\n <ZapIcon\n className={cn(\n \"size-3.5\",\n activeModel && props.latencyMode === \"fast\" && \"fill-current\",\n )}\n />\n </button>\n ) : null\n }\n />\n <p className=\"px-2.5 pt-1 pb-1 text-xs font-medium tracking-wide text-og-fg-subtle uppercase\">\n {messages.thinking}\n </p>\n {focusModel.catalog.capabilities?.inputModalities.includes(\"image\") === false ? (\n <p className=\"px-2.5 pb-1.5 text-xs leading-relaxed text-og-fg-subtle\">\n Unsupported attachments stay in the session but are hidden from this model.\n </p>\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {effortOptionsForModel(focusModel.catalog).map((effort) => {\n const selected = activeModel && effort === props.effort;\n return (\n <button\n key={effort}\n type=\"button\"\n disabled={!focusModel.selectable}\n onClick={() => selectEffort(focusModel, effort)}\n className={cn(\n \"flex h-8 w-full cursor-pointer items-center rounded-og-sm px-2.5 text-left text-sm text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n selected && \"bg-og-surface-2\",\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">{labelReasoningEffort(effort)}</span>\n {selected ? (\n <CheckIcon\n className=\"ml-auto size-3.5 shrink-0\"\n data-testid=\"model-picker-effort-check\"\n />\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n );\n } else {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div data-testid=\"model-picker-menu\" className=\"relative overflow-hidden\">\n {props.error ? (\n <p className=\"px-2 py-1 text-xs text-og-status-failed\" role=\"alert\">\n {props.error}\n </p>\n ) : null}\n <PickerAnimatedPage pageKey={pageKey} direction={direction}>\n {body}\n </PickerAnimatedPage>\n </div>\n );\n}\n\nexport function ModelPolicyPicker(props: ModelPolicyPickerProps) {\n const [open, setOpen] = useState(false);\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const [nav, setNav] = usePickerNavState(props.sessionKey, rows, props.model);\n const selected = findPickerRow(rows, props.model);\n\n if (props.loading) {\n return (\n <span\n className={cn(\n \"inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2\",\n props.className,\n )}\n aria-label={messages.loading}\n data-testid=\"model-picker-loading\"\n />\n );\n }\n return (\n <DropdownMenu.Root open={open} onOpenChange={setOpen}>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n disabled={props.disabled}\n aria-label={messages.label}\n className={cn(\n \"inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-7 max-sm:max-w-[9rem] max-sm:px-2\",\n props.className,\n )}\n >\n <BillingClassMark\n billingClass={\n selected?.billingClass ??\n (props.model.startsWith(\"codex/\") ? \"codex_subscription\" : \"opengeni_credits\")\n }\n className=\"text-og-fg\"\n />\n <span className=\"truncate font-medium text-og-fg\">{selected?.label ?? props.model}</span>\n <span className=\"max-sm:hidden\">{labelReasoningEffort(props.effort)}</span>\n {props.latencyMode === \"fast\" ? (\n <ZapIcon\n className=\"size-3.5 shrink-0 fill-current stroke-current text-og-fg\"\n aria-label={messages.fast}\n data-testid=\"model-picker-fast-icon\"\n />\n ) : null}\n <ChevronDownIcon className=\"size-3 shrink-0\" />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content\n align=\"start\"\n side={props.menuSide ?? \"bottom\"}\n sideOffset={8}\n collisionPadding={12}\n onCloseAutoFocus={(event) => event.preventDefault()}\n className=\"z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-72 max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg\"\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n <ModelPolicyPickerMenu {...props} nav={nav} onNavChange={setNav} />\n </div>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.d.ts
CHANGED
|
@@ -31,3 +31,5 @@ export type EmbeddedFileAttachmentClientLike = Pick<OpenGeniClient, "uploadFile"
|
|
|
31
31
|
export type EmbeddedHumanInputSessionClientLike = EmbeddedSessionEventClientLike & Pick<OpenGeniClient, "listHumanInputRequests" | "submitHumanInputResponse">;
|
|
32
32
|
/** Exact client surface required by MCP approval-policy hooks. */
|
|
33
33
|
export type EmbeddedSessionMcpApprovalPolicyClientLike = EmbeddedSessionEventClientLike & Pick<OpenGeniClient, "updateSessionMcpApprovalPolicy">;
|
|
34
|
+
/** Exact client surface required by the public realtime React subpath. */
|
|
35
|
+
export type EmbeddedRealtimeSessionClientLike = Pick<OpenGeniClient, "getWorkspaceRealtimeModelCatalog" | "beginSessionRealtime" | "heartbeatSessionRealtime" | "negotiateCodexRealtimeWebrtc" | "negotiateGatewayRealtime" | "activateCodexRealtimeConnection" | "syncSessionRealtimeLedger" | "endSessionRealtime">;
|
|
@@ -23,10 +23,14 @@ export type ChatComposerProps = {
|
|
|
23
23
|
autoFocus?: boolean | undefined;
|
|
24
24
|
/** Replaces the default keyboard hint under the field. */
|
|
25
25
|
hint?: string | undefined;
|
|
26
|
+
/** App controls ahead of attach (e.g. mobile “+” overflow). */
|
|
27
|
+
controlsLeading?: ReactNode | undefined;
|
|
26
28
|
/** App controls in the footer row, replacing the hint. */
|
|
27
29
|
controlsStart?: ReactNode | undefined;
|
|
28
30
|
/** App actions beside Pause/Send, ordered before the built-in actions. */
|
|
29
31
|
actionsStart?: ReactNode | undefined;
|
|
32
|
+
/** Extra classes for the built-in attach control (e.g. `max-sm:hidden`). */
|
|
33
|
+
attachButtonClassName?: string | undefined;
|
|
30
34
|
/** Provider-neutral speech capability. Provider configuration stays in workspace settings. */
|
|
31
35
|
transcription?: ComposerTranscriptionControlProps | undefined;
|
|
32
36
|
/** Content rendered above the textarea, inside the field chrome. */
|
|
@@ -51,4 +55,4 @@ export type ChatComposerProps = {
|
|
|
51
55
|
* the public controller and compound primitives exported by the composer
|
|
52
56
|
* subpath, so custom and default layouts share one behavioral implementation.
|
|
53
57
|
*/
|
|
54
|
-
export declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsStart, actionsStart, transcription, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, messages, }: ChatComposerProps): import("react/jsx-runtime").JSX.Element;
|
|
58
|
+
export declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsLeading, controlsStart, actionsStart, attachButtonClassName, transcription, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, messages, }: ChatComposerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -15,11 +15,16 @@ export type HumanInputFormMessages = {
|
|
|
15
15
|
deadlineLabel: string;
|
|
16
16
|
formatDeadline: (value: string) => string;
|
|
17
17
|
required: string;
|
|
18
|
-
minLength: (count: number) => string;
|
|
19
|
-
maxLength: (count: number) => string;
|
|
20
18
|
otherRequired: string;
|
|
21
19
|
minSelections: (count: number) => string;
|
|
22
20
|
maxSelections: (count: number) => string;
|
|
21
|
+
optional: string;
|
|
22
|
+
/** Shown when the question list overflows the card and more content is below. */
|
|
23
|
+
moreBelow: string;
|
|
24
|
+
questionCount: (count: number) => string;
|
|
25
|
+
collapse: string;
|
|
26
|
+
expand: string;
|
|
27
|
+
selectionHint: (min: number | null | undefined, max: number | null | undefined) => string | null;
|
|
23
28
|
};
|
|
24
29
|
export declare const defaultHumanInputFormMessages: HumanInputFormMessages;
|
|
25
30
|
export type HumanInputFormProps = {
|
|
@@ -29,18 +34,23 @@ export type HumanInputFormProps = {
|
|
|
29
34
|
error?: string | null | undefined;
|
|
30
35
|
title?: ReactNode;
|
|
31
36
|
description?: ReactNode;
|
|
37
|
+
/** e.g. "1 of 2" when a host is stepping through parallel requests. */
|
|
38
|
+
progressLabel?: ReactNode;
|
|
32
39
|
submitLabel?: string | undefined;
|
|
33
40
|
skipLabel?: string | undefined;
|
|
34
41
|
messages?: Partial<HumanInputFormMessages> | undefined;
|
|
35
42
|
autoFocus?: boolean | undefined;
|
|
43
|
+
/** Start collapsed to a compact bar (drafts still retained). */
|
|
44
|
+
defaultCollapsed?: boolean | undefined;
|
|
36
45
|
className?: string | undefined;
|
|
37
46
|
};
|
|
38
47
|
/**
|
|
39
|
-
* Styled but host-neutral renderer for one structured request.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
48
|
+
* Styled but host-neutral renderer for one structured request. Matches the
|
|
49
|
+
* waiting-tone decision language of ApprovalSurface: question-first, compact
|
|
50
|
+
* options, sticky ask/submit chrome. Hosts can replace title/description or
|
|
51
|
+
* use `useHumanInputRequests` headlessly.
|
|
42
52
|
*/
|
|
43
|
-
export declare function HumanInputForm({ request, onSubmit, submitting, error, title, description, submitLabel, skipLabel, messages: messageOverrides, autoFocus, className, }: HumanInputFormProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
export declare function HumanInputForm({ request, onSubmit, submitting, error, title, description, progressLabel, submitLabel, skipLabel, messages: messageOverrides, autoFocus, defaultCollapsed, className, }: HumanInputFormProps): import("react/jsx-runtime").JSX.Element;
|
|
44
54
|
export declare function answersFromDrafts(questions: HumanInputQuestion[], drafts: Record<string, HumanInputAnswerDraft>, messageOverrides?: Partial<HumanInputFormMessages>): {
|
|
45
55
|
answers: HumanInputAnswer[];
|
|
46
56
|
errors: Record<string, string>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SessionHumanInputRequest, SubmitHumanInputResponseRequest } from "@opengeni/sdk";
|
|
2
|
+
import { type HumanInputFormMessages } from "./human-input-form";
|
|
3
|
+
export type HumanInputSurfaceProps = {
|
|
4
|
+
requests: SessionHumanInputRequest[];
|
|
5
|
+
onSubmit: (requestId: string, response: SubmitHumanInputResponseRequest) => void | Promise<void>;
|
|
6
|
+
respondingRequestId?: string | null | undefined;
|
|
7
|
+
error?: string | null | undefined;
|
|
8
|
+
messages?: Partial<HumanInputFormMessages> | undefined;
|
|
9
|
+
className?: string | undefined;
|
|
10
|
+
/** Forwarded to the active form. Defaults true. */
|
|
11
|
+
autoFocus?: boolean | undefined;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* One decision shell for pending structured human-input requests. Parallel
|
|
15
|
+
* freezes are shown one at a time (oldest first) as “N of M”; Continue/Skip
|
|
16
|
+
* settles the active request and the next remaining set advances automatically
|
|
17
|
+
* when the authoritative pending list updates.
|
|
18
|
+
*/
|
|
19
|
+
export declare function HumanInputSurface({ requests, onSubmit, respondingRequestId, error, messages, className, autoFocus, }: HumanInputSurfaceProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MachineInputMember } from "../timeline/types";
|
|
2
|
+
export declare const MACHINE_INPUT_META: Record<MachineInputMember["kind"], string>;
|
|
3
|
+
/**
|
|
4
|
+
* Collapsed landmark label for a coalesced machine-input batch.
|
|
5
|
+
* Same-kind batches get a natural plural; mixed kinds stay short.
|
|
6
|
+
*/
|
|
7
|
+
export declare function machineInputBatchLabel(members: readonly MachineInputMember[]): string;
|
|
8
|
+
/** Strip protocol prefixes and worker/session UUIDs from display summaries. */
|
|
9
|
+
export declare function cleanMachineInputSummary(summary: string): string;
|
|
10
|
+
export declare function readableMachineInputSource(sourceId: string): string | null;
|
|
11
|
+
/** True when a cleaned single-member summary adds meaning beyond the pill label. */
|
|
12
|
+
export declare function machineInputSummaryIsUseful(kind: MachineInputMember["kind"], cleanedSummary: string): boolean;
|
|
@@ -43,6 +43,11 @@ export type MessageTimelineProps = {
|
|
|
43
43
|
* `createDefaultToolRegistry({ entries })` to add custom tool renderers.
|
|
44
44
|
*/
|
|
45
45
|
toolRegistry?: ToolRegistry | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Display name of the session's active compute target (Connected Machine or
|
|
48
|
+
* cloud sandbox). When set, exec_command collapsed previews prefix `on {label}`.
|
|
49
|
+
*/
|
|
50
|
+
computeLabel?: string | null | undefined;
|
|
46
51
|
/** Customize collapsed turn facets for this timeline instance. */
|
|
47
52
|
turnSummary?: TurnSummaryOptions | undefined;
|
|
48
53
|
/** Follow new events when pinned to the bottom. Defaults to true. */
|
|
@@ -77,7 +82,7 @@ export type MessageTimelineProps = {
|
|
|
77
82
|
* cards, goal markers, and status transitions. Owns stick-to-bottom scrolling
|
|
78
83
|
* with a "jump to latest" affordance when the reader scrolls back.
|
|
79
84
|
*/
|
|
80
|
-
export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, resolveProviderLogo, toolRegistry, turnSummary, autoFollow, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, emptyState, className, }: MessageTimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
85
|
+
export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, resolveProviderLogo, toolRegistry, computeLabel, turnSummary, autoFollow, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, emptyState, className, }: MessageTimelineProps): import("react/jsx-runtime").JSX.Element;
|
|
81
86
|
/**
|
|
82
87
|
* Settled turns lift the final agent answer out as a sibling group. Include it
|
|
83
88
|
* in "Copy turn" when present so the chip copies the full assistant reply.
|