@opengeni/react 0.57.0 → 1.0.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 +3 -1
- package/dist/realtime/realtime-control.d.ts +4 -0
- package/dist/realtime.js +102 -15
- package/dist/realtime.js.map +1 -1
- package/package.json +2 -2
- package/src/realtime/realtime-control.tsx +147 -20
package/README.md
CHANGED
|
@@ -353,7 +353,9 @@ exported `EmbeddedRealtimeSessionClientLike` requires only catalog, begin,
|
|
|
353
353
|
Codex/Gateway negotiation, activation, heartbeat, ledger sync, and end. For
|
|
354
354
|
custom layouts, use `useSessionRealtime`, `useRealtimeModelSelection`,
|
|
355
355
|
`RealtimeVoiceControl`, and `RealtimeModelPickerMenu`; the batteries-included
|
|
356
|
-
wrappers remain the recommended path.
|
|
356
|
+
wrappers remain the recommended path. Concurrent catalog reads are deduplicated
|
|
357
|
+
per client and workspace, and successful results are reused for a short bounded
|
|
358
|
+
window so embedded controls can remount without refetching.
|
|
357
359
|
|
|
358
360
|
The reference consumer is `demo/realtime.html`. Run `bun run demo` from
|
|
359
361
|
`packages/react`, then open `http://localhost:3100/realtime.html?mode=mock` for
|
|
@@ -38,6 +38,8 @@ export declare function useSessionRealtime(options: {
|
|
|
38
38
|
model?: SessionRealtimeModel | undefined;
|
|
39
39
|
modelAvailable?: boolean | undefined;
|
|
40
40
|
modelUnavailableReason?: string | null | undefined;
|
|
41
|
+
/** Model-visible application context captured with each durable realtime message. */
|
|
42
|
+
getModelContext?: (() => string | undefined) | undefined;
|
|
41
43
|
/** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */
|
|
42
44
|
controllerFactory?: SessionRealtimeControllerFactory | undefined;
|
|
43
45
|
}): {
|
|
@@ -63,6 +65,8 @@ export declare function SessionRealtimeControl(props: {
|
|
|
63
65
|
events: SessionEvent[];
|
|
64
66
|
eventsReady: boolean;
|
|
65
67
|
codexConnected: boolean;
|
|
68
|
+
/** Model-visible application context captured with each durable realtime message. */
|
|
69
|
+
getModelContext?: (() => string | undefined) | undefined;
|
|
66
70
|
realtimeAutostartModel?: SessionRealtimeModel | undefined;
|
|
67
71
|
onRealtimeAutostartConsumed?: (() => void) | undefined;
|
|
68
72
|
/** Host hides dictate (and can choreograph layout) while realtime voice is live. */
|
package/dist/realtime.js
CHANGED
|
@@ -206,6 +206,72 @@ var REALTIME_PROVIDER_META = {
|
|
|
206
206
|
"Your Gateway": { billingClass: "byok", hint: "Billed to your AI Gateway" }
|
|
207
207
|
};
|
|
208
208
|
var REALTIME_MODEL_STORAGE_PREFIX = "opengeni:realtime-model";
|
|
209
|
+
var REALTIME_MODEL_CATALOG_CACHE_TTL_MS = 6e4;
|
|
210
|
+
var REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES = 64;
|
|
211
|
+
var realtimeModelCatalogCache = /* @__PURE__ */ new WeakMap();
|
|
212
|
+
function realtimeModelFallback(codexConnected) {
|
|
213
|
+
return [
|
|
214
|
+
{
|
|
215
|
+
...CODEX_LIVE_MODEL,
|
|
216
|
+
available: codexConnected,
|
|
217
|
+
unavailableReason: codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
function realtimeModelCatalogCacheFor(client) {
|
|
222
|
+
const existing = realtimeModelCatalogCache.get(client);
|
|
223
|
+
if (existing) return existing;
|
|
224
|
+
const created = /* @__PURE__ */ new Map();
|
|
225
|
+
realtimeModelCatalogCache.set(client, created);
|
|
226
|
+
return created;
|
|
227
|
+
}
|
|
228
|
+
function readCachedRealtimeModelCatalog(client, workspaceId, now = Date.now()) {
|
|
229
|
+
const cache = realtimeModelCatalogCache.get(client);
|
|
230
|
+
const entry = cache?.get(workspaceId);
|
|
231
|
+
if (!entry || entry.state === "loading") return null;
|
|
232
|
+
if (entry.expiresAt > now) return entry.models;
|
|
233
|
+
cache?.delete(workspaceId);
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function pruneRealtimeModelCatalogCache(cache, now) {
|
|
237
|
+
for (const [workspaceId, entry] of cache) {
|
|
238
|
+
if (entry.state === "ready" && entry.expiresAt <= now) cache.delete(workspaceId);
|
|
239
|
+
}
|
|
240
|
+
while (cache.size >= REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES) {
|
|
241
|
+
const oldestWorkspaceId = cache.keys().next().value;
|
|
242
|
+
if (oldestWorkspaceId === void 0) return;
|
|
243
|
+
cache.delete(oldestWorkspaceId);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function loadRealtimeModelCatalog(client, workspaceId) {
|
|
247
|
+
const load = client.getWorkspaceRealtimeModelCatalog;
|
|
248
|
+
if (!load) return null;
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
const cache = realtimeModelCatalogCacheFor(client);
|
|
251
|
+
const entry = cache.get(workspaceId);
|
|
252
|
+
if (entry?.state === "loading") return entry.promise;
|
|
253
|
+
if (entry?.state === "ready" && entry.expiresAt > now) return Promise.resolve(entry.models);
|
|
254
|
+
if (entry) cache.delete(workspaceId);
|
|
255
|
+
pruneRealtimeModelCatalogCache(cache, now);
|
|
256
|
+
const promise = load.call(client, workspaceId).then((response) => response.models.map(toRealtimeModelOption)).then((models) => {
|
|
257
|
+
const current = cache.get(workspaceId);
|
|
258
|
+
if (current?.state === "loading" && current.promise === promise) {
|
|
259
|
+
cache.delete(workspaceId);
|
|
260
|
+
cache.set(workspaceId, {
|
|
261
|
+
state: "ready",
|
|
262
|
+
models,
|
|
263
|
+
expiresAt: Date.now() + REALTIME_MODEL_CATALOG_CACHE_TTL_MS
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return models;
|
|
267
|
+
}).catch((error) => {
|
|
268
|
+
const current = cache.get(workspaceId);
|
|
269
|
+
if (current?.state === "loading" && current.promise === promise) cache.delete(workspaceId);
|
|
270
|
+
throw error;
|
|
271
|
+
});
|
|
272
|
+
cache.set(workspaceId, { state: "loading", promise });
|
|
273
|
+
return promise;
|
|
274
|
+
}
|
|
209
275
|
function codexRealtimeAdmissionAllowed(input) {
|
|
210
276
|
return codexRealtimeAdmissionBlocker(input) === null;
|
|
211
277
|
}
|
|
@@ -226,6 +292,8 @@ function useSessionRealtime(options) {
|
|
|
226
292
|
const audioRef = useRef(null);
|
|
227
293
|
const controllerRef = useRef(null);
|
|
228
294
|
const controllerModelRef = useRef(null);
|
|
295
|
+
const modelContextProviderRef = useRef(options.getModelContext);
|
|
296
|
+
modelContextProviderRef.current = options.getModelContext;
|
|
229
297
|
const [snapshot, setSnapshot] = useState(() => ({
|
|
230
298
|
status: hasStoredSessionRealtimeOwnerProof({
|
|
231
299
|
workspaceId,
|
|
@@ -265,7 +333,8 @@ function useSessionRealtime(options) {
|
|
|
265
333
|
workspaceId,
|
|
266
334
|
sessionId: options.sessionId,
|
|
267
335
|
...audioRef.current ? { remoteAudio: audioRef.current } : {},
|
|
268
|
-
model
|
|
336
|
+
model,
|
|
337
|
+
getModelContext: () => modelContextProviderRef.current?.()
|
|
269
338
|
});
|
|
270
339
|
controllerRef.current = controller;
|
|
271
340
|
controllerModelRef.current = model;
|
|
@@ -437,28 +506,46 @@ function useRealtimeModelSelection(options) {
|
|
|
437
506
|
workspaceId: options.workspaceId
|
|
438
507
|
});
|
|
439
508
|
const activeModel = options.activeModel;
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
available: options.codexConnected,
|
|
444
|
-
unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
|
|
445
|
-
}
|
|
446
|
-
]);
|
|
447
|
-
const [selectedModelId, setSelectedModelId] = useState(
|
|
448
|
-
() => readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id
|
|
509
|
+
const initialCachedCatalog = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
510
|
+
const [catalog, setCatalog] = useState(
|
|
511
|
+
() => initialCachedCatalog ?? realtimeModelFallback(options.codexConnected)
|
|
449
512
|
);
|
|
513
|
+
const catalogScopeRef = useRef({
|
|
514
|
+
client,
|
|
515
|
+
workspaceId,
|
|
516
|
+
source: initialCachedCatalog ? "cache" : "fallback"
|
|
517
|
+
});
|
|
518
|
+
const [selectedModelId, setSelectedModelId] = useState(() => {
|
|
519
|
+
const preferred = readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id;
|
|
520
|
+
if (!initialCachedCatalog) return preferred;
|
|
521
|
+
if (initialCachedCatalog.some((model) => model.id === preferred && model.available)) {
|
|
522
|
+
return preferred;
|
|
523
|
+
}
|
|
524
|
+
return initialCachedCatalog.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
525
|
+
});
|
|
450
526
|
useEffect(() => {
|
|
451
527
|
let disposed = false;
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
void load.call(client, workspaceId).then((response) => {
|
|
455
|
-
if (disposed) return;
|
|
456
|
-
const models = response.models.map(toRealtimeModelOption);
|
|
528
|
+
const applyCatalog = (models) => {
|
|
529
|
+
catalogScopeRef.current = { client, workspaceId, source: "cache" };
|
|
457
530
|
setCatalog(models);
|
|
458
531
|
setSelectedModelId((current) => {
|
|
459
532
|
if (models.some((model) => model.id === current && model.available)) return current;
|
|
460
533
|
return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
461
534
|
});
|
|
535
|
+
};
|
|
536
|
+
const cached = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
537
|
+
if (cached) {
|
|
538
|
+
const scope = catalogScopeRef.current;
|
|
539
|
+
if (scope.client !== client || scope.workspaceId !== workspaceId || scope.source !== "cache") {
|
|
540
|
+
applyCatalog(cached);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const loading = loadRealtimeModelCatalog(client, workspaceId);
|
|
545
|
+
if (!loading) return;
|
|
546
|
+
void loading.then((models) => {
|
|
547
|
+
if (disposed) return;
|
|
548
|
+
applyCatalog(models);
|
|
462
549
|
}).catch(() => void 0);
|
|
463
550
|
return () => {
|
|
464
551
|
disposed = true;
|
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/realtime/realtime-control.tsx","../src/realtime/dropdown-menu.tsx"],"sourcesContent":["import { type EffectiveSessionControl, type SessionEvent, type SessionStatus } from \"@opengeni/sdk\";\nimport {\n hasStoredSessionRealtimeOwnerProof,\n projectSessionRealtimeLifecycle,\n type SessionRealtimeClientLike,\n type SessionRealtimeController,\n type SessionRealtimeControllerSnapshot,\n type CreateSessionRealtimeControllerOptions,\n type SessionRealtimeModel,\n type WorkspaceRealtimeModelCatalogItem,\n} from \"@opengeni/sdk/realtime\";\nimport {\n AudioLinesIcon,\n CheckIcon,\n ChevronDownIcon,\n CircleAlertIcon,\n LoaderCircleIcon,\n MicIcon,\n MicOffIcon,\n RotateCcwIcon,\n SquareIcon,\n Volume2Icon,\n VolumeXIcon,\n} from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport {\n type ReactNode,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n BillingClassMark,\n PickerAnimatedPage,\n PickerBackHeader,\n PickerNavRow,\n} from \"../components/model-policy-picker\";\nimport type { EmbeddedRealtimeSessionClientLike } from \"../client\";\nimport { cn } from \"../lib/cn\";\nimport type { PickerBillingClass as BillingClass } from \"../model-policy\";\nimport { useEmbeddedRealtimeSession } from \"../session-context\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\";\n\nexport type RealtimeModelOption = {\n id: SessionRealtimeModel;\n label: string;\n provider: \"OpenGeni\" | \"Connected Codex\" | \"Connected SuperGrok\" | \"Your Gateway\";\n description: string;\n available: boolean;\n unavailableReason: string | null;\n recommended: boolean;\n};\n\nconst CODEX_LIVE_MODEL: RealtimeModelOption = {\n id: \"gpt-live-1-boulder-alpha\",\n label: \"Codex Live\",\n provider: \"Connected Codex\",\n description: \"Deep session integration\",\n available: false,\n unavailableReason: \"Connect Codex to use this voice model\",\n recommended: false,\n};\n\nconst REALTIME_MODEL_PROVIDERS = [\n \"OpenGeni\",\n \"Connected Codex\",\n \"Connected SuperGrok\",\n \"Your Gateway\",\n] as const;\ntype RealtimeModelProvider = (typeof REALTIME_MODEL_PROVIDERS)[number];\n\nconst REALTIME_PROVIDER_META: Record<\n RealtimeModelProvider,\n { billingClass: BillingClass; hint: string }\n> = {\n OpenGeni: { billingClass: \"opengeni_credits\", hint: \"Will use credits\" },\n \"Connected Codex\": {\n billingClass: \"codex_subscription\",\n hint: \"ChatGPT / Codex plan\",\n },\n \"Connected SuperGrok\": {\n billingClass: \"supergrok_subscription\",\n hint: \"SuperGrok / xAI plan\",\n },\n \"Your Gateway\": { billingClass: \"byok\", hint: \"Billed to your AI Gateway\" },\n};\nconst REALTIME_MODEL_STORAGE_PREFIX = \"opengeni:realtime-model\";\n\ntype RealtimeControllerClient = EmbeddedRealtimeSessionClientLike & SessionRealtimeClientLike;\n\nexport type SessionRealtimeControllerFactory = (\n options: CreateSessionRealtimeControllerOptions,\n) => SessionRealtimeController;\n\ntype AdmissionInput = {\n sessionStatus: SessionStatus;\n controlState: \"active\" | \"paused\";\n settlement: { state: string } | null;\n codexConnected: boolean;\n lifecycleActive: boolean;\n};\n\nexport function codexRealtimeAdmissionAllowed(input: AdmissionInput): boolean {\n return codexRealtimeAdmissionBlocker(input) === null;\n}\n\nexport function codexRealtimeAdmissionBlocker(input: AdmissionInput): string | null {\n if (input.sessionStatus === \"cancelled\") return \"This session was cancelled.\";\n if (input.controlState !== \"active\") return \"Resume this session before starting voice.\";\n if (input.settlement !== null) return \"Wait for the current session transition to finish.\";\n if (!input.codexConnected) return \"Connect Codex to use this voice model.\";\n if (input.lifecycleActive) return \"Voice is already active for this session.\";\n return null;\n}\n\nexport function useSessionRealtime(options: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n sessionId: string;\n sessionStatus: SessionStatus;\n effectiveControl: EffectiveSessionControl;\n events: SessionEvent[];\n eventsReady: boolean;\n codexConnected: boolean;\n model?: SessionRealtimeModel | undefined;\n modelAvailable?: boolean | undefined;\n modelUnavailableReason?: string | null | undefined;\n /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */\n controllerFactory?: SessionRealtimeControllerFactory | undefined;\n}) {\n const { client, workspaceId } = useEmbeddedRealtimeSession({\n client: options.client,\n workspaceId: options.workspaceId,\n });\n const model = options.model ?? CODEX_LIVE_MODEL.id;\n const audioRef = useRef<HTMLAudioElement>(null);\n const controllerRef = useRef<SessionRealtimeController | null>(null);\n const controllerModelRef = useRef<SessionRealtimeModel | null>(null);\n const [snapshot, setSnapshot] = useState<SessionRealtimeControllerSnapshot>(() => ({\n status: hasStoredSessionRealtimeOwnerProof({\n workspaceId,\n sessionId: options.sessionId,\n model,\n })\n ? \"recovering\"\n : \"idle\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: null,\n error: null,\n }));\n const lifecycle = useMemo(\n () => projectSessionRealtimeLifecycle(options.events),\n [options.events],\n );\n const lifecycleRef = useRef(lifecycle);\n const eventsReadyRef = useRef(options.eventsReady);\n lifecycleRef.current = lifecycle;\n eventsReadyRef.current = options.eventsReady;\n const lifecycleActive = lifecycle?.state === \"active\";\n\n useEffect(() => {\n let disposed = false;\n let controller: SessionRealtimeController | null = null;\n let unsubscribe: (() => void) | null = null;\n void import(\"@opengeni/sdk/realtime\")\n .then(({ createSessionRealtimeController }) => {\n if (disposed) return;\n const controllerFactory = options.controllerFactory ?? createSessionRealtimeController;\n controller = controllerFactory({\n client,\n workspaceId,\n sessionId: options.sessionId,\n ...(audioRef.current ? { remoteAudio: audioRef.current } : {}),\n model,\n });\n controllerRef.current = controller;\n controllerModelRef.current = model;\n unsubscribe = controller.subscribe(setSnapshot);\n if (eventsReadyRef.current) {\n void controller.observeLifecycle(lifecycleRef.current).catch(() => undefined);\n }\n })\n .catch((error: unknown) => {\n if (disposed) return;\n setSnapshot({\n status: \"error\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: {\n kind: \"negotiation_failure\",\n message:\n error instanceof Error ? error.message : \"Codex realtime controller failed to load\",\n recoverable: false,\n connectionGeneration: 0,\n attempt: 0,\n },\n error:\n error instanceof Error ? error.message : \"Codex realtime controller failed to load\",\n });\n });\n return () => {\n disposed = true;\n unsubscribe?.();\n controller?.close();\n if (controllerRef.current === controller) {\n controllerRef.current = null;\n controllerModelRef.current = null;\n }\n };\n }, [client, model, options.controllerFactory, options.sessionId, workspaceId]);\n\n useEffect(() => {\n if (!options.eventsReady) return;\n void controllerRef.current?.observeLifecycle(lifecycle).catch(() => undefined);\n }, [lifecycle, options.eventsReady]);\n\n const start = useCallback(async () => {\n const controller = controllerRef.current;\n if (!controller || controllerModelRef.current !== model) {\n throw new Error(\"The selected voice model is still preparing\");\n }\n await controller.start();\n }, [model]);\n const stop = useCallback(async () => {\n await controllerRef.current?.stop();\n }, []);\n const retry = useCallback(async () => {\n await controllerRef.current?.retry();\n }, []);\n const retryAudibleOutput = useCallback(async () => {\n await controllerRef.current?.retryAudibleOutput();\n }, []);\n const setInputMuted = useCallback((muted: boolean) => {\n controllerRef.current?.setInputMuted(muted);\n }, []);\n const setOutputMuted = useCallback((muted: boolean) => {\n controllerRef.current?.setOutputMuted(muted);\n }, []);\n const admissionInput = {\n sessionStatus: options.sessionStatus,\n controlState: options.effectiveControl.state,\n settlement: options.effectiveControl.settlement,\n codexConnected: options.modelAvailable ?? options.codexConnected,\n lifecycleActive,\n } satisfies AdmissionInput;\n const admissionBlocker =\n options.modelAvailable === false\n ? (options.modelUnavailableReason ?? \"This voice model is unavailable.\")\n : codexRealtimeAdmissionBlocker(admissionInput);\n const canStart =\n controllerRef.current !== null &&\n controllerModelRef.current === model &&\n [\"idle\", \"error\"].includes(snapshot.status) &&\n admissionBlocker === null;\n\n return {\n snapshot,\n lifecycleActive,\n canStart,\n admissionBlocker,\n codexConnected: options.codexConnected,\n audioRef,\n start,\n stop,\n retry,\n retryAudibleOutput,\n setInputMuted,\n setOutputMuted,\n };\n}\n\nexport function SessionRealtimeControl(props: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n sessionId: string;\n sessionStatus: SessionStatus;\n effectiveControl: EffectiveSessionControl;\n events: SessionEvent[];\n eventsReady: boolean;\n codexConnected: boolean;\n realtimeAutostartModel?: SessionRealtimeModel | undefined;\n onRealtimeAutostartConsumed?: (() => void) | undefined;\n /** Host hides dictate (and can choreograph layout) while realtime voice is live. */\n onVoiceActiveChange?: ((active: boolean) => void) | undefined;\n /**\n * `split` attaches the model chevron at every breakpoint (public default).\n * `split-desktop` hides it below `sm` when the host owns mobile selection.\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` never attaches a model menu to the bar button.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */\n controllerFactory?: SessionRealtimeControllerFactory | undefined;\n}) {\n const lifecycle = useMemo(() => projectSessionRealtimeLifecycle(props.events), [props.events]);\n const selection = useRealtimeModelSelection({\n client: props.client,\n workspaceId: props.workspaceId,\n codexConnected: props.codexConnected,\n activeModel: lifecycle?.state === \"active\" ? lifecycle.model : null,\n });\n const selectedModel = selection.selectedModel;\n const realtime = useSessionRealtime({\n ...props,\n model: selectedModel.id,\n modelAvailable: selectedModel.available,\n modelUnavailableReason: selectedModel.unavailableReason,\n });\n const autostartModelRef = useRef(props.realtimeAutostartModel ?? null);\n const autostartStartedRef = useRef(false);\n\n const { models, selectedModel: selectedRealtimeModel, selectModel } = selection;\n const { canStart, start } = realtime;\n const onRealtimeAutostartConsumed = props.onRealtimeAutostartConsumed;\n const onVoiceActiveChange = props.onVoiceActiveChange;\n const voiceActive = realtime.snapshot.mode?.state === \"active\";\n\n useEffect(() => {\n onVoiceActiveChange?.(voiceActive);\n }, [onVoiceActiveChange, voiceActive]);\n\n useEffect(() => {\n const pending = autostartModelRef.current;\n if (!pending || autostartStartedRef.current || lifecycle?.state === \"active\") return;\n const available = models.some((model) => model.id === pending && model.available);\n if (!available) return;\n if (selectedRealtimeModel.id !== pending) {\n selectModel(pending);\n return;\n }\n if (!canStart) return;\n autostartStartedRef.current = true;\n void start()\n .then(() => {\n autostartModelRef.current = null;\n onRealtimeAutostartConsumed?.();\n })\n .catch(() => {\n autostartStartedRef.current = false;\n });\n }, [\n canStart,\n lifecycle?.state,\n models,\n props.sessionId,\n props.workspaceId,\n onRealtimeAutostartConsumed,\n selectModel,\n selectedRealtimeModel.id,\n start,\n ]);\n\n return (\n <RealtimeVoiceControl\n snapshot={realtime.snapshot}\n canStart={realtime.canStart}\n admissionBlocker={realtime.admissionBlocker}\n modelAvailable={selectedModel.available}\n menuSide=\"top\"\n modelMenu={props.modelMenu ?? \"split\"}\n audioRef={realtime.audioRef}\n selectedModel={selectedModel}\n models={selection.models}\n onSelectModel={realtime.lifecycleActive ? undefined : selection.selectModel}\n onStart={realtime.start}\n onStop={realtime.stop}\n onRetry={realtime.retry}\n onRetryAudibleOutput={realtime.retryAudibleOutput}\n onSetInputMuted={realtime.setInputMuted}\n onSetOutputMuted={realtime.setOutputMuted}\n />\n );\n}\n\nexport function useRealtimeModelSelection(options: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n codexConnected: boolean;\n activeModel?: SessionRealtimeModel | null | undefined;\n}) {\n const { client, workspaceId } = useEmbeddedRealtimeSession({\n client: options.client,\n workspaceId: options.workspaceId,\n });\n const activeModel = options.activeModel;\n const [catalog, setCatalog] = useState<RealtimeModelOption[]>(() => [\n {\n ...CODEX_LIVE_MODEL,\n available: options.codexConnected,\n unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,\n },\n ]);\n const [selectedModelId, setSelectedModelId] = useState<SessionRealtimeModel>(\n () => readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id,\n );\n\n useEffect(() => {\n let disposed = false;\n const load = client.getWorkspaceRealtimeModelCatalog;\n if (!load) return;\n void load\n .call(client, workspaceId)\n .then((response) => {\n if (disposed) return;\n const models = response.models.map(toRealtimeModelOption);\n setCatalog(models);\n setSelectedModelId((current) => {\n if (models.some((model) => model.id === current && model.available)) return current;\n return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;\n });\n })\n .catch(() => undefined);\n return () => {\n disposed = true;\n };\n }, [client, workspaceId]);\n\n useEffect(() => {\n if (activeModel) setSelectedModelId(activeModel);\n }, [activeModel]);\n\n const selectedModel =\n catalog.find((model) => model.id === selectedModelId) ??\n ({\n ...CODEX_LIVE_MODEL,\n available: options.codexConnected,\n unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,\n } satisfies RealtimeModelOption);\n const selectModel = useCallback(\n (value: string) => {\n const model = catalog.find((candidate) => candidate.id === value);\n if (!model || !model.available || activeModel) return;\n setSelectedModelId(model.id);\n writeRealtimeModelPreference(workspaceId, model.id);\n },\n [activeModel, catalog, workspaceId],\n );\n\n return { models: catalog, selectedModel, selectModel };\n}\n\nconst IDLE_REALTIME_SNAPSHOT: SessionRealtimeControllerSnapshot = {\n status: \"idle\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: null,\n error: null,\n};\n\nexport function NewSessionRealtimeControl(props: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n codexConnected: boolean;\n disabled?: boolean | undefined;\n disabledReason?: string | null | undefined;\n onStart: (model: SessionRealtimeModel) => Promise<boolean>;\n /** Parent-owned selection (e.g. shared with the mobile “+” voice-model panel). */\n models?: readonly RealtimeModelOption[] | undefined;\n selectedModel?: RealtimeModelOption | undefined;\n onSelectModel?: ((modelId: string) => void) | undefined;\n /**\n * `split` attaches the model chevron at every breakpoint (public default).\n * `split-desktop` hides it below `sm` when the host owns mobile selection (e.g. “+”).\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` never attaches a model menu to the bar button.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n}) {\n const internalSelection = useRealtimeModelSelection({\n client: props.client,\n workspaceId: props.workspaceId,\n codexConnected: props.codexConnected,\n });\n const selection =\n props.models && props.selectedModel && props.onSelectModel\n ? {\n models: props.models,\n selectedModel: props.selectedModel,\n selectModel: props.onSelectModel,\n }\n : internalSelection;\n const audioRef = useRef<HTMLAudioElement>(null);\n const [starting, setStarting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const snapshot = useMemo<SessionRealtimeControllerSnapshot>(\n () =>\n starting\n ? { ...IDLE_REALTIME_SNAPSHOT, status: \"starting\" }\n : error\n ? { ...IDLE_REALTIME_SNAPSHOT, status: \"error\", error }\n : IDLE_REALTIME_SNAPSHOT,\n [error, starting],\n );\n const canStart = selection.selectedModel.available && props.disabled !== true && !starting;\n const onStart = props.onStart;\n const selectedModelId = selection.selectedModel.id;\n const start = useCallback(async () => {\n if (!canStart) return;\n setStarting(true);\n setError(null);\n try {\n const started = await onStart(selectedModelId);\n if (!started) setError(\"The voice session could not be created.\");\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n } finally {\n setStarting(false);\n }\n }, [canStart, onStart, selectedModelId]);\n\n return (\n <RealtimeVoiceControl\n snapshot={snapshot}\n canStart={canStart}\n admissionBlocker={selection.selectedModel.unavailableReason ?? props.disabledReason ?? null}\n modelAvailable={selection.selectedModel.available}\n selectionDisabled={starting}\n menuSide=\"bottom\"\n modelMenu={props.modelMenu ?? \"split\"}\n showDiagnostics={false}\n audioRef={audioRef}\n selectedModel={selection.selectedModel}\n models={selection.models}\n onSelectModel={selection.selectModel}\n onStart={start}\n onStop={async () => undefined}\n onRetry={start}\n onRetryAudibleOutput={async () => undefined}\n onSetInputMuted={() => undefined}\n onSetOutputMuted={() => undefined}\n />\n );\n}\n\nexport function RealtimeVoiceControl(props: {\n snapshot: SessionRealtimeControllerSnapshot;\n canStart: boolean;\n admissionBlocker?: string | null | undefined;\n modelAvailable: boolean;\n selectionDisabled?: boolean | undefined;\n /** Prefer `bottom` on home/new-session; `top` for the docked session composer. */\n menuSide?: \"top\" | \"bottom\" | undefined;\n /**\n * `split` = start + model chevron at every breakpoint (public SDK default).\n * `split-desktop` = chevron from `sm` up; below that the host owns selection.\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` = start button only.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n showDiagnostics?: boolean;\n /** Extra classes on the in-bar mute cluster (e.g. `max-sm:hidden` when mutes live in “+”). */\n muteControlsClassName?: string | undefined;\n audioRef: RefObject<HTMLAudioElement | null>;\n selectedModel?: RealtimeModelOption | undefined;\n models?: readonly RealtimeModelOption[] | undefined;\n onSelectModel?: ((modelId: string) => void) | undefined;\n onStart: () => Promise<void>;\n onStop: () => Promise<void>;\n onRetry: () => Promise<void>;\n onRetryAudibleOutput: () => Promise<void>;\n onSetInputMuted: (muted: boolean) => void;\n onSetOutputMuted: (muted: boolean) => void;\n}) {\n const reduceMotion = useReducedMotion();\n const selectedModel = props.selectedModel ?? {\n ...CODEX_LIVE_MODEL,\n available: props.modelAvailable,\n unavailableReason: props.modelAvailable ? null : CODEX_LIVE_MODEL.unavailableReason,\n };\n const models = props.models ?? [selectedModel];\n const status = statusContent(\n props.snapshot,\n props.modelAvailable,\n props.canStart,\n props.admissionBlocker ?? null,\n selectedModel.label,\n );\n const modeOwned = props.snapshot.mode?.state === \"active\";\n const retryConnection =\n modeOwned &&\n props.snapshot.status !== \"stopping\" &&\n props.snapshot.status !== \"active\" &&\n props.snapshot.diagnostic?.recoverable === true;\n const audioBlocked = props.snapshot.audibleOutput === \"blocked\" && !props.snapshot.outputMuted;\n const mainDisabled =\n props.snapshot.status === \"stopping\" ||\n props.snapshot.status === \"lost_owner\" ||\n (!modeOwned && !props.canStart) ||\n (props.snapshot.status === \"starting\" && !modeOwned);\n const mainLabel = audioBlocked\n ? \"Resume voice audio\"\n : retryConnection\n ? \"Retry voice connection\"\n : modeOwned\n ? \"End voice conversation\"\n : `Start voice with ${selectedModel.label}`;\n const runMainAction = audioBlocked\n ? props.onRetryAudibleOutput\n : retryConnection\n ? props.onRetry\n : modeOwned\n ? props.onStop\n : props.onStart;\n const diagnosticsVisible = props.showDiagnostics ?? import.meta.env.DEV;\n const modelMenu = props.modelMenu ?? \"split\";\n const showAttachedModelMenu = modelMenu === \"split\" || modelMenu === \"split-desktop\";\n const desktopOnlyModelMenu = modelMenu === \"split-desktop\";\n const [pickerOpen, setPickerOpen] = useState(false);\n const [pickerProvider, setPickerProvider] = useState<RealtimeModelProvider | null>(\n selectedModel.provider,\n );\n const [pickerDirection, setPickerDirection] = useState<1 | -1>(1);\n\n useEffect(() => {\n if (!pickerOpen) setPickerProvider(selectedModel.provider);\n }, [pickerOpen, selectedModel.provider]);\n\n return (\n <div\n role=\"group\"\n aria-label=\"Realtime voice\"\n data-picker-side={props.menuSide ?? \"top\"}\n data-model-menu={modelMenu}\n className=\"og-realtime-control inline-flex shrink-0 items-center\"\n >\n <audio ref={props.audioRef} autoPlay className=\"hidden\" aria-hidden=\"true\" />\n <RealtimeComposerGlow phase={status.phase} reduceMotion={reduceMotion === true} />\n <AnimatePresence initial={false}>\n {modeOwned ? (\n <motion.div\n key=\"realtime-mute-controls\"\n data-testid=\"realtime-mute-controls\"\n // After dictate collapses and the model slides left, bloom mutes\n // out from the primary voice control. Exit is immediate (no delay).\n initial={reduceMotion ? false : { opacity: 0, width: 0 }}\n animate={{\n opacity: 1,\n width: \"auto\",\n transition: reduceMotion\n ? { duration: 0 }\n : { delay: 0.3, duration: 0.48, ease: [0.22, 1, 0.36, 1] },\n }}\n {...(reduceMotion\n ? {}\n : {\n exit: {\n opacity: 0,\n width: 0,\n transition: { duration: 0.28, ease: [0.4, 0, 1, 1] },\n },\n })}\n className={cn(\n \"inline-flex shrink-0 items-center overflow-hidden\",\n props.muteControlsClassName,\n )}\n >\n <motion.div\n className=\"inline-flex items-center gap-0.5 pr-1\"\n initial={reduceMotion ? false : { x: 18, opacity: 0 }}\n animate={{\n x: 0,\n opacity: 1,\n transition: reduceMotion\n ? { duration: 0 }\n : {\n delay: 0.32,\n type: \"spring\",\n stiffness: 320,\n damping: 30,\n mass: 0.8,\n },\n }}\n {...(reduceMotion\n ? {}\n : {\n exit: {\n x: 14,\n opacity: 0,\n transition: { duration: 0.22, ease: [0.4, 0, 1, 1] },\n },\n })}\n >\n <RealtimeMuteButton\n muted={props.snapshot.inputMuted}\n kind=\"microphone\"\n reduceMotion={reduceMotion === true}\n enterDelay={reduceMotion ? 0 : 0.34}\n onToggle={() => props.onSetInputMuted(!props.snapshot.inputMuted)}\n />\n <RealtimeMuteButton\n muted={props.snapshot.outputMuted}\n kind=\"output\"\n reduceMotion={reduceMotion === true}\n enterDelay={reduceMotion ? 0 : 0.4}\n onToggle={() => props.onSetOutputMuted(!props.snapshot.outputMuted)}\n />\n </motion.div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n <div className=\"inline-flex shrink-0 items-center\">\n <motion.button\n type=\"button\"\n data-testid=\"realtime-primary-action\"\n data-phase={status.phase}\n aria-label={mainLabel}\n aria-pressed={modeOwned}\n title={mainLabel}\n disabled={mainDisabled}\n {...(reduceMotion ? {} : { whileTap: { scale: 0.92 } })}\n transition={{ type: \"spring\", stiffness: 520, damping: 30 }}\n onClick={() => void runMainAction().catch(() => undefined)}\n className={cn(\n // Match transcription mic: ghost icon when idle; filled only when live.\n // Public contract: coarse pointers keep a 44px target; split menu uses\n // left-only rounding at `sm+` where the chevron attaches.\n \"og-realtime-primary relative inline-flex size-8 items-center justify-center outline-hidden\",\n \"rounded-og-md transition-[background-color,border-color,color,box-shadow] duration-200 ease-og-out\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45\",\n \"disabled:cursor-not-allowed disabled:opacity-45 pointer-coarse:size-11\",\n showAttachedModelMenu &&\n (desktopOnlyModelMenu\n ? \"sm:rounded-l-og-md sm:rounded-r-none\"\n : \"rounded-l-og-md rounded-r-none\"),\n voiceButtonTone(status.phase),\n )}\n >\n <RealtimeActionGlyph\n phase={status.phase}\n audioBlocked={audioBlocked}\n reduceMotion={reduceMotion === true}\n />\n </motion.button>\n\n {showAttachedModelMenu ? (\n <DropdownMenu open={pickerOpen} onOpenChange={setPickerOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Choose voice model and options\"\n title={`Voice model: ${selectedModel.label}`}\n disabled={props.selectionDisabled}\n className={cn(\n // Keep the split trigger compact on desktop while preserving a\n // full 44px mobile target independent of pointer-media support.\n \"og-realtime-model-trigger inline-flex h-11 w-11 items-center justify-center rounded-r-og-md outline-hidden sm:h-8 sm:w-6\",\n \"transition-[background-color,border-color,color] duration-200 ease-og-out\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45\",\n desktopOnlyModelMenu && \"hidden sm:inline-flex\",\n voiceChevronTone(status.phase),\n )}\n >\n <ChevronDownIcon className=\"size-3\" aria-hidden />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={props.menuSide ?? \"top\"}\n align=\"end\"\n sideOffset={8}\n collisionPadding={12}\n className=\"og-realtime-menu flex w-72 max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] flex-col overflow-hidden rounded-xl border-border bg-surface p-1.5 shadow-xl\"\n onCloseAutoFocus={(event) => event.preventDefault()}\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n <RealtimeModelPickerMenu\n models={models}\n selectedModel={selectedModel}\n provider={pickerProvider}\n direction={pickerDirection}\n disabled={\n props.selectionDisabled === true || props.snapshot.mode?.state === \"active\"\n }\n onProviderChange={(provider, direction) => {\n setPickerDirection(direction);\n setPickerProvider(provider);\n }}\n onSelect={(modelId) => {\n props.onSelectModel?.(modelId);\n setPickerOpen(false);\n }}\n />\n </div>\n\n {status.phase !== \"idle\" ? (\n <>\n <DropdownMenuSeparator className=\"mx-1 bg-og-border\" />\n <div className=\"px-2.5 py-2\" role=\"status\" aria-live=\"polite\">\n <div className=\"flex items-center gap-2 text-og-sm font-medium text-og-fg\">\n <RealtimeStatusDot\n phase={status.phase}\n reduceMotion={reduceMotion === true}\n />\n {status.label}\n </div>\n <p className=\"mt-1 text-og-xs leading-5 text-og-fg-subtle\">{status.detail}</p>\n </div>\n </>\n ) : null}\n\n {audioBlocked ? (\n <DropdownMenuItem\n className=\"rounded-og-md\"\n onSelect={() => void props.onRetryAudibleOutput().catch(() => undefined)}\n >\n <Volume2Icon />\n Resume audio\n </DropdownMenuItem>\n ) : null}\n {retryConnection ? (\n <DropdownMenuItem\n className=\"rounded-og-md\"\n onSelect={() => void props.onRetry().catch(() => undefined)}\n >\n <RotateCcwIcon />\n Retry connection\n </DropdownMenuItem>\n ) : null}\n {modeOwned && props.snapshot.status !== \"lost_owner\" ? (\n <DropdownMenuItem\n variant=\"destructive\"\n className=\"rounded-og-md\"\n disabled={props.snapshot.status === \"stopping\"}\n onSelect={() => void props.onStop().catch(() => undefined)}\n >\n <SquareIcon />\n End voice conversation\n </DropdownMenuItem>\n ) : null}\n\n {diagnosticsVisible ? <RealtimeDiagnosticsMenu snapshot={props.snapshot} /> : null}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n </div>\n\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {status.label}. {status.detail}\n </span>\n {props.snapshot.error ? (\n <span className=\"sr-only\" role=\"alert\">\n {props.snapshot.error}\n </span>\n ) : null}\n </div>\n );\n}\n\n/** Drill-in body for the mobile composer “+” menu — same catalog as the desktop chevron. */\nexport function RealtimeVoiceModelPanel(props: {\n models: readonly RealtimeModelOption[];\n selectedModel: RealtimeModelOption;\n disabled?: boolean | undefined;\n leading?: ReactNode | undefined;\n onSelect: (modelId: SessionRealtimeModel) => void;\n}) {\n const [provider, setProvider] = useState<RealtimeModelProvider | null>(null);\n const [direction, setDirection] = useState<1 | -1>(1);\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\" data-testid=\"realtime-voice-model-panel\">\n <div className=\"flex shrink-0 items-start gap-1 px-2 pt-1 pb-1.5\">\n {props.leading}\n <div className=\"min-w-0\">\n <div className=\"text-og-menu font-medium text-og-fg\">Voice model</div>\n <p className=\"mt-0.5 text-og-control text-og-fg-subtle\">\n Used when you start a voice conversation.\n </p>\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain px-1.5 pb-1.5\">\n <RealtimeModelPickerMenu\n models={props.models}\n selectedModel={props.selectedModel}\n provider={provider}\n direction={direction}\n disabled={props.disabled === true}\n onProviderChange={(next, nextDirection) => {\n setDirection(nextDirection);\n setProvider(next);\n }}\n onSelect={props.onSelect}\n />\n </div>\n </div>\n );\n}\n\nfunction RealtimeMuteButton(props: {\n muted: boolean;\n kind: \"microphone\" | \"output\";\n reduceMotion: boolean;\n enterDelay?: number | undefined;\n onToggle(): void;\n}) {\n const microphone = props.kind === \"microphone\";\n const label = microphone\n ? props.muted\n ? \"Unmute microphone\"\n : \"Mute microphone\"\n : props.muted\n ? \"Unmute voice audio\"\n : \"Mute voice audio\";\n const Icon = microphone\n ? props.muted\n ? MicOffIcon\n : MicIcon\n : props.muted\n ? VolumeXIcon\n : Volume2Icon;\n\n return (\n <motion.button\n type=\"button\"\n aria-label={label}\n aria-pressed={props.muted}\n title={label}\n initial={props.reduceMotion ? false : { scale: 0.72, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n transition={{\n delay: props.enterDelay ?? 0,\n type: \"spring\",\n stiffness: 420,\n damping: 26,\n mass: 0.7,\n }}\n {...(props.reduceMotion ? {} : { whileTap: { scale: 0.92 } })}\n onClick={props.onToggle}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-og-md outline-hidden\",\n \"transition-[background-color,border-color,color] duration-150 ease-og-out\",\n \"focus-visible:ring-2 focus-visible:ring-og-accent/45 pointer-coarse:size-11\",\n props.muted\n ? \"border border-og-accent/35 bg-og-accent-soft text-og-accent\"\n : \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\",\n )}\n >\n <Icon className=\"size-3.5\" aria-hidden />\n </motion.button>\n );\n}\n\nexport function RealtimeModelPickerMenu(props: {\n models: readonly RealtimeModelOption[];\n selectedModel: RealtimeModelOption;\n provider: RealtimeModelProvider | null;\n direction: 1 | -1;\n disabled: boolean;\n onProviderChange: (provider: RealtimeModelProvider | null, direction: 1 | -1) => void;\n onSelect: (modelId: SessionRealtimeModel) => void;\n}) {\n const providers = REALTIME_MODEL_PROVIDERS.filter((provider) =>\n props.models.some((model) => model.provider === provider),\n );\n const pageKey = props.provider ? `voice-models:${props.provider}` : \"voice-providers\";\n const body = props.provider ? (\n <div data-testid=\"realtime-model-picker-models\">\n <PickerBackHeader\n label={props.provider}\n icon={\n <BillingClassMark\n billingClass={REALTIME_PROVIDER_META[props.provider].billingClass}\n aria-label=\"\"\n />\n }\n onBack={() => props.onProviderChange(null, -1)}\n />\n <div className=\"flex flex-col gap-0.5\">\n {props.models\n .filter((model) => model.provider === props.provider)\n .map((model) => {\n const selected = model.id === props.selectedModel.id;\n return (\n <PickerNavRow\n key={model.id}\n label={model.label}\n hint={\n model.unavailableReason ??\n (model.recommended ? `Recommended · ${model.description}` : model.description)\n }\n disabled={props.disabled || !model.available}\n title={model.unavailableReason ?? model.description}\n active={selected}\n showChevron={false}\n trailing={selected ? <CheckIcon className=\"size-3.5\" aria-hidden /> : undefined}\n testId={`realtime-model-choice-${model.id}`}\n onClick={() => props.onSelect(model.id)}\n />\n );\n })}\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"realtime-model-picker-providers\">\n {providers.map((provider) => {\n const meta = REALTIME_PROVIDER_META[provider];\n return (\n <PickerNavRow\n key={provider}\n label={provider}\n hint={meta.hint}\n icon={<BillingClassMark billingClass={meta.billingClass} aria-label=\"\" />}\n active={props.selectedModel.provider === provider}\n testId={`realtime-model-provider-${meta.billingClass}`}\n onClick={() => props.onProviderChange(provider, 1)}\n />\n );\n })}\n </div>\n );\n\n return (\n <div className=\"relative overflow-hidden\" data-testid=\"realtime-model-picker-menu\">\n <PickerAnimatedPage pageKey={pageKey} direction={props.direction}>\n {body}\n </PickerAnimatedPage>\n </div>\n );\n}\n\nfunction RealtimeDiagnosticsMenu({ snapshot }: { snapshot: SessionRealtimeControllerSnapshot }) {\n const bridge = snapshot.bridge;\n const rows = [\n [\"controller\", snapshot.status],\n [\"microphone\", snapshot.microphone],\n [\"microphone muted\", String(snapshot.inputMuted)],\n [\"audio\", snapshot.audibleOutput],\n [\"audio muted\", String(snapshot.outputMuted)],\n [\"generation\", String(snapshot.connectionGeneration)],\n [\"reconnect attempt\", String(snapshot.reconnectAttempt)],\n [\"mode\", snapshot.mode?.state ?? \"none\"],\n [\"mode version\", snapshot.mode ? String(snapshot.mode.version) : \"—\"],\n [\"connection epoch\", snapshot.mode ? String(snapshot.mode.connectionEpoch) : \"—\"],\n [\"provider started\", bridge ? String(bridge.providerStarted) : \"false\"],\n [\"provider speaking\", bridge ? String(bridge.speaking) : \"false\"],\n [\"delegation\", bridge?.activeDelegationId ? \"active\" : \"none\"],\n [\"pending durable events\", bridge ? String(bridge.pendingInbound) : \"0\"],\n [\"pending bytes\", bridge ? String(bridge.pendingInboundBytes) : \"0\"],\n [\"client ack\", bridge?.clientAckThroughSequence?.toString() ?? \"—\"],\n [\"ignored provider events\", bridge ? String(bridge.ignoredEventCount) : \"0\"],\n [\"last ignored event\", bridge?.lastIgnoredEventType ?? \"none\"],\n [\"bridge error\", bridge?.lastError ?? \"none\"],\n [\"diagnostic\", snapshot.diagnostic?.kind ?? \"none\"],\n ] as const;\n\n return (\n <>\n <DropdownMenuSeparator className=\"mx-1 bg-og-border\" />\n <DropdownMenuSub>\n <DropdownMenuSubTrigger className=\"rounded-og-md text-og-fg-muted\">\n Realtime diagnostics\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-80 w-72 overflow-y-auto rounded-og-lg border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg\">\n {rows.map(([label, value]) => (\n <DropdownMenuItem\n key={label}\n disabled\n className=\"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] gap-3 rounded-og-md font-mono text-[11px] opacity-100\"\n >\n <span className=\"truncate text-og-fg-subtle\">{label}</span>\n <span className=\"truncate text-right text-og-fg\">{value}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n </>\n );\n}\n\ntype RealtimeVisualPhase =\n | \"idle\"\n | \"connecting\"\n | \"listening\"\n | \"speaking\"\n | \"reconnecting\"\n | \"blocked\"\n | \"unavailable\"\n | \"error\"\n | \"elsewhere\"\n | \"stopping\";\n\nfunction statusContent(\n snapshot: SessionRealtimeControllerSnapshot,\n modelAvailable: boolean,\n canStart: boolean,\n admissionBlocker: string | null,\n modelLabel: string,\n): { phase: RealtimeVisualPhase; label: string; detail: string } {\n if (snapshot.audibleOutput === \"blocked\" && !snapshot.outputMuted) {\n return {\n phase: \"blocked\",\n label: \"Audio output blocked\",\n detail: \"Resume audio to hear the existing voice connection.\",\n };\n }\n switch (snapshot.status) {\n case \"starting\":\n return {\n phase: \"connecting\",\n label: \"Connecting…\",\n detail: \"Preparing your microphone and live voice.\",\n };\n case \"active\":\n return snapshot.bridge?.speaking\n ? {\n phase: \"speaking\",\n label: \"Speaking\",\n detail: \"Interrupt naturally whenever you want.\",\n }\n : {\n phase: \"listening\",\n label: \"Listening\",\n detail: \"Voice and typed messages share this session.\",\n };\n case \"stopping\":\n return {\n phase: \"stopping\",\n label: \"Ending voice…\",\n detail: \"Saving the remaining conversation context.\",\n };\n case \"recovering\":\n return {\n phase: \"reconnecting\",\n label: \"Reconnecting…\",\n detail: snapshot.error ?? \"Keeping this voice conversation connected.\",\n };\n case \"lost_owner\":\n return {\n phase: \"elsewhere\",\n label: \"Voice active elsewhere\",\n detail: \"Return to the browser that started it, or wait for that connection to expire.\",\n };\n case \"error\":\n return {\n phase: \"error\",\n label: \"Voice unavailable\",\n detail: snapshot.error ?? \"The connection could not be started.\",\n };\n case \"idle\":\n if (!modelAvailable) {\n return {\n phase: \"unavailable\",\n label: \"Voice model unavailable\",\n detail: admissionBlocker ?? \"Choose another voice model.\",\n };\n }\n if (!canStart && admissionBlocker) {\n return {\n phase: \"unavailable\",\n label: \"Voice unavailable\",\n detail: admissionBlocker,\n };\n }\n return {\n phase: \"idle\",\n label: \"Start voice\",\n detail: `Talk live with ${modelLabel}.`,\n };\n }\n}\n\nfunction RealtimeComposerGlow(props: { phase: RealtimeVisualPhase; reduceMotion: boolean }) {\n const visible = [\n \"connecting\",\n \"listening\",\n \"speaking\",\n \"reconnecting\",\n \"blocked\",\n \"error\",\n \"stopping\",\n ].includes(props.phase);\n return (\n <AnimatePresence initial={false}>\n {visible ? (\n <motion.span\n aria-hidden\n key={props.phase}\n initial={{ opacity: 0 }}\n animate={\n props.reduceMotion\n ? { opacity: 0.48 }\n : props.phase === \"speaking\"\n ? { opacity: [0.42, 0.82, 0.52] }\n : props.phase === \"listening\"\n ? { opacity: [0.34, 0.62, 0.42] }\n : { opacity: [0.28, 0.58, 0.28] }\n }\n exit={{ opacity: 0 }}\n transition={\n props.reduceMotion\n ? { duration: 0.12 }\n : {\n duration: props.phase === \"speaking\" ? 1.15 : 2.1,\n ease: \"easeInOut\",\n repeat: Number.POSITIVE_INFINITY,\n }\n }\n className={cn(\n \"pointer-events-none absolute -inset-px z-[1] rounded-og-lg ring-1 ring-inset shadow-og-glow\",\n props.phase === \"speaking\"\n ? \"ring-og-status-running/50\"\n : props.phase === \"blocked\" || props.phase === \"error\"\n ? \"ring-og-status-waiting/45\"\n : \"ring-og-accent/45\",\n )}\n />\n ) : null}\n </AnimatePresence>\n );\n}\n\nfunction RealtimeActionGlyph(props: {\n phase: RealtimeVisualPhase;\n audioBlocked: boolean;\n reduceMotion: boolean;\n}) {\n if ([\"connecting\", \"reconnecting\", \"stopping\"].includes(props.phase)) {\n return <LoaderCircleIcon className=\"size-3.5 animate-og-spin\" aria-hidden />;\n }\n if (props.phase === \"blocked\") {\n return props.audioBlocked ? (\n <Volume2Icon className=\"size-3.5\" aria-hidden />\n ) : (\n <CircleAlertIcon className=\"size-3.5\" aria-hidden />\n );\n }\n if (props.phase === \"error\" || props.phase === \"elsewhere\" || props.phase === \"unavailable\") {\n return <CircleAlertIcon className=\"size-3.5\" aria-hidden />;\n }\n if (props.phase === \"listening\" || props.phase === \"speaking\") {\n return (\n <span className=\"relative inline-flex size-4 items-center justify-center\">\n {!props.reduceMotion ? (\n <motion.span\n aria-hidden\n className=\"absolute inset-0 rounded-full border border-current\"\n animate={{ opacity: [0.65, 0], scale: [0.65, 1.45] }}\n transition={{\n duration: props.phase === \"speaking\" ? 0.9 : 1.5,\n ease: \"easeOut\",\n repeat: Number.POSITIVE_INFINITY,\n }}\n />\n ) : null}\n <SquareIcon className=\"relative size-2.5 fill-current\" aria-hidden />\n </span>\n );\n }\n return <AudioLinesIcon className=\"size-4\" aria-hidden />;\n}\n\nfunction RealtimeStatusDot(props: { phase: RealtimeVisualPhase; reduceMotion: boolean }) {\n const live = props.phase === \"listening\" || props.phase === \"speaking\";\n return (\n <span className=\"relative inline-flex size-2 shrink-0\" aria-hidden>\n {live && !props.reduceMotion ? (\n <motion.span\n className=\"absolute inset-0 rounded-full bg-og-status-running\"\n animate={{ opacity: [0.45, 0], scale: [1, 2.2] }}\n transition={{\n duration: 1.4,\n ease: \"easeOut\",\n repeat: Number.POSITIVE_INFINITY,\n }}\n />\n ) : null}\n <span\n className={cn(\n \"relative size-2 rounded-full\",\n live\n ? \"bg-og-status-running\"\n : props.phase === \"blocked\" || props.phase === \"error\"\n ? \"bg-og-status-waiting\"\n : props.phase === \"elsewhere\" || props.phase === \"unavailable\"\n ? \"bg-og-fg-subtle\"\n : \"bg-og-accent\",\n )}\n />\n </span>\n );\n}\n\nfunction voiceButtonTone(phase: RealtimeVisualPhase): string {\n if (phase === \"listening\" || phase === \"speaking\") {\n return \"border border-og-accent/45 bg-og-accent text-og-accent-fg shadow-og-sm hover:bg-og-accent-strong\";\n }\n if (phase === \"blocked\" || phase === \"error\") {\n return \"border border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting hover:bg-og-status-waiting/15\";\n }\n if (phase === \"connecting\" || phase === \"reconnecting\" || phase === \"stopping\") {\n return \"border border-og-accent/35 bg-og-accent-soft text-og-accent\";\n }\n // Idle: same ghost treatment as the composer transcription mic — no box.\n return \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\";\n}\n\nfunction voiceChevronTone(phase: RealtimeVisualPhase): string {\n if (phase === \"listening\" || phase === \"speaking\") {\n return \"border border-l-0 border-og-accent/45 bg-og-accent text-og-accent-fg hover:bg-og-accent-strong\";\n }\n if (phase === \"blocked\" || phase === \"error\") {\n return \"border border-l-0 border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting hover:bg-og-status-waiting/15\";\n }\n if (phase === \"connecting\" || phase === \"reconnecting\" || phase === \"stopping\") {\n return \"border border-l-0 border-og-accent/35 bg-og-accent-soft text-og-accent\";\n }\n return \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\";\n}\n\nfunction toRealtimeModelOption(model: WorkspaceRealtimeModelCatalogItem): RealtimeModelOption {\n return { ...model };\n}\n\nfunction readRealtimeModelPreference(workspaceId: string): SessionRealtimeModel | null {\n if (typeof localStorage === \"undefined\") return null;\n try {\n const value = localStorage.getItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`);\n return isRealtimeModel(value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeRealtimeModelPreference(workspaceId: string, model: SessionRealtimeModel): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`, model);\n } catch {\n // Voice selection still works when browser storage is unavailable.\n }\n}\n\nfunction isRealtimeModel(value: string | null): value is SessionRealtimeModel {\n return (\n value === \"gpt-live-1-boulder-alpha\" ||\n value === \"opengeni-gateway/openai/gpt-realtime-2.1\" ||\n value === \"opengeni-gateway/openai/gpt-realtime-mini\" ||\n value === \"opengeni-gateway/xai/grok-voice-think-fast-2.0\" ||\n value === \"supergrok/grok-voice-think-fast-2.0\" ||\n value === \"workspace-gateway/openai/gpt-realtime-2.1\" ||\n value === \"workspace-gateway/openai/gpt-realtime-mini\" ||\n value === \"workspace-gateway/xai/grok-voice-think-fast-2.0\"\n );\n}\n\n/** @deprecated Use the provider-neutral realtime names. */\nexport const useSessionCodexRealtime = useSessionRealtime;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const useWorkspaceRealtimeModelSelection = useRealtimeModelSelection;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const SessionCodexRealtimeControl = SessionRealtimeControl;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const CodexRealtimeControl = RealtimeVoiceControl;\n","\"use client\";\n\nimport { ChevronRightIcon } from \"lucide-react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport { createContext, useContext, type ComponentProps, type Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { usePortalTokenSource, usePortalTokenStyle } from \"../lib/use-portal-token-style\";\n\ntype DropdownMenuSourceContextValue = {\n source: HTMLElement | null;\n ref: (node: HTMLElement | null) => void;\n};\n\nconst DropdownMenuSourceContext = createContext<DropdownMenuSourceContextValue | null>(null);\n\nfunction assignRef<T>(ref: Ref<T> | undefined, value: T | null) {\n if (typeof ref === \"function\") ref(value);\n else if (ref) ref.current = value;\n}\n\nfunction DropdownMenu(props: ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n const source = usePortalTokenSource<HTMLElement>();\n return (\n <DropdownMenuSourceContext.Provider value={source}>\n <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n </DropdownMenuSourceContext.Provider>\n );\n}\n\nfunction DropdownMenuTrigger({\n ref,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n const sourceRef = useContext(DropdownMenuSourceContext);\n return (\n <DropdownMenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n ref={(node) => {\n sourceRef?.ref(node);\n assignRef(ref, node);\n }}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n style,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n const source = useContext(DropdownMenuSourceContext);\n const portalStyle = usePortalTokenStyle(source?.source ?? null);\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n \"og-root z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className,\n )}\n style={{ ...portalStyle, ...style }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-og-menu outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub(props: ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-og-menu outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className=\"ml-auto size-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n );\n}\n\nfunction DropdownMenuSubContent({\n className,\n style,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n const source = useContext(DropdownMenuSourceContext);\n const portalStyle = usePortalTokenStyle(source?.source ?? null);\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"og-root z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className,\n )}\n style={{ ...portalStyle, ...style }}\n {...props}\n />\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/realtime/realtime-control.tsx","../src/realtime/dropdown-menu.tsx"],"sourcesContent":["import { type EffectiveSessionControl, type SessionEvent, type SessionStatus } from \"@opengeni/sdk\";\nimport {\n hasStoredSessionRealtimeOwnerProof,\n projectSessionRealtimeLifecycle,\n type SessionRealtimeClientLike,\n type SessionRealtimeController,\n type SessionRealtimeControllerSnapshot,\n type CreateSessionRealtimeControllerOptions,\n type SessionRealtimeModel,\n type WorkspaceRealtimeModelCatalogItem,\n} from \"@opengeni/sdk/realtime\";\nimport {\n AudioLinesIcon,\n CheckIcon,\n ChevronDownIcon,\n CircleAlertIcon,\n LoaderCircleIcon,\n MicIcon,\n MicOffIcon,\n RotateCcwIcon,\n SquareIcon,\n Volume2Icon,\n VolumeXIcon,\n} from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport {\n type ReactNode,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n BillingClassMark,\n PickerAnimatedPage,\n PickerBackHeader,\n PickerNavRow,\n} from \"../components/model-policy-picker\";\nimport type { EmbeddedRealtimeSessionClientLike } from \"../client\";\nimport { cn } from \"../lib/cn\";\nimport type { PickerBillingClass as BillingClass } from \"../model-policy\";\nimport { useEmbeddedRealtimeSession } from \"../session-context\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\";\n\nexport type RealtimeModelOption = {\n id: SessionRealtimeModel;\n label: string;\n provider: \"OpenGeni\" | \"Connected Codex\" | \"Connected SuperGrok\" | \"Your Gateway\";\n description: string;\n available: boolean;\n unavailableReason: string | null;\n recommended: boolean;\n};\n\nconst CODEX_LIVE_MODEL: RealtimeModelOption = {\n id: \"gpt-live-1-boulder-alpha\",\n label: \"Codex Live\",\n provider: \"Connected Codex\",\n description: \"Deep session integration\",\n available: false,\n unavailableReason: \"Connect Codex to use this voice model\",\n recommended: false,\n};\n\nconst REALTIME_MODEL_PROVIDERS = [\n \"OpenGeni\",\n \"Connected Codex\",\n \"Connected SuperGrok\",\n \"Your Gateway\",\n] as const;\ntype RealtimeModelProvider = (typeof REALTIME_MODEL_PROVIDERS)[number];\n\nconst REALTIME_PROVIDER_META: Record<\n RealtimeModelProvider,\n { billingClass: BillingClass; hint: string }\n> = {\n OpenGeni: { billingClass: \"opengeni_credits\", hint: \"Will use credits\" },\n \"Connected Codex\": {\n billingClass: \"codex_subscription\",\n hint: \"ChatGPT / Codex plan\",\n },\n \"Connected SuperGrok\": {\n billingClass: \"supergrok_subscription\",\n hint: \"SuperGrok / xAI plan\",\n },\n \"Your Gateway\": { billingClass: \"byok\", hint: \"Billed to your AI Gateway\" },\n};\nconst REALTIME_MODEL_STORAGE_PREFIX = \"opengeni:realtime-model\";\nconst REALTIME_MODEL_CATALOG_CACHE_TTL_MS = 60_000;\nconst REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES = 64;\n\ntype RealtimeControllerClient = EmbeddedRealtimeSessionClientLike & SessionRealtimeClientLike;\n\ntype RealtimeModelCatalogCacheEntry =\n | { state: \"loading\"; promise: Promise<RealtimeModelOption[]> }\n | { state: \"ready\"; models: RealtimeModelOption[]; expiresAt: number };\n\n// Scope advisory availability to the exact client object and workspace. The\n// server still authorizes every realtime operation; changing principals should\n// replace the client just as it does for the rest of the SDK state.\nconst realtimeModelCatalogCache = new WeakMap<\n RealtimeControllerClient,\n Map<string, RealtimeModelCatalogCacheEntry>\n>();\n\nfunction realtimeModelFallback(codexConnected: boolean): RealtimeModelOption[] {\n return [\n {\n ...CODEX_LIVE_MODEL,\n available: codexConnected,\n unavailableReason: codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,\n },\n ];\n}\n\nfunction realtimeModelCatalogCacheFor(\n client: RealtimeControllerClient,\n): Map<string, RealtimeModelCatalogCacheEntry> {\n const existing = realtimeModelCatalogCache.get(client);\n if (existing) return existing;\n const created = new Map<string, RealtimeModelCatalogCacheEntry>();\n realtimeModelCatalogCache.set(client, created);\n return created;\n}\n\nfunction readCachedRealtimeModelCatalog(\n client: RealtimeControllerClient,\n workspaceId: string,\n now = Date.now(),\n): RealtimeModelOption[] | null {\n const cache = realtimeModelCatalogCache.get(client);\n const entry = cache?.get(workspaceId);\n if (!entry || entry.state === \"loading\") return null;\n if (entry.expiresAt > now) return entry.models;\n cache?.delete(workspaceId);\n return null;\n}\n\nfunction pruneRealtimeModelCatalogCache(\n cache: Map<string, RealtimeModelCatalogCacheEntry>,\n now: number,\n): void {\n for (const [workspaceId, entry] of cache) {\n if (entry.state === \"ready\" && entry.expiresAt <= now) cache.delete(workspaceId);\n }\n while (cache.size >= REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES) {\n const oldestWorkspaceId = cache.keys().next().value;\n if (oldestWorkspaceId === undefined) return;\n cache.delete(oldestWorkspaceId);\n }\n}\n\nfunction loadRealtimeModelCatalog(\n client: RealtimeControllerClient,\n workspaceId: string,\n): Promise<RealtimeModelOption[]> | null {\n const load = client.getWorkspaceRealtimeModelCatalog;\n if (!load) return null;\n const now = Date.now();\n const cache = realtimeModelCatalogCacheFor(client);\n const entry = cache.get(workspaceId);\n if (entry?.state === \"loading\") return entry.promise;\n if (entry?.state === \"ready\" && entry.expiresAt > now) return Promise.resolve(entry.models);\n if (entry) cache.delete(workspaceId);\n pruneRealtimeModelCatalogCache(cache, now);\n\n const promise = load\n .call(client, workspaceId)\n .then((response) => response.models.map(toRealtimeModelOption))\n .then((models) => {\n const current = cache.get(workspaceId);\n if (current?.state === \"loading\" && current.promise === promise) {\n cache.delete(workspaceId);\n cache.set(workspaceId, {\n state: \"ready\",\n models,\n expiresAt: Date.now() + REALTIME_MODEL_CATALOG_CACHE_TTL_MS,\n });\n }\n return models;\n })\n .catch((error: unknown) => {\n const current = cache.get(workspaceId);\n if (current?.state === \"loading\" && current.promise === promise) cache.delete(workspaceId);\n throw error;\n });\n cache.set(workspaceId, { state: \"loading\", promise });\n return promise;\n}\n\nexport type SessionRealtimeControllerFactory = (\n options: CreateSessionRealtimeControllerOptions,\n) => SessionRealtimeController;\n\ntype AdmissionInput = {\n sessionStatus: SessionStatus;\n controlState: \"active\" | \"paused\";\n settlement: { state: string } | null;\n codexConnected: boolean;\n lifecycleActive: boolean;\n};\n\nexport function codexRealtimeAdmissionAllowed(input: AdmissionInput): boolean {\n return codexRealtimeAdmissionBlocker(input) === null;\n}\n\nexport function codexRealtimeAdmissionBlocker(input: AdmissionInput): string | null {\n if (input.sessionStatus === \"cancelled\") return \"This session was cancelled.\";\n if (input.controlState !== \"active\") return \"Resume this session before starting voice.\";\n if (input.settlement !== null) return \"Wait for the current session transition to finish.\";\n if (!input.codexConnected) return \"Connect Codex to use this voice model.\";\n if (input.lifecycleActive) return \"Voice is already active for this session.\";\n return null;\n}\n\nexport function useSessionRealtime(options: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n sessionId: string;\n sessionStatus: SessionStatus;\n effectiveControl: EffectiveSessionControl;\n events: SessionEvent[];\n eventsReady: boolean;\n codexConnected: boolean;\n model?: SessionRealtimeModel | undefined;\n modelAvailable?: boolean | undefined;\n modelUnavailableReason?: string | null | undefined;\n /** Model-visible application context captured with each durable realtime message. */\n getModelContext?: (() => string | undefined) | undefined;\n /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */\n controllerFactory?: SessionRealtimeControllerFactory | undefined;\n}) {\n const { client, workspaceId } = useEmbeddedRealtimeSession({\n client: options.client,\n workspaceId: options.workspaceId,\n });\n const model = options.model ?? CODEX_LIVE_MODEL.id;\n const audioRef = useRef<HTMLAudioElement>(null);\n const controllerRef = useRef<SessionRealtimeController | null>(null);\n const controllerModelRef = useRef<SessionRealtimeModel | null>(null);\n const modelContextProviderRef = useRef(options.getModelContext);\n modelContextProviderRef.current = options.getModelContext;\n const [snapshot, setSnapshot] = useState<SessionRealtimeControllerSnapshot>(() => ({\n status: hasStoredSessionRealtimeOwnerProof({\n workspaceId,\n sessionId: options.sessionId,\n model,\n })\n ? \"recovering\"\n : \"idle\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: null,\n error: null,\n }));\n const lifecycle = useMemo(\n () => projectSessionRealtimeLifecycle(options.events),\n [options.events],\n );\n const lifecycleRef = useRef(lifecycle);\n const eventsReadyRef = useRef(options.eventsReady);\n lifecycleRef.current = lifecycle;\n eventsReadyRef.current = options.eventsReady;\n const lifecycleActive = lifecycle?.state === \"active\";\n\n useEffect(() => {\n let disposed = false;\n let controller: SessionRealtimeController | null = null;\n let unsubscribe: (() => void) | null = null;\n void import(\"@opengeni/sdk/realtime\")\n .then(({ createSessionRealtimeController }) => {\n if (disposed) return;\n const controllerFactory = options.controllerFactory ?? createSessionRealtimeController;\n controller = controllerFactory({\n client,\n workspaceId,\n sessionId: options.sessionId,\n ...(audioRef.current ? { remoteAudio: audioRef.current } : {}),\n model,\n getModelContext: () => modelContextProviderRef.current?.(),\n });\n controllerRef.current = controller;\n controllerModelRef.current = model;\n unsubscribe = controller.subscribe(setSnapshot);\n if (eventsReadyRef.current) {\n void controller.observeLifecycle(lifecycleRef.current).catch(() => undefined);\n }\n })\n .catch((error: unknown) => {\n if (disposed) return;\n setSnapshot({\n status: \"error\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: {\n kind: \"negotiation_failure\",\n message:\n error instanceof Error ? error.message : \"Codex realtime controller failed to load\",\n recoverable: false,\n connectionGeneration: 0,\n attempt: 0,\n },\n error:\n error instanceof Error ? error.message : \"Codex realtime controller failed to load\",\n });\n });\n return () => {\n disposed = true;\n unsubscribe?.();\n controller?.close();\n if (controllerRef.current === controller) {\n controllerRef.current = null;\n controllerModelRef.current = null;\n }\n };\n }, [client, model, options.controllerFactory, options.sessionId, workspaceId]);\n\n useEffect(() => {\n if (!options.eventsReady) return;\n void controllerRef.current?.observeLifecycle(lifecycle).catch(() => undefined);\n }, [lifecycle, options.eventsReady]);\n\n const start = useCallback(async () => {\n const controller = controllerRef.current;\n if (!controller || controllerModelRef.current !== model) {\n throw new Error(\"The selected voice model is still preparing\");\n }\n await controller.start();\n }, [model]);\n const stop = useCallback(async () => {\n await controllerRef.current?.stop();\n }, []);\n const retry = useCallback(async () => {\n await controllerRef.current?.retry();\n }, []);\n const retryAudibleOutput = useCallback(async () => {\n await controllerRef.current?.retryAudibleOutput();\n }, []);\n const setInputMuted = useCallback((muted: boolean) => {\n controllerRef.current?.setInputMuted(muted);\n }, []);\n const setOutputMuted = useCallback((muted: boolean) => {\n controllerRef.current?.setOutputMuted(muted);\n }, []);\n const admissionInput = {\n sessionStatus: options.sessionStatus,\n controlState: options.effectiveControl.state,\n settlement: options.effectiveControl.settlement,\n codexConnected: options.modelAvailable ?? options.codexConnected,\n lifecycleActive,\n } satisfies AdmissionInput;\n const admissionBlocker =\n options.modelAvailable === false\n ? (options.modelUnavailableReason ?? \"This voice model is unavailable.\")\n : codexRealtimeAdmissionBlocker(admissionInput);\n const canStart =\n controllerRef.current !== null &&\n controllerModelRef.current === model &&\n [\"idle\", \"error\"].includes(snapshot.status) &&\n admissionBlocker === null;\n\n return {\n snapshot,\n lifecycleActive,\n canStart,\n admissionBlocker,\n codexConnected: options.codexConnected,\n audioRef,\n start,\n stop,\n retry,\n retryAudibleOutput,\n setInputMuted,\n setOutputMuted,\n };\n}\n\nexport function SessionRealtimeControl(props: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n sessionId: string;\n sessionStatus: SessionStatus;\n effectiveControl: EffectiveSessionControl;\n events: SessionEvent[];\n eventsReady: boolean;\n codexConnected: boolean;\n /** Model-visible application context captured with each durable realtime message. */\n getModelContext?: (() => string | undefined) | undefined;\n realtimeAutostartModel?: SessionRealtimeModel | undefined;\n onRealtimeAutostartConsumed?: (() => void) | undefined;\n /** Host hides dictate (and can choreograph layout) while realtime voice is live. */\n onVoiceActiveChange?: ((active: boolean) => void) | undefined;\n /**\n * `split` attaches the model chevron at every breakpoint (public default).\n * `split-desktop` hides it below `sm` when the host owns mobile selection.\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` never attaches a model menu to the bar button.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */\n controllerFactory?: SessionRealtimeControllerFactory | undefined;\n}) {\n const lifecycle = useMemo(() => projectSessionRealtimeLifecycle(props.events), [props.events]);\n const selection = useRealtimeModelSelection({\n client: props.client,\n workspaceId: props.workspaceId,\n codexConnected: props.codexConnected,\n activeModel: lifecycle?.state === \"active\" ? lifecycle.model : null,\n });\n const selectedModel = selection.selectedModel;\n const realtime = useSessionRealtime({\n ...props,\n model: selectedModel.id,\n modelAvailable: selectedModel.available,\n modelUnavailableReason: selectedModel.unavailableReason,\n });\n const autostartModelRef = useRef(props.realtimeAutostartModel ?? null);\n const autostartStartedRef = useRef(false);\n\n const { models, selectedModel: selectedRealtimeModel, selectModel } = selection;\n const { canStart, start } = realtime;\n const onRealtimeAutostartConsumed = props.onRealtimeAutostartConsumed;\n const onVoiceActiveChange = props.onVoiceActiveChange;\n const voiceActive = realtime.snapshot.mode?.state === \"active\";\n\n useEffect(() => {\n onVoiceActiveChange?.(voiceActive);\n }, [onVoiceActiveChange, voiceActive]);\n\n useEffect(() => {\n const pending = autostartModelRef.current;\n if (!pending || autostartStartedRef.current || lifecycle?.state === \"active\") return;\n const available = models.some((model) => model.id === pending && model.available);\n if (!available) return;\n if (selectedRealtimeModel.id !== pending) {\n selectModel(pending);\n return;\n }\n if (!canStart) return;\n autostartStartedRef.current = true;\n void start()\n .then(() => {\n autostartModelRef.current = null;\n onRealtimeAutostartConsumed?.();\n })\n .catch(() => {\n autostartStartedRef.current = false;\n });\n }, [\n canStart,\n lifecycle?.state,\n models,\n props.sessionId,\n props.workspaceId,\n onRealtimeAutostartConsumed,\n selectModel,\n selectedRealtimeModel.id,\n start,\n ]);\n\n return (\n <RealtimeVoiceControl\n snapshot={realtime.snapshot}\n canStart={realtime.canStart}\n admissionBlocker={realtime.admissionBlocker}\n modelAvailable={selectedModel.available}\n menuSide=\"top\"\n modelMenu={props.modelMenu ?? \"split\"}\n audioRef={realtime.audioRef}\n selectedModel={selectedModel}\n models={selection.models}\n onSelectModel={realtime.lifecycleActive ? undefined : selection.selectModel}\n onStart={realtime.start}\n onStop={realtime.stop}\n onRetry={realtime.retry}\n onRetryAudibleOutput={realtime.retryAudibleOutput}\n onSetInputMuted={realtime.setInputMuted}\n onSetOutputMuted={realtime.setOutputMuted}\n />\n );\n}\n\nexport function useRealtimeModelSelection(options: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n codexConnected: boolean;\n activeModel?: SessionRealtimeModel | null | undefined;\n}) {\n const { client, workspaceId } = useEmbeddedRealtimeSession({\n client: options.client,\n workspaceId: options.workspaceId,\n });\n const activeModel = options.activeModel;\n const initialCachedCatalog = readCachedRealtimeModelCatalog(client, workspaceId);\n const [catalog, setCatalog] = useState<RealtimeModelOption[]>(\n () => initialCachedCatalog ?? realtimeModelFallback(options.codexConnected),\n );\n const catalogScopeRef = useRef({\n client,\n workspaceId,\n source: initialCachedCatalog ? (\"cache\" as const) : (\"fallback\" as const),\n });\n const [selectedModelId, setSelectedModelId] = useState<SessionRealtimeModel>(() => {\n const preferred = readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id;\n if (!initialCachedCatalog) return preferred;\n if (initialCachedCatalog.some((model) => model.id === preferred && model.available)) {\n return preferred;\n }\n return initialCachedCatalog.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;\n });\n\n useEffect(() => {\n let disposed = false;\n const applyCatalog = (models: RealtimeModelOption[]) => {\n catalogScopeRef.current = { client, workspaceId, source: \"cache\" };\n setCatalog(models);\n setSelectedModelId((current) => {\n if (models.some((model) => model.id === current && model.available)) return current;\n return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;\n });\n };\n const cached = readCachedRealtimeModelCatalog(client, workspaceId);\n if (cached) {\n const scope = catalogScopeRef.current;\n if (\n scope.client !== client ||\n scope.workspaceId !== workspaceId ||\n scope.source !== \"cache\"\n ) {\n applyCatalog(cached);\n }\n return;\n }\n const loading = loadRealtimeModelCatalog(client, workspaceId);\n if (!loading) return;\n void loading\n .then((models) => {\n if (disposed) return;\n applyCatalog(models);\n })\n .catch(() => undefined);\n return () => {\n disposed = true;\n };\n }, [client, workspaceId]);\n\n useEffect(() => {\n if (activeModel) setSelectedModelId(activeModel);\n }, [activeModel]);\n\n const selectedModel =\n catalog.find((model) => model.id === selectedModelId) ??\n ({\n ...CODEX_LIVE_MODEL,\n available: options.codexConnected,\n unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,\n } satisfies RealtimeModelOption);\n const selectModel = useCallback(\n (value: string) => {\n const model = catalog.find((candidate) => candidate.id === value);\n if (!model || !model.available || activeModel) return;\n setSelectedModelId(model.id);\n writeRealtimeModelPreference(workspaceId, model.id);\n },\n [activeModel, catalog, workspaceId],\n );\n\n return { models: catalog, selectedModel, selectModel };\n}\n\nconst IDLE_REALTIME_SNAPSHOT: SessionRealtimeControllerSnapshot = {\n status: \"idle\",\n realtimeId: null,\n mode: null,\n bridge: null,\n microphone: \"inactive\",\n inputMuted: false,\n audibleOutput: \"inactive\",\n outputMuted: false,\n connectionGeneration: 0,\n reconnectAttempt: 0,\n diagnostic: null,\n error: null,\n};\n\nexport function NewSessionRealtimeControl(props: {\n client?: RealtimeControllerClient | undefined;\n workspaceId?: string | undefined;\n codexConnected: boolean;\n disabled?: boolean | undefined;\n disabledReason?: string | null | undefined;\n onStart: (model: SessionRealtimeModel) => Promise<boolean>;\n /** Parent-owned selection (e.g. shared with the mobile “+” voice-model panel). */\n models?: readonly RealtimeModelOption[] | undefined;\n selectedModel?: RealtimeModelOption | undefined;\n onSelectModel?: ((modelId: string) => void) | undefined;\n /**\n * `split` attaches the model chevron at every breakpoint (public default).\n * `split-desktop` hides it below `sm` when the host owns mobile selection (e.g. “+”).\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` never attaches a model menu to the bar button.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n}) {\n const internalSelection = useRealtimeModelSelection({\n client: props.client,\n workspaceId: props.workspaceId,\n codexConnected: props.codexConnected,\n });\n const selection =\n props.models && props.selectedModel && props.onSelectModel\n ? {\n models: props.models,\n selectedModel: props.selectedModel,\n selectModel: props.onSelectModel,\n }\n : internalSelection;\n const audioRef = useRef<HTMLAudioElement>(null);\n const [starting, setStarting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const snapshot = useMemo<SessionRealtimeControllerSnapshot>(\n () =>\n starting\n ? { ...IDLE_REALTIME_SNAPSHOT, status: \"starting\" }\n : error\n ? { ...IDLE_REALTIME_SNAPSHOT, status: \"error\", error }\n : IDLE_REALTIME_SNAPSHOT,\n [error, starting],\n );\n const canStart = selection.selectedModel.available && props.disabled !== true && !starting;\n const onStart = props.onStart;\n const selectedModelId = selection.selectedModel.id;\n const start = useCallback(async () => {\n if (!canStart) return;\n setStarting(true);\n setError(null);\n try {\n const started = await onStart(selectedModelId);\n if (!started) setError(\"The voice session could not be created.\");\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n } finally {\n setStarting(false);\n }\n }, [canStart, onStart, selectedModelId]);\n\n return (\n <RealtimeVoiceControl\n snapshot={snapshot}\n canStart={canStart}\n admissionBlocker={selection.selectedModel.unavailableReason ?? props.disabledReason ?? null}\n modelAvailable={selection.selectedModel.available}\n selectionDisabled={starting}\n menuSide=\"bottom\"\n modelMenu={props.modelMenu ?? \"split\"}\n showDiagnostics={false}\n audioRef={audioRef}\n selectedModel={selection.selectedModel}\n models={selection.models}\n onSelectModel={selection.selectModel}\n onStart={start}\n onStop={async () => undefined}\n onRetry={start}\n onRetryAudibleOutput={async () => undefined}\n onSetInputMuted={() => undefined}\n onSetOutputMuted={() => undefined}\n />\n );\n}\n\nexport function RealtimeVoiceControl(props: {\n snapshot: SessionRealtimeControllerSnapshot;\n canStart: boolean;\n admissionBlocker?: string | null | undefined;\n modelAvailable: boolean;\n selectionDisabled?: boolean | undefined;\n /** Prefer `bottom` on home/new-session; `top` for the docked session composer. */\n menuSide?: \"top\" | \"bottom\" | undefined;\n /**\n * `split` = start + model chevron at every breakpoint (public SDK default).\n * `split-desktop` = chevron from `sm` up; below that the host owns selection.\n * Inside a container-responsive Composer.Root, `sm` follows that composer.\n * `none` = start button only.\n */\n modelMenu?: \"split\" | \"split-desktop\" | \"none\" | undefined;\n showDiagnostics?: boolean;\n /** Extra classes on the in-bar mute cluster (e.g. `max-sm:hidden` when mutes live in “+”). */\n muteControlsClassName?: string | undefined;\n audioRef: RefObject<HTMLAudioElement | null>;\n selectedModel?: RealtimeModelOption | undefined;\n models?: readonly RealtimeModelOption[] | undefined;\n onSelectModel?: ((modelId: string) => void) | undefined;\n onStart: () => Promise<void>;\n onStop: () => Promise<void>;\n onRetry: () => Promise<void>;\n onRetryAudibleOutput: () => Promise<void>;\n onSetInputMuted: (muted: boolean) => void;\n onSetOutputMuted: (muted: boolean) => void;\n}) {\n const reduceMotion = useReducedMotion();\n const selectedModel = props.selectedModel ?? {\n ...CODEX_LIVE_MODEL,\n available: props.modelAvailable,\n unavailableReason: props.modelAvailable ? null : CODEX_LIVE_MODEL.unavailableReason,\n };\n const models = props.models ?? [selectedModel];\n const status = statusContent(\n props.snapshot,\n props.modelAvailable,\n props.canStart,\n props.admissionBlocker ?? null,\n selectedModel.label,\n );\n const modeOwned = props.snapshot.mode?.state === \"active\";\n const retryConnection =\n modeOwned &&\n props.snapshot.status !== \"stopping\" &&\n props.snapshot.status !== \"active\" &&\n props.snapshot.diagnostic?.recoverable === true;\n const audioBlocked = props.snapshot.audibleOutput === \"blocked\" && !props.snapshot.outputMuted;\n const mainDisabled =\n props.snapshot.status === \"stopping\" ||\n props.snapshot.status === \"lost_owner\" ||\n (!modeOwned && !props.canStart) ||\n (props.snapshot.status === \"starting\" && !modeOwned);\n const mainLabel = audioBlocked\n ? \"Resume voice audio\"\n : retryConnection\n ? \"Retry voice connection\"\n : modeOwned\n ? \"End voice conversation\"\n : `Start voice with ${selectedModel.label}`;\n const runMainAction = audioBlocked\n ? props.onRetryAudibleOutput\n : retryConnection\n ? props.onRetry\n : modeOwned\n ? props.onStop\n : props.onStart;\n const diagnosticsVisible = props.showDiagnostics ?? import.meta.env.DEV;\n const modelMenu = props.modelMenu ?? \"split\";\n const showAttachedModelMenu = modelMenu === \"split\" || modelMenu === \"split-desktop\";\n const desktopOnlyModelMenu = modelMenu === \"split-desktop\";\n const [pickerOpen, setPickerOpen] = useState(false);\n const [pickerProvider, setPickerProvider] = useState<RealtimeModelProvider | null>(\n selectedModel.provider,\n );\n const [pickerDirection, setPickerDirection] = useState<1 | -1>(1);\n\n useEffect(() => {\n if (!pickerOpen) setPickerProvider(selectedModel.provider);\n }, [pickerOpen, selectedModel.provider]);\n\n return (\n <div\n role=\"group\"\n aria-label=\"Realtime voice\"\n data-picker-side={props.menuSide ?? \"top\"}\n data-model-menu={modelMenu}\n className=\"og-realtime-control inline-flex shrink-0 items-center\"\n >\n <audio ref={props.audioRef} autoPlay className=\"hidden\" aria-hidden=\"true\" />\n <RealtimeComposerGlow phase={status.phase} reduceMotion={reduceMotion === true} />\n <AnimatePresence initial={false}>\n {modeOwned ? (\n <motion.div\n key=\"realtime-mute-controls\"\n data-testid=\"realtime-mute-controls\"\n // After dictate collapses and the model slides left, bloom mutes\n // out from the primary voice control. Exit is immediate (no delay).\n initial={reduceMotion ? false : { opacity: 0, width: 0 }}\n animate={{\n opacity: 1,\n width: \"auto\",\n transition: reduceMotion\n ? { duration: 0 }\n : { delay: 0.3, duration: 0.48, ease: [0.22, 1, 0.36, 1] },\n }}\n {...(reduceMotion\n ? {}\n : {\n exit: {\n opacity: 0,\n width: 0,\n transition: { duration: 0.28, ease: [0.4, 0, 1, 1] },\n },\n })}\n className={cn(\n \"inline-flex shrink-0 items-center overflow-hidden\",\n props.muteControlsClassName,\n )}\n >\n <motion.div\n className=\"inline-flex items-center gap-0.5 pr-1\"\n initial={reduceMotion ? false : { x: 18, opacity: 0 }}\n animate={{\n x: 0,\n opacity: 1,\n transition: reduceMotion\n ? { duration: 0 }\n : {\n delay: 0.32,\n type: \"spring\",\n stiffness: 320,\n damping: 30,\n mass: 0.8,\n },\n }}\n {...(reduceMotion\n ? {}\n : {\n exit: {\n x: 14,\n opacity: 0,\n transition: { duration: 0.22, ease: [0.4, 0, 1, 1] },\n },\n })}\n >\n <RealtimeMuteButton\n muted={props.snapshot.inputMuted}\n kind=\"microphone\"\n reduceMotion={reduceMotion === true}\n enterDelay={reduceMotion ? 0 : 0.34}\n onToggle={() => props.onSetInputMuted(!props.snapshot.inputMuted)}\n />\n <RealtimeMuteButton\n muted={props.snapshot.outputMuted}\n kind=\"output\"\n reduceMotion={reduceMotion === true}\n enterDelay={reduceMotion ? 0 : 0.4}\n onToggle={() => props.onSetOutputMuted(!props.snapshot.outputMuted)}\n />\n </motion.div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n <div className=\"inline-flex shrink-0 items-center\">\n <motion.button\n type=\"button\"\n data-testid=\"realtime-primary-action\"\n data-phase={status.phase}\n aria-label={mainLabel}\n aria-pressed={modeOwned}\n title={mainLabel}\n disabled={mainDisabled}\n {...(reduceMotion ? {} : { whileTap: { scale: 0.92 } })}\n transition={{ type: \"spring\", stiffness: 520, damping: 30 }}\n onClick={() => void runMainAction().catch(() => undefined)}\n className={cn(\n // Match transcription mic: ghost icon when idle; filled only when live.\n // Public contract: coarse pointers keep a 44px target; split menu uses\n // left-only rounding at `sm+` where the chevron attaches.\n \"og-realtime-primary relative inline-flex size-8 items-center justify-center outline-hidden\",\n \"rounded-og-md transition-[background-color,border-color,color,box-shadow] duration-200 ease-og-out\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45\",\n \"disabled:cursor-not-allowed disabled:opacity-45 pointer-coarse:size-11\",\n showAttachedModelMenu &&\n (desktopOnlyModelMenu\n ? \"sm:rounded-l-og-md sm:rounded-r-none\"\n : \"rounded-l-og-md rounded-r-none\"),\n voiceButtonTone(status.phase),\n )}\n >\n <RealtimeActionGlyph\n phase={status.phase}\n audioBlocked={audioBlocked}\n reduceMotion={reduceMotion === true}\n />\n </motion.button>\n\n {showAttachedModelMenu ? (\n <DropdownMenu open={pickerOpen} onOpenChange={setPickerOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Choose voice model and options\"\n title={`Voice model: ${selectedModel.label}`}\n disabled={props.selectionDisabled}\n className={cn(\n // Keep the split trigger compact on desktop while preserving a\n // full 44px mobile target independent of pointer-media support.\n \"og-realtime-model-trigger inline-flex h-11 w-11 items-center justify-center rounded-r-og-md outline-hidden sm:h-8 sm:w-6\",\n \"transition-[background-color,border-color,color] duration-200 ease-og-out\",\n \"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45\",\n desktopOnlyModelMenu && \"hidden sm:inline-flex\",\n voiceChevronTone(status.phase),\n )}\n >\n <ChevronDownIcon className=\"size-3\" aria-hidden />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={props.menuSide ?? \"top\"}\n align=\"end\"\n sideOffset={8}\n collisionPadding={12}\n className=\"og-realtime-menu flex w-72 max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] flex-col overflow-hidden rounded-xl border-border bg-surface p-1.5 shadow-xl\"\n onCloseAutoFocus={(event) => event.preventDefault()}\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n <RealtimeModelPickerMenu\n models={models}\n selectedModel={selectedModel}\n provider={pickerProvider}\n direction={pickerDirection}\n disabled={\n props.selectionDisabled === true || props.snapshot.mode?.state === \"active\"\n }\n onProviderChange={(provider, direction) => {\n setPickerDirection(direction);\n setPickerProvider(provider);\n }}\n onSelect={(modelId) => {\n props.onSelectModel?.(modelId);\n setPickerOpen(false);\n }}\n />\n </div>\n\n {status.phase !== \"idle\" ? (\n <>\n <DropdownMenuSeparator className=\"mx-1 bg-og-border\" />\n <div className=\"px-2.5 py-2\" role=\"status\" aria-live=\"polite\">\n <div className=\"flex items-center gap-2 text-og-sm font-medium text-og-fg\">\n <RealtimeStatusDot\n phase={status.phase}\n reduceMotion={reduceMotion === true}\n />\n {status.label}\n </div>\n <p className=\"mt-1 text-og-xs leading-5 text-og-fg-subtle\">{status.detail}</p>\n </div>\n </>\n ) : null}\n\n {audioBlocked ? (\n <DropdownMenuItem\n className=\"rounded-og-md\"\n onSelect={() => void props.onRetryAudibleOutput().catch(() => undefined)}\n >\n <Volume2Icon />\n Resume audio\n </DropdownMenuItem>\n ) : null}\n {retryConnection ? (\n <DropdownMenuItem\n className=\"rounded-og-md\"\n onSelect={() => void props.onRetry().catch(() => undefined)}\n >\n <RotateCcwIcon />\n Retry connection\n </DropdownMenuItem>\n ) : null}\n {modeOwned && props.snapshot.status !== \"lost_owner\" ? (\n <DropdownMenuItem\n variant=\"destructive\"\n className=\"rounded-og-md\"\n disabled={props.snapshot.status === \"stopping\"}\n onSelect={() => void props.onStop().catch(() => undefined)}\n >\n <SquareIcon />\n End voice conversation\n </DropdownMenuItem>\n ) : null}\n\n {diagnosticsVisible ? <RealtimeDiagnosticsMenu snapshot={props.snapshot} /> : null}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n </div>\n\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {status.label}. {status.detail}\n </span>\n {props.snapshot.error ? (\n <span className=\"sr-only\" role=\"alert\">\n {props.snapshot.error}\n </span>\n ) : null}\n </div>\n );\n}\n\n/** Drill-in body for the mobile composer “+” menu — same catalog as the desktop chevron. */\nexport function RealtimeVoiceModelPanel(props: {\n models: readonly RealtimeModelOption[];\n selectedModel: RealtimeModelOption;\n disabled?: boolean | undefined;\n leading?: ReactNode | undefined;\n onSelect: (modelId: SessionRealtimeModel) => void;\n}) {\n const [provider, setProvider] = useState<RealtimeModelProvider | null>(null);\n const [direction, setDirection] = useState<1 | -1>(1);\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\" data-testid=\"realtime-voice-model-panel\">\n <div className=\"flex shrink-0 items-start gap-1 px-2 pt-1 pb-1.5\">\n {props.leading}\n <div className=\"min-w-0\">\n <div className=\"text-og-menu font-medium text-og-fg\">Voice model</div>\n <p className=\"mt-0.5 text-og-control text-og-fg-subtle\">\n Used when you start a voice conversation.\n </p>\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain px-1.5 pb-1.5\">\n <RealtimeModelPickerMenu\n models={props.models}\n selectedModel={props.selectedModel}\n provider={provider}\n direction={direction}\n disabled={props.disabled === true}\n onProviderChange={(next, nextDirection) => {\n setDirection(nextDirection);\n setProvider(next);\n }}\n onSelect={props.onSelect}\n />\n </div>\n </div>\n );\n}\n\nfunction RealtimeMuteButton(props: {\n muted: boolean;\n kind: \"microphone\" | \"output\";\n reduceMotion: boolean;\n enterDelay?: number | undefined;\n onToggle(): void;\n}) {\n const microphone = props.kind === \"microphone\";\n const label = microphone\n ? props.muted\n ? \"Unmute microphone\"\n : \"Mute microphone\"\n : props.muted\n ? \"Unmute voice audio\"\n : \"Mute voice audio\";\n const Icon = microphone\n ? props.muted\n ? MicOffIcon\n : MicIcon\n : props.muted\n ? VolumeXIcon\n : Volume2Icon;\n\n return (\n <motion.button\n type=\"button\"\n aria-label={label}\n aria-pressed={props.muted}\n title={label}\n initial={props.reduceMotion ? false : { scale: 0.72, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n transition={{\n delay: props.enterDelay ?? 0,\n type: \"spring\",\n stiffness: 420,\n damping: 26,\n mass: 0.7,\n }}\n {...(props.reduceMotion ? {} : { whileTap: { scale: 0.92 } })}\n onClick={props.onToggle}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-og-md outline-hidden\",\n \"transition-[background-color,border-color,color] duration-150 ease-og-out\",\n \"focus-visible:ring-2 focus-visible:ring-og-accent/45 pointer-coarse:size-11\",\n props.muted\n ? \"border border-og-accent/35 bg-og-accent-soft text-og-accent\"\n : \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\",\n )}\n >\n <Icon className=\"size-3.5\" aria-hidden />\n </motion.button>\n );\n}\n\nexport function RealtimeModelPickerMenu(props: {\n models: readonly RealtimeModelOption[];\n selectedModel: RealtimeModelOption;\n provider: RealtimeModelProvider | null;\n direction: 1 | -1;\n disabled: boolean;\n onProviderChange: (provider: RealtimeModelProvider | null, direction: 1 | -1) => void;\n onSelect: (modelId: SessionRealtimeModel) => void;\n}) {\n const providers = REALTIME_MODEL_PROVIDERS.filter((provider) =>\n props.models.some((model) => model.provider === provider),\n );\n const pageKey = props.provider ? `voice-models:${props.provider}` : \"voice-providers\";\n const body = props.provider ? (\n <div data-testid=\"realtime-model-picker-models\">\n <PickerBackHeader\n label={props.provider}\n icon={\n <BillingClassMark\n billingClass={REALTIME_PROVIDER_META[props.provider].billingClass}\n aria-label=\"\"\n />\n }\n onBack={() => props.onProviderChange(null, -1)}\n />\n <div className=\"flex flex-col gap-0.5\">\n {props.models\n .filter((model) => model.provider === props.provider)\n .map((model) => {\n const selected = model.id === props.selectedModel.id;\n return (\n <PickerNavRow\n key={model.id}\n label={model.label}\n hint={\n model.unavailableReason ??\n (model.recommended ? `Recommended · ${model.description}` : model.description)\n }\n disabled={props.disabled || !model.available}\n title={model.unavailableReason ?? model.description}\n active={selected}\n showChevron={false}\n trailing={selected ? <CheckIcon className=\"size-3.5\" aria-hidden /> : undefined}\n testId={`realtime-model-choice-${model.id}`}\n onClick={() => props.onSelect(model.id)}\n />\n );\n })}\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"realtime-model-picker-providers\">\n {providers.map((provider) => {\n const meta = REALTIME_PROVIDER_META[provider];\n return (\n <PickerNavRow\n key={provider}\n label={provider}\n hint={meta.hint}\n icon={<BillingClassMark billingClass={meta.billingClass} aria-label=\"\" />}\n active={props.selectedModel.provider === provider}\n testId={`realtime-model-provider-${meta.billingClass}`}\n onClick={() => props.onProviderChange(provider, 1)}\n />\n );\n })}\n </div>\n );\n\n return (\n <div className=\"relative overflow-hidden\" data-testid=\"realtime-model-picker-menu\">\n <PickerAnimatedPage pageKey={pageKey} direction={props.direction}>\n {body}\n </PickerAnimatedPage>\n </div>\n );\n}\n\nfunction RealtimeDiagnosticsMenu({ snapshot }: { snapshot: SessionRealtimeControllerSnapshot }) {\n const bridge = snapshot.bridge;\n const rows = [\n [\"controller\", snapshot.status],\n [\"microphone\", snapshot.microphone],\n [\"microphone muted\", String(snapshot.inputMuted)],\n [\"audio\", snapshot.audibleOutput],\n [\"audio muted\", String(snapshot.outputMuted)],\n [\"generation\", String(snapshot.connectionGeneration)],\n [\"reconnect attempt\", String(snapshot.reconnectAttempt)],\n [\"mode\", snapshot.mode?.state ?? \"none\"],\n [\"mode version\", snapshot.mode ? String(snapshot.mode.version) : \"—\"],\n [\"connection epoch\", snapshot.mode ? String(snapshot.mode.connectionEpoch) : \"—\"],\n [\"provider started\", bridge ? String(bridge.providerStarted) : \"false\"],\n [\"provider speaking\", bridge ? String(bridge.speaking) : \"false\"],\n [\"delegation\", bridge?.activeDelegationId ? \"active\" : \"none\"],\n [\"pending durable events\", bridge ? String(bridge.pendingInbound) : \"0\"],\n [\"pending bytes\", bridge ? String(bridge.pendingInboundBytes) : \"0\"],\n [\"client ack\", bridge?.clientAckThroughSequence?.toString() ?? \"—\"],\n [\"ignored provider events\", bridge ? String(bridge.ignoredEventCount) : \"0\"],\n [\"last ignored event\", bridge?.lastIgnoredEventType ?? \"none\"],\n [\"bridge error\", bridge?.lastError ?? \"none\"],\n [\"diagnostic\", snapshot.diagnostic?.kind ?? \"none\"],\n ] as const;\n\n return (\n <>\n <DropdownMenuSeparator className=\"mx-1 bg-og-border\" />\n <DropdownMenuSub>\n <DropdownMenuSubTrigger className=\"rounded-og-md text-og-fg-muted\">\n Realtime diagnostics\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-80 w-72 overflow-y-auto rounded-og-lg border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg\">\n {rows.map(([label, value]) => (\n <DropdownMenuItem\n key={label}\n disabled\n className=\"grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] gap-3 rounded-og-md font-mono text-[11px] opacity-100\"\n >\n <span className=\"truncate text-og-fg-subtle\">{label}</span>\n <span className=\"truncate text-right text-og-fg\">{value}</span>\n </DropdownMenuItem>\n ))}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n </>\n );\n}\n\ntype RealtimeVisualPhase =\n | \"idle\"\n | \"connecting\"\n | \"listening\"\n | \"speaking\"\n | \"reconnecting\"\n | \"blocked\"\n | \"unavailable\"\n | \"error\"\n | \"elsewhere\"\n | \"stopping\";\n\nfunction statusContent(\n snapshot: SessionRealtimeControllerSnapshot,\n modelAvailable: boolean,\n canStart: boolean,\n admissionBlocker: string | null,\n modelLabel: string,\n): { phase: RealtimeVisualPhase; label: string; detail: string } {\n if (snapshot.audibleOutput === \"blocked\" && !snapshot.outputMuted) {\n return {\n phase: \"blocked\",\n label: \"Audio output blocked\",\n detail: \"Resume audio to hear the existing voice connection.\",\n };\n }\n switch (snapshot.status) {\n case \"starting\":\n return {\n phase: \"connecting\",\n label: \"Connecting…\",\n detail: \"Preparing your microphone and live voice.\",\n };\n case \"active\":\n return snapshot.bridge?.speaking\n ? {\n phase: \"speaking\",\n label: \"Speaking\",\n detail: \"Interrupt naturally whenever you want.\",\n }\n : {\n phase: \"listening\",\n label: \"Listening\",\n detail: \"Voice and typed messages share this session.\",\n };\n case \"stopping\":\n return {\n phase: \"stopping\",\n label: \"Ending voice…\",\n detail: \"Saving the remaining conversation context.\",\n };\n case \"recovering\":\n return {\n phase: \"reconnecting\",\n label: \"Reconnecting…\",\n detail: snapshot.error ?? \"Keeping this voice conversation connected.\",\n };\n case \"lost_owner\":\n return {\n phase: \"elsewhere\",\n label: \"Voice active elsewhere\",\n detail: \"Return to the browser that started it, or wait for that connection to expire.\",\n };\n case \"error\":\n return {\n phase: \"error\",\n label: \"Voice unavailable\",\n detail: snapshot.error ?? \"The connection could not be started.\",\n };\n case \"idle\":\n if (!modelAvailable) {\n return {\n phase: \"unavailable\",\n label: \"Voice model unavailable\",\n detail: admissionBlocker ?? \"Choose another voice model.\",\n };\n }\n if (!canStart && admissionBlocker) {\n return {\n phase: \"unavailable\",\n label: \"Voice unavailable\",\n detail: admissionBlocker,\n };\n }\n return {\n phase: \"idle\",\n label: \"Start voice\",\n detail: `Talk live with ${modelLabel}.`,\n };\n }\n}\n\nfunction RealtimeComposerGlow(props: { phase: RealtimeVisualPhase; reduceMotion: boolean }) {\n const visible = [\n \"connecting\",\n \"listening\",\n \"speaking\",\n \"reconnecting\",\n \"blocked\",\n \"error\",\n \"stopping\",\n ].includes(props.phase);\n return (\n <AnimatePresence initial={false}>\n {visible ? (\n <motion.span\n aria-hidden\n key={props.phase}\n initial={{ opacity: 0 }}\n animate={\n props.reduceMotion\n ? { opacity: 0.48 }\n : props.phase === \"speaking\"\n ? { opacity: [0.42, 0.82, 0.52] }\n : props.phase === \"listening\"\n ? { opacity: [0.34, 0.62, 0.42] }\n : { opacity: [0.28, 0.58, 0.28] }\n }\n exit={{ opacity: 0 }}\n transition={\n props.reduceMotion\n ? { duration: 0.12 }\n : {\n duration: props.phase === \"speaking\" ? 1.15 : 2.1,\n ease: \"easeInOut\",\n repeat: Number.POSITIVE_INFINITY,\n }\n }\n className={cn(\n \"pointer-events-none absolute -inset-px z-[1] rounded-og-lg ring-1 ring-inset shadow-og-glow\",\n props.phase === \"speaking\"\n ? \"ring-og-status-running/50\"\n : props.phase === \"blocked\" || props.phase === \"error\"\n ? \"ring-og-status-waiting/45\"\n : \"ring-og-accent/45\",\n )}\n />\n ) : null}\n </AnimatePresence>\n );\n}\n\nfunction RealtimeActionGlyph(props: {\n phase: RealtimeVisualPhase;\n audioBlocked: boolean;\n reduceMotion: boolean;\n}) {\n if ([\"connecting\", \"reconnecting\", \"stopping\"].includes(props.phase)) {\n return <LoaderCircleIcon className=\"size-3.5 animate-og-spin\" aria-hidden />;\n }\n if (props.phase === \"blocked\") {\n return props.audioBlocked ? (\n <Volume2Icon className=\"size-3.5\" aria-hidden />\n ) : (\n <CircleAlertIcon className=\"size-3.5\" aria-hidden />\n );\n }\n if (props.phase === \"error\" || props.phase === \"elsewhere\" || props.phase === \"unavailable\") {\n return <CircleAlertIcon className=\"size-3.5\" aria-hidden />;\n }\n if (props.phase === \"listening\" || props.phase === \"speaking\") {\n return (\n <span className=\"relative inline-flex size-4 items-center justify-center\">\n {!props.reduceMotion ? (\n <motion.span\n aria-hidden\n className=\"absolute inset-0 rounded-full border border-current\"\n animate={{ opacity: [0.65, 0], scale: [0.65, 1.45] }}\n transition={{\n duration: props.phase === \"speaking\" ? 0.9 : 1.5,\n ease: \"easeOut\",\n repeat: Number.POSITIVE_INFINITY,\n }}\n />\n ) : null}\n <SquareIcon className=\"relative size-2.5 fill-current\" aria-hidden />\n </span>\n );\n }\n return <AudioLinesIcon className=\"size-4\" aria-hidden />;\n}\n\nfunction RealtimeStatusDot(props: { phase: RealtimeVisualPhase; reduceMotion: boolean }) {\n const live = props.phase === \"listening\" || props.phase === \"speaking\";\n return (\n <span className=\"relative inline-flex size-2 shrink-0\" aria-hidden>\n {live && !props.reduceMotion ? (\n <motion.span\n className=\"absolute inset-0 rounded-full bg-og-status-running\"\n animate={{ opacity: [0.45, 0], scale: [1, 2.2] }}\n transition={{\n duration: 1.4,\n ease: \"easeOut\",\n repeat: Number.POSITIVE_INFINITY,\n }}\n />\n ) : null}\n <span\n className={cn(\n \"relative size-2 rounded-full\",\n live\n ? \"bg-og-status-running\"\n : props.phase === \"blocked\" || props.phase === \"error\"\n ? \"bg-og-status-waiting\"\n : props.phase === \"elsewhere\" || props.phase === \"unavailable\"\n ? \"bg-og-fg-subtle\"\n : \"bg-og-accent\",\n )}\n />\n </span>\n );\n}\n\nfunction voiceButtonTone(phase: RealtimeVisualPhase): string {\n if (phase === \"listening\" || phase === \"speaking\") {\n return \"border border-og-accent/45 bg-og-accent text-og-accent-fg shadow-og-sm hover:bg-og-accent-strong\";\n }\n if (phase === \"blocked\" || phase === \"error\") {\n return \"border border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting hover:bg-og-status-waiting/15\";\n }\n if (phase === \"connecting\" || phase === \"reconnecting\" || phase === \"stopping\") {\n return \"border border-og-accent/35 bg-og-accent-soft text-og-accent\";\n }\n // Idle: same ghost treatment as the composer transcription mic — no box.\n return \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\";\n}\n\nfunction voiceChevronTone(phase: RealtimeVisualPhase): string {\n if (phase === \"listening\" || phase === \"speaking\") {\n return \"border border-l-0 border-og-accent/45 bg-og-accent text-og-accent-fg hover:bg-og-accent-strong\";\n }\n if (phase === \"blocked\" || phase === \"error\") {\n return \"border border-l-0 border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting hover:bg-og-status-waiting/15\";\n }\n if (phase === \"connecting\" || phase === \"reconnecting\" || phase === \"stopping\") {\n return \"border border-l-0 border-og-accent/35 bg-og-accent-soft text-og-accent\";\n }\n return \"text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg\";\n}\n\nfunction toRealtimeModelOption(model: WorkspaceRealtimeModelCatalogItem): RealtimeModelOption {\n return { ...model };\n}\n\nfunction readRealtimeModelPreference(workspaceId: string): SessionRealtimeModel | null {\n if (typeof localStorage === \"undefined\") return null;\n try {\n const value = localStorage.getItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`);\n return isRealtimeModel(value) ? value : null;\n } catch {\n return null;\n }\n}\n\nfunction writeRealtimeModelPreference(workspaceId: string, model: SessionRealtimeModel): void {\n if (typeof localStorage === \"undefined\") return;\n try {\n localStorage.setItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`, model);\n } catch {\n // Voice selection still works when browser storage is unavailable.\n }\n}\n\nfunction isRealtimeModel(value: string | null): value is SessionRealtimeModel {\n return (\n value === \"gpt-live-1-boulder-alpha\" ||\n value === \"opengeni-gateway/openai/gpt-realtime-2.1\" ||\n value === \"opengeni-gateway/openai/gpt-realtime-mini\" ||\n value === \"opengeni-gateway/xai/grok-voice-think-fast-2.0\" ||\n value === \"supergrok/grok-voice-think-fast-2.0\" ||\n value === \"workspace-gateway/openai/gpt-realtime-2.1\" ||\n value === \"workspace-gateway/openai/gpt-realtime-mini\" ||\n value === \"workspace-gateway/xai/grok-voice-think-fast-2.0\"\n );\n}\n\n/** @deprecated Use the provider-neutral realtime names. */\nexport const useSessionCodexRealtime = useSessionRealtime;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const useWorkspaceRealtimeModelSelection = useRealtimeModelSelection;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const SessionCodexRealtimeControl = SessionRealtimeControl;\n/** @deprecated Use the provider-neutral realtime names. */\nexport const CodexRealtimeControl = RealtimeVoiceControl;\n","\"use client\";\n\nimport { ChevronRightIcon } from \"lucide-react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport { createContext, useContext, type ComponentProps, type Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { usePortalTokenSource, usePortalTokenStyle } from \"../lib/use-portal-token-style\";\n\ntype DropdownMenuSourceContextValue = {\n source: HTMLElement | null;\n ref: (node: HTMLElement | null) => void;\n};\n\nconst DropdownMenuSourceContext = createContext<DropdownMenuSourceContextValue | null>(null);\n\nfunction assignRef<T>(ref: Ref<T> | undefined, value: T | null) {\n if (typeof ref === \"function\") ref(value);\n else if (ref) ref.current = value;\n}\n\nfunction DropdownMenu(props: ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n const source = usePortalTokenSource<HTMLElement>();\n return (\n <DropdownMenuSourceContext.Provider value={source}>\n <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n </DropdownMenuSourceContext.Provider>\n );\n}\n\nfunction DropdownMenuTrigger({\n ref,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n const sourceRef = useContext(DropdownMenuSourceContext);\n return (\n <DropdownMenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n ref={(node) => {\n sourceRef?.ref(node);\n assignRef(ref, node);\n }}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n style,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n const source = useContext(DropdownMenuSourceContext);\n const portalStyle = usePortalTokenStyle(source?.source ?? null);\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n className={cn(\n \"og-root z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className,\n )}\n style={{ ...portalStyle, ...style }}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-og-menu outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub(props: ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-og-menu outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className=\"ml-auto size-4\" />\n </DropdownMenuPrimitive.SubTrigger>\n );\n}\n\nfunction DropdownMenuSubContent({\n className,\n style,\n ...props\n}: ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n const source = useContext(DropdownMenuSourceContext);\n const portalStyle = usePortalTokenStyle(source?.source ?? null);\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"og-root z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className,\n )}\n style={{ ...portalStyle, ...style }}\n {...props}\n />\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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": {
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"@dnd-kit/utilities": "3.2.2",
|
|
117
117
|
"@fontsource-variable/noto-sans-arabic": "5.2.10",
|
|
118
118
|
"@fontsource-variable/noto-sans-jp": "5.2.10",
|
|
119
|
-
"@opengeni/sdk": "^0.
|
|
119
|
+
"@opengeni/sdk": "^1.0.1",
|
|
120
120
|
"clsx": "^2.1.1",
|
|
121
121
|
"lucide-react": "^1.8.0",
|
|
122
122
|
"motion": "^12.0.0",
|
|
@@ -98,9 +98,108 @@ const REALTIME_PROVIDER_META: Record<
|
|
|
98
98
|
"Your Gateway": { billingClass: "byok", hint: "Billed to your AI Gateway" },
|
|
99
99
|
};
|
|
100
100
|
const REALTIME_MODEL_STORAGE_PREFIX = "opengeni:realtime-model";
|
|
101
|
+
const REALTIME_MODEL_CATALOG_CACHE_TTL_MS = 60_000;
|
|
102
|
+
const REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES = 64;
|
|
101
103
|
|
|
102
104
|
type RealtimeControllerClient = EmbeddedRealtimeSessionClientLike & SessionRealtimeClientLike;
|
|
103
105
|
|
|
106
|
+
type RealtimeModelCatalogCacheEntry =
|
|
107
|
+
| { state: "loading"; promise: Promise<RealtimeModelOption[]> }
|
|
108
|
+
| { state: "ready"; models: RealtimeModelOption[]; expiresAt: number };
|
|
109
|
+
|
|
110
|
+
// Scope advisory availability to the exact client object and workspace. The
|
|
111
|
+
// server still authorizes every realtime operation; changing principals should
|
|
112
|
+
// replace the client just as it does for the rest of the SDK state.
|
|
113
|
+
const realtimeModelCatalogCache = new WeakMap<
|
|
114
|
+
RealtimeControllerClient,
|
|
115
|
+
Map<string, RealtimeModelCatalogCacheEntry>
|
|
116
|
+
>();
|
|
117
|
+
|
|
118
|
+
function realtimeModelFallback(codexConnected: boolean): RealtimeModelOption[] {
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
...CODEX_LIVE_MODEL,
|
|
122
|
+
available: codexConnected,
|
|
123
|
+
unavailableReason: codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function realtimeModelCatalogCacheFor(
|
|
129
|
+
client: RealtimeControllerClient,
|
|
130
|
+
): Map<string, RealtimeModelCatalogCacheEntry> {
|
|
131
|
+
const existing = realtimeModelCatalogCache.get(client);
|
|
132
|
+
if (existing) return existing;
|
|
133
|
+
const created = new Map<string, RealtimeModelCatalogCacheEntry>();
|
|
134
|
+
realtimeModelCatalogCache.set(client, created);
|
|
135
|
+
return created;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function readCachedRealtimeModelCatalog(
|
|
139
|
+
client: RealtimeControllerClient,
|
|
140
|
+
workspaceId: string,
|
|
141
|
+
now = Date.now(),
|
|
142
|
+
): RealtimeModelOption[] | null {
|
|
143
|
+
const cache = realtimeModelCatalogCache.get(client);
|
|
144
|
+
const entry = cache?.get(workspaceId);
|
|
145
|
+
if (!entry || entry.state === "loading") return null;
|
|
146
|
+
if (entry.expiresAt > now) return entry.models;
|
|
147
|
+
cache?.delete(workspaceId);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function pruneRealtimeModelCatalogCache(
|
|
152
|
+
cache: Map<string, RealtimeModelCatalogCacheEntry>,
|
|
153
|
+
now: number,
|
|
154
|
+
): void {
|
|
155
|
+
for (const [workspaceId, entry] of cache) {
|
|
156
|
+
if (entry.state === "ready" && entry.expiresAt <= now) cache.delete(workspaceId);
|
|
157
|
+
}
|
|
158
|
+
while (cache.size >= REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES) {
|
|
159
|
+
const oldestWorkspaceId = cache.keys().next().value;
|
|
160
|
+
if (oldestWorkspaceId === undefined) return;
|
|
161
|
+
cache.delete(oldestWorkspaceId);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function loadRealtimeModelCatalog(
|
|
166
|
+
client: RealtimeControllerClient,
|
|
167
|
+
workspaceId: string,
|
|
168
|
+
): Promise<RealtimeModelOption[]> | null {
|
|
169
|
+
const load = client.getWorkspaceRealtimeModelCatalog;
|
|
170
|
+
if (!load) return null;
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const cache = realtimeModelCatalogCacheFor(client);
|
|
173
|
+
const entry = cache.get(workspaceId);
|
|
174
|
+
if (entry?.state === "loading") return entry.promise;
|
|
175
|
+
if (entry?.state === "ready" && entry.expiresAt > now) return Promise.resolve(entry.models);
|
|
176
|
+
if (entry) cache.delete(workspaceId);
|
|
177
|
+
pruneRealtimeModelCatalogCache(cache, now);
|
|
178
|
+
|
|
179
|
+
const promise = load
|
|
180
|
+
.call(client, workspaceId)
|
|
181
|
+
.then((response) => response.models.map(toRealtimeModelOption))
|
|
182
|
+
.then((models) => {
|
|
183
|
+
const current = cache.get(workspaceId);
|
|
184
|
+
if (current?.state === "loading" && current.promise === promise) {
|
|
185
|
+
cache.delete(workspaceId);
|
|
186
|
+
cache.set(workspaceId, {
|
|
187
|
+
state: "ready",
|
|
188
|
+
models,
|
|
189
|
+
expiresAt: Date.now() + REALTIME_MODEL_CATALOG_CACHE_TTL_MS,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return models;
|
|
193
|
+
})
|
|
194
|
+
.catch((error: unknown) => {
|
|
195
|
+
const current = cache.get(workspaceId);
|
|
196
|
+
if (current?.state === "loading" && current.promise === promise) cache.delete(workspaceId);
|
|
197
|
+
throw error;
|
|
198
|
+
});
|
|
199
|
+
cache.set(workspaceId, { state: "loading", promise });
|
|
200
|
+
return promise;
|
|
201
|
+
}
|
|
202
|
+
|
|
104
203
|
export type SessionRealtimeControllerFactory = (
|
|
105
204
|
options: CreateSessionRealtimeControllerOptions,
|
|
106
205
|
) => SessionRealtimeController;
|
|
@@ -138,6 +237,8 @@ export function useSessionRealtime(options: {
|
|
|
138
237
|
model?: SessionRealtimeModel | undefined;
|
|
139
238
|
modelAvailable?: boolean | undefined;
|
|
140
239
|
modelUnavailableReason?: string | null | undefined;
|
|
240
|
+
/** Model-visible application context captured with each durable realtime message. */
|
|
241
|
+
getModelContext?: (() => string | undefined) | undefined;
|
|
141
242
|
/** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */
|
|
142
243
|
controllerFactory?: SessionRealtimeControllerFactory | undefined;
|
|
143
244
|
}) {
|
|
@@ -149,6 +250,8 @@ export function useSessionRealtime(options: {
|
|
|
149
250
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
150
251
|
const controllerRef = useRef<SessionRealtimeController | null>(null);
|
|
151
252
|
const controllerModelRef = useRef<SessionRealtimeModel | null>(null);
|
|
253
|
+
const modelContextProviderRef = useRef(options.getModelContext);
|
|
254
|
+
modelContextProviderRef.current = options.getModelContext;
|
|
152
255
|
const [snapshot, setSnapshot] = useState<SessionRealtimeControllerSnapshot>(() => ({
|
|
153
256
|
status: hasStoredSessionRealtimeOwnerProof({
|
|
154
257
|
workspaceId,
|
|
@@ -193,6 +296,7 @@ export function useSessionRealtime(options: {
|
|
|
193
296
|
sessionId: options.sessionId,
|
|
194
297
|
...(audioRef.current ? { remoteAudio: audioRef.current } : {}),
|
|
195
298
|
model,
|
|
299
|
+
getModelContext: () => modelContextProviderRef.current?.(),
|
|
196
300
|
});
|
|
197
301
|
controllerRef.current = controller;
|
|
198
302
|
controllerModelRef.current = model;
|
|
@@ -306,6 +410,8 @@ export function SessionRealtimeControl(props: {
|
|
|
306
410
|
events: SessionEvent[];
|
|
307
411
|
eventsReady: boolean;
|
|
308
412
|
codexConnected: boolean;
|
|
413
|
+
/** Model-visible application context captured with each durable realtime message. */
|
|
414
|
+
getModelContext?: (() => string | undefined) | undefined;
|
|
309
415
|
realtimeAutostartModel?: SessionRealtimeModel | undefined;
|
|
310
416
|
onRealtimeAutostartConsumed?: (() => void) | undefined;
|
|
311
417
|
/** Host hides dictate (and can choreograph layout) while realtime voice is live. */
|
|
@@ -411,31 +517,52 @@ export function useRealtimeModelSelection(options: {
|
|
|
411
517
|
workspaceId: options.workspaceId,
|
|
412
518
|
});
|
|
413
519
|
const activeModel = options.activeModel;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
available: options.codexConnected,
|
|
418
|
-
unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason,
|
|
419
|
-
},
|
|
420
|
-
]);
|
|
421
|
-
const [selectedModelId, setSelectedModelId] = useState<SessionRealtimeModel>(
|
|
422
|
-
() => readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id,
|
|
520
|
+
const initialCachedCatalog = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
521
|
+
const [catalog, setCatalog] = useState<RealtimeModelOption[]>(
|
|
522
|
+
() => initialCachedCatalog ?? realtimeModelFallback(options.codexConnected),
|
|
423
523
|
);
|
|
524
|
+
const catalogScopeRef = useRef({
|
|
525
|
+
client,
|
|
526
|
+
workspaceId,
|
|
527
|
+
source: initialCachedCatalog ? ("cache" as const) : ("fallback" as const),
|
|
528
|
+
});
|
|
529
|
+
const [selectedModelId, setSelectedModelId] = useState<SessionRealtimeModel>(() => {
|
|
530
|
+
const preferred = readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id;
|
|
531
|
+
if (!initialCachedCatalog) return preferred;
|
|
532
|
+
if (initialCachedCatalog.some((model) => model.id === preferred && model.available)) {
|
|
533
|
+
return preferred;
|
|
534
|
+
}
|
|
535
|
+
return initialCachedCatalog.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
536
|
+
});
|
|
424
537
|
|
|
425
538
|
useEffect(() => {
|
|
426
539
|
let disposed = false;
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
540
|
+
const applyCatalog = (models: RealtimeModelOption[]) => {
|
|
541
|
+
catalogScopeRef.current = { client, workspaceId, source: "cache" };
|
|
542
|
+
setCatalog(models);
|
|
543
|
+
setSelectedModelId((current) => {
|
|
544
|
+
if (models.some((model) => model.id === current && model.available)) return current;
|
|
545
|
+
return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
const cached = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
549
|
+
if (cached) {
|
|
550
|
+
const scope = catalogScopeRef.current;
|
|
551
|
+
if (
|
|
552
|
+
scope.client !== client ||
|
|
553
|
+
scope.workspaceId !== workspaceId ||
|
|
554
|
+
scope.source !== "cache"
|
|
555
|
+
) {
|
|
556
|
+
applyCatalog(cached);
|
|
557
|
+
}
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const loading = loadRealtimeModelCatalog(client, workspaceId);
|
|
561
|
+
if (!loading) return;
|
|
562
|
+
void loading
|
|
563
|
+
.then((models) => {
|
|
432
564
|
if (disposed) return;
|
|
433
|
-
|
|
434
|
-
setCatalog(models);
|
|
435
|
-
setSelectedModelId((current) => {
|
|
436
|
-
if (models.some((model) => model.id === current && model.available)) return current;
|
|
437
|
-
return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
438
|
-
});
|
|
565
|
+
applyCatalog(models);
|
|
439
566
|
})
|
|
440
567
|
.catch(() => undefined);
|
|
441
568
|
return () => {
|