@navai/voice-frontend 0.1.1 → 0.1.3
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.en.md +320 -0
- package/README.es.md +320 -0
- package/README.md +320 -126
- package/dist/Orb-B4OSC3XR.js +6 -0
- package/dist/chunk-KBBRQQLK.js +531 -0
- package/dist/index.cjs +966 -2
- package/dist/index.d.cts +104 -1
- package/dist/index.d.ts +104 -1
- package/dist/index.js +417 -2
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Orb,
|
|
3
|
+
useNavaiVoiceOrbStyles
|
|
4
|
+
} from "./chunk-KBBRQQLK.js";
|
|
5
|
+
|
|
1
6
|
// src/agent.ts
|
|
2
7
|
import { RealtimeAgent, tool } from "@openai/agents/realtime";
|
|
3
8
|
import { z } from "zod";
|
|
@@ -728,6 +733,7 @@ function emitWarnings(warnings) {
|
|
|
728
733
|
}
|
|
729
734
|
function useWebVoiceAgent(options) {
|
|
730
735
|
const sessionRef = useRef(null);
|
|
736
|
+
const attachedRealtimeSessionRef = useRef(null);
|
|
731
737
|
const runtimeConfigPromise = useMemo(
|
|
732
738
|
() => resolveNavaiFrontendRuntimeConfig({
|
|
733
739
|
moduleLoaders: options.moduleLoaders,
|
|
@@ -758,15 +764,61 @@ function useWebVoiceAgent(options) {
|
|
|
758
764
|
[options.apiBaseUrl, options.env]
|
|
759
765
|
);
|
|
760
766
|
const [status, setStatus] = useState("idle");
|
|
767
|
+
const [agentVoiceState, setAgentVoiceState] = useState("idle");
|
|
761
768
|
const [error, setError] = useState(null);
|
|
769
|
+
const setAgentVoiceStateIfChanged = useCallback((next) => {
|
|
770
|
+
setAgentVoiceState((current) => current === next ? current : next);
|
|
771
|
+
}, []);
|
|
772
|
+
const handleSessionAudioStart = useCallback(() => {
|
|
773
|
+
setAgentVoiceStateIfChanged("speaking");
|
|
774
|
+
}, [setAgentVoiceStateIfChanged]);
|
|
775
|
+
const handleSessionAudioStopped = useCallback(() => {
|
|
776
|
+
setAgentVoiceStateIfChanged("idle");
|
|
777
|
+
}, [setAgentVoiceStateIfChanged]);
|
|
778
|
+
const handleSessionAudioInterrupted = useCallback(() => {
|
|
779
|
+
setAgentVoiceStateIfChanged("idle");
|
|
780
|
+
}, [setAgentVoiceStateIfChanged]);
|
|
781
|
+
const handleSessionError = useCallback(() => {
|
|
782
|
+
setAgentVoiceStateIfChanged("idle");
|
|
783
|
+
}, [setAgentVoiceStateIfChanged]);
|
|
784
|
+
const detachSessionAudioListeners = useCallback(() => {
|
|
785
|
+
const attachedSession = attachedRealtimeSessionRef.current;
|
|
786
|
+
if (!attachedSession) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
attachedSession.off("audio_start", handleSessionAudioStart);
|
|
790
|
+
attachedSession.off("audio_stopped", handleSessionAudioStopped);
|
|
791
|
+
attachedSession.off("audio_interrupted", handleSessionAudioInterrupted);
|
|
792
|
+
attachedSession.off("error", handleSessionError);
|
|
793
|
+
attachedRealtimeSessionRef.current = null;
|
|
794
|
+
}, [handleSessionAudioInterrupted, handleSessionAudioStart, handleSessionAudioStopped, handleSessionError]);
|
|
795
|
+
const attachSessionAudioListeners = useCallback(
|
|
796
|
+
(session) => {
|
|
797
|
+
detachSessionAudioListeners();
|
|
798
|
+
session.on("audio_start", handleSessionAudioStart);
|
|
799
|
+
session.on("audio_stopped", handleSessionAudioStopped);
|
|
800
|
+
session.on("audio_interrupted", handleSessionAudioInterrupted);
|
|
801
|
+
session.on("error", handleSessionError);
|
|
802
|
+
attachedRealtimeSessionRef.current = session;
|
|
803
|
+
},
|
|
804
|
+
[
|
|
805
|
+
detachSessionAudioListeners,
|
|
806
|
+
handleSessionAudioInterrupted,
|
|
807
|
+
handleSessionAudioStart,
|
|
808
|
+
handleSessionAudioStopped,
|
|
809
|
+
handleSessionError
|
|
810
|
+
]
|
|
811
|
+
);
|
|
762
812
|
const stop = useCallback(() => {
|
|
813
|
+
detachSessionAudioListeners();
|
|
763
814
|
try {
|
|
764
815
|
sessionRef.current?.close();
|
|
765
816
|
} finally {
|
|
766
817
|
sessionRef.current = null;
|
|
767
818
|
setStatus("idle");
|
|
819
|
+
setAgentVoiceStateIfChanged("idle");
|
|
768
820
|
}
|
|
769
|
-
}, []);
|
|
821
|
+
}, [detachSessionAudioListeners, setAgentVoiceStateIfChanged]);
|
|
770
822
|
useEffect(() => {
|
|
771
823
|
return () => {
|
|
772
824
|
stop();
|
|
@@ -778,6 +830,7 @@ function useWebVoiceAgent(options) {
|
|
|
778
830
|
}
|
|
779
831
|
setError(null);
|
|
780
832
|
setStatus("connecting");
|
|
833
|
+
setAgentVoiceStateIfChanged("idle");
|
|
781
834
|
try {
|
|
782
835
|
const runtimeConfig = await runtimeConfigPromise;
|
|
783
836
|
const requestPayload = runtimeConfig.modelOverride ? { model: runtimeConfig.modelOverride } : {};
|
|
@@ -792,6 +845,7 @@ function useWebVoiceAgent(options) {
|
|
|
792
845
|
});
|
|
793
846
|
emitWarnings([...runtimeConfig.warnings, ...backendFunctionsResult.warnings, ...warnings]);
|
|
794
847
|
const session = new RealtimeSession(agent);
|
|
848
|
+
attachSessionAudioListeners(session);
|
|
795
849
|
if (runtimeConfig.modelOverride) {
|
|
796
850
|
await session.connect({ apiKey: secretPayload.value, model: runtimeConfig.modelOverride });
|
|
797
851
|
} else {
|
|
@@ -803,28 +857,389 @@ function useWebVoiceAgent(options) {
|
|
|
803
857
|
const message = formatError(startError);
|
|
804
858
|
setError(message);
|
|
805
859
|
setStatus("error");
|
|
860
|
+
setAgentVoiceStateIfChanged("idle");
|
|
861
|
+
detachSessionAudioListeners();
|
|
806
862
|
try {
|
|
807
863
|
sessionRef.current?.close();
|
|
808
864
|
} catch {
|
|
809
865
|
}
|
|
810
866
|
sessionRef.current = null;
|
|
811
867
|
}
|
|
812
|
-
}, [
|
|
868
|
+
}, [
|
|
869
|
+
attachSessionAudioListeners,
|
|
870
|
+
backendClient,
|
|
871
|
+
detachSessionAudioListeners,
|
|
872
|
+
options.navigate,
|
|
873
|
+
runtimeConfigPromise,
|
|
874
|
+
setAgentVoiceStateIfChanged,
|
|
875
|
+
status
|
|
876
|
+
]);
|
|
813
877
|
return {
|
|
814
878
|
status,
|
|
879
|
+
agentVoiceState,
|
|
815
880
|
error,
|
|
816
881
|
isConnecting: status === "connecting",
|
|
817
882
|
isConnected: status === "connected",
|
|
883
|
+
isAgentSpeaking: agentVoiceState === "speaking",
|
|
818
884
|
start,
|
|
819
885
|
stop
|
|
820
886
|
};
|
|
821
887
|
}
|
|
888
|
+
|
|
889
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
890
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
|
|
891
|
+
|
|
892
|
+
// src/orb/dynamic.tsx
|
|
893
|
+
import { lazy, Suspense, useEffect as useEffect2, useState as useState2 } from "react";
|
|
894
|
+
import { jsx } from "react/jsx-runtime";
|
|
895
|
+
function dynamic(loader, options = {}) {
|
|
896
|
+
const { ssr = true, loading: LoadingComponent } = options;
|
|
897
|
+
const LazyComponent = lazy(async () => {
|
|
898
|
+
const loaded = await loader();
|
|
899
|
+
if (typeof loaded === "function") {
|
|
900
|
+
return { default: loaded };
|
|
901
|
+
}
|
|
902
|
+
return loaded;
|
|
903
|
+
});
|
|
904
|
+
function DynamicComponent(props) {
|
|
905
|
+
const [isClientReady, setIsClientReady] = useState2(ssr);
|
|
906
|
+
useEffect2(() => {
|
|
907
|
+
if (!ssr) {
|
|
908
|
+
setIsClientReady(true);
|
|
909
|
+
}
|
|
910
|
+
}, [ssr]);
|
|
911
|
+
if (!isClientReady) {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
const fallback = LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null;
|
|
915
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback, children: /* @__PURE__ */ jsx(LazyComponent, { ...props }) });
|
|
916
|
+
}
|
|
917
|
+
DynamicComponent.displayName = "DynamicComponent";
|
|
918
|
+
return DynamicComponent;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
922
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
923
|
+
var Orb2 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
|
|
924
|
+
ssr: false
|
|
925
|
+
});
|
|
926
|
+
var ORB_DELAY_MS_MIN = 0;
|
|
927
|
+
var ORB_DELAY_MS_MAX = 6e4;
|
|
928
|
+
var DEFAULT_AUTOPLAY_DELAY_MS = 9e3;
|
|
929
|
+
var DEFAULT_REVEAL_DELAY_MS = 5200;
|
|
930
|
+
function clampNavaiOrbDelayMs(value, fallback) {
|
|
931
|
+
const numericValue = Number.isFinite(value) ? value : fallback;
|
|
932
|
+
return Math.min(ORB_DELAY_MS_MAX, Math.max(ORB_DELAY_MS_MIN, numericValue));
|
|
933
|
+
}
|
|
934
|
+
function NavaiHeroOrb({
|
|
935
|
+
className = "",
|
|
936
|
+
backgroundColor = "#000000",
|
|
937
|
+
isAgentSpeaking = false,
|
|
938
|
+
hoverIntensitySpeaking = 0.66,
|
|
939
|
+
hoverIntensityIdle = 0.08,
|
|
940
|
+
revealDelayMs = DEFAULT_REVEAL_DELAY_MS,
|
|
941
|
+
autoplayDelayMs = DEFAULT_AUTOPLAY_DELAY_MS
|
|
942
|
+
}) {
|
|
943
|
+
useNavaiVoiceOrbStyles();
|
|
944
|
+
const resolvedRevealDelayMs = clampNavaiOrbDelayMs(revealDelayMs, DEFAULT_REVEAL_DELAY_MS);
|
|
945
|
+
const resolvedAutoplayDelayMs = clampNavaiOrbDelayMs(autoplayDelayMs, DEFAULT_AUTOPLAY_DELAY_MS);
|
|
946
|
+
const [isOrbReady, setIsOrbReady] = useState3(resolvedRevealDelayMs === 0);
|
|
947
|
+
const [isOrbAutoAnimating, setIsOrbAutoAnimating] = useState3(resolvedAutoplayDelayMs === 0);
|
|
948
|
+
useEffect3(() => {
|
|
949
|
+
if (typeof window === "undefined" || resolvedRevealDelayMs === 0) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const revealOrb = () => setIsOrbReady(true);
|
|
953
|
+
window.addEventListener("pointerdown", revealOrb, { passive: true, once: true });
|
|
954
|
+
window.addEventListener("touchstart", revealOrb, { passive: true, once: true });
|
|
955
|
+
window.addEventListener("keydown", revealOrb, { once: true });
|
|
956
|
+
const timeoutId = window.setTimeout(revealOrb, resolvedRevealDelayMs);
|
|
957
|
+
return () => {
|
|
958
|
+
window.removeEventListener("pointerdown", revealOrb);
|
|
959
|
+
window.removeEventListener("touchstart", revealOrb);
|
|
960
|
+
window.removeEventListener("keydown", revealOrb);
|
|
961
|
+
window.clearTimeout(timeoutId);
|
|
962
|
+
};
|
|
963
|
+
}, [resolvedRevealDelayMs]);
|
|
964
|
+
useEffect3(() => {
|
|
965
|
+
if (typeof window === "undefined" || resolvedAutoplayDelayMs === 0) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
let started = false;
|
|
969
|
+
const startOrbAnimation = () => {
|
|
970
|
+
if (started) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
started = true;
|
|
974
|
+
setIsOrbAutoAnimating(true);
|
|
975
|
+
};
|
|
976
|
+
if (navigator.userActivation?.hasBeenActive) {
|
|
977
|
+
startOrbAnimation();
|
|
978
|
+
}
|
|
979
|
+
window.addEventListener("pointerdown", startOrbAnimation, { passive: true, once: true });
|
|
980
|
+
window.addEventListener("touchstart", startOrbAnimation, { passive: true, once: true });
|
|
981
|
+
window.addEventListener("keydown", startOrbAnimation, { once: true });
|
|
982
|
+
const timeoutId = window.setTimeout(startOrbAnimation, resolvedAutoplayDelayMs);
|
|
983
|
+
return () => {
|
|
984
|
+
window.removeEventListener("pointerdown", startOrbAnimation);
|
|
985
|
+
window.removeEventListener("touchstart", startOrbAnimation);
|
|
986
|
+
window.removeEventListener("keydown", startOrbAnimation);
|
|
987
|
+
window.clearTimeout(timeoutId);
|
|
988
|
+
};
|
|
989
|
+
}, [resolvedAutoplayDelayMs]);
|
|
990
|
+
const orbHoverIntensity = useMemo2(() => {
|
|
991
|
+
return isAgentSpeaking ? hoverIntensitySpeaking : hoverIntensityIdle;
|
|
992
|
+
}, [hoverIntensityIdle, hoverIntensitySpeaking, isAgentSpeaking]);
|
|
993
|
+
if (!isOrbReady) {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
return /* @__PURE__ */ jsx2("div", { className: ["navai-voice-orb-hero", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx2(
|
|
997
|
+
Orb2,
|
|
998
|
+
{
|
|
999
|
+
hoverIntensity: orbHoverIntensity,
|
|
1000
|
+
rotateOnHover: true,
|
|
1001
|
+
forceHoverState: isAgentSpeaking,
|
|
1002
|
+
enablePointerHover: false,
|
|
1003
|
+
animate: isAgentSpeaking || isOrbAutoAnimating,
|
|
1004
|
+
backgroundColor
|
|
1005
|
+
}
|
|
1006
|
+
) });
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/orb/NavaiMiniOrbDock.tsx
|
|
1010
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1011
|
+
var Orb3 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
|
|
1012
|
+
ssr: false
|
|
1013
|
+
});
|
|
1014
|
+
function NavaiMiniOrbDock({
|
|
1015
|
+
className = "",
|
|
1016
|
+
style,
|
|
1017
|
+
themeMode = "dark",
|
|
1018
|
+
placement = "bottom-right",
|
|
1019
|
+
isActive = false,
|
|
1020
|
+
isConnected = false,
|
|
1021
|
+
isDisabled = false,
|
|
1022
|
+
isAgentSpeaking = false,
|
|
1023
|
+
animateOrb = true,
|
|
1024
|
+
backgroundColor = "#060914",
|
|
1025
|
+
buttonAriaLabel,
|
|
1026
|
+
buttonIcon,
|
|
1027
|
+
buttonType = "button",
|
|
1028
|
+
onButtonClick,
|
|
1029
|
+
statusMessage = "",
|
|
1030
|
+
isError = false,
|
|
1031
|
+
ariaMessage = ""
|
|
1032
|
+
}) {
|
|
1033
|
+
useNavaiVoiceOrbStyles();
|
|
1034
|
+
const dockClassName = ["navai-voice-orb-dock", `is-${placement}`, themeMode === "light" ? "is-light" : "", className].filter(Boolean).join(" ");
|
|
1035
|
+
const shouldHighlightOrb = isAgentSpeaking || isActive;
|
|
1036
|
+
const orbHoverIntensity = isAgentSpeaking ? 0.66 : 0.08;
|
|
1037
|
+
return /* @__PURE__ */ jsxs("aside", { className: dockClassName, style, children: [
|
|
1038
|
+
/* @__PURE__ */ jsxs("div", { className: "navai-voice-orb-wrap", children: [
|
|
1039
|
+
/* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-surface", shouldHighlightOrb ? "is-highlighted" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
|
|
1040
|
+
Orb3,
|
|
1041
|
+
{
|
|
1042
|
+
hoverIntensity: orbHoverIntensity,
|
|
1043
|
+
rotateOnHover: true,
|
|
1044
|
+
forceHoverState: isAgentSpeaking,
|
|
1045
|
+
enablePointerHover: false,
|
|
1046
|
+
animate: animateOrb,
|
|
1047
|
+
backgroundColor
|
|
1048
|
+
}
|
|
1049
|
+
) }),
|
|
1050
|
+
/* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-button-shell", isConnected ? "is-active" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
|
|
1051
|
+
"button",
|
|
1052
|
+
{
|
|
1053
|
+
type: buttonType,
|
|
1054
|
+
className: [
|
|
1055
|
+
"navai-voice-orb-button",
|
|
1056
|
+
isConnected ? "is-active" : "",
|
|
1057
|
+
isActive && !isConnected ? "is-connecting" : ""
|
|
1058
|
+
].filter(Boolean).join(" "),
|
|
1059
|
+
onClick: onButtonClick,
|
|
1060
|
+
disabled: isDisabled,
|
|
1061
|
+
"aria-label": buttonAriaLabel,
|
|
1062
|
+
children: buttonIcon
|
|
1063
|
+
}
|
|
1064
|
+
) })
|
|
1065
|
+
] }),
|
|
1066
|
+
statusMessage ? /* @__PURE__ */ jsx3("p", { className: ["navai-voice-orb-status", isError ? "is-error" : ""].filter(Boolean).join(" "), role: "status", children: statusMessage }) : null,
|
|
1067
|
+
/* @__PURE__ */ jsx3("span", { className: "navai-voice-orb-live", "aria-live": "polite", children: ariaMessage })
|
|
1068
|
+
] });
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
1072
|
+
import { useEffect as useEffect4 } from "react";
|
|
1073
|
+
|
|
1074
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
1075
|
+
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
1076
|
+
|
|
1077
|
+
// src/orb/NavaiVoiceOrbDockMicIcon.tsx
|
|
1078
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1079
|
+
function NavaiVoiceOrbDockMicIcon({
|
|
1080
|
+
isActive = false,
|
|
1081
|
+
size = 20
|
|
1082
|
+
}) {
|
|
1083
|
+
return /* @__PURE__ */ jsxs2(
|
|
1084
|
+
"svg",
|
|
1085
|
+
{
|
|
1086
|
+
width: size,
|
|
1087
|
+
height: size,
|
|
1088
|
+
viewBox: "0 0 24 24",
|
|
1089
|
+
fill: "none",
|
|
1090
|
+
stroke: "currentColor",
|
|
1091
|
+
strokeWidth: "2",
|
|
1092
|
+
strokeLinecap: "round",
|
|
1093
|
+
strokeLinejoin: "round",
|
|
1094
|
+
"aria-hidden": "true",
|
|
1095
|
+
className: ["navai-voice-orb-icon", isActive ? "is-pulsing" : ""].filter(Boolean).join(" "),
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 3a3 3 0 0 0-3 3v6a3 3 0 1 0 6 0V6a3 3 0 0 0-3-3Z" }),
|
|
1098
|
+
/* @__PURE__ */ jsx4("path", { d: "M19 10v2a7 7 0 1 1-14 0v-2" }),
|
|
1099
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 19v3" })
|
|
1100
|
+
]
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/orb/NavaiVoiceOrbDockSpinnerIcon.tsx
|
|
1106
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1107
|
+
function NavaiVoiceOrbDockSpinnerIcon({
|
|
1108
|
+
size = 20
|
|
1109
|
+
}) {
|
|
1110
|
+
const style = {
|
|
1111
|
+
width: size,
|
|
1112
|
+
height: size
|
|
1113
|
+
};
|
|
1114
|
+
return /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "navai-voice-orb-spinner", style });
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
1118
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1119
|
+
var DEFAULT_MESSAGES = {
|
|
1120
|
+
ariaStart: "Activate NAVAI voice",
|
|
1121
|
+
ariaStop: "Deactivate NAVAI voice",
|
|
1122
|
+
idle: "NAVAI ready to start.",
|
|
1123
|
+
connecting: "Connecting NAVAI voice...",
|
|
1124
|
+
listening: "NAVAI is listening.",
|
|
1125
|
+
speaking: "NAVAI is speaking.",
|
|
1126
|
+
errorPrefix: "NAVAI error"
|
|
1127
|
+
};
|
|
1128
|
+
function resolveNavaiVoiceOrbRuntimeSnapshot(agent) {
|
|
1129
|
+
return {
|
|
1130
|
+
status: agent.status,
|
|
1131
|
+
agentVoiceState: agent.agentVoiceState,
|
|
1132
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
1133
|
+
error: agent.error
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
function resolveStatusMessage(runtimeSnapshot, messages) {
|
|
1137
|
+
if (runtimeSnapshot.error) {
|
|
1138
|
+
return `${messages.errorPrefix}: ${runtimeSnapshot.error}`;
|
|
1139
|
+
}
|
|
1140
|
+
if (runtimeSnapshot.isAgentSpeaking) {
|
|
1141
|
+
return messages.speaking;
|
|
1142
|
+
}
|
|
1143
|
+
if (runtimeSnapshot.status === "connecting") {
|
|
1144
|
+
return messages.connecting;
|
|
1145
|
+
}
|
|
1146
|
+
if (runtimeSnapshot.status === "connected") {
|
|
1147
|
+
return messages.listening;
|
|
1148
|
+
}
|
|
1149
|
+
return messages.idle;
|
|
1150
|
+
}
|
|
1151
|
+
function NavaiVoiceOrbDock({
|
|
1152
|
+
agent,
|
|
1153
|
+
className,
|
|
1154
|
+
style,
|
|
1155
|
+
themeMode = "dark",
|
|
1156
|
+
placement = "bottom-right",
|
|
1157
|
+
backgroundColorLight = "#f4f6fb",
|
|
1158
|
+
backgroundColorDark = "#060914",
|
|
1159
|
+
showStatus = true,
|
|
1160
|
+
messages
|
|
1161
|
+
}) {
|
|
1162
|
+
const resolvedMessages = useMemo3(() => ({ ...DEFAULT_MESSAGES, ...messages }), [messages]);
|
|
1163
|
+
const runtimeSnapshot = useMemo3(() => resolveNavaiVoiceOrbRuntimeSnapshot(agent), [agent]);
|
|
1164
|
+
const statusMessage = showStatus ? resolveStatusMessage(runtimeSnapshot, resolvedMessages) : "";
|
|
1165
|
+
const isError = runtimeSnapshot.status === "error" || Boolean(runtimeSnapshot.error);
|
|
1166
|
+
const isConnecting = runtimeSnapshot.status === "connecting";
|
|
1167
|
+
const isActive = runtimeSnapshot.status === "connecting" || runtimeSnapshot.status === "connected";
|
|
1168
|
+
const isDisabled = agent.isConnecting;
|
|
1169
|
+
const shouldAnimateOrb = runtimeSnapshot.status !== "error";
|
|
1170
|
+
const handleToggle = useCallback2(() => {
|
|
1171
|
+
if (agent.isConnecting) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (agent.isConnected) {
|
|
1175
|
+
agent.stop();
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
void agent.start();
|
|
1179
|
+
}, [agent]);
|
|
1180
|
+
return /* @__PURE__ */ jsx6(
|
|
1181
|
+
NavaiMiniOrbDock,
|
|
1182
|
+
{
|
|
1183
|
+
className,
|
|
1184
|
+
style,
|
|
1185
|
+
themeMode,
|
|
1186
|
+
placement,
|
|
1187
|
+
isActive,
|
|
1188
|
+
isConnected: agent.isConnected,
|
|
1189
|
+
isDisabled,
|
|
1190
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
1191
|
+
animateOrb: shouldAnimateOrb,
|
|
1192
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
1193
|
+
buttonAriaLabel: isConnecting ? resolvedMessages.connecting : agent.isConnected ? resolvedMessages.ariaStop : resolvedMessages.ariaStart,
|
|
1194
|
+
buttonIcon: isConnecting ? /* @__PURE__ */ jsx6(NavaiVoiceOrbDockSpinnerIcon, {}) : /* @__PURE__ */ jsx6(NavaiVoiceOrbDockMicIcon, { isActive: agent.isConnected || agent.isAgentSpeaking }),
|
|
1195
|
+
onButtonClick: handleToggle,
|
|
1196
|
+
statusMessage,
|
|
1197
|
+
isError,
|
|
1198
|
+
ariaMessage: statusMessage
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
1204
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1205
|
+
function NavaiVoiceHeroOrb({
|
|
1206
|
+
agent,
|
|
1207
|
+
themeMode = "dark",
|
|
1208
|
+
backgroundColorLight = "#ffffff",
|
|
1209
|
+
backgroundColorDark = "#000000",
|
|
1210
|
+
onRuntimeSnapshotChange,
|
|
1211
|
+
...orbProps
|
|
1212
|
+
}) {
|
|
1213
|
+
const runtimeSnapshot = resolveNavaiVoiceOrbRuntimeSnapshot(agent);
|
|
1214
|
+
useEffect4(() => {
|
|
1215
|
+
if (typeof onRuntimeSnapshotChange === "function") {
|
|
1216
|
+
onRuntimeSnapshotChange(runtimeSnapshot);
|
|
1217
|
+
}
|
|
1218
|
+
}, [onRuntimeSnapshotChange, runtimeSnapshot]);
|
|
1219
|
+
return /* @__PURE__ */ jsx7(
|
|
1220
|
+
NavaiHeroOrb,
|
|
1221
|
+
{
|
|
1222
|
+
...orbProps,
|
|
1223
|
+
isAgentSpeaking: runtimeSnapshot.isAgentSpeaking,
|
|
1224
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
1225
|
+
className: themeMode === "light" ? "is-light" : ""
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
822
1229
|
export {
|
|
1230
|
+
NavaiHeroOrb,
|
|
1231
|
+
NavaiMiniOrbDock,
|
|
1232
|
+
NavaiVoiceHeroOrb,
|
|
1233
|
+
NavaiVoiceOrbDock,
|
|
1234
|
+
NavaiVoiceOrbDockMicIcon,
|
|
1235
|
+
Orb,
|
|
823
1236
|
buildNavaiAgent,
|
|
1237
|
+
clampNavaiOrbDelayMs,
|
|
824
1238
|
createNavaiBackendClient,
|
|
825
1239
|
getNavaiRoutePromptLines,
|
|
826
1240
|
loadNavaiFunctions,
|
|
827
1241
|
resolveNavaiFrontendRuntimeConfig,
|
|
828
1242
|
resolveNavaiRoute,
|
|
1243
|
+
resolveNavaiVoiceOrbRuntimeSnapshot,
|
|
829
1244
|
useWebVoiceAgent
|
|
830
1245
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navai/voice-frontend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Frontend helpers to build OpenAI Realtime voice agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"bin",
|
|
23
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"README.es.md",
|
|
25
|
+
"README.en.md"
|
|
24
26
|
],
|
|
25
27
|
"scripts": {
|
|
26
28
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"@openai/agents": "^0.4.14",
|
|
35
|
+
"ogl": "^1.0.11",
|
|
33
36
|
"zod": "^4.0.0"
|
|
34
37
|
},
|
|
35
38
|
"peerDependencies": {
|