@opengeni/react 1.4.0 → 1.4.2
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 +8 -0
- package/dist/accounts.js +2 -0
- package/dist/accounts.js.map +1 -1
- package/dist/artifacts-document.js +5 -4
- package/dist/artifacts-presentation.js +5 -4
- package/dist/artifacts-spreadsheet.js +4 -3
- package/dist/artifacts.js +14 -13
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-WGJ43YGI.js +17 -0
- package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
- package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
- package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
- package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
- package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
- package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
- package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
- package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
- package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
- package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
- package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
- package/dist/{chunk-MBMPHOMA.js → chunk-55DXD22O.js} +3 -2
- package/dist/{chunk-MBMPHOMA.js.map → chunk-55DXD22O.js.map} +1 -1
- package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
- package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
- package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
- package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
- package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
- package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
- package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
- package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
- package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
- package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
- package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
- package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
- package/dist/{chunk-LJ2UTJJE.js → chunk-CREMJMVN.js} +181 -96
- package/dist/chunk-CREMJMVN.js.map +1 -0
- package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
- package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
- package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
- package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
- package/dist/{chunk-WZMYFJD3.js → chunk-G6YRCIYJ.js} +16 -7
- package/dist/{chunk-WZMYFJD3.js.map → chunk-G6YRCIYJ.js.map} +1 -1
- package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
- package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
- package/dist/{chunk-CJUO7J5Z.js → chunk-HXYOSSQO.js} +3 -2
- package/dist/{chunk-CJUO7J5Z.js.map → chunk-HXYOSSQO.js.map} +1 -1
- package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
- package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
- package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
- package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
- package/dist/{chunk-LN6TCXD3.js → chunk-IUHHPMAX.js} +184 -48
- package/dist/chunk-IUHHPMAX.js.map +1 -0
- package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
- package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
- package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
- package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
- package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
- package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
- package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
- package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
- package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
- package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
- package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
- package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
- package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
- package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/{chunk-GYVRFZDY.js → chunk-MDIXFKA3.js} +9 -8
- package/dist/{chunk-GYVRFZDY.js.map → chunk-MDIXFKA3.js.map} +1 -1
- package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
- package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
- package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
- package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
- package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
- package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
- package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
- package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
- package/dist/chunk-PHBFJCEU.js.map +1 -0
- package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
- package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
- package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
- package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
- package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
- package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
- package/dist/{chunk-OA6HFRQP.js → chunk-QMMCJGFI.js} +4 -3
- package/dist/{chunk-OA6HFRQP.js.map → chunk-QMMCJGFI.js.map} +1 -1
- package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
- package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
- package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
- package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
- package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
- package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
- package/dist/{chunk-AWZLIMKX.js → chunk-T46QZH3E.js} +3 -1
- package/dist/{chunk-AWZLIMKX.js.map → chunk-T46QZH3E.js.map} +1 -1
- package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
- package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/{chunk-H5X55XH7.js → chunk-UDPH5SLV.js} +4 -3
- package/dist/{chunk-H5X55XH7.js.map → chunk-UDPH5SLV.js.map} +1 -1
- package/dist/{chunk-HDJWDK7W.js → chunk-VCSERYXH.js} +10 -9
- package/dist/{chunk-HDJWDK7W.js.map → chunk-VCSERYXH.js.map} +1 -1
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
- package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
- package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
- package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
- package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
- package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
- package/dist/chunk-YVFRBXST.js +1420 -0
- package/dist/chunk-YVFRBXST.js.map +1 -0
- package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
- package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
- package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
- package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
- package/dist/clipboard.js +3 -2
- package/dist/components/composer-transcription-control.d.ts +4 -0
- package/dist/components/session-conversation.d.ts +6 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.js +15 -13
- package/dist/computer-viewer-46UIN7F3.js +20 -0
- package/dist/connect.js +15 -12
- package/dist/connect.js.map +1 -1
- package/dist/desktop.js +6 -5
- package/dist/desktop.js.map +1 -1
- package/dist/diffs.js +2 -1
- package/dist/diffs.js.map +1 -1
- package/dist/editor.js +4 -3
- package/dist/editor.js.map +1 -1
- package/dist/embedded-voice-NIL4RDX3.js +69 -0
- package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
- package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
- package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/index.js +47 -45
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +14 -13
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-GPYM2JNU.js → latest-question-2POFPYNB.js} +5 -4
- package/dist/{latest-question-GPYM2JNU.js.map → latest-question-2POFPYNB.js.map} +1 -1
- package/dist/machines.js +8 -7
- package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
- package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
- package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
- package/dist/model-policy.js +2 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
- package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
- package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
- package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
- package/dist/realtime/embedded-voice.d.ts +20 -0
- package/dist/realtime.js +22 -1353
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-34CJJNOY.js +21 -0
- package/dist/session-ui.js +32 -30
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +10 -9
- package/dist/sites.js +2 -1
- package/dist/terminal.js +6 -5
- package/dist/terminal.js.map +1 -1
- package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
- package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
- package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
- package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
- package/dist/usage.js +6 -5
- package/dist/usage.js.map +1 -1
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +3 -3
- package/src/components/composer-transcription-control.tsx +109 -27
- package/src/components/markdown.tsx +7 -2
- package/src/components/message-timeline.tsx +10 -1
- package/src/components/session-conversation.tsx +44 -0
- package/src/components/session-proxy-scope.tsx +26 -5
- package/src/composer-voice-events.ts +12 -0
- package/src/connection-discovery.tsx +4 -1
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-goal.ts +12 -1
- package/src/hooks/use-realtime-voice-models.ts +67 -0
- package/src/hooks/use-voice-input.ts +109 -18
- package/src/realtime/embedded-voice.tsx +81 -0
- package/src/realtime/realtime-control.tsx +82 -17
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +72 -18
- package/styles/index.css +49 -0
- package/styles/responsive.css +61 -4
- package/styles/session-capability.css +1 -1
- package/dist/browser-viewer-SHYKQRNX.js +0 -16
- package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
- package/dist/chunk-I7IWRQLN.js.map +0 -1
- package/dist/chunk-IWIDV7SB.js +0 -1
- package/dist/chunk-LJ2UTJJE.js.map +0 -1
- package/dist/chunk-LN6TCXD3.js.map +0 -1
- package/dist/computer-viewer-6SRGWRUX.js +0 -19
- package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
- package/dist/sandbox-files-GORZOUUJ.js +0 -20
- /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-WGJ43YGI.js.map} +0 -0
- /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
- /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
- /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-46UIN7F3.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
- /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
package/dist/realtime.js
CHANGED
|
@@ -1,1357 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import
|
|
23
|
-
cn
|
|
24
|
-
} from "./chunk-22KP6LR5.js";
|
|
25
|
-
|
|
26
|
-
// src/realtime/realtime-control.tsx
|
|
27
|
-
import {
|
|
28
|
-
hasStoredSessionRealtimeOwnerProof,
|
|
29
|
-
projectSessionRealtimeLifecycle
|
|
30
|
-
} from "@opengeni/sdk/realtime";
|
|
31
|
-
import {
|
|
32
|
-
AudioLinesIcon,
|
|
33
|
-
CheckIcon,
|
|
34
|
-
ChevronDownIcon,
|
|
35
|
-
CircleAlertIcon,
|
|
36
|
-
LoaderCircleIcon,
|
|
37
|
-
MicIcon,
|
|
38
|
-
MicOffIcon,
|
|
39
|
-
RotateCcwIcon,
|
|
40
|
-
SquareIcon,
|
|
41
|
-
Volume2Icon,
|
|
42
|
-
VolumeXIcon
|
|
43
|
-
} from "lucide-react";
|
|
44
|
-
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
45
|
-
import {
|
|
46
|
-
useCallback,
|
|
47
|
-
useEffect,
|
|
48
|
-
useMemo,
|
|
49
|
-
useRef,
|
|
50
|
-
useState
|
|
51
|
-
} from "react";
|
|
52
|
-
|
|
53
|
-
// src/realtime/dropdown-menu.tsx
|
|
54
|
-
import { ChevronRightIcon } from "lucide-react";
|
|
55
|
-
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
56
|
-
import { createContext, useContext } from "react";
|
|
57
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
58
|
-
var MENU_MOTION_CLASS = "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";
|
|
59
|
-
var DropdownMenuSourceContext = createContext(null);
|
|
60
|
-
function assignRef(ref, value) {
|
|
61
|
-
if (typeof ref === "function") ref(value);
|
|
62
|
-
else if (ref) ref.current = value;
|
|
63
|
-
}
|
|
64
|
-
function DropdownMenu(props) {
|
|
65
|
-
const source = usePortalTokenSource();
|
|
66
|
-
return /* @__PURE__ */ jsx(DropdownMenuSourceContext.Provider, { value: source, children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props }) });
|
|
67
|
-
}
|
|
68
|
-
function DropdownMenuTrigger({
|
|
69
|
-
ref,
|
|
70
|
-
...props
|
|
71
|
-
}) {
|
|
72
|
-
const sourceRef = useContext(DropdownMenuSourceContext);
|
|
73
|
-
return /* @__PURE__ */ jsx(
|
|
74
|
-
DropdownMenuPrimitive.Trigger,
|
|
75
|
-
{
|
|
76
|
-
"data-slot": "dropdown-menu-trigger",
|
|
77
|
-
ref: (node) => {
|
|
78
|
-
sourceRef?.ref(node);
|
|
79
|
-
assignRef(ref, node);
|
|
80
|
-
},
|
|
81
|
-
...props
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
function DropdownMenuContent({
|
|
86
|
-
className,
|
|
87
|
-
sideOffset = 4,
|
|
88
|
-
style,
|
|
89
|
-
...props
|
|
90
|
-
}) {
|
|
91
|
-
const source = useContext(DropdownMenuSourceContext);
|
|
92
|
-
const portalStyle = usePortalTokenStyle(source?.source ?? null);
|
|
93
|
-
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
94
|
-
DropdownMenuPrimitive.Content,
|
|
95
|
-
{
|
|
96
|
-
"data-slot": "dropdown-menu-content",
|
|
97
|
-
sideOffset,
|
|
98
|
-
className: cn(
|
|
99
|
-
"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",
|
|
100
|
-
MENU_SURFACE_CLASS,
|
|
101
|
-
MENU_MOTION_CLASS,
|
|
102
|
-
className
|
|
103
|
-
),
|
|
104
|
-
style: { ...portalStyle, ...style },
|
|
105
|
-
...props
|
|
106
|
-
}
|
|
107
|
-
) });
|
|
108
|
-
}
|
|
109
|
-
function DropdownMenuItem({
|
|
110
|
-
className,
|
|
111
|
-
inset,
|
|
112
|
-
variant = "default",
|
|
113
|
-
...props
|
|
114
|
-
}) {
|
|
115
|
-
return /* @__PURE__ */ jsx(
|
|
116
|
-
DropdownMenuPrimitive.Item,
|
|
117
|
-
{
|
|
118
|
-
"data-slot": "dropdown-menu-item",
|
|
119
|
-
"data-inset": inset,
|
|
120
|
-
"data-variant": variant,
|
|
121
|
-
className: cn(MENU_ITEM_CLASS, "data-[inset]:pl-9", className),
|
|
122
|
-
...props
|
|
123
|
-
}
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
function DropdownMenuSeparator({
|
|
127
|
-
className,
|
|
128
|
-
...props
|
|
129
|
-
}) {
|
|
130
|
-
return /* @__PURE__ */ jsx(
|
|
131
|
-
DropdownMenuPrimitive.Separator,
|
|
132
|
-
{
|
|
133
|
-
"data-slot": "dropdown-menu-separator",
|
|
134
|
-
className: cn(MENU_SEPARATOR_CLASS, className),
|
|
135
|
-
...props
|
|
136
|
-
}
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
function DropdownMenuSub(props) {
|
|
140
|
-
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Sub, { "data-slot": "dropdown-menu-sub", ...props });
|
|
141
|
-
}
|
|
142
|
-
function DropdownMenuSubTrigger({
|
|
143
|
-
className,
|
|
144
|
-
inset,
|
|
145
|
-
children,
|
|
146
|
-
...props
|
|
147
|
-
}) {
|
|
148
|
-
return /* @__PURE__ */ jsxs(
|
|
149
|
-
DropdownMenuPrimitive.SubTrigger,
|
|
150
|
-
{
|
|
151
|
-
"data-slot": "dropdown-menu-sub-trigger",
|
|
152
|
-
"data-inset": inset,
|
|
153
|
-
className: cn(MENU_ITEM_CLASS, "data-[inset]:pl-9", className),
|
|
154
|
-
...props,
|
|
155
|
-
children: [
|
|
156
|
-
children,
|
|
157
|
-
/* @__PURE__ */ jsx(ChevronRightIcon, { "aria-hidden": "true", className: cn(MENU_CHEVRON_CLASS, "ml-auto") })
|
|
158
|
-
]
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
function DropdownMenuSubContent({
|
|
163
|
-
className,
|
|
164
|
-
style,
|
|
165
|
-
...props
|
|
166
|
-
}) {
|
|
167
|
-
const source = useContext(DropdownMenuSourceContext);
|
|
168
|
-
const portalStyle = usePortalTokenStyle(source?.source ?? null);
|
|
169
|
-
return /* @__PURE__ */ jsx(
|
|
170
|
-
DropdownMenuPrimitive.SubContent,
|
|
171
|
-
{
|
|
172
|
-
"data-slot": "dropdown-menu-sub-content",
|
|
173
|
-
className: cn(
|
|
174
|
-
"og-root z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden",
|
|
175
|
-
MENU_SURFACE_CLASS,
|
|
176
|
-
MENU_MOTION_CLASS,
|
|
177
|
-
className
|
|
178
|
-
),
|
|
179
|
-
style: { ...portalStyle, ...style },
|
|
180
|
-
...props
|
|
181
|
-
}
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/realtime/realtime-control.tsx
|
|
186
|
-
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
187
|
-
function realtimeRefusalLabel(code) {
|
|
188
|
-
switch (code) {
|
|
189
|
-
case "insufficient_credits":
|
|
190
|
-
return "Out of credits";
|
|
191
|
-
case "allowance_exhausted":
|
|
192
|
-
case "monthly_model_cost_limit":
|
|
193
|
-
return "Usage limit reached";
|
|
194
|
-
case "realtime_voice_unavailable":
|
|
195
|
-
return "Voice unavailable";
|
|
196
|
-
default:
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
var CODEX_LIVE_MODEL = {
|
|
201
|
-
id: "gpt-live-1-boulder-alpha",
|
|
202
|
-
label: "Codex Live",
|
|
203
|
-
provider: "Connected Codex",
|
|
204
|
-
description: "Deep session integration",
|
|
205
|
-
available: false,
|
|
206
|
-
unavailableReason: "Connect Codex to use this voice model",
|
|
207
|
-
recommended: false
|
|
208
|
-
};
|
|
209
|
-
var REALTIME_MODEL_PROVIDERS = [
|
|
210
|
-
"OpenGeni",
|
|
211
|
-
"Connected Codex",
|
|
212
|
-
"Connected SuperGrok",
|
|
213
|
-
"Your Gateway"
|
|
214
|
-
];
|
|
215
|
-
var REALTIME_PROVIDER_META = {
|
|
216
|
-
OpenGeni: { billingClass: "opengeni_credits", hint: "Will use credits" },
|
|
217
|
-
"Connected Codex": {
|
|
218
|
-
billingClass: "codex_subscription",
|
|
219
|
-
hint: "ChatGPT / Codex plan"
|
|
220
|
-
},
|
|
221
|
-
"Connected SuperGrok": {
|
|
222
|
-
billingClass: "supergrok_subscription",
|
|
223
|
-
hint: "SuperGrok / xAI plan"
|
|
224
|
-
},
|
|
225
|
-
"Your Gateway": { billingClass: "byok", hint: "Billed to your AI Gateway" }
|
|
226
|
-
};
|
|
227
|
-
var REALTIME_MODEL_STORAGE_PREFIX = "opengeni:realtime-model";
|
|
228
|
-
var REALTIME_MODEL_CATALOG_CACHE_TTL_MS = 6e4;
|
|
229
|
-
var REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES = 64;
|
|
230
|
-
var realtimeModelCatalogCache = /* @__PURE__ */ new WeakMap();
|
|
231
|
-
function realtimeModelFallback(codexConnected) {
|
|
232
|
-
return [
|
|
233
|
-
{
|
|
234
|
-
...CODEX_LIVE_MODEL,
|
|
235
|
-
available: codexConnected,
|
|
236
|
-
unavailableReason: codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
|
|
237
|
-
}
|
|
238
|
-
];
|
|
239
|
-
}
|
|
240
|
-
function realtimeModelCatalogCacheFor(client) {
|
|
241
|
-
const existing = realtimeModelCatalogCache.get(client);
|
|
242
|
-
if (existing) return existing;
|
|
243
|
-
const created = /* @__PURE__ */ new Map();
|
|
244
|
-
realtimeModelCatalogCache.set(client, created);
|
|
245
|
-
return created;
|
|
246
|
-
}
|
|
247
|
-
function readCachedRealtimeModelCatalog(client, workspaceId, now = Date.now()) {
|
|
248
|
-
const cache = realtimeModelCatalogCache.get(client);
|
|
249
|
-
const entry = cache?.get(workspaceId);
|
|
250
|
-
if (!entry || entry.state === "loading") return null;
|
|
251
|
-
if (entry.expiresAt > now) return entry.models;
|
|
252
|
-
cache?.delete(workspaceId);
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
function pruneRealtimeModelCatalogCache(cache, now) {
|
|
256
|
-
for (const [workspaceId, entry] of cache) {
|
|
257
|
-
if (entry.state === "ready" && entry.expiresAt <= now) cache.delete(workspaceId);
|
|
258
|
-
}
|
|
259
|
-
while (cache.size >= REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES) {
|
|
260
|
-
const oldestWorkspaceId = cache.keys().next().value;
|
|
261
|
-
if (oldestWorkspaceId === void 0) return;
|
|
262
|
-
cache.delete(oldestWorkspaceId);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
function loadRealtimeModelCatalog(client, workspaceId, signal) {
|
|
266
|
-
const load = client.getWorkspaceRealtimeModelCatalog;
|
|
267
|
-
if (!load) return null;
|
|
268
|
-
if (signal?.aborted) {
|
|
269
|
-
return Promise.reject(signal.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
270
|
-
}
|
|
271
|
-
const now = Date.now();
|
|
272
|
-
const cache = realtimeModelCatalogCacheFor(client);
|
|
273
|
-
const entry = cache.get(workspaceId);
|
|
274
|
-
if (entry?.state === "loading" && !entry.controller.signal.aborted) {
|
|
275
|
-
return consumeRealtimeModelCatalog(entry, signal);
|
|
276
|
-
}
|
|
277
|
-
if (entry?.state === "ready" && entry.expiresAt > now) return Promise.resolve(entry.models);
|
|
278
|
-
if (entry) cache.delete(workspaceId);
|
|
279
|
-
pruneRealtimeModelCatalogCache(cache, now);
|
|
280
|
-
const controller = new AbortController();
|
|
281
|
-
const promise = load.call(client, workspaceId, { signal: controller.signal }).then((response) => response.models.map(toRealtimeModelOption)).then((models) => {
|
|
282
|
-
const current = cache.get(workspaceId);
|
|
283
|
-
if (current?.state === "loading" && current.promise === promise) {
|
|
284
|
-
cache.delete(workspaceId);
|
|
285
|
-
cache.set(workspaceId, {
|
|
286
|
-
state: "ready",
|
|
287
|
-
models,
|
|
288
|
-
expiresAt: Date.now() + REALTIME_MODEL_CATALOG_CACHE_TTL_MS
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
return models;
|
|
292
|
-
}).catch((error) => {
|
|
293
|
-
const current = cache.get(workspaceId);
|
|
294
|
-
if (current?.state === "loading" && current.promise === promise) cache.delete(workspaceId);
|
|
295
|
-
throw error;
|
|
296
|
-
});
|
|
297
|
-
const loading = {
|
|
298
|
-
state: "loading",
|
|
299
|
-
promise,
|
|
300
|
-
controller,
|
|
301
|
-
consumers: 0
|
|
302
|
-
};
|
|
303
|
-
cache.set(workspaceId, loading);
|
|
304
|
-
return consumeRealtimeModelCatalog(loading, signal);
|
|
305
|
-
}
|
|
306
|
-
function consumeRealtimeModelCatalog(entry, signal) {
|
|
307
|
-
entry.consumers += 1;
|
|
308
|
-
return new Promise((resolve, reject) => {
|
|
309
|
-
let released = false;
|
|
310
|
-
const release = (abandoned) => {
|
|
311
|
-
if (released) return;
|
|
312
|
-
released = true;
|
|
313
|
-
entry.consumers = Math.max(0, entry.consumers - 1);
|
|
314
|
-
if (abandoned && entry.consumers === 0) {
|
|
315
|
-
entry.controller.abort(
|
|
316
|
-
signal?.reason ?? new DOMException("Request abandoned", "AbortError")
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
const onAbort = () => {
|
|
321
|
-
release(true);
|
|
322
|
-
reject(signal?.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
323
|
-
};
|
|
324
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
325
|
-
void entry.promise.then(resolve, reject).finally(() => {
|
|
326
|
-
signal?.removeEventListener("abort", onAbort);
|
|
327
|
-
release(false);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function codexRealtimeAdmissionAllowed(input) {
|
|
332
|
-
return codexRealtimeAdmissionBlocker(input) === null;
|
|
333
|
-
}
|
|
334
|
-
function codexRealtimeAdmissionBlocker(input) {
|
|
335
|
-
if (input.sessionStatus === "cancelled") return "This session was cancelled.";
|
|
336
|
-
if (input.controlState !== "active") return "Resume this session before starting voice.";
|
|
337
|
-
if (input.settlement !== null) return "Wait for the current session transition to finish.";
|
|
338
|
-
if (!input.codexConnected) return "Connect Codex to use this voice model.";
|
|
339
|
-
if (input.lifecycleActive) return "Voice is already active for this session.";
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
function useSessionRealtime(options) {
|
|
343
|
-
const { client, workspaceId } = useEmbeddedRealtimeSession({
|
|
344
|
-
client: options.client,
|
|
345
|
-
workspaceId: options.workspaceId
|
|
346
|
-
});
|
|
347
|
-
const model = options.model ?? CODEX_LIVE_MODEL.id;
|
|
348
|
-
const audioRef = useRef(null);
|
|
349
|
-
const controllerRef = useRef(null);
|
|
350
|
-
const controllerModelRef = useRef(null);
|
|
351
|
-
const modelContextProviderRef = useRef(options.getModelContext);
|
|
352
|
-
modelContextProviderRef.current = options.getModelContext;
|
|
353
|
-
const [snapshot, setSnapshot] = useState(() => ({
|
|
354
|
-
status: hasStoredSessionRealtimeOwnerProof({
|
|
355
|
-
workspaceId,
|
|
356
|
-
sessionId: options.sessionId,
|
|
357
|
-
model
|
|
358
|
-
}) ? "recovering" : "idle",
|
|
359
|
-
realtimeId: null,
|
|
360
|
-
mode: null,
|
|
361
|
-
bridge: null,
|
|
362
|
-
microphone: "inactive",
|
|
363
|
-
inputMuted: false,
|
|
364
|
-
audibleOutput: "inactive",
|
|
365
|
-
outputMuted: false,
|
|
366
|
-
connectionGeneration: 0,
|
|
367
|
-
reconnectAttempt: 0,
|
|
368
|
-
diagnostic: null,
|
|
369
|
-
error: null
|
|
370
|
-
}));
|
|
371
|
-
const lifecycle = useMemo(
|
|
372
|
-
() => projectSessionRealtimeLifecycle(options.events),
|
|
373
|
-
[options.events]
|
|
374
|
-
);
|
|
375
|
-
const lifecycleRef = useRef(lifecycle);
|
|
376
|
-
const eventsReadyRef = useRef(options.eventsReady);
|
|
377
|
-
lifecycleRef.current = lifecycle;
|
|
378
|
-
eventsReadyRef.current = options.eventsReady;
|
|
379
|
-
const lifecycleActive = sessionRealtimeLifecycleIsActive(lifecycle);
|
|
380
|
-
useEffect(() => {
|
|
381
|
-
let disposed = false;
|
|
382
|
-
let controller = null;
|
|
383
|
-
let unsubscribe = null;
|
|
384
|
-
void import("@opengeni/sdk/realtime").then(({ createSessionRealtimeController }) => {
|
|
385
|
-
if (disposed) return;
|
|
386
|
-
const controllerFactory = options.controllerFactory ?? createSessionRealtimeController;
|
|
387
|
-
controller = controllerFactory({
|
|
388
|
-
client,
|
|
389
|
-
workspaceId,
|
|
390
|
-
sessionId: options.sessionId,
|
|
391
|
-
...audioRef.current ? { remoteAudio: audioRef.current } : {},
|
|
392
|
-
model,
|
|
393
|
-
getModelContext: () => modelContextProviderRef.current?.()
|
|
394
|
-
});
|
|
395
|
-
controllerRef.current = controller;
|
|
396
|
-
controllerModelRef.current = model;
|
|
397
|
-
unsubscribe = controller.subscribe(setSnapshot);
|
|
398
|
-
if (eventsReadyRef.current) {
|
|
399
|
-
void controller.observeLifecycle(lifecycleRef.current).catch(() => void 0);
|
|
400
|
-
}
|
|
401
|
-
}).catch((error) => {
|
|
402
|
-
if (disposed) return;
|
|
403
|
-
setSnapshot({
|
|
404
|
-
status: "error",
|
|
405
|
-
realtimeId: null,
|
|
406
|
-
mode: null,
|
|
407
|
-
bridge: null,
|
|
408
|
-
microphone: "inactive",
|
|
409
|
-
inputMuted: false,
|
|
410
|
-
audibleOutput: "inactive",
|
|
411
|
-
outputMuted: false,
|
|
412
|
-
connectionGeneration: 0,
|
|
413
|
-
reconnectAttempt: 0,
|
|
414
|
-
diagnostic: {
|
|
415
|
-
kind: "negotiation_failure",
|
|
416
|
-
message: error instanceof Error ? error.message : "Codex realtime controller failed to load",
|
|
417
|
-
recoverable: false,
|
|
418
|
-
connectionGeneration: 0,
|
|
419
|
-
attempt: 0
|
|
420
|
-
},
|
|
421
|
-
error: error instanceof Error ? error.message : "Codex realtime controller failed to load"
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
return () => {
|
|
425
|
-
disposed = true;
|
|
426
|
-
unsubscribe?.();
|
|
427
|
-
controller?.close();
|
|
428
|
-
if (controllerRef.current === controller) {
|
|
429
|
-
controllerRef.current = null;
|
|
430
|
-
controllerModelRef.current = null;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
}, [client, model, options.controllerFactory, options.sessionId, workspaceId]);
|
|
434
|
-
useEffect(() => {
|
|
435
|
-
if (!options.eventsReady) return;
|
|
436
|
-
void controllerRef.current?.observeLifecycle(lifecycle).catch(() => void 0);
|
|
437
|
-
}, [lifecycle, options.eventsReady]);
|
|
438
|
-
const start = useCallback(async () => {
|
|
439
|
-
const controller = controllerRef.current;
|
|
440
|
-
if (!controller || controllerModelRef.current !== model) {
|
|
441
|
-
throw new Error("The selected voice model is still preparing");
|
|
442
|
-
}
|
|
443
|
-
await controller.start();
|
|
444
|
-
}, [model]);
|
|
445
|
-
const stop = useCallback(async () => {
|
|
446
|
-
await controllerRef.current?.stop();
|
|
447
|
-
}, []);
|
|
448
|
-
const retry = useCallback(async () => {
|
|
449
|
-
await controllerRef.current?.retry();
|
|
450
|
-
}, []);
|
|
451
|
-
const retryAudibleOutput = useCallback(async () => {
|
|
452
|
-
await controllerRef.current?.retryAudibleOutput();
|
|
453
|
-
}, []);
|
|
454
|
-
const setInputMuted = useCallback((muted) => {
|
|
455
|
-
controllerRef.current?.setInputMuted(muted);
|
|
456
|
-
}, []);
|
|
457
|
-
const setOutputMuted = useCallback((muted) => {
|
|
458
|
-
controllerRef.current?.setOutputMuted(muted);
|
|
459
|
-
}, []);
|
|
460
|
-
const admissionInput = {
|
|
461
|
-
sessionStatus: options.sessionStatus,
|
|
462
|
-
controlState: options.effectiveControl.state,
|
|
463
|
-
settlement: options.effectiveControl.settlement,
|
|
464
|
-
codexConnected: options.modelAvailable ?? options.codexConnected,
|
|
465
|
-
lifecycleActive
|
|
466
|
-
};
|
|
467
|
-
const admissionBlocker = options.modelAvailable === false ? options.modelUnavailableReason ?? "This voice model is unavailable." : codexRealtimeAdmissionBlocker(admissionInput);
|
|
468
|
-
const canStart = controllerRef.current !== null && controllerModelRef.current === model && ["idle", "error"].includes(snapshot.status) && admissionBlocker === null;
|
|
469
|
-
return {
|
|
470
|
-
snapshot,
|
|
471
|
-
lifecycleActive,
|
|
472
|
-
canStart,
|
|
473
|
-
admissionBlocker,
|
|
474
|
-
codexConnected: options.codexConnected,
|
|
475
|
-
audioRef,
|
|
476
|
-
start,
|
|
477
|
-
stop,
|
|
478
|
-
retry,
|
|
479
|
-
retryAudibleOutput,
|
|
480
|
-
setInputMuted,
|
|
481
|
-
setOutputMuted
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
function sessionRealtimeLifecycleIsActive(lifecycle) {
|
|
485
|
-
if (lifecycle?.state !== "active") return false;
|
|
486
|
-
const leaseExpiresAt = Date.parse(lifecycle.leaseExpiresAt);
|
|
487
|
-
return !Number.isFinite(leaseExpiresAt) || leaseExpiresAt > Date.now();
|
|
488
|
-
}
|
|
489
|
-
function SessionRealtimeControl(props) {
|
|
490
|
-
const lifecycle = useMemo(() => projectSessionRealtimeLifecycle(props.events), [props.events]);
|
|
491
|
-
const lifecycleActive = sessionRealtimeLifecycleIsActive(lifecycle);
|
|
492
|
-
const selection = useRealtimeModelSelection({
|
|
493
|
-
client: props.client,
|
|
494
|
-
workspaceId: props.workspaceId,
|
|
495
|
-
codexConnected: props.codexConnected,
|
|
496
|
-
activeModel: lifecycleActive && lifecycle?.state === "active" ? lifecycle.model : null
|
|
497
|
-
});
|
|
498
|
-
const selectedModel = selection.selectedModel;
|
|
499
|
-
const realtime = useSessionRealtime({
|
|
500
|
-
...props,
|
|
501
|
-
model: selectedModel.id,
|
|
502
|
-
modelAvailable: selectedModel.available,
|
|
503
|
-
modelUnavailableReason: selectedModel.unavailableReason
|
|
504
|
-
});
|
|
505
|
-
const autostartModelRef = useRef(props.realtimeAutostartModel ?? null);
|
|
506
|
-
const autostartStartedRef = useRef(false);
|
|
507
|
-
const { models, selectedModel: selectedRealtimeModel, selectModel } = selection;
|
|
508
|
-
const { canStart, start } = realtime;
|
|
509
|
-
const onRealtimeAutostartConsumed = props.onRealtimeAutostartConsumed;
|
|
510
|
-
const onVoiceActiveChange = props.onVoiceActiveChange;
|
|
511
|
-
const voiceActive = realtime.snapshot.mode?.state === "active";
|
|
512
|
-
useEffect(() => {
|
|
513
|
-
onVoiceActiveChange?.(voiceActive);
|
|
514
|
-
}, [onVoiceActiveChange, voiceActive]);
|
|
515
|
-
useEffect(() => {
|
|
516
|
-
const pending = autostartModelRef.current;
|
|
517
|
-
if (!pending || autostartStartedRef.current || lifecycleActive) return;
|
|
518
|
-
const available = models.some((model) => model.id === pending && model.available);
|
|
519
|
-
if (!available) return;
|
|
520
|
-
if (selectedRealtimeModel.id !== pending) {
|
|
521
|
-
selectModel(pending);
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
if (!canStart) return;
|
|
525
|
-
autostartStartedRef.current = true;
|
|
526
|
-
void start().then(() => {
|
|
527
|
-
autostartModelRef.current = null;
|
|
528
|
-
onRealtimeAutostartConsumed?.();
|
|
529
|
-
}).catch(() => {
|
|
530
|
-
autostartStartedRef.current = false;
|
|
531
|
-
});
|
|
532
|
-
}, [
|
|
533
|
-
canStart,
|
|
534
|
-
lifecycleActive,
|
|
535
|
-
models,
|
|
536
|
-
props.sessionId,
|
|
537
|
-
props.workspaceId,
|
|
538
|
-
onRealtimeAutostartConsumed,
|
|
539
|
-
selectModel,
|
|
540
|
-
selectedRealtimeModel.id,
|
|
541
|
-
start
|
|
542
|
-
]);
|
|
543
|
-
return /* @__PURE__ */ jsx2(
|
|
544
|
-
RealtimeVoiceControl,
|
|
545
|
-
{
|
|
546
|
-
snapshot: realtime.snapshot,
|
|
547
|
-
canStart: realtime.canStart,
|
|
548
|
-
admissionBlocker: realtime.admissionBlocker,
|
|
549
|
-
modelAvailable: selectedModel.available,
|
|
550
|
-
menuSide: "top",
|
|
551
|
-
modelMenu: props.modelMenu ?? "split",
|
|
552
|
-
showDiagnostics: props.showDiagnostics,
|
|
553
|
-
audioRef: realtime.audioRef,
|
|
554
|
-
selectedModel,
|
|
555
|
-
models: selection.models,
|
|
556
|
-
onSelectModel: realtime.lifecycleActive ? void 0 : selection.selectModel,
|
|
557
|
-
onStart: realtime.start,
|
|
558
|
-
onStop: realtime.stop,
|
|
559
|
-
onRetry: realtime.retry,
|
|
560
|
-
onRetryAudibleOutput: realtime.retryAudibleOutput,
|
|
561
|
-
onSetInputMuted: realtime.setInputMuted,
|
|
562
|
-
onSetOutputMuted: realtime.setOutputMuted
|
|
563
|
-
}
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
function useRealtimeModelSelection(options) {
|
|
567
|
-
const { client, workspaceId } = useEmbeddedRealtimeSession({
|
|
568
|
-
client: options.client,
|
|
569
|
-
workspaceId: options.workspaceId
|
|
570
|
-
});
|
|
571
|
-
const activeModel = options.activeModel;
|
|
572
|
-
const initialCachedCatalog = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
573
|
-
const [catalog, setCatalog] = useState(
|
|
574
|
-
() => initialCachedCatalog ?? realtimeModelFallback(options.codexConnected)
|
|
575
|
-
);
|
|
576
|
-
const catalogScopeRef = useRef({
|
|
577
|
-
client,
|
|
578
|
-
workspaceId,
|
|
579
|
-
source: initialCachedCatalog ? "cache" : "fallback"
|
|
580
|
-
});
|
|
581
|
-
const [selectedModelId, setSelectedModelId] = useState(() => {
|
|
582
|
-
const preferred = readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id;
|
|
583
|
-
if (!initialCachedCatalog) return preferred;
|
|
584
|
-
if (initialCachedCatalog.some((model) => model.id === preferred && model.available)) {
|
|
585
|
-
return preferred;
|
|
586
|
-
}
|
|
587
|
-
return initialCachedCatalog.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
588
|
-
});
|
|
589
|
-
useEffect(() => {
|
|
590
|
-
let disposed = false;
|
|
591
|
-
const requestAbort = new AbortController();
|
|
592
|
-
const applyCatalog = (models) => {
|
|
593
|
-
catalogScopeRef.current = { client, workspaceId, source: "cache" };
|
|
594
|
-
setCatalog(models);
|
|
595
|
-
setSelectedModelId((current) => {
|
|
596
|
-
if (models.some((model) => model.id === current && model.available)) return current;
|
|
597
|
-
return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
|
|
598
|
-
});
|
|
599
|
-
};
|
|
600
|
-
const cached = readCachedRealtimeModelCatalog(client, workspaceId);
|
|
601
|
-
if (cached) {
|
|
602
|
-
const scope = catalogScopeRef.current;
|
|
603
|
-
if (scope.client !== client || scope.workspaceId !== workspaceId || scope.source !== "cache") {
|
|
604
|
-
applyCatalog(cached);
|
|
605
|
-
}
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
const loading = loadRealtimeModelCatalog(client, workspaceId, requestAbort.signal);
|
|
609
|
-
if (!loading) return;
|
|
610
|
-
void loading.then((models) => {
|
|
611
|
-
if (disposed) return;
|
|
612
|
-
applyCatalog(models);
|
|
613
|
-
}).catch(() => void 0);
|
|
614
|
-
return () => {
|
|
615
|
-
disposed = true;
|
|
616
|
-
requestAbort.abort();
|
|
617
|
-
};
|
|
618
|
-
}, [client, workspaceId]);
|
|
619
|
-
useEffect(() => {
|
|
620
|
-
if (activeModel) setSelectedModelId(activeModel);
|
|
621
|
-
}, [activeModel]);
|
|
622
|
-
const selectedModel = catalog.find((model) => model.id === selectedModelId) ?? {
|
|
623
|
-
...CODEX_LIVE_MODEL,
|
|
624
|
-
available: options.codexConnected,
|
|
625
|
-
unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
|
|
626
|
-
};
|
|
627
|
-
const selectModel = useCallback(
|
|
628
|
-
(value) => {
|
|
629
|
-
const model = catalog.find((candidate) => candidate.id === value);
|
|
630
|
-
if (!model || !model.available || activeModel) return;
|
|
631
|
-
setSelectedModelId(model.id);
|
|
632
|
-
writeRealtimeModelPreference(workspaceId, model.id);
|
|
633
|
-
},
|
|
634
|
-
[activeModel, catalog, workspaceId]
|
|
635
|
-
);
|
|
636
|
-
return { models: catalog, selectedModel, selectModel };
|
|
637
|
-
}
|
|
638
|
-
var IDLE_REALTIME_SNAPSHOT = {
|
|
639
|
-
status: "idle",
|
|
640
|
-
realtimeId: null,
|
|
641
|
-
mode: null,
|
|
642
|
-
bridge: null,
|
|
643
|
-
microphone: "inactive",
|
|
644
|
-
inputMuted: false,
|
|
645
|
-
audibleOutput: "inactive",
|
|
646
|
-
outputMuted: false,
|
|
647
|
-
connectionGeneration: 0,
|
|
648
|
-
reconnectAttempt: 0,
|
|
649
|
-
diagnostic: null,
|
|
650
|
-
error: null
|
|
651
|
-
};
|
|
652
|
-
function NewSessionRealtimeControl(props) {
|
|
653
|
-
if (props.models && props.selectedModel && props.onSelectModel) {
|
|
654
|
-
return /* @__PURE__ */ jsx2(
|
|
655
|
-
NewSessionRealtimeControlView,
|
|
656
|
-
{
|
|
657
|
-
...props,
|
|
658
|
-
selection: {
|
|
659
|
-
models: props.models,
|
|
660
|
-
selectedModel: props.selectedModel,
|
|
661
|
-
selectModel: props.onSelectModel
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
return /* @__PURE__ */ jsx2(NewSessionRealtimeControlWithInternalSelection, { ...props });
|
|
667
|
-
}
|
|
668
|
-
function NewSessionRealtimeControlWithInternalSelection(props) {
|
|
669
|
-
const selection = useRealtimeModelSelection({
|
|
670
|
-
client: props.client,
|
|
671
|
-
workspaceId: props.workspaceId,
|
|
672
|
-
codexConnected: props.codexConnected
|
|
673
|
-
});
|
|
674
|
-
return /* @__PURE__ */ jsx2(NewSessionRealtimeControlView, { ...props, selection });
|
|
675
|
-
}
|
|
676
|
-
function NewSessionRealtimeControlView(props) {
|
|
677
|
-
const selection = props.selection;
|
|
678
|
-
const audioRef = useRef(null);
|
|
679
|
-
const [starting, setStarting] = useState(false);
|
|
680
|
-
const [error, setError] = useState(null);
|
|
681
|
-
const snapshot = useMemo(
|
|
682
|
-
() => starting ? { ...IDLE_REALTIME_SNAPSHOT, status: "starting" } : error ? { ...IDLE_REALTIME_SNAPSHOT, status: "error", error } : IDLE_REALTIME_SNAPSHOT,
|
|
683
|
-
[error, starting]
|
|
684
|
-
);
|
|
685
|
-
const canStart = selection.selectedModel.available && props.disabled !== true && !starting;
|
|
686
|
-
const onStart = props.onStart;
|
|
687
|
-
const selectedModelId = selection.selectedModel.id;
|
|
688
|
-
const start = useCallback(async () => {
|
|
689
|
-
if (!canStart) return;
|
|
690
|
-
setStarting(true);
|
|
691
|
-
setError(null);
|
|
692
|
-
try {
|
|
693
|
-
const started = await onStart(selectedModelId);
|
|
694
|
-
if (!started) setError("The voice session could not be created.");
|
|
695
|
-
} catch (cause) {
|
|
696
|
-
setError(cause instanceof Error ? cause.message : String(cause));
|
|
697
|
-
} finally {
|
|
698
|
-
setStarting(false);
|
|
699
|
-
}
|
|
700
|
-
}, [canStart, onStart, selectedModelId]);
|
|
701
|
-
return /* @__PURE__ */ jsx2(
|
|
702
|
-
RealtimeVoiceControl,
|
|
703
|
-
{
|
|
704
|
-
snapshot,
|
|
705
|
-
canStart,
|
|
706
|
-
admissionBlocker: selection.selectedModel.unavailableReason ?? props.disabledReason ?? null,
|
|
707
|
-
modelAvailable: selection.selectedModel.available,
|
|
708
|
-
selectionDisabled: starting,
|
|
709
|
-
menuSide: "bottom",
|
|
710
|
-
modelMenu: props.modelMenu ?? "split",
|
|
711
|
-
showDiagnostics: false,
|
|
712
|
-
audioRef,
|
|
713
|
-
selectedModel: selection.selectedModel,
|
|
714
|
-
models: selection.models,
|
|
715
|
-
onSelectModel: selection.selectModel,
|
|
716
|
-
onStart: start,
|
|
717
|
-
onStop: async () => void 0,
|
|
718
|
-
onRetry: start,
|
|
719
|
-
onRetryAudibleOutput: async () => void 0,
|
|
720
|
-
onSetInputMuted: () => void 0,
|
|
721
|
-
onSetOutputMuted: () => void 0
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
function RealtimeVoiceControl(props) {
|
|
726
|
-
const reduceMotion = useReducedMotion();
|
|
727
|
-
const selectedModel = props.selectedModel ?? {
|
|
728
|
-
...CODEX_LIVE_MODEL,
|
|
729
|
-
available: props.modelAvailable,
|
|
730
|
-
unavailableReason: props.modelAvailable ? null : CODEX_LIVE_MODEL.unavailableReason
|
|
731
|
-
};
|
|
732
|
-
const models = props.models ?? [selectedModel];
|
|
733
|
-
const status = statusContent(
|
|
734
|
-
props.snapshot,
|
|
735
|
-
props.modelAvailable,
|
|
736
|
-
props.canStart,
|
|
737
|
-
props.admissionBlocker ?? null,
|
|
738
|
-
selectedModel.label,
|
|
739
|
-
selectedModel.unavailableCode ?? null
|
|
740
|
-
);
|
|
741
|
-
const modeOwned = props.snapshot.mode?.state === "active";
|
|
742
|
-
const retryConnection = modeOwned && props.snapshot.status !== "stopping" && props.snapshot.status !== "active" && props.snapshot.diagnostic?.recoverable === true;
|
|
743
|
-
const audioBlocked = props.snapshot.audibleOutput === "blocked" && !props.snapshot.outputMuted;
|
|
744
|
-
const mainDisabled = props.snapshot.status === "stopping" || props.snapshot.status === "lost_owner" || !modeOwned && !props.canStart || props.snapshot.status === "starting" && !modeOwned;
|
|
745
|
-
const mainLabel = audioBlocked ? "Resume voice audio" : retryConnection ? "Retry voice connection" : modeOwned ? "End voice conversation" : `Start voice with ${selectedModel.label}`;
|
|
746
|
-
const runMainAction = audioBlocked ? props.onRetryAudibleOutput : retryConnection ? props.onRetry : modeOwned ? props.onStop : props.onStart;
|
|
747
|
-
const diagnosticsVisible = props.showDiagnostics ?? false;
|
|
748
|
-
const modelMenu = props.modelMenu ?? "split";
|
|
749
|
-
const showAttachedModelMenu = modelMenu === "split" || modelMenu === "split-desktop";
|
|
750
|
-
const desktopOnlyModelMenu = modelMenu === "split-desktop";
|
|
751
|
-
const [pickerOpen, setPickerOpen] = useState(false);
|
|
752
|
-
const [pickerProvider, setPickerProvider] = useState(
|
|
753
|
-
selectedModel.provider
|
|
754
|
-
);
|
|
755
|
-
const [pickerDirection, setPickerDirection] = useState(1);
|
|
756
|
-
useEffect(() => {
|
|
757
|
-
if (!pickerOpen) setPickerProvider(selectedModel.provider);
|
|
758
|
-
}, [pickerOpen, selectedModel.provider]);
|
|
759
|
-
return /* @__PURE__ */ jsxs2(
|
|
760
|
-
"div",
|
|
761
|
-
{
|
|
762
|
-
role: "group",
|
|
763
|
-
"aria-label": "Realtime voice",
|
|
764
|
-
"data-picker-side": props.menuSide ?? "top",
|
|
765
|
-
"data-model-menu": modelMenu,
|
|
766
|
-
className: "og-realtime-control inline-flex shrink-0 items-center",
|
|
767
|
-
children: [
|
|
768
|
-
/* @__PURE__ */ jsx2("audio", { ref: props.audioRef, autoPlay: true, className: "hidden", "aria-hidden": "true" }),
|
|
769
|
-
/* @__PURE__ */ jsx2(RealtimeComposerGlow, { phase: status.phase, reduceMotion: reduceMotion === true }),
|
|
770
|
-
/* @__PURE__ */ jsx2(AnimatePresence, { initial: false, children: modeOwned ? /* @__PURE__ */ jsx2(
|
|
771
|
-
motion.div,
|
|
772
|
-
{
|
|
773
|
-
"data-testid": "realtime-mute-controls",
|
|
774
|
-
initial: reduceMotion ? false : { opacity: 0, width: 0 },
|
|
775
|
-
animate: {
|
|
776
|
-
opacity: 1,
|
|
777
|
-
width: "auto",
|
|
778
|
-
transition: reduceMotion ? { duration: 0 } : { delay: 0.3, duration: 0.48, ease: [0.22, 1, 0.36, 1] }
|
|
779
|
-
},
|
|
780
|
-
...reduceMotion ? {} : {
|
|
781
|
-
exit: {
|
|
782
|
-
opacity: 0,
|
|
783
|
-
width: 0,
|
|
784
|
-
transition: { duration: 0.28, ease: [0.4, 0, 1, 1] }
|
|
785
|
-
}
|
|
786
|
-
},
|
|
787
|
-
className: cn(
|
|
788
|
-
"inline-flex shrink-0 items-center overflow-hidden",
|
|
789
|
-
props.muteControlsClassName
|
|
790
|
-
),
|
|
791
|
-
children: /* @__PURE__ */ jsxs2(
|
|
792
|
-
motion.div,
|
|
793
|
-
{
|
|
794
|
-
className: "inline-flex items-center gap-0.5 pr-1",
|
|
795
|
-
initial: reduceMotion ? false : { x: 18, opacity: 0 },
|
|
796
|
-
animate: {
|
|
797
|
-
x: 0,
|
|
798
|
-
opacity: 1,
|
|
799
|
-
transition: reduceMotion ? { duration: 0 } : {
|
|
800
|
-
delay: 0.32,
|
|
801
|
-
type: "spring",
|
|
802
|
-
stiffness: 320,
|
|
803
|
-
damping: 30,
|
|
804
|
-
mass: 0.8
|
|
805
|
-
}
|
|
806
|
-
},
|
|
807
|
-
...reduceMotion ? {} : {
|
|
808
|
-
exit: {
|
|
809
|
-
x: 14,
|
|
810
|
-
opacity: 0,
|
|
811
|
-
transition: { duration: 0.22, ease: [0.4, 0, 1, 1] }
|
|
812
|
-
}
|
|
813
|
-
},
|
|
814
|
-
children: [
|
|
815
|
-
/* @__PURE__ */ jsx2(
|
|
816
|
-
RealtimeMuteButton,
|
|
817
|
-
{
|
|
818
|
-
muted: props.snapshot.inputMuted,
|
|
819
|
-
kind: "microphone",
|
|
820
|
-
reduceMotion: reduceMotion === true,
|
|
821
|
-
enterDelay: reduceMotion ? 0 : 0.34,
|
|
822
|
-
onToggle: () => props.onSetInputMuted(!props.snapshot.inputMuted)
|
|
823
|
-
}
|
|
824
|
-
),
|
|
825
|
-
/* @__PURE__ */ jsx2(
|
|
826
|
-
RealtimeMuteButton,
|
|
827
|
-
{
|
|
828
|
-
muted: props.snapshot.outputMuted,
|
|
829
|
-
kind: "output",
|
|
830
|
-
reduceMotion: reduceMotion === true,
|
|
831
|
-
enterDelay: reduceMotion ? 0 : 0.4,
|
|
832
|
-
onToggle: () => props.onSetOutputMuted(!props.snapshot.outputMuted)
|
|
833
|
-
}
|
|
834
|
-
)
|
|
835
|
-
]
|
|
836
|
-
}
|
|
837
|
-
)
|
|
838
|
-
},
|
|
839
|
-
"realtime-mute-controls"
|
|
840
|
-
) : null }),
|
|
841
|
-
/* @__PURE__ */ jsxs2("div", { className: "inline-flex shrink-0 items-center", children: [
|
|
842
|
-
/* @__PURE__ */ jsx2(
|
|
843
|
-
motion.button,
|
|
844
|
-
{
|
|
845
|
-
type: "button",
|
|
846
|
-
"data-testid": "realtime-primary-action",
|
|
847
|
-
"data-phase": status.phase,
|
|
848
|
-
"aria-label": mainLabel,
|
|
849
|
-
"aria-pressed": modeOwned,
|
|
850
|
-
title: mainLabel,
|
|
851
|
-
disabled: mainDisabled,
|
|
852
|
-
...reduceMotion ? {} : { whileTap: { scale: 0.92 } },
|
|
853
|
-
transition: { type: "spring", stiffness: 520, damping: 30 },
|
|
854
|
-
onClick: () => void runMainAction().catch(() => void 0),
|
|
855
|
-
className: cn(
|
|
856
|
-
// Match transcription mic: ghost icon when idle; filled only when live.
|
|
857
|
-
// Public contract: coarse pointers keep a 44px target; split menu uses
|
|
858
|
-
// left-only rounding at `sm+` where the chevron attaches.
|
|
859
|
-
"og-realtime-primary relative inline-flex size-8 items-center justify-center outline-hidden",
|
|
860
|
-
"rounded-og-md transition-[background-color,border-color,color,box-shadow] duration-200 ease-og-out",
|
|
861
|
-
"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45",
|
|
862
|
-
"disabled:cursor-not-allowed disabled:opacity-45 pointer-coarse:size-11",
|
|
863
|
-
showAttachedModelMenu && (desktopOnlyModelMenu ? "sm:rounded-l-og-md sm:rounded-r-none" : "rounded-l-og-md rounded-r-none"),
|
|
864
|
-
voiceButtonTone(status.phase)
|
|
865
|
-
),
|
|
866
|
-
children: /* @__PURE__ */ jsx2(
|
|
867
|
-
RealtimeActionGlyph,
|
|
868
|
-
{
|
|
869
|
-
phase: status.phase,
|
|
870
|
-
audioBlocked,
|
|
871
|
-
reduceMotion: reduceMotion === true
|
|
872
|
-
}
|
|
873
|
-
)
|
|
874
|
-
}
|
|
875
|
-
),
|
|
876
|
-
showAttachedModelMenu ? /* @__PURE__ */ jsxs2(DropdownMenu, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
|
|
877
|
-
/* @__PURE__ */ jsx2(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
878
|
-
"button",
|
|
879
|
-
{
|
|
880
|
-
type: "button",
|
|
881
|
-
"aria-label": "Choose voice model and options",
|
|
882
|
-
title: `Voice model: ${selectedModel.label}`,
|
|
883
|
-
disabled: props.selectionDisabled,
|
|
884
|
-
className: cn(
|
|
885
|
-
// Keep the split trigger compact on desktop while preserving a
|
|
886
|
-
// full 44px mobile target independent of pointer-media support.
|
|
887
|
-
"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",
|
|
888
|
-
"transition-[background-color,border-color,color] duration-200 ease-og-out",
|
|
889
|
-
"focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-og-accent/45",
|
|
890
|
-
desktopOnlyModelMenu && "hidden sm:inline-flex",
|
|
891
|
-
voiceChevronTone(status.phase)
|
|
892
|
-
),
|
|
893
|
-
children: /* @__PURE__ */ jsx2(ChevronDownIcon, { className: "size-3", "aria-hidden": true })
|
|
894
|
-
}
|
|
895
|
-
) }),
|
|
896
|
-
/* @__PURE__ */ jsxs2(
|
|
897
|
-
DropdownMenuContent,
|
|
898
|
-
{
|
|
899
|
-
side: props.menuSide ?? "top",
|
|
900
|
-
align: "end",
|
|
901
|
-
sideOffset: 8,
|
|
902
|
-
collisionPadding: 12,
|
|
903
|
-
className: "og-realtime-menu flex w-72 max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] flex-col overflow-hidden",
|
|
904
|
-
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
905
|
-
children: [
|
|
906
|
-
/* @__PURE__ */ jsx2("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: /* @__PURE__ */ jsx2(
|
|
907
|
-
RealtimeModelPickerMenu,
|
|
908
|
-
{
|
|
909
|
-
models,
|
|
910
|
-
selectedModel,
|
|
911
|
-
provider: pickerProvider,
|
|
912
|
-
direction: pickerDirection,
|
|
913
|
-
disabled: props.selectionDisabled === true || props.snapshot.mode?.state === "active",
|
|
914
|
-
onProviderChange: (provider, direction) => {
|
|
915
|
-
setPickerDirection(direction);
|
|
916
|
-
setPickerProvider(provider);
|
|
917
|
-
},
|
|
918
|
-
onSelect: (modelId) => {
|
|
919
|
-
props.onSelectModel?.(modelId);
|
|
920
|
-
setPickerOpen(false);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
) }),
|
|
924
|
-
status.phase !== "idle" && // Say it once: the selected model's row already carries its reason.
|
|
925
|
-
!(status.phase === "unavailable" && pickerProvider === selectedModel.provider && selectedModel.unavailableReason === status.detail) ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
926
|
-
/* @__PURE__ */ jsx2(DropdownMenuSeparator, {}),
|
|
927
|
-
/* @__PURE__ */ jsxs2("div", { className: "px-2.5 py-2", role: "status", "aria-live": "polite", children: [
|
|
928
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-og-sm font-medium text-og-fg", children: [
|
|
929
|
-
/* @__PURE__ */ jsx2(
|
|
930
|
-
RealtimeStatusDot,
|
|
931
|
-
{
|
|
932
|
-
phase: status.phase,
|
|
933
|
-
reduceMotion: reduceMotion === true
|
|
934
|
-
}
|
|
935
|
-
),
|
|
936
|
-
status.label
|
|
937
|
-
] }),
|
|
938
|
-
/* @__PURE__ */ jsx2("p", { className: "mt-1 text-og-xs leading-5 text-og-fg-subtle", children: status.detail })
|
|
939
|
-
] })
|
|
940
|
-
] }) : null,
|
|
941
|
-
audioBlocked ? /* @__PURE__ */ jsxs2(
|
|
942
|
-
DropdownMenuItem,
|
|
943
|
-
{
|
|
944
|
-
onSelect: () => void props.onRetryAudibleOutput().catch(() => void 0),
|
|
945
|
-
children: [
|
|
946
|
-
/* @__PURE__ */ jsx2(Volume2Icon, {}),
|
|
947
|
-
"Resume audio"
|
|
948
|
-
]
|
|
949
|
-
}
|
|
950
|
-
) : null,
|
|
951
|
-
retryConnection ? /* @__PURE__ */ jsxs2(DropdownMenuItem, { onSelect: () => void props.onRetry().catch(() => void 0), children: [
|
|
952
|
-
/* @__PURE__ */ jsx2(RotateCcwIcon, {}),
|
|
953
|
-
"Retry connection"
|
|
954
|
-
] }) : null,
|
|
955
|
-
modeOwned && props.snapshot.status !== "lost_owner" ? /* @__PURE__ */ jsxs2(
|
|
956
|
-
DropdownMenuItem,
|
|
957
|
-
{
|
|
958
|
-
variant: "destructive",
|
|
959
|
-
disabled: props.snapshot.status === "stopping",
|
|
960
|
-
onSelect: () => void props.onStop().catch(() => void 0),
|
|
961
|
-
children: [
|
|
962
|
-
/* @__PURE__ */ jsx2(SquareIcon, {}),
|
|
963
|
-
"End voice conversation"
|
|
964
|
-
]
|
|
965
|
-
}
|
|
966
|
-
) : null,
|
|
967
|
-
diagnosticsVisible ? /* @__PURE__ */ jsx2(RealtimeDiagnosticsMenu, { snapshot: props.snapshot }) : null
|
|
968
|
-
]
|
|
969
|
-
}
|
|
970
|
-
)
|
|
971
|
-
] }) : null
|
|
972
|
-
] }),
|
|
973
|
-
/* @__PURE__ */ jsxs2("span", { className: "sr-only", role: "status", "aria-live": "polite", children: [
|
|
974
|
-
status.label,
|
|
975
|
-
". ",
|
|
976
|
-
status.detail
|
|
977
|
-
] }),
|
|
978
|
-
props.snapshot.error ? /* @__PURE__ */ jsx2("span", { className: "sr-only", role: "alert", children: props.snapshot.error }) : null
|
|
979
|
-
]
|
|
980
|
-
}
|
|
981
|
-
);
|
|
982
|
-
}
|
|
983
|
-
function RealtimeVoiceModelPanel(props) {
|
|
984
|
-
const [provider, setProvider] = useState(null);
|
|
985
|
-
const [direction, setDirection] = useState(1);
|
|
986
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col", "data-testid": "realtime-voice-model-panel", children: [
|
|
987
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex min-h-10 shrink-0 items-center gap-2 border-b border-og-border px-2 pb-2", children: [
|
|
988
|
-
props.leading,
|
|
989
|
-
/* @__PURE__ */ jsx2("div", { className: "min-w-0 flex-1 text-og-menu font-medium text-og-fg", children: "Voice model" })
|
|
990
|
-
] }),
|
|
991
|
-
/* @__PURE__ */ jsxs2("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain p-2", children: [
|
|
992
|
-
/* @__PURE__ */ jsx2("p", { className: "mb-3 text-og-control text-og-fg-muted", children: "Used when you start a voice conversation." }),
|
|
993
|
-
/* @__PURE__ */ jsx2(
|
|
994
|
-
RealtimeModelPickerMenu,
|
|
995
|
-
{
|
|
996
|
-
models: props.models,
|
|
997
|
-
selectedModel: props.selectedModel,
|
|
998
|
-
provider,
|
|
999
|
-
direction,
|
|
1000
|
-
disabled: props.disabled === true,
|
|
1001
|
-
onProviderChange: (next, nextDirection) => {
|
|
1002
|
-
setDirection(nextDirection);
|
|
1003
|
-
setProvider(next);
|
|
1004
|
-
},
|
|
1005
|
-
onSelect: props.onSelect
|
|
1006
|
-
}
|
|
1007
|
-
)
|
|
1008
|
-
] })
|
|
1009
|
-
] });
|
|
1010
|
-
}
|
|
1011
|
-
function RealtimeMuteButton(props) {
|
|
1012
|
-
const microphone = props.kind === "microphone";
|
|
1013
|
-
const label = microphone ? props.muted ? "Unmute microphone" : "Mute microphone" : props.muted ? "Unmute voice audio" : "Mute voice audio";
|
|
1014
|
-
const Icon = microphone ? props.muted ? MicOffIcon : MicIcon : props.muted ? VolumeXIcon : Volume2Icon;
|
|
1015
|
-
return /* @__PURE__ */ jsx2(
|
|
1016
|
-
motion.button,
|
|
1017
|
-
{
|
|
1018
|
-
type: "button",
|
|
1019
|
-
"aria-label": label,
|
|
1020
|
-
"aria-pressed": props.muted,
|
|
1021
|
-
title: label,
|
|
1022
|
-
initial: props.reduceMotion ? false : { scale: 0.72, opacity: 0 },
|
|
1023
|
-
animate: { scale: 1, opacity: 1 },
|
|
1024
|
-
transition: {
|
|
1025
|
-
delay: props.enterDelay ?? 0,
|
|
1026
|
-
type: "spring",
|
|
1027
|
-
stiffness: 420,
|
|
1028
|
-
damping: 26,
|
|
1029
|
-
mass: 0.7
|
|
1030
|
-
},
|
|
1031
|
-
...props.reduceMotion ? {} : { whileTap: { scale: 0.92 } },
|
|
1032
|
-
onClick: props.onToggle,
|
|
1033
|
-
className: cn(
|
|
1034
|
-
"inline-flex size-8 shrink-0 items-center justify-center rounded-og-md outline-hidden",
|
|
1035
|
-
"transition-[background-color,border-color,color] duration-150 ease-og-out",
|
|
1036
|
-
"focus-visible:ring-2 focus-visible:ring-og-accent/45 pointer-coarse:size-11",
|
|
1037
|
-
props.muted ? "border border-og-accent/35 bg-og-accent-soft text-og-accent" : "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg"
|
|
1038
|
-
),
|
|
1039
|
-
children: /* @__PURE__ */ jsx2(Icon, { className: "size-3.5", "aria-hidden": true })
|
|
1040
|
-
}
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
function RealtimeModelPickerMenu(props) {
|
|
1044
|
-
const providers = REALTIME_MODEL_PROVIDERS.filter(
|
|
1045
|
-
(provider) => props.models.some((model) => model.provider === provider)
|
|
1046
|
-
);
|
|
1047
|
-
const pageKey = props.provider ? `voice-models:${props.provider}` : "voice-providers";
|
|
1048
|
-
const body = props.provider ? /* @__PURE__ */ jsxs2("div", { "data-testid": "realtime-model-picker-models", children: [
|
|
1049
|
-
/* @__PURE__ */ jsx2(
|
|
1050
|
-
PickerBackHeader,
|
|
1051
|
-
{
|
|
1052
|
-
label: props.provider,
|
|
1053
|
-
icon: /* @__PURE__ */ jsx2(
|
|
1054
|
-
BillingClassMark,
|
|
1055
|
-
{
|
|
1056
|
-
billingClass: REALTIME_PROVIDER_META[props.provider].billingClass,
|
|
1057
|
-
"aria-label": ""
|
|
1058
|
-
}
|
|
1059
|
-
),
|
|
1060
|
-
onBack: () => props.onProviderChange(null, -1)
|
|
1061
|
-
}
|
|
1062
|
-
),
|
|
1063
|
-
/* @__PURE__ */ jsx2("div", { className: "flex flex-col gap-0.5", children: props.models.filter((model) => model.provider === props.provider).map((model) => {
|
|
1064
|
-
const selected = model.id === props.selectedModel.id;
|
|
1065
|
-
return /* @__PURE__ */ jsx2(
|
|
1066
|
-
PickerNavRow,
|
|
1067
|
-
{
|
|
1068
|
-
label: model.label,
|
|
1069
|
-
hint: model.unavailableReason ?? (model.recommended ? `Recommended \xB7 ${model.description}` : model.description),
|
|
1070
|
-
disabled: props.disabled || !model.available,
|
|
1071
|
-
title: model.unavailableReason ?? model.description,
|
|
1072
|
-
active: selected,
|
|
1073
|
-
showChevron: false,
|
|
1074
|
-
trailing: selected ? /* @__PURE__ */ jsx2(CheckIcon, { className: MENU_CHECK_CLASS, "aria-hidden": true }) : void 0,
|
|
1075
|
-
testId: `realtime-model-choice-${model.id}`,
|
|
1076
|
-
onClick: () => props.onSelect(model.id)
|
|
1077
|
-
},
|
|
1078
|
-
model.id
|
|
1079
|
-
);
|
|
1080
|
-
}) })
|
|
1081
|
-
] }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col gap-0.5", "data-testid": "realtime-model-picker-providers", children: providers.map((provider) => {
|
|
1082
|
-
const meta = REALTIME_PROVIDER_META[provider];
|
|
1083
|
-
return /* @__PURE__ */ jsx2(
|
|
1084
|
-
PickerNavRow,
|
|
1085
|
-
{
|
|
1086
|
-
label: provider,
|
|
1087
|
-
hint: meta.hint,
|
|
1088
|
-
icon: /* @__PURE__ */ jsx2(BillingClassMark, { billingClass: meta.billingClass, "aria-label": "" }),
|
|
1089
|
-
active: props.selectedModel.provider === provider,
|
|
1090
|
-
testId: `realtime-model-provider-${meta.billingClass}`,
|
|
1091
|
-
onClick: () => props.onProviderChange(provider, 1)
|
|
1092
|
-
},
|
|
1093
|
-
provider
|
|
1094
|
-
);
|
|
1095
|
-
}) });
|
|
1096
|
-
return /* @__PURE__ */ jsx2("div", { className: "relative overflow-hidden", "data-testid": "realtime-model-picker-menu", children: /* @__PURE__ */ jsx2(PickerAnimatedPage, { pageKey, direction: props.direction, children: body }) });
|
|
1097
|
-
}
|
|
1098
|
-
function RealtimeDiagnosticsMenu({ snapshot }) {
|
|
1099
|
-
const bridge = snapshot.bridge;
|
|
1100
|
-
const rows = [
|
|
1101
|
-
["controller", snapshot.status],
|
|
1102
|
-
["microphone", snapshot.microphone],
|
|
1103
|
-
["microphone muted", String(snapshot.inputMuted)],
|
|
1104
|
-
["audio", snapshot.audibleOutput],
|
|
1105
|
-
["audio muted", String(snapshot.outputMuted)],
|
|
1106
|
-
["generation", String(snapshot.connectionGeneration)],
|
|
1107
|
-
["reconnect attempt", String(snapshot.reconnectAttempt)],
|
|
1108
|
-
["mode", snapshot.mode?.state ?? "none"],
|
|
1109
|
-
["mode version", snapshot.mode ? String(snapshot.mode.version) : "\u2014"],
|
|
1110
|
-
["connection epoch", snapshot.mode ? String(snapshot.mode.connectionEpoch) : "\u2014"],
|
|
1111
|
-
["provider started", bridge ? String(bridge.providerStarted) : "false"],
|
|
1112
|
-
["provider speaking", bridge ? String(bridge.speaking) : "false"],
|
|
1113
|
-
["delegation", bridge?.activeDelegationId ? "active" : "none"],
|
|
1114
|
-
["pending durable events", bridge ? String(bridge.pendingInbound) : "0"],
|
|
1115
|
-
["pending bytes", bridge ? String(bridge.pendingInboundBytes) : "0"],
|
|
1116
|
-
["client ack", bridge?.clientAckThroughSequence?.toString() ?? "\u2014"],
|
|
1117
|
-
["ignored provider events", bridge ? String(bridge.ignoredEventCount) : "0"],
|
|
1118
|
-
["last ignored event", bridge?.lastIgnoredEventType ?? "none"],
|
|
1119
|
-
["bridge error", bridge?.lastError ?? "none"],
|
|
1120
|
-
["diagnostic", snapshot.diagnostic?.kind ?? "none"]
|
|
1121
|
-
];
|
|
1122
|
-
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1123
|
-
/* @__PURE__ */ jsx2(DropdownMenuSeparator, {}),
|
|
1124
|
-
/* @__PURE__ */ jsxs2(DropdownMenuSub, { children: [
|
|
1125
|
-
/* @__PURE__ */ jsx2(DropdownMenuSubTrigger, { className: "text-og-fg-muted", children: "Realtime diagnostics" }),
|
|
1126
|
-
/* @__PURE__ */ jsx2(DropdownMenuSubContent, { className: "max-h-80 w-72 overflow-y-auto", children: rows.map(([label, value]) => /* @__PURE__ */ jsxs2(
|
|
1127
|
-
DropdownMenuItem,
|
|
1128
|
-
{
|
|
1129
|
-
disabled: true,
|
|
1130
|
-
className: "grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] gap-3 rounded-og-md font-mono text-[11px] opacity-100",
|
|
1131
|
-
children: [
|
|
1132
|
-
/* @__PURE__ */ jsx2("span", { className: "truncate text-og-fg-subtle", children: label }),
|
|
1133
|
-
/* @__PURE__ */ jsx2("span", { className: "truncate text-right text-og-fg", children: value })
|
|
1134
|
-
]
|
|
1135
|
-
},
|
|
1136
|
-
label
|
|
1137
|
-
)) })
|
|
1138
|
-
] })
|
|
1139
|
-
] });
|
|
1140
|
-
}
|
|
1141
|
-
function statusContent(snapshot, modelAvailable, canStart, admissionBlocker, modelLabel, unavailableCode = null) {
|
|
1142
|
-
if (snapshot.audibleOutput === "blocked" && !snapshot.outputMuted) {
|
|
1143
|
-
return {
|
|
1144
|
-
phase: "blocked",
|
|
1145
|
-
label: "Audio output blocked",
|
|
1146
|
-
detail: "Resume audio to hear the existing voice connection."
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
switch (snapshot.status) {
|
|
1150
|
-
case "starting":
|
|
1151
|
-
return {
|
|
1152
|
-
phase: "connecting",
|
|
1153
|
-
label: "Connecting\u2026",
|
|
1154
|
-
detail: "Preparing your microphone and live voice."
|
|
1155
|
-
};
|
|
1156
|
-
case "active":
|
|
1157
|
-
return snapshot.bridge?.speaking ? {
|
|
1158
|
-
phase: "speaking",
|
|
1159
|
-
label: "Speaking",
|
|
1160
|
-
detail: "Interrupt naturally whenever you want."
|
|
1161
|
-
} : {
|
|
1162
|
-
phase: "listening",
|
|
1163
|
-
label: "Listening",
|
|
1164
|
-
detail: "Voice and typed messages share this session."
|
|
1165
|
-
};
|
|
1166
|
-
case "stopping":
|
|
1167
|
-
return {
|
|
1168
|
-
phase: "stopping",
|
|
1169
|
-
label: "Ending voice\u2026",
|
|
1170
|
-
detail: "Saving the remaining conversation context."
|
|
1171
|
-
};
|
|
1172
|
-
case "recovering":
|
|
1173
|
-
return {
|
|
1174
|
-
phase: "reconnecting",
|
|
1175
|
-
label: "Reconnecting\u2026",
|
|
1176
|
-
detail: snapshot.error ?? "Keeping this voice conversation connected."
|
|
1177
|
-
};
|
|
1178
|
-
case "lost_owner":
|
|
1179
|
-
return {
|
|
1180
|
-
phase: "elsewhere",
|
|
1181
|
-
label: "Voice active elsewhere",
|
|
1182
|
-
detail: "Return to the browser that started it, or wait for that connection to expire."
|
|
1183
|
-
};
|
|
1184
|
-
case "error":
|
|
1185
|
-
if (snapshot.refusal) {
|
|
1186
|
-
return {
|
|
1187
|
-
phase: "unavailable",
|
|
1188
|
-
label: realtimeRefusalLabel(snapshot.refusal.code) ?? "Voice unavailable",
|
|
1189
|
-
detail: snapshot.refusal.message
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
return {
|
|
1193
|
-
phase: "error",
|
|
1194
|
-
label: "Voice unavailable",
|
|
1195
|
-
detail: snapshot.error ?? "The connection could not be started."
|
|
1196
|
-
};
|
|
1197
|
-
case "idle":
|
|
1198
|
-
if (!modelAvailable) {
|
|
1199
|
-
return {
|
|
1200
|
-
phase: "unavailable",
|
|
1201
|
-
label: realtimeRefusalLabel(unavailableCode) ?? "Voice model unavailable",
|
|
1202
|
-
detail: admissionBlocker ?? "Choose another voice model."
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
if (!canStart && admissionBlocker) {
|
|
1206
|
-
return {
|
|
1207
|
-
phase: "unavailable",
|
|
1208
|
-
label: "Voice unavailable",
|
|
1209
|
-
detail: admissionBlocker
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
return {
|
|
1213
|
-
phase: "idle",
|
|
1214
|
-
label: "Start voice",
|
|
1215
|
-
detail: `Talk live with ${modelLabel}.`
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
function RealtimeComposerGlow(props) {
|
|
1220
|
-
const visible = [
|
|
1221
|
-
"connecting",
|
|
1222
|
-
"listening",
|
|
1223
|
-
"speaking",
|
|
1224
|
-
"reconnecting",
|
|
1225
|
-
"blocked",
|
|
1226
|
-
"error",
|
|
1227
|
-
"stopping"
|
|
1228
|
-
].includes(props.phase);
|
|
1229
|
-
return /* @__PURE__ */ jsx2(AnimatePresence, { initial: false, children: visible ? /* @__PURE__ */ jsx2(
|
|
1230
|
-
motion.span,
|
|
1231
|
-
{
|
|
1232
|
-
"aria-hidden": true,
|
|
1233
|
-
initial: { opacity: 0 },
|
|
1234
|
-
animate: props.reduceMotion ? { opacity: 0.48 } : props.phase === "speaking" ? { opacity: [0.42, 0.82, 0.52] } : props.phase === "listening" ? { opacity: [0.34, 0.62, 0.42] } : { opacity: [0.28, 0.58, 0.28] },
|
|
1235
|
-
exit: { opacity: 0 },
|
|
1236
|
-
transition: props.reduceMotion ? { duration: 0.12 } : {
|
|
1237
|
-
duration: props.phase === "speaking" ? 1.15 : 2.1,
|
|
1238
|
-
ease: "easeInOut",
|
|
1239
|
-
repeat: Number.POSITIVE_INFINITY
|
|
1240
|
-
},
|
|
1241
|
-
className: cn(
|
|
1242
|
-
"pointer-events-none absolute -inset-px z-[1] rounded-og-lg ring-1 ring-inset shadow-og-glow",
|
|
1243
|
-
props.phase === "speaking" ? "ring-og-status-running/50" : props.phase === "blocked" || props.phase === "error" ? "ring-og-status-waiting/45" : "ring-og-accent/45"
|
|
1244
|
-
)
|
|
1245
|
-
},
|
|
1246
|
-
props.phase
|
|
1247
|
-
) : null });
|
|
1248
|
-
}
|
|
1249
|
-
function RealtimeActionGlyph(props) {
|
|
1250
|
-
if (["connecting", "reconnecting", "stopping"].includes(props.phase)) {
|
|
1251
|
-
return /* @__PURE__ */ jsx2(LoaderCircleIcon, { className: "size-3.5 animate-og-spin", "aria-hidden": true });
|
|
1252
|
-
}
|
|
1253
|
-
if (props.phase === "blocked") {
|
|
1254
|
-
return props.audioBlocked ? /* @__PURE__ */ jsx2(Volume2Icon, { className: "size-3.5", "aria-hidden": true }) : /* @__PURE__ */ jsx2(CircleAlertIcon, { className: "size-3.5", "aria-hidden": true });
|
|
1255
|
-
}
|
|
1256
|
-
if (props.phase === "error" || props.phase === "elsewhere" || props.phase === "unavailable") {
|
|
1257
|
-
return /* @__PURE__ */ jsx2(CircleAlertIcon, { className: "size-3.5", "aria-hidden": true });
|
|
1258
|
-
}
|
|
1259
|
-
if (props.phase === "listening" || props.phase === "speaking") {
|
|
1260
|
-
return /* @__PURE__ */ jsxs2("span", { className: "relative inline-flex size-4 items-center justify-center", children: [
|
|
1261
|
-
!props.reduceMotion ? /* @__PURE__ */ jsx2(
|
|
1262
|
-
motion.span,
|
|
1263
|
-
{
|
|
1264
|
-
"aria-hidden": true,
|
|
1265
|
-
className: "absolute inset-0 rounded-full border border-current",
|
|
1266
|
-
animate: { opacity: [0.65, 0], scale: [0.65, 1.45] },
|
|
1267
|
-
transition: {
|
|
1268
|
-
duration: props.phase === "speaking" ? 0.9 : 1.5,
|
|
1269
|
-
ease: "easeOut",
|
|
1270
|
-
repeat: Number.POSITIVE_INFINITY
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
) : null,
|
|
1274
|
-
/* @__PURE__ */ jsx2(SquareIcon, { className: "relative size-2.5 fill-current", "aria-hidden": true })
|
|
1275
|
-
] });
|
|
1276
|
-
}
|
|
1277
|
-
return /* @__PURE__ */ jsx2(AudioLinesIcon, { className: "size-4", "aria-hidden": true });
|
|
1278
|
-
}
|
|
1279
|
-
function RealtimeStatusDot(props) {
|
|
1280
|
-
const live = props.phase === "listening" || props.phase === "speaking";
|
|
1281
|
-
return /* @__PURE__ */ jsxs2("span", { className: "relative inline-flex size-2 shrink-0", "aria-hidden": true, children: [
|
|
1282
|
-
live && !props.reduceMotion ? /* @__PURE__ */ jsx2(
|
|
1283
|
-
motion.span,
|
|
1284
|
-
{
|
|
1285
|
-
className: "absolute inset-0 rounded-full bg-og-status-running",
|
|
1286
|
-
animate: { opacity: [0.45, 0], scale: [1, 2.2] },
|
|
1287
|
-
transition: {
|
|
1288
|
-
duration: 1.4,
|
|
1289
|
-
ease: "easeOut",
|
|
1290
|
-
repeat: Number.POSITIVE_INFINITY
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
) : null,
|
|
1294
|
-
/* @__PURE__ */ jsx2(
|
|
1295
|
-
"span",
|
|
1296
|
-
{
|
|
1297
|
-
className: cn(
|
|
1298
|
-
"relative size-2 rounded-full",
|
|
1299
|
-
live ? "bg-og-status-running" : props.phase === "blocked" || props.phase === "error" ? "bg-og-status-waiting" : props.phase === "elsewhere" || props.phase === "unavailable" ? "bg-og-fg-subtle" : "bg-og-accent"
|
|
1300
|
-
)
|
|
1301
|
-
}
|
|
1302
|
-
)
|
|
1303
|
-
] });
|
|
1304
|
-
}
|
|
1305
|
-
function voiceButtonTone(phase) {
|
|
1306
|
-
if (phase === "listening" || phase === "speaking") {
|
|
1307
|
-
return "border border-og-primary-border bg-og-primary text-og-primary-fg hover:bg-og-primary-hover";
|
|
1308
|
-
}
|
|
1309
|
-
if (phase === "blocked" || phase === "error") {
|
|
1310
|
-
return "border border-og-status-waiting/35 bg-og-status-waiting/10 text-og-status-waiting hover:bg-og-status-waiting/15";
|
|
1311
|
-
}
|
|
1312
|
-
if (phase === "connecting" || phase === "reconnecting" || phase === "stopping") {
|
|
1313
|
-
return "border border-og-accent/35 bg-og-accent-soft text-og-accent";
|
|
1314
|
-
}
|
|
1315
|
-
return "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg";
|
|
1316
|
-
}
|
|
1317
|
-
function voiceChevronTone(phase) {
|
|
1318
|
-
if (phase === "listening" || phase === "speaking") {
|
|
1319
|
-
return "border border-l-0 border-og-primary-border bg-og-primary text-og-primary-fg hover:bg-og-primary-hover";
|
|
1320
|
-
}
|
|
1321
|
-
if (phase === "blocked" || phase === "error") {
|
|
1322
|
-
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";
|
|
1323
|
-
}
|
|
1324
|
-
if (phase === "connecting" || phase === "reconnecting" || phase === "stopping") {
|
|
1325
|
-
return "border border-l-0 border-og-accent/35 bg-og-accent-soft text-og-accent";
|
|
1326
|
-
}
|
|
1327
|
-
return "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg";
|
|
1328
|
-
}
|
|
1329
|
-
function toRealtimeModelOption(model) {
|
|
1330
|
-
return { ...model };
|
|
1331
|
-
}
|
|
1332
|
-
function readRealtimeModelPreference(workspaceId) {
|
|
1333
|
-
if (typeof localStorage === "undefined") return null;
|
|
1334
|
-
try {
|
|
1335
|
-
const value = localStorage.getItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`);
|
|
1336
|
-
return isRealtimeModel(value) ? value : null;
|
|
1337
|
-
} catch {
|
|
1338
|
-
return null;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
function writeRealtimeModelPreference(workspaceId, model) {
|
|
1342
|
-
if (typeof localStorage === "undefined") return;
|
|
1343
|
-
try {
|
|
1344
|
-
localStorage.setItem(`${REALTIME_MODEL_STORAGE_PREFIX}:${workspaceId}`, model);
|
|
1345
|
-
} catch {
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
function isRealtimeModel(value) {
|
|
1349
|
-
return value === "opengeni-azure/gpt-live-1" || value === "gpt-live-1-boulder-alpha" || value === "opengeni-gateway/openai/gpt-realtime-2.1" || value === "opengeni-gateway/openai/gpt-realtime-mini" || value === "opengeni-gateway/xai/grok-voice-think-fast-2.0" || value === "supergrok/grok-voice-think-fast-2.0" || value === "workspace-gateway/openai/gpt-realtime-2.1" || value === "workspace-gateway/openai/gpt-realtime-mini" || value === "workspace-gateway/xai/grok-voice-think-fast-2.0";
|
|
1350
|
-
}
|
|
1351
|
-
var useSessionCodexRealtime = useSessionRealtime;
|
|
1352
|
-
var useWorkspaceRealtimeModelSelection = useRealtimeModelSelection;
|
|
1353
|
-
var SessionCodexRealtimeControl = SessionRealtimeControl;
|
|
1354
|
-
var CodexRealtimeControl = RealtimeVoiceControl;
|
|
3
|
+
CodexRealtimeControl,
|
|
4
|
+
NewSessionRealtimeControl,
|
|
5
|
+
RealtimeModelPickerMenu,
|
|
6
|
+
RealtimeVoiceControl,
|
|
7
|
+
RealtimeVoiceModelPanel,
|
|
8
|
+
SessionCodexRealtimeControl,
|
|
9
|
+
SessionRealtimeControl,
|
|
10
|
+
codexRealtimeAdmissionAllowed,
|
|
11
|
+
codexRealtimeAdmissionBlocker,
|
|
12
|
+
useRealtimeModelSelection,
|
|
13
|
+
useSessionCodexRealtime,
|
|
14
|
+
useSessionRealtime,
|
|
15
|
+
useWorkspaceRealtimeModelSelection
|
|
16
|
+
} from "./chunk-YVFRBXST.js";
|
|
17
|
+
import "./chunk-NX4ADH73.js";
|
|
18
|
+
import "./chunk-L2J3PU7A.js";
|
|
19
|
+
import "./chunk-PMV53IMM.js";
|
|
20
|
+
import "./chunk-XS67YG5R.js";
|
|
21
|
+
import "./chunk-XEA5BCMA.js";
|
|
22
|
+
import "./chunk-XTD7TZHW.js";
|
|
23
|
+
import "./chunk-TXS65BB2.js";
|
|
1355
24
|
export {
|
|
1356
25
|
CodexRealtimeControl,
|
|
1357
26
|
NewSessionRealtimeControl,
|