@navai/voice-frontend 0.1.2 → 0.1.5
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 +65 -24
- package/README.es.md +65 -24
- package/README.md +82 -28
- package/bin/generate-web-module-loaders.mjs +31 -3
- package/dist/Orb-B4OSC3XR.js +6 -0
- package/dist/chunk-KBBRQQLK.js +531 -0
- package/dist/index.cjs +1340 -68
- package/dist/index.d.cts +145 -22
- package/dist/index.d.ts +145 -22
- package/dist/index.js +791 -68
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
6
9
|
var __export = (target, all) => {
|
|
7
10
|
for (var name in all)
|
|
8
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,15 +20,561 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
20
|
};
|
|
18
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
22
|
|
|
23
|
+
// src/orb/styles.ts
|
|
24
|
+
function ensureNavaiVoiceOrbStyles() {
|
|
25
|
+
if (typeof document === "undefined") {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (document.getElementById(NAVAI_VOICE_ORB_STYLE_ID)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const style = document.createElement("style");
|
|
32
|
+
style.id = NAVAI_VOICE_ORB_STYLE_ID;
|
|
33
|
+
style.textContent = NAVAI_VOICE_ORB_CSS;
|
|
34
|
+
document.head.appendChild(style);
|
|
35
|
+
}
|
|
36
|
+
function useNavaiVoiceOrbStyles() {
|
|
37
|
+
(0, import_react2.useEffect)(() => {
|
|
38
|
+
ensureNavaiVoiceOrbStyles();
|
|
39
|
+
}, []);
|
|
40
|
+
}
|
|
41
|
+
var import_react2, NAVAI_VOICE_ORB_STYLE_ID, NAVAI_VOICE_ORB_CSS;
|
|
42
|
+
var init_styles = __esm({
|
|
43
|
+
"src/orb/styles.ts"() {
|
|
44
|
+
"use strict";
|
|
45
|
+
import_react2 = require("react");
|
|
46
|
+
NAVAI_VOICE_ORB_STYLE_ID = "navai-voice-orb-styles";
|
|
47
|
+
NAVAI_VOICE_ORB_CSS = `
|
|
48
|
+
.navai-orb-container { position: relative; z-index: 0; width: 100%; height: 100%; }
|
|
49
|
+
.navai-voice-orb-dock { display: grid; justify-items: center; gap: 0.6rem; }
|
|
50
|
+
.navai-voice-orb-dock.is-bottom-right,
|
|
51
|
+
.navai-voice-orb-dock.is-bottom-left { position: fixed; bottom: calc(1rem + env(safe-area-inset-bottom)); z-index: 70; }
|
|
52
|
+
.navai-voice-orb-dock.is-bottom-right { right: calc(1rem + env(safe-area-inset-right)); }
|
|
53
|
+
.navai-voice-orb-dock.is-bottom-left { left: calc(1rem + env(safe-area-inset-left)); }
|
|
54
|
+
.navai-voice-orb-wrap { position: relative; width: clamp(4.2rem, 8vw, 5.5rem); aspect-ratio: 1 / 1; display: grid; place-items: center; }
|
|
55
|
+
.navai-voice-orb-surface { position: absolute; inset: 0; border-radius: 999px; overflow: hidden; transition: transform 180ms ease, filter 180ms ease, opacity 180ms ease; }
|
|
56
|
+
.navai-voice-orb-surface::after { content: ""; position: absolute; inset: 10%; border-radius: inherit; background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.1), rgba(6,9,20,0)); pointer-events: none; }
|
|
57
|
+
.navai-voice-orb-surface.is-highlighted { transform: scale(1.03); filter: saturate(1.08); }
|
|
58
|
+
.navai-voice-orb-surface .navai-orb-container { border-radius: inherit; overflow: hidden; }
|
|
59
|
+
.navai-voice-orb-surface .navai-orb-container canvas { display: block; width: 100% !important; height: 100% !important; transform: scale(1.08); transform-origin: center; }
|
|
60
|
+
.navai-voice-orb-button-shell { position: relative; z-index: 1; display: grid; place-items: center; width: clamp(2.7rem, 5vw, 3.15rem); height: clamp(2.7rem, 5vw, 3.15rem); border-radius: 999px; border: 1px solid rgba(156,182,255,0.4); background: rgba(10,14,36,0.72); box-shadow: inset 0 0 24px rgba(8,12,31,0.28), 0 10px 22px rgba(4,8,24,0.42); backdrop-filter: blur(8px); }
|
|
61
|
+
.navai-voice-orb-button-shell.is-active { border-color: rgba(255,156,192,0.92); box-shadow: 0 0 0 2px rgba(255,112,160,0.18), inset 0 0 28px rgba(61,14,40,0.3), 0 12px 26px rgba(85,16,49,0.42); }
|
|
62
|
+
.navai-voice-orb-button { display: grid; place-items: center; width: clamp(2.05rem, 4vw, 2.4rem); height: clamp(2.05rem, 4vw, 2.4rem); border-radius: 999px; border: 1px solid rgba(162,193,255,0.58); background: linear-gradient(145deg, rgba(245,249,255,0.98), rgba(223,233,255,0.94)); color: #2154d9; box-shadow: 0 8px 18px rgba(14,26,61,0.34); cursor: pointer; transition: transform 160ms ease, box-shadow 160ms ease, opacity 160ms ease, border-color 160ms ease; }
|
|
63
|
+
.navai-voice-orb-button:hover:not(:disabled) { transform: translateY(-1px) scale(1.02); }
|
|
64
|
+
.navai-voice-orb-button:focus-visible { outline: 2px solid rgba(191,219,254,0.92); outline-offset: 3px; }
|
|
65
|
+
.navai-voice-orb-button.is-active { background: rgba(255,92,132,0.96); color: rgba(255,255,255,0.98); border-color: rgba(255,164,190,0.98); box-shadow: 0 0 0 3px rgba(255,108,154,0.24), 0 10px 24px rgba(96,14,39,0.42); }
|
|
66
|
+
.navai-voice-orb-button.is-connecting { background: linear-gradient(145deg, rgba(255,246,214,0.98), rgba(252,220,128,0.94)); color: #7c4300; border-color: rgba(251,191,36,0.9); }
|
|
67
|
+
.navai-voice-orb-button:disabled { opacity: 0.74; cursor: not-allowed; }
|
|
68
|
+
.navai-voice-orb-status { margin: 0; max-width: min(18rem, 80vw); padding: 0.45rem 0.6rem; border-radius: 0.7rem; border: 1px solid rgba(244,114,182,0.24); background: rgba(6,12,28,0.84); color: rgba(230,240,255,0.96); font-size: 0.74rem; line-height: 1.35; text-align: center; box-shadow: 0 10px 20px rgba(4,8,24,0.24); }
|
|
69
|
+
.navai-voice-orb-status.is-error { border-color: rgba(248,113,113,0.46); background: rgba(69,10,10,0.84); color: rgba(254,202,202,0.98); }
|
|
70
|
+
.navai-voice-orb-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
71
|
+
.navai-voice-orb-hero { width: min(32rem, 100%); aspect-ratio: 1 / 1; }
|
|
72
|
+
.navai-voice-orb-icon { display: block; }
|
|
73
|
+
.navai-voice-orb-icon.is-pulsing { animation: navai-voice-orb-pulse 1.05s ease-in-out infinite; }
|
|
74
|
+
.navai-voice-orb-spinner { display: block; box-sizing: border-box; border-radius: 999px; border: 2px solid currentColor; border-right-color: transparent; animation: navai-voice-orb-spin 0.72s linear infinite; }
|
|
75
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button-shell,
|
|
76
|
+
.navai-voice-orb-hero.is-light { color: #10245e; }
|
|
77
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button-shell { border-color: rgba(121,146,220,0.34); background: rgba(244,249,255,0.88); box-shadow: inset 0 0 20px rgba(111,134,183,0.14), 0 10px 22px rgba(86,104,149,0.22); }
|
|
78
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button-shell.is-active { border-color: rgba(255,142,188,0.82); box-shadow: 0 0 0 2px rgba(255,130,183,0.18), inset 0 0 24px rgba(255,141,191,0.2), 0 10px 24px rgba(141,81,110,0.24); }
|
|
79
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button { border-color: rgba(70,136,246,0.42); background: linear-gradient(145deg, rgba(255,255,255,0.98), rgba(228,238,255,0.95)); color: #1d3d9a; box-shadow: 0 8px 18px rgba(71,85,105,0.18); }
|
|
80
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button.is-connecting { background: linear-gradient(145deg, rgba(255,246,214,0.98), rgba(252,220,128,0.94)); color: #7c4300; border-color: rgba(251,191,36,0.9); }
|
|
81
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-button.is-active { background: rgba(255,92,132,0.96); color: rgba(255,255,255,0.98); border-color: rgba(255,164,190,0.98); box-shadow: 0 0 0 3px rgba(255,108,154,0.24), 0 10px 24px rgba(96,14,39,0.28); }
|
|
82
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-status { border-color: rgba(59,130,246,0.18); background: rgba(255,255,255,0.92); color: #1f2937; box-shadow: 0 10px 20px rgba(148,163,184,0.18); }
|
|
83
|
+
.navai-voice-orb-dock.is-light .navai-voice-orb-status.is-error { border-color: rgba(248,113,113,0.34); background: rgba(255,241,242,0.96); color: #9f1239; }
|
|
84
|
+
@keyframes navai-voice-orb-pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.08); opacity: 0.78; } 100% { transform: scale(1); opacity: 1; } }
|
|
85
|
+
@keyframes navai-voice-orb-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
86
|
+
@media (max-width: 640px) {
|
|
87
|
+
.navai-voice-orb-dock.is-bottom-right, .navai-voice-orb-dock.is-bottom-left { bottom: calc(0.8rem + env(safe-area-inset-bottom)); }
|
|
88
|
+
.navai-voice-orb-dock.is-bottom-right { right: calc(0.8rem + env(safe-area-inset-right)); }
|
|
89
|
+
.navai-voice-orb-dock.is-bottom-left { left: calc(0.8rem + env(safe-area-inset-left)); }
|
|
90
|
+
.navai-voice-orb-wrap { width: clamp(3.8rem, 22vw, 4.4rem); }
|
|
91
|
+
.navai-voice-orb-button-shell { width: clamp(2.45rem, 13vw, 2.82rem); height: clamp(2.45rem, 13vw, 2.82rem); }
|
|
92
|
+
.navai-voice-orb-button { width: clamp(1.85rem, 10vw, 2.18rem); height: clamp(1.85rem, 10vw, 2.18rem); }
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// src/orb/Orb.tsx
|
|
99
|
+
var Orb_exports = {};
|
|
100
|
+
__export(Orb_exports, {
|
|
101
|
+
default: () => Orb
|
|
102
|
+
});
|
|
103
|
+
function Orb({
|
|
104
|
+
hue = 0,
|
|
105
|
+
autoHueShift = true,
|
|
106
|
+
hueShiftMin = 0,
|
|
107
|
+
hueShiftMax = 360,
|
|
108
|
+
hueShiftHalfCycleSeconds = 30,
|
|
109
|
+
hoverIntensity = 0.2,
|
|
110
|
+
rotateOnHover = true,
|
|
111
|
+
forceHoverState = false,
|
|
112
|
+
enablePointerHover = true,
|
|
113
|
+
backgroundColor = "#000000",
|
|
114
|
+
animate = true
|
|
115
|
+
}) {
|
|
116
|
+
useNavaiVoiceOrbStyles();
|
|
117
|
+
const containerRef = (0, import_react3.useRef)(null);
|
|
118
|
+
const hueRef = (0, import_react3.useRef)(hue);
|
|
119
|
+
const autoHueShiftRef = (0, import_react3.useRef)(autoHueShift);
|
|
120
|
+
const hueShiftMinRef = (0, import_react3.useRef)(hueShiftMin);
|
|
121
|
+
const hueShiftMaxRef = (0, import_react3.useRef)(hueShiftMax);
|
|
122
|
+
const hueShiftHalfCycleSecondsRef = (0, import_react3.useRef)(hueShiftHalfCycleSeconds);
|
|
123
|
+
const hoverIntensityRef = (0, import_react3.useRef)(hoverIntensity);
|
|
124
|
+
const rotateOnHoverRef = (0, import_react3.useRef)(rotateOnHover);
|
|
125
|
+
const forceHoverStateRef = (0, import_react3.useRef)(forceHoverState);
|
|
126
|
+
const enablePointerHoverRef = (0, import_react3.useRef)(enablePointerHover);
|
|
127
|
+
const backgroundColorVecRef = (0, import_react3.useRef)(hexToVec3(backgroundColor));
|
|
128
|
+
const animateRef = (0, import_react3.useRef)(animate);
|
|
129
|
+
(0, import_react3.useEffect)(() => {
|
|
130
|
+
hueRef.current = hue;
|
|
131
|
+
}, [hue]);
|
|
132
|
+
(0, import_react3.useEffect)(() => {
|
|
133
|
+
autoHueShiftRef.current = autoHueShift;
|
|
134
|
+
}, [autoHueShift]);
|
|
135
|
+
(0, import_react3.useEffect)(() => {
|
|
136
|
+
hueShiftMinRef.current = hueShiftMin;
|
|
137
|
+
}, [hueShiftMin]);
|
|
138
|
+
(0, import_react3.useEffect)(() => {
|
|
139
|
+
hueShiftMaxRef.current = hueShiftMax;
|
|
140
|
+
}, [hueShiftMax]);
|
|
141
|
+
(0, import_react3.useEffect)(() => {
|
|
142
|
+
hueShiftHalfCycleSecondsRef.current = hueShiftHalfCycleSeconds;
|
|
143
|
+
}, [hueShiftHalfCycleSeconds]);
|
|
144
|
+
(0, import_react3.useEffect)(() => {
|
|
145
|
+
hoverIntensityRef.current = hoverIntensity;
|
|
146
|
+
}, [hoverIntensity]);
|
|
147
|
+
(0, import_react3.useEffect)(() => {
|
|
148
|
+
rotateOnHoverRef.current = rotateOnHover;
|
|
149
|
+
}, [rotateOnHover]);
|
|
150
|
+
(0, import_react3.useEffect)(() => {
|
|
151
|
+
forceHoverStateRef.current = forceHoverState;
|
|
152
|
+
}, [forceHoverState]);
|
|
153
|
+
(0, import_react3.useEffect)(() => {
|
|
154
|
+
enablePointerHoverRef.current = enablePointerHover;
|
|
155
|
+
}, [enablePointerHover]);
|
|
156
|
+
(0, import_react3.useEffect)(() => {
|
|
157
|
+
backgroundColorVecRef.current = hexToVec3(backgroundColor);
|
|
158
|
+
}, [backgroundColor]);
|
|
159
|
+
(0, import_react3.useEffect)(() => {
|
|
160
|
+
animateRef.current = animate;
|
|
161
|
+
}, [animate]);
|
|
162
|
+
(0, import_react3.useEffect)(() => {
|
|
163
|
+
const container = containerRef.current;
|
|
164
|
+
if (!container) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const renderer = new import_ogl.Renderer({ alpha: true, premultipliedAlpha: false });
|
|
168
|
+
const gl = renderer.gl;
|
|
169
|
+
gl.clearColor(0, 0, 0, 0);
|
|
170
|
+
container.appendChild(gl.canvas);
|
|
171
|
+
const geometry = new import_ogl.Triangle(gl);
|
|
172
|
+
const program = new import_ogl.Program(gl, {
|
|
173
|
+
vertex: VERTEX_SHADER,
|
|
174
|
+
fragment: FRAGMENT_SHADER,
|
|
175
|
+
uniforms: {
|
|
176
|
+
iTime: { value: 0 },
|
|
177
|
+
iResolution: {
|
|
178
|
+
value: new import_ogl.Vec3(gl.canvas.width, gl.canvas.height, gl.canvas.width / Math.max(gl.canvas.height, 1))
|
|
179
|
+
},
|
|
180
|
+
hue: { value: hueRef.current },
|
|
181
|
+
hover: { value: 0 },
|
|
182
|
+
rot: { value: 0 },
|
|
183
|
+
hoverIntensity: { value: hoverIntensityRef.current },
|
|
184
|
+
backgroundColor: { value: backgroundColorVecRef.current }
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const mesh = new import_ogl.Mesh(gl, { geometry, program });
|
|
188
|
+
const resize = () => {
|
|
189
|
+
const dpr = window.devicePixelRatio || 1;
|
|
190
|
+
const width = Math.max(container.clientWidth, 1);
|
|
191
|
+
const height = Math.max(container.clientHeight, 1);
|
|
192
|
+
renderer.setSize(width * dpr, height * dpr);
|
|
193
|
+
gl.canvas.style.width = `${width}px`;
|
|
194
|
+
gl.canvas.style.height = `${height}px`;
|
|
195
|
+
program.uniforms.iResolution.value.set(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height);
|
|
196
|
+
};
|
|
197
|
+
window.addEventListener("resize", resize);
|
|
198
|
+
resize();
|
|
199
|
+
let targetHover = 0;
|
|
200
|
+
let lastTime = 0;
|
|
201
|
+
let currentRotation = 0;
|
|
202
|
+
const rotationSpeed = 0.3;
|
|
203
|
+
const handlePointerMove = (event) => {
|
|
204
|
+
if (!enablePointerHoverRef.current) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const rect = container.getBoundingClientRect();
|
|
208
|
+
const x = event.clientX - rect.left;
|
|
209
|
+
const y = event.clientY - rect.top;
|
|
210
|
+
const size = Math.min(rect.width, rect.height);
|
|
211
|
+
const centerX = rect.width / 2;
|
|
212
|
+
const centerY = rect.height / 2;
|
|
213
|
+
const uvX = (x - centerX) / size * 2;
|
|
214
|
+
const uvY = (y - centerY) / size * 2;
|
|
215
|
+
targetHover = Math.sqrt(uvX * uvX + uvY * uvY) < 0.8 ? 1 : 0;
|
|
216
|
+
};
|
|
217
|
+
const handlePointerLeave = () => {
|
|
218
|
+
if (!enablePointerHoverRef.current) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
targetHover = 0;
|
|
222
|
+
};
|
|
223
|
+
container.addEventListener("mousemove", handlePointerMove);
|
|
224
|
+
container.addEventListener("mouseleave", handlePointerLeave);
|
|
225
|
+
let animationFrameId = 0;
|
|
226
|
+
let idleTimerId = 0;
|
|
227
|
+
let lastRenderTime = 0;
|
|
228
|
+
let hasRenderedStaticFrame = false;
|
|
229
|
+
const frameIntervalMs = 1e3 / 24;
|
|
230
|
+
const idleCheckIntervalMs = 750;
|
|
231
|
+
const getAnimatedHueValue = (timeMs = 0) => {
|
|
232
|
+
if (!autoHueShiftRef.current) {
|
|
233
|
+
return hueRef.current;
|
|
234
|
+
}
|
|
235
|
+
const minHue = hueShiftMinRef.current;
|
|
236
|
+
const maxHue = hueShiftMaxRef.current;
|
|
237
|
+
const halfCycleSeconds = hueShiftHalfCycleSecondsRef.current;
|
|
238
|
+
const hueRange = maxHue - minHue;
|
|
239
|
+
if (halfCycleSeconds <= 0 || hueRange <= 0) {
|
|
240
|
+
return minHue;
|
|
241
|
+
}
|
|
242
|
+
const fullCycleSeconds = halfCycleSeconds * 2;
|
|
243
|
+
const elapsedSeconds = timeMs * 1e-3;
|
|
244
|
+
const cycleSeconds = (elapsedSeconds % fullCycleSeconds + fullCycleSeconds) % fullCycleSeconds;
|
|
245
|
+
const halfCycleProgress = cycleSeconds / halfCycleSeconds;
|
|
246
|
+
const wave = halfCycleProgress <= 1 ? halfCycleProgress : 2 - halfCycleProgress;
|
|
247
|
+
return minHue + wave * hueRange;
|
|
248
|
+
};
|
|
249
|
+
const renderStaticFrame = (timeMs = 0) => {
|
|
250
|
+
program.uniforms.iTime.value = 0;
|
|
251
|
+
program.uniforms.hue.value = getAnimatedHueValue(timeMs);
|
|
252
|
+
program.uniforms.hoverIntensity.value = hoverIntensityRef.current;
|
|
253
|
+
program.uniforms.backgroundColor.value = backgroundColorVecRef.current;
|
|
254
|
+
program.uniforms.hover.value = 0;
|
|
255
|
+
program.uniforms.rot.value = currentRotation;
|
|
256
|
+
renderer.render({ scene: mesh });
|
|
257
|
+
};
|
|
258
|
+
const scheduleNextFrame = () => {
|
|
259
|
+
if (animateRef.current && !document.hidden) {
|
|
260
|
+
animationFrameId = window.requestAnimationFrame(update);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
idleTimerId = window.setTimeout(() => {
|
|
264
|
+
animationFrameId = window.requestAnimationFrame(update);
|
|
265
|
+
}, idleCheckIntervalMs);
|
|
266
|
+
};
|
|
267
|
+
const update = (timeMs) => {
|
|
268
|
+
if (document.hidden) {
|
|
269
|
+
scheduleNextFrame();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!animateRef.current) {
|
|
273
|
+
if (!hasRenderedStaticFrame) {
|
|
274
|
+
renderStaticFrame(timeMs);
|
|
275
|
+
hasRenderedStaticFrame = true;
|
|
276
|
+
}
|
|
277
|
+
scheduleNextFrame();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
hasRenderedStaticFrame = false;
|
|
281
|
+
if (timeMs - lastRenderTime < frameIntervalMs) {
|
|
282
|
+
scheduleNextFrame();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
lastRenderTime = timeMs;
|
|
286
|
+
const deltaSeconds = (timeMs - lastTime) * 1e-3;
|
|
287
|
+
lastTime = timeMs;
|
|
288
|
+
program.uniforms.iTime.value = timeMs * 1e-3;
|
|
289
|
+
program.uniforms.hue.value = getAnimatedHueValue(timeMs);
|
|
290
|
+
program.uniforms.hoverIntensity.value = hoverIntensityRef.current;
|
|
291
|
+
program.uniforms.backgroundColor.value = backgroundColorVecRef.current;
|
|
292
|
+
const effectiveHover = forceHoverStateRef.current ? 1 : enablePointerHoverRef.current ? targetHover : 0;
|
|
293
|
+
program.uniforms.hover.value += (effectiveHover - program.uniforms.hover.value) * 0.1;
|
|
294
|
+
if (rotateOnHoverRef.current && effectiveHover > 0.5) {
|
|
295
|
+
currentRotation += deltaSeconds * rotationSpeed;
|
|
296
|
+
}
|
|
297
|
+
program.uniforms.rot.value = currentRotation;
|
|
298
|
+
renderer.render({ scene: mesh });
|
|
299
|
+
scheduleNextFrame();
|
|
300
|
+
};
|
|
301
|
+
scheduleNextFrame();
|
|
302
|
+
return () => {
|
|
303
|
+
window.cancelAnimationFrame(animationFrameId);
|
|
304
|
+
window.clearTimeout(idleTimerId);
|
|
305
|
+
window.removeEventListener("resize", resize);
|
|
306
|
+
container.removeEventListener("mousemove", handlePointerMove);
|
|
307
|
+
container.removeEventListener("mouseleave", handlePointerLeave);
|
|
308
|
+
if (gl.canvas.parentElement === container) {
|
|
309
|
+
container.removeChild(gl.canvas);
|
|
310
|
+
}
|
|
311
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
312
|
+
};
|
|
313
|
+
}, []);
|
|
314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className: "navai-orb-container" });
|
|
315
|
+
}
|
|
316
|
+
function hslToRgb(hue, saturation, lightness) {
|
|
317
|
+
if (saturation === 0) {
|
|
318
|
+
return new import_ogl.Vec3(lightness, lightness, lightness);
|
|
319
|
+
}
|
|
320
|
+
const hueToRgb = (p2, q2, t) => {
|
|
321
|
+
let normalizedT = t;
|
|
322
|
+
if (normalizedT < 0) {
|
|
323
|
+
normalizedT += 1;
|
|
324
|
+
}
|
|
325
|
+
if (normalizedT > 1) {
|
|
326
|
+
normalizedT -= 1;
|
|
327
|
+
}
|
|
328
|
+
if (normalizedT < 1 / 6) {
|
|
329
|
+
return p2 + (q2 - p2) * 6 * normalizedT;
|
|
330
|
+
}
|
|
331
|
+
if (normalizedT < 1 / 2) {
|
|
332
|
+
return q2;
|
|
333
|
+
}
|
|
334
|
+
if (normalizedT < 2 / 3) {
|
|
335
|
+
return p2 + (q2 - p2) * (2 / 3 - normalizedT) * 6;
|
|
336
|
+
}
|
|
337
|
+
return p2;
|
|
338
|
+
};
|
|
339
|
+
const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
|
|
340
|
+
const p = 2 * lightness - q;
|
|
341
|
+
return new import_ogl.Vec3(hueToRgb(p, q, hue + 1 / 3), hueToRgb(p, q, hue), hueToRgb(p, q, hue - 1 / 3));
|
|
342
|
+
}
|
|
343
|
+
function hexToVec3(color) {
|
|
344
|
+
if (color.startsWith("#")) {
|
|
345
|
+
const hex = color.slice(1);
|
|
346
|
+
if (hex.length === 3) {
|
|
347
|
+
return new import_ogl.Vec3(
|
|
348
|
+
Number.parseInt(`${hex[0]}${hex[0]}`, 16) / 255,
|
|
349
|
+
Number.parseInt(`${hex[1]}${hex[1]}`, 16) / 255,
|
|
350
|
+
Number.parseInt(`${hex[2]}${hex[2]}`, 16) / 255
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (hex.length >= 6) {
|
|
354
|
+
return new import_ogl.Vec3(
|
|
355
|
+
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
356
|
+
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
357
|
+
Number.parseInt(hex.slice(4, 6), 16) / 255
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
362
|
+
if (rgbMatch) {
|
|
363
|
+
const [, red, green, blue] = rgbMatch;
|
|
364
|
+
return new import_ogl.Vec3(
|
|
365
|
+
Number.parseInt(red ?? "0", 10) / 255,
|
|
366
|
+
Number.parseInt(green ?? "0", 10) / 255,
|
|
367
|
+
Number.parseInt(blue ?? "0", 10) / 255
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const hslMatch = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/);
|
|
371
|
+
if (hslMatch) {
|
|
372
|
+
const [, hue, saturation, lightness] = hslMatch;
|
|
373
|
+
return hslToRgb(
|
|
374
|
+
Number.parseInt(hue ?? "0", 10) / 360,
|
|
375
|
+
Number.parseInt(saturation ?? "0", 10) / 100,
|
|
376
|
+
Number.parseInt(lightness ?? "0", 10) / 100
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return new import_ogl.Vec3(0, 0, 0);
|
|
380
|
+
}
|
|
381
|
+
var import_ogl, import_react3, import_jsx_runtime, VERTEX_SHADER, FRAGMENT_SHADER;
|
|
382
|
+
var init_Orb = __esm({
|
|
383
|
+
"src/orb/Orb.tsx"() {
|
|
384
|
+
"use strict";
|
|
385
|
+
import_ogl = require("ogl");
|
|
386
|
+
import_react3 = require("react");
|
|
387
|
+
init_styles();
|
|
388
|
+
import_jsx_runtime = require("react/jsx-runtime");
|
|
389
|
+
VERTEX_SHADER = /* glsl */
|
|
390
|
+
`
|
|
391
|
+
precision highp float;
|
|
392
|
+
attribute vec2 position;
|
|
393
|
+
attribute vec2 uv;
|
|
394
|
+
varying vec2 vUv;
|
|
395
|
+
|
|
396
|
+
void main() {
|
|
397
|
+
vUv = uv;
|
|
398
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
401
|
+
FRAGMENT_SHADER = /* glsl */
|
|
402
|
+
`
|
|
403
|
+
precision highp float;
|
|
404
|
+
|
|
405
|
+
uniform float iTime;
|
|
406
|
+
uniform vec3 iResolution;
|
|
407
|
+
uniform float hue;
|
|
408
|
+
uniform float hover;
|
|
409
|
+
uniform float rot;
|
|
410
|
+
uniform float hoverIntensity;
|
|
411
|
+
uniform vec3 backgroundColor;
|
|
412
|
+
varying vec2 vUv;
|
|
413
|
+
|
|
414
|
+
vec3 rgb2yiq(vec3 c) {
|
|
415
|
+
float y = dot(c, vec3(0.299, 0.587, 0.114));
|
|
416
|
+
float i = dot(c, vec3(0.596, -0.274, -0.322));
|
|
417
|
+
float q = dot(c, vec3(0.211, -0.523, 0.312));
|
|
418
|
+
return vec3(y, i, q);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
vec3 yiq2rgb(vec3 c) {
|
|
422
|
+
float r = c.x + 0.956 * c.y + 0.621 * c.z;
|
|
423
|
+
float g = c.x - 0.272 * c.y - 0.647 * c.z;
|
|
424
|
+
float b = c.x - 1.106 * c.y + 1.703 * c.z;
|
|
425
|
+
return vec3(r, g, b);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
vec3 adjustHue(vec3 color, float hueDeg) {
|
|
429
|
+
float hueRad = hueDeg * 3.14159265 / 180.0;
|
|
430
|
+
vec3 yiq = rgb2yiq(color);
|
|
431
|
+
float cosA = cos(hueRad);
|
|
432
|
+
float sinA = sin(hueRad);
|
|
433
|
+
float i = yiq.y * cosA - yiq.z * sinA;
|
|
434
|
+
float q = yiq.y * sinA + yiq.z * cosA;
|
|
435
|
+
yiq.y = i;
|
|
436
|
+
yiq.z = q;
|
|
437
|
+
return yiq2rgb(yiq);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
vec3 hash33(vec3 p3) {
|
|
441
|
+
p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787));
|
|
442
|
+
p3 += dot(p3, p3.yxz + 19.19);
|
|
443
|
+
return -1.0 + 2.0 * fract(vec3(
|
|
444
|
+
p3.x + p3.y,
|
|
445
|
+
p3.x + p3.z,
|
|
446
|
+
p3.y + p3.z
|
|
447
|
+
) * p3.zyx);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
float snoise3(vec3 p) {
|
|
451
|
+
const float K1 = 0.333333333;
|
|
452
|
+
const float K2 = 0.166666667;
|
|
453
|
+
vec3 i = floor(p + (p.x + p.y + p.z) * K1);
|
|
454
|
+
vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
|
|
455
|
+
vec3 e = step(vec3(0.0), d0 - d0.yzx);
|
|
456
|
+
vec3 i1 = e * (1.0 - e.zxy);
|
|
457
|
+
vec3 i2 = 1.0 - e.zxy * (1.0 - e);
|
|
458
|
+
vec3 d1 = d0 - (i1 - K2);
|
|
459
|
+
vec3 d2 = d0 - (i2 - K1);
|
|
460
|
+
vec3 d3 = d0 - 0.5;
|
|
461
|
+
vec4 h = max(0.6 - vec4(
|
|
462
|
+
dot(d0, d0),
|
|
463
|
+
dot(d1, d1),
|
|
464
|
+
dot(d2, d2),
|
|
465
|
+
dot(d3, d3)
|
|
466
|
+
), 0.0);
|
|
467
|
+
vec4 n = h * h * h * h * vec4(
|
|
468
|
+
dot(d0, hash33(i)),
|
|
469
|
+
dot(d1, hash33(i + i1)),
|
|
470
|
+
dot(d2, hash33(i + i2)),
|
|
471
|
+
dot(d3, hash33(i + 1.0))
|
|
472
|
+
);
|
|
473
|
+
return dot(vec4(31.316), n);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
vec4 extractAlpha(vec3 colorIn) {
|
|
477
|
+
float a = max(max(colorIn.r, colorIn.g), colorIn.b);
|
|
478
|
+
return vec4(colorIn.rgb / (a + 1e-5), a);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const vec3 baseColor1 = vec3(0.611765, 0.262745, 0.996078);
|
|
482
|
+
const vec3 baseColor2 = vec3(0.298039, 0.760784, 0.913725);
|
|
483
|
+
const vec3 baseColor3 = vec3(0.062745, 0.078431, 0.600000);
|
|
484
|
+
const float innerRadius = 0.6;
|
|
485
|
+
const float noiseScale = 0.65;
|
|
486
|
+
|
|
487
|
+
float light1(float intensity, float attenuation, float dist) {
|
|
488
|
+
return intensity / (1.0 + dist * attenuation);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
float light2(float intensity, float attenuation, float dist) {
|
|
492
|
+
return intensity / (1.0 + dist * dist * attenuation);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
vec4 draw(vec2 uv) {
|
|
496
|
+
vec3 color1 = adjustHue(baseColor1, hue);
|
|
497
|
+
vec3 color2 = adjustHue(baseColor2, hue);
|
|
498
|
+
vec3 color3 = adjustHue(baseColor3, hue);
|
|
499
|
+
|
|
500
|
+
float ang = atan(uv.y, uv.x);
|
|
501
|
+
float len = length(uv);
|
|
502
|
+
float invLen = len > 0.0 ? 1.0 / len : 0.0;
|
|
503
|
+
float bgLuminance = dot(backgroundColor, vec3(0.299, 0.587, 0.114));
|
|
504
|
+
|
|
505
|
+
float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5;
|
|
506
|
+
float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0);
|
|
507
|
+
float d0 = distance(uv, (r0 * invLen) * uv);
|
|
508
|
+
float v0 = light1(1.0, 10.0, d0);
|
|
509
|
+
|
|
510
|
+
v0 *= smoothstep(r0 * 1.05, r0, len);
|
|
511
|
+
float innerFade = smoothstep(r0 * 0.8, r0 * 0.95, len);
|
|
512
|
+
v0 *= mix(innerFade, 1.0, bgLuminance * 0.7);
|
|
513
|
+
float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5;
|
|
514
|
+
|
|
515
|
+
float a = iTime * -1.0;
|
|
516
|
+
vec2 pos = vec2(cos(a), sin(a)) * r0;
|
|
517
|
+
float d = distance(uv, pos);
|
|
518
|
+
float v1 = light2(1.5, 5.0, d);
|
|
519
|
+
v1 *= light1(1.0, 50.0, d0);
|
|
520
|
+
|
|
521
|
+
float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len);
|
|
522
|
+
float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len);
|
|
523
|
+
|
|
524
|
+
vec3 colBase = mix(color1, color2, cl);
|
|
525
|
+
float fadeAmount = mix(1.0, 0.1, bgLuminance);
|
|
526
|
+
vec3 darkCol = mix(color3, colBase, v0);
|
|
527
|
+
darkCol = (darkCol + v1) * v2 * v3;
|
|
528
|
+
darkCol = clamp(darkCol, 0.0, 1.0);
|
|
529
|
+
|
|
530
|
+
vec3 lightCol = (colBase + v1) * mix(1.0, v2 * v3, fadeAmount);
|
|
531
|
+
lightCol = mix(backgroundColor, lightCol, v0);
|
|
532
|
+
lightCol = clamp(lightCol, 0.0, 1.0);
|
|
533
|
+
|
|
534
|
+
return extractAlpha(mix(darkCol, lightCol, bgLuminance));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
vec4 mainImage(vec2 fragCoord) {
|
|
538
|
+
vec2 center = iResolution.xy * 0.5;
|
|
539
|
+
float size = min(iResolution.x, iResolution.y);
|
|
540
|
+
vec2 uv = (fragCoord - center) / size * 2.0;
|
|
541
|
+
|
|
542
|
+
float angle = rot;
|
|
543
|
+
float s = sin(angle);
|
|
544
|
+
float c = cos(angle);
|
|
545
|
+
uv = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y);
|
|
546
|
+
uv.x += hover * hoverIntensity * 0.1 * sin(uv.y * 10.0 + iTime);
|
|
547
|
+
uv.y += hover * hoverIntensity * 0.1 * sin(uv.x * 10.0 + iTime);
|
|
548
|
+
|
|
549
|
+
return draw(uv);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
void main() {
|
|
553
|
+
vec2 fragCoord = vUv * iResolution.xy;
|
|
554
|
+
vec4 col = mainImage(fragCoord);
|
|
555
|
+
gl_FragColor = vec4(col.rgb * col.a, col.a);
|
|
556
|
+
}
|
|
557
|
+
`;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
20
561
|
// src/index.ts
|
|
21
562
|
var index_exports = {};
|
|
22
563
|
__export(index_exports, {
|
|
564
|
+
NavaiHeroOrb: () => NavaiHeroOrb,
|
|
565
|
+
NavaiMiniOrbDock: () => NavaiMiniOrbDock,
|
|
566
|
+
NavaiVoiceHeroOrb: () => NavaiVoiceHeroOrb,
|
|
567
|
+
NavaiVoiceOrbDock: () => NavaiVoiceOrbDock,
|
|
568
|
+
NavaiVoiceOrbDockMicIcon: () => NavaiVoiceOrbDockMicIcon,
|
|
569
|
+
Orb: () => Orb,
|
|
23
570
|
buildNavaiAgent: () => buildNavaiAgent,
|
|
571
|
+
clampNavaiOrbDelayMs: () => clampNavaiOrbDelayMs,
|
|
24
572
|
createNavaiBackendClient: () => createNavaiBackendClient,
|
|
25
573
|
getNavaiRoutePromptLines: () => getNavaiRoutePromptLines,
|
|
26
574
|
loadNavaiFunctions: () => loadNavaiFunctions,
|
|
27
575
|
resolveNavaiFrontendRuntimeConfig: () => resolveNavaiFrontendRuntimeConfig,
|
|
28
576
|
resolveNavaiRoute: () => resolveNavaiRoute,
|
|
577
|
+
resolveNavaiVoiceOrbRuntimeSnapshot: () => resolveNavaiVoiceOrbRuntimeSnapshot,
|
|
29
578
|
useWebVoiceAgent: () => useWebVoiceAgent
|
|
30
579
|
});
|
|
31
580
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -274,27 +823,33 @@ function getNavaiRoutePromptLines(routes = []) {
|
|
|
274
823
|
// src/agent.ts
|
|
275
824
|
var RESERVED_TOOL_NAMES = /* @__PURE__ */ new Set(["navigate_to", "execute_app_function"]);
|
|
276
825
|
var TOOL_NAME_REGEXP = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
826
|
+
var DEBUG_PREFIX = "[navai debug]";
|
|
277
827
|
function toErrorMessage2(error) {
|
|
278
828
|
return error instanceof Error ? error.message : String(error);
|
|
279
829
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
830
|
+
function debugLog(message, details) {
|
|
831
|
+
if (details === void 0) {
|
|
832
|
+
console.log(`${DEBUG_PREFIX} ${message}`);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
console.log(`${DEBUG_PREFIX} ${message}`, details);
|
|
836
|
+
}
|
|
837
|
+
function normalizeBackendFunctions(backendFunctions, functionsRegistry, warnings) {
|
|
283
838
|
const backendFunctionsByName = /* @__PURE__ */ new Map();
|
|
284
839
|
const backendFunctionsOrdered = [];
|
|
285
|
-
for (const backendFunction of
|
|
840
|
+
for (const backendFunction of backendFunctions ?? []) {
|
|
286
841
|
const name = backendFunction.name.trim().toLowerCase();
|
|
287
842
|
if (!name) {
|
|
288
843
|
continue;
|
|
289
844
|
}
|
|
290
845
|
if (functionsRegistry.byName.has(name)) {
|
|
291
|
-
|
|
846
|
+
warnings.push(
|
|
292
847
|
`[navai] Ignored backend function "${backendFunction.name}": name conflicts with a frontend function.`
|
|
293
848
|
);
|
|
294
849
|
continue;
|
|
295
850
|
}
|
|
296
851
|
if (backendFunctionsByName.has(name)) {
|
|
297
|
-
|
|
852
|
+
warnings.push(`[navai] Ignored duplicated backend function "${backendFunction.name}".`);
|
|
298
853
|
continue;
|
|
299
854
|
}
|
|
300
855
|
const normalizedDefinition = {
|
|
@@ -304,94 +859,119 @@ async function buildNavaiAgent(options) {
|
|
|
304
859
|
backendFunctionsByName.set(name, normalizedDefinition);
|
|
305
860
|
backendFunctionsOrdered.push(normalizedDefinition);
|
|
306
861
|
}
|
|
862
|
+
return backendFunctionsOrdered;
|
|
863
|
+
}
|
|
864
|
+
function createExecuteAppFunction(input) {
|
|
865
|
+
const backendFunctionsByName = new Map(input.backendFunctions.map((item) => [item.name, item]));
|
|
307
866
|
const availableFunctionNames = [
|
|
308
|
-
...functionsRegistry.ordered.map((item) => item.name),
|
|
309
|
-
...
|
|
867
|
+
...input.functionsRegistry.ordered.map((item) => item.name),
|
|
868
|
+
...input.backendFunctions.map((item) => item.name)
|
|
310
869
|
];
|
|
311
|
-
const aliasWarnings = [];
|
|
312
|
-
const directFunctionToolNames = [...new Set(availableFunctionNames)].map((name) => name.trim().toLowerCase()).filter((name) => {
|
|
313
|
-
if (!name) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
if (RESERVED_TOOL_NAMES.has(name)) {
|
|
317
|
-
aliasWarnings.push(
|
|
318
|
-
`[navai] Function "${name}" is available only via execute_app_function because its name conflicts with a built-in tool.`
|
|
319
|
-
);
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
|
-
if (!TOOL_NAME_REGEXP.test(name)) {
|
|
323
|
-
aliasWarnings.push(
|
|
324
|
-
`[navai] Function "${name}" is available only via execute_app_function because its name is not a valid tool id.`
|
|
325
|
-
);
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
return true;
|
|
329
|
-
});
|
|
330
870
|
const executeAppFunction = async (requestedName, payload) => {
|
|
331
871
|
const requested = requestedName.trim().toLowerCase();
|
|
332
|
-
|
|
872
|
+
debugLog("execute_app_function called", { requestedName, requested, payload });
|
|
873
|
+
const frontendDefinition = input.functionsRegistry.byName.get(requested);
|
|
333
874
|
if (frontendDefinition) {
|
|
334
875
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
876
|
+
debugLog("executing frontend function", {
|
|
877
|
+
functionName: frontendDefinition.name,
|
|
878
|
+
source: frontendDefinition.source
|
|
879
|
+
});
|
|
880
|
+
const result = await frontendDefinition.run(payload ?? {}, input.context);
|
|
881
|
+
const response = {
|
|
882
|
+
ok: true,
|
|
883
|
+
function_name: frontendDefinition.name,
|
|
884
|
+
source: frontendDefinition.source,
|
|
885
|
+
result
|
|
886
|
+
};
|
|
887
|
+
debugLog("frontend function completed", response);
|
|
888
|
+
return response;
|
|
337
889
|
} catch (error) {
|
|
338
|
-
|
|
890
|
+
const failure = {
|
|
339
891
|
ok: false,
|
|
340
892
|
function_name: frontendDefinition.name,
|
|
341
893
|
error: "Function execution failed.",
|
|
342
894
|
details: toErrorMessage2(error)
|
|
343
895
|
};
|
|
896
|
+
debugLog("frontend function failed", failure);
|
|
897
|
+
return failure;
|
|
344
898
|
}
|
|
345
899
|
}
|
|
346
900
|
const backendDefinition = backendFunctionsByName.get(requested);
|
|
347
901
|
if (!backendDefinition) {
|
|
348
|
-
|
|
902
|
+
const failure = {
|
|
349
903
|
ok: false,
|
|
350
904
|
error: "Unknown or disallowed function.",
|
|
351
905
|
available_functions: availableFunctionNames
|
|
352
906
|
};
|
|
907
|
+
debugLog("execute_app_function rejected unknown function", failure);
|
|
908
|
+
return failure;
|
|
353
909
|
}
|
|
354
|
-
if (!
|
|
355
|
-
|
|
910
|
+
if (!input.executeBackendFunction) {
|
|
911
|
+
const failure = {
|
|
356
912
|
ok: false,
|
|
357
913
|
function_name: backendDefinition.name,
|
|
358
914
|
error: "Backend function execution is not configured."
|
|
359
915
|
};
|
|
916
|
+
debugLog("backend function execution unavailable", failure);
|
|
917
|
+
return failure;
|
|
360
918
|
}
|
|
361
919
|
try {
|
|
362
|
-
|
|
920
|
+
debugLog("executing backend function", {
|
|
921
|
+
functionName: backendDefinition.name,
|
|
922
|
+
source: backendDefinition.source ?? "backend"
|
|
923
|
+
});
|
|
924
|
+
const result = await input.executeBackendFunction({
|
|
363
925
|
functionName: backendDefinition.name,
|
|
364
926
|
payload: payload ?? null
|
|
365
927
|
});
|
|
366
|
-
|
|
928
|
+
const response = {
|
|
367
929
|
ok: true,
|
|
368
930
|
function_name: backendDefinition.name,
|
|
369
931
|
source: backendDefinition.source ?? "backend",
|
|
370
932
|
result
|
|
371
933
|
};
|
|
934
|
+
debugLog("backend function completed", response);
|
|
935
|
+
return response;
|
|
372
936
|
} catch (error) {
|
|
373
|
-
|
|
937
|
+
const failure = {
|
|
374
938
|
ok: false,
|
|
375
939
|
function_name: backendDefinition.name,
|
|
376
940
|
error: "Function execution failed.",
|
|
377
941
|
details: toErrorMessage2(error)
|
|
378
942
|
};
|
|
943
|
+
debugLog("backend function failed", failure);
|
|
944
|
+
return failure;
|
|
379
945
|
}
|
|
380
946
|
};
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
947
|
+
return {
|
|
948
|
+
availableFunctionNames,
|
|
949
|
+
executeAppFunction
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function createFunctionTools(input) {
|
|
953
|
+
const aliasWarnings = [];
|
|
954
|
+
const availableFunctionNames = [
|
|
955
|
+
...input.functionsRegistry.ordered.map((item) => item.name),
|
|
956
|
+
...input.backendFunctions.map((item) => item.name)
|
|
957
|
+
];
|
|
958
|
+
const directFunctionToolNames = input.includeDirectAliases === false ? [] : [...new Set(availableFunctionNames)].map((name) => name.trim().toLowerCase()).filter((name) => {
|
|
959
|
+
if (!name) {
|
|
960
|
+
return false;
|
|
394
961
|
}
|
|
962
|
+
if (RESERVED_TOOL_NAMES.has(name)) {
|
|
963
|
+
aliasWarnings.push(
|
|
964
|
+
`[navai] Function "${name}" is available only via execute_app_function because its name conflicts with a built-in tool.`
|
|
965
|
+
);
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
if (!TOOL_NAME_REGEXP.test(name)) {
|
|
969
|
+
aliasWarnings.push(
|
|
970
|
+
`[navai] Function "${name}" is available only via execute_app_function because its name is not a valid tool id.`
|
|
971
|
+
);
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
return true;
|
|
395
975
|
});
|
|
396
976
|
const executeFunctionTool = (0, import_realtime.tool)({
|
|
397
977
|
name: "execute_app_function",
|
|
@@ -402,36 +982,146 @@ async function buildNavaiAgent(options) {
|
|
|
402
982
|
"Payload object. Use null when no arguments are needed. Use payload.args as array for function args, payload.constructorArgs for class constructors, payload.methodArgs for class methods."
|
|
403
983
|
)
|
|
404
984
|
}),
|
|
405
|
-
execute: async ({ function_name, payload }) => await executeAppFunction(function_name, payload)
|
|
985
|
+
execute: async ({ function_name, payload }) => await input.executeAppFunction(function_name, payload)
|
|
406
986
|
});
|
|
407
987
|
const directFunctionTools = directFunctionToolNames.map(
|
|
408
988
|
(functionName) => (0, import_realtime.tool)({
|
|
409
989
|
name: functionName,
|
|
410
990
|
description: `Direct alias for execute_app_function("${functionName}").`,
|
|
411
991
|
parameters: import_zod.z.object({
|
|
412
|
-
payload: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).nullable().
|
|
992
|
+
payload: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).nullable().describe(
|
|
413
993
|
"Payload object. Optional. Use payload.args as array for function args, payload.constructorArgs for class constructors, payload.methodArgs for class methods."
|
|
414
994
|
)
|
|
415
995
|
}),
|
|
416
|
-
execute: async ({ payload }) => await executeAppFunction(functionName, payload ?? null)
|
|
996
|
+
execute: async ({ payload }) => await input.executeAppFunction(functionName, payload ?? null)
|
|
417
997
|
})
|
|
418
998
|
);
|
|
419
|
-
|
|
420
|
-
|
|
999
|
+
return {
|
|
1000
|
+
aliasWarnings,
|
|
1001
|
+
availableFunctionNames,
|
|
1002
|
+
executeFunctionTool,
|
|
1003
|
+
directFunctionTools
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function buildFunctionLines(functionsRegistry, backendFunctions) {
|
|
1007
|
+
return functionsRegistry.ordered.length + backendFunctions.length > 0 ? [
|
|
421
1008
|
...functionsRegistry.ordered.map((item) => `- ${item.name}: ${item.description}`),
|
|
422
|
-
...
|
|
423
|
-
(item) => `- ${item.name}: ${item.description ?? "Execute backend function."}`
|
|
424
|
-
)
|
|
1009
|
+
...backendFunctions.map((item) => `- ${item.name}: ${item.description ?? "Execute backend function."}`)
|
|
425
1010
|
] : ["- none"];
|
|
1011
|
+
}
|
|
1012
|
+
async function buildNavaiAgent(options) {
|
|
1013
|
+
const aggregatedWarnings = [];
|
|
1014
|
+
const configuredAgents = (options.agents ?? []).filter(
|
|
1015
|
+
(agent2) => Object.keys(agent2.functionModuleLoaders ?? {}).length > 0
|
|
1016
|
+
);
|
|
1017
|
+
const primaryAgentConfig = configuredAgents.find((agent2) => agent2.key === options.primaryAgentKey) ?? configuredAgents.find((agent2) => agent2.isPrimary) ?? configuredAgents[0];
|
|
1018
|
+
const primaryFunctionLoaders = primaryAgentConfig?.functionModuleLoaders ?? options.functionModuleLoaders ?? {};
|
|
1019
|
+
const functionsRegistry = await loadNavaiFunctions(primaryFunctionLoaders);
|
|
1020
|
+
const backendFunctionsOrdered = normalizeBackendFunctions(options.backendFunctions, functionsRegistry, aggregatedWarnings);
|
|
1021
|
+
const primaryExecutionSurface = createExecuteAppFunction({
|
|
1022
|
+
functionsRegistry,
|
|
1023
|
+
backendFunctions: backendFunctionsOrdered,
|
|
1024
|
+
executeBackendFunction: options.executeBackendFunction,
|
|
1025
|
+
context: options
|
|
1026
|
+
});
|
|
1027
|
+
const primaryFunctionTools = createFunctionTools({
|
|
1028
|
+
functionsRegistry,
|
|
1029
|
+
backendFunctions: backendFunctionsOrdered,
|
|
1030
|
+
executeAppFunction: primaryExecutionSurface.executeAppFunction
|
|
1031
|
+
});
|
|
1032
|
+
aggregatedWarnings.push(...functionsRegistry.warnings, ...primaryFunctionTools.aliasWarnings);
|
|
1033
|
+
const navigateTool = (0, import_realtime.tool)({
|
|
1034
|
+
name: "navigate_to",
|
|
1035
|
+
description: "Navigate to an allowed route in the current app.",
|
|
1036
|
+
parameters: import_zod.z.object({
|
|
1037
|
+
target: import_zod.z.string().min(1).describe("Route name or route path. Example: perfil, ajustes, /profile, /settings")
|
|
1038
|
+
}),
|
|
1039
|
+
execute: async ({ target }) => {
|
|
1040
|
+
debugLog("navigate_to called", { target });
|
|
1041
|
+
const path = resolveNavaiRoute(target, options.routes);
|
|
1042
|
+
if (!path) {
|
|
1043
|
+
const failure = { ok: false, error: "Unknown or disallowed route." };
|
|
1044
|
+
debugLog("navigate_to rejected", failure);
|
|
1045
|
+
return failure;
|
|
1046
|
+
}
|
|
1047
|
+
options.navigate(path);
|
|
1048
|
+
const response = { ok: true, path };
|
|
1049
|
+
debugLog("navigate_to completed", response);
|
|
1050
|
+
return response;
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
const routeLines = getNavaiRoutePromptLines(options.routes);
|
|
1054
|
+
const functionLines = buildFunctionLines(functionsRegistry, backendFunctionsOrdered);
|
|
1055
|
+
const specialistAgents = [];
|
|
1056
|
+
const specialistLines = [];
|
|
1057
|
+
for (const runtimeAgent of configuredAgents) {
|
|
1058
|
+
if (primaryAgentConfig && runtimeAgent.key === primaryAgentConfig.key) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const specialistRegistry = await loadNavaiFunctions(runtimeAgent.functionModuleLoaders);
|
|
1062
|
+
const specialistWarnings = [...specialistRegistry.warnings];
|
|
1063
|
+
const specialistBackendFunctions = normalizeBackendFunctions(
|
|
1064
|
+
options.backendFunctions,
|
|
1065
|
+
specialistRegistry,
|
|
1066
|
+
specialistWarnings
|
|
1067
|
+
);
|
|
1068
|
+
const specialistExecutionSurface = createExecuteAppFunction({
|
|
1069
|
+
functionsRegistry: specialistRegistry,
|
|
1070
|
+
backendFunctions: specialistBackendFunctions,
|
|
1071
|
+
executeBackendFunction: options.executeBackendFunction,
|
|
1072
|
+
context: options
|
|
1073
|
+
});
|
|
1074
|
+
const specialistFunctionTools = createFunctionTools({
|
|
1075
|
+
functionsRegistry: specialistRegistry,
|
|
1076
|
+
backendFunctions: specialistBackendFunctions,
|
|
1077
|
+
executeAppFunction: specialistExecutionSurface.executeAppFunction,
|
|
1078
|
+
includeDirectAliases: false
|
|
1079
|
+
});
|
|
1080
|
+
specialistWarnings.push(...specialistFunctionTools.aliasWarnings);
|
|
1081
|
+
aggregatedWarnings.push(...specialistWarnings);
|
|
1082
|
+
const specialistInstructions = [
|
|
1083
|
+
runtimeAgent.instructions ?? `You are the ${runtimeAgent.name} specialist agent for this web app.`,
|
|
1084
|
+
"Allowed app functions:",
|
|
1085
|
+
...buildFunctionLines(specialistRegistry, specialistBackendFunctions),
|
|
1086
|
+
"Rules:",
|
|
1087
|
+
"- Always use execute_app_function for app actions.",
|
|
1088
|
+
"- When no arguments are needed, call execute_app_function with payload set to null.",
|
|
1089
|
+
"- Use only the functions available to this specialist agent.",
|
|
1090
|
+
"- Do not navigate unless one of your allowed functions explicitly does so.",
|
|
1091
|
+
"- Return a concise result to the main NAVAI agent."
|
|
1092
|
+
].join("\n");
|
|
1093
|
+
debugLog("creating specialist agent", {
|
|
1094
|
+
key: runtimeAgent.key,
|
|
1095
|
+
name: runtimeAgent.name,
|
|
1096
|
+
functions: [
|
|
1097
|
+
...specialistRegistry.ordered.map((item) => item.name),
|
|
1098
|
+
...specialistBackendFunctions.map((item) => item.name)
|
|
1099
|
+
]
|
|
1100
|
+
});
|
|
1101
|
+
const specialistAgent = new import_realtime.RealtimeAgent({
|
|
1102
|
+
name: runtimeAgent.name,
|
|
1103
|
+
handoffDescription: runtimeAgent.handoffDescription ?? runtimeAgent.description ?? `Delegate specialist work to ${runtimeAgent.name}.`,
|
|
1104
|
+
instructions: specialistInstructions,
|
|
1105
|
+
tools: [specialistFunctionTools.executeFunctionTool]
|
|
1106
|
+
});
|
|
1107
|
+
specialistAgents.push(specialistAgent);
|
|
1108
|
+
specialistLines.push(
|
|
1109
|
+
`- ${runtimeAgent.name}: ${runtimeAgent.description ?? runtimeAgent.handoffDescription ?? "Specialist agent available by delegation."}`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
426
1112
|
const instructions = [
|
|
427
|
-
options.baseInstructions ?? "You are
|
|
1113
|
+
primaryAgentConfig?.instructions ?? options.baseInstructions ?? "You are the main NAVAI voice agent embedded in a web app.",
|
|
428
1114
|
"Allowed routes:",
|
|
429
1115
|
...routeLines,
|
|
430
1116
|
"Allowed app functions:",
|
|
431
1117
|
...functionLines,
|
|
1118
|
+
"Available specialist agents:",
|
|
1119
|
+
...specialistLines.length > 0 ? specialistLines : ["- none"],
|
|
432
1120
|
"Rules:",
|
|
433
1121
|
"- If user asks to go/open a section, always call navigate_to.",
|
|
434
|
-
"- If user asks to run an internal action, call execute_app_function or the matching direct function tool.",
|
|
1122
|
+
"- If user asks to run an internal action that belongs to you, call execute_app_function or the matching direct function tool.",
|
|
1123
|
+
"- If the task clearly belongs to a specialist agent, hand off to that specialist agent.",
|
|
1124
|
+
"- Food recommendations, fast food, hamburgers, pizza, tacos, snacks, and meal suggestions belong to the food specialist.",
|
|
435
1125
|
"- Always include payload in execute_app_function. Use null when no arguments are needed.",
|
|
436
1126
|
"- For execute_app_function, pass arguments using payload.args (array).",
|
|
437
1127
|
"- For class methods, pass payload.constructorArgs and payload.methodArgs.",
|
|
@@ -439,11 +1129,23 @@ async function buildNavaiAgent(options) {
|
|
|
439
1129
|
"- If destination/action is unclear, ask a brief clarifying question."
|
|
440
1130
|
].join("\n");
|
|
441
1131
|
const agent = new import_realtime.RealtimeAgent({
|
|
442
|
-
name: options.agentName ?? "Navai Voice Agent",
|
|
1132
|
+
name: primaryAgentConfig?.name ?? options.agentName ?? "Navai Voice Agent",
|
|
443
1133
|
instructions,
|
|
444
|
-
|
|
1134
|
+
handoffs: specialistAgents,
|
|
1135
|
+
tools: [
|
|
1136
|
+
navigateTool,
|
|
1137
|
+
primaryFunctionTools.executeFunctionTool,
|
|
1138
|
+
...primaryFunctionTools.directFunctionTools
|
|
1139
|
+
]
|
|
1140
|
+
});
|
|
1141
|
+
debugLog("created primary agent", {
|
|
1142
|
+
name: primaryAgentConfig?.name ?? options.agentName ?? "Navai Voice Agent",
|
|
1143
|
+
primaryAgentKey: primaryAgentConfig?.key ?? null,
|
|
1144
|
+
directFunctions: functionsRegistry.ordered.map((item) => item.name),
|
|
1145
|
+
backendFunctions: backendFunctionsOrdered.map((item) => item.name),
|
|
1146
|
+
specialistDelegates: configuredAgents.filter((runtimeAgent) => runtimeAgent.key !== primaryAgentConfig?.key).map((runtimeAgent) => runtimeAgent.key)
|
|
445
1147
|
});
|
|
446
|
-
return { agent, warnings:
|
|
1148
|
+
return { agent, warnings: aggregatedWarnings };
|
|
447
1149
|
}
|
|
448
1150
|
|
|
449
1151
|
// src/backend.ts
|
|
@@ -566,6 +1268,7 @@ function createNavaiBackendClient(options = {}) {
|
|
|
566
1268
|
// src/runtime.ts
|
|
567
1269
|
var ROUTES_ENV_KEYS = ["NAVAI_ROUTES_FILE"];
|
|
568
1270
|
var FUNCTIONS_ENV_KEYS = ["NAVAI_FUNCTIONS_FOLDERS"];
|
|
1271
|
+
var AGENTS_ENV_KEYS = ["NAVAI_AGENTS_FOLDERS"];
|
|
569
1272
|
var MODEL_ENV_KEYS = ["NAVAI_REALTIME_MODEL"];
|
|
570
1273
|
async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
571
1274
|
const warnings = [];
|
|
@@ -575,6 +1278,7 @@ async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
|
575
1278
|
const defaultFunctionsFolder = options.defaultFunctionsFolder ?? "src/ai/functions-modules";
|
|
576
1279
|
const routesFile = readOptional2(options.routesFile) ?? readFirstOptionalEnv(options.env, ROUTES_ENV_KEYS) ?? defaultRoutesFile;
|
|
577
1280
|
const functionsFolders = readOptional2(options.functionsFolders) ?? readFirstOptionalEnv(options.env, FUNCTIONS_ENV_KEYS) ?? defaultFunctionsFolder;
|
|
1281
|
+
const agentsFolders = readOptional2(options.agentsFolders) ?? readFirstOptionalEnv(options.env, AGENTS_ENV_KEYS);
|
|
578
1282
|
const modelOverride = readOptional2(options.modelOverride) ?? readFirstOptionalEnv(options.env, MODEL_ENV_KEYS);
|
|
579
1283
|
const routes = await resolveRoutes({
|
|
580
1284
|
routesFile,
|
|
@@ -586,12 +1290,23 @@ async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
|
586
1290
|
const functionModuleLoaders = resolveFunctionModuleLoaders({
|
|
587
1291
|
indexedLoaders,
|
|
588
1292
|
functionsFolders,
|
|
1293
|
+
agentsFolders,
|
|
589
1294
|
defaultFunctionsFolder,
|
|
590
1295
|
warnings
|
|
591
1296
|
});
|
|
1297
|
+
const agents = await resolveRuntimeAgents({
|
|
1298
|
+
indexedLoaders,
|
|
1299
|
+
functionModuleLoaders,
|
|
1300
|
+
functionsFolders,
|
|
1301
|
+
agentsFolders,
|
|
1302
|
+
defaultFunctionsFolder
|
|
1303
|
+
});
|
|
1304
|
+
const primaryAgentKey = agents.find((agent) => agent.isPrimary)?.key;
|
|
592
1305
|
return {
|
|
593
1306
|
routes,
|
|
594
1307
|
functionModuleLoaders,
|
|
1308
|
+
agents,
|
|
1309
|
+
primaryAgentKey,
|
|
595
1310
|
modelOverride,
|
|
596
1311
|
warnings
|
|
597
1312
|
};
|
|
@@ -627,10 +1342,11 @@ async function resolveRoutes(input) {
|
|
|
627
1342
|
}
|
|
628
1343
|
function resolveFunctionModuleLoaders(input) {
|
|
629
1344
|
const configuredTokens = input.functionsFolders.split(",").map((value) => value.trim()).filter(Boolean);
|
|
1345
|
+
const agentFolders = parseCsvList(input.agentsFolders);
|
|
630
1346
|
const tokens = configuredTokens.length > 0 ? configuredTokens : [input.defaultFunctionsFolder];
|
|
631
|
-
const matchers = tokens.map((token) => createPathMatcher(token));
|
|
1347
|
+
const matchers = tokens.map((token) => createPathMatcher(token, agentFolders));
|
|
632
1348
|
const matchedEntries = input.indexedLoaders.filter(
|
|
633
|
-
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && matchers.some((matcher) => matcher(entry.normalizedPath))
|
|
1349
|
+
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && !isAgentConfigPath(entry.normalizedPath) && matchers.some((matcher) => matcher(entry.normalizedPath))
|
|
634
1350
|
);
|
|
635
1351
|
if (matchedEntries.length > 0) {
|
|
636
1352
|
return Object.fromEntries(matchedEntries.map((entry) => [entry.rawPath, entry.load]));
|
|
@@ -640,9 +1356,9 @@ function resolveFunctionModuleLoaders(input) {
|
|
|
640
1356
|
`[navai] NAVAI_FUNCTIONS_FOLDERS did not match any module: "${input.functionsFolders}". Falling back to "${input.defaultFunctionsFolder}".`
|
|
641
1357
|
);
|
|
642
1358
|
}
|
|
643
|
-
const
|
|
1359
|
+
const fallbackMatcherWithAgents = createPathMatcher(input.defaultFunctionsFolder, agentFolders);
|
|
644
1360
|
const fallbackEntries = input.indexedLoaders.filter(
|
|
645
|
-
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") &&
|
|
1361
|
+
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && !isAgentConfigPath(entry.normalizedPath) && fallbackMatcherWithAgents(entry.normalizedPath)
|
|
646
1362
|
);
|
|
647
1363
|
return Object.fromEntries(fallbackEntries.map((entry) => [entry.rawPath, entry.load]));
|
|
648
1364
|
}
|
|
@@ -685,7 +1401,7 @@ function buildModuleCandidates(inputPath) {
|
|
|
685
1401
|
}
|
|
686
1402
|
return [srcPrefixed, `${srcPrefixed}.ts`, `${srcPrefixed}.js`, `${srcPrefixed}/index.ts`, `${srcPrefixed}/index.js`];
|
|
687
1403
|
}
|
|
688
|
-
function createPathMatcher(input) {
|
|
1404
|
+
function createPathMatcher(input, agentFolders = []) {
|
|
689
1405
|
const raw = normalizePath(input);
|
|
690
1406
|
if (!raw) {
|
|
691
1407
|
return () => false;
|
|
@@ -703,8 +1419,141 @@ function createPathMatcher(input) {
|
|
|
703
1419
|
return (path) => path === normalized;
|
|
704
1420
|
}
|
|
705
1421
|
const base = normalized.replace(/\/+$/, "");
|
|
1422
|
+
const normalizedAgents = agentFolders.map(normalizePathSegment).filter(Boolean);
|
|
1423
|
+
if (normalizedAgents.length > 0) {
|
|
1424
|
+
return (path) => {
|
|
1425
|
+
if (!path.startsWith(`${base}/`)) {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
const suffix = path.slice(base.length + 1);
|
|
1429
|
+
const firstSegment = suffix.split("/", 1)[0] ?? "";
|
|
1430
|
+
return normalizedAgents.includes(firstSegment);
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
706
1433
|
return (path) => path === base || path.startsWith(`${base}/`);
|
|
707
1434
|
}
|
|
1435
|
+
async function resolveRuntimeAgents(input) {
|
|
1436
|
+
const configuredAgents = parseCsvList(input.agentsFolders);
|
|
1437
|
+
if (configuredAgents.length === 0) {
|
|
1438
|
+
return [];
|
|
1439
|
+
}
|
|
1440
|
+
const loaderByPath = new Map(input.indexedLoaders.map((entry) => [entry.normalizedPath, entry]));
|
|
1441
|
+
const baseDirectories = resolveAgentBaseDirectories(input.functionsFolders, input.defaultFunctionsFolder);
|
|
1442
|
+
const groupedLoaders = /* @__PURE__ */ new Map();
|
|
1443
|
+
for (const [rawPath, load] of Object.entries(input.functionModuleLoaders)) {
|
|
1444
|
+
const agentKey = extractAgentKeyFromPath(rawPath, baseDirectories, configuredAgents);
|
|
1445
|
+
if (!agentKey) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
const current = groupedLoaders.get(agentKey) ?? {};
|
|
1449
|
+
current[rawPath] = load;
|
|
1450
|
+
groupedLoaders.set(agentKey, current);
|
|
1451
|
+
}
|
|
1452
|
+
const configuredPrimaryKey = configuredAgents[0];
|
|
1453
|
+
const agents = [];
|
|
1454
|
+
for (const agentKey of configuredAgents) {
|
|
1455
|
+
const functionLoaders = groupedLoaders.get(agentKey);
|
|
1456
|
+
if (!functionLoaders || Object.keys(functionLoaders).length === 0) {
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const config = await loadAgentModuleConfig(agentKey, baseDirectories, loaderByPath);
|
|
1460
|
+
agents.push({
|
|
1461
|
+
key: config.key?.trim() || agentKey,
|
|
1462
|
+
name: readOptional2(config.name) ?? humanizeAgentKey(agentKey),
|
|
1463
|
+
description: readOptional2(config.description),
|
|
1464
|
+
handoffDescription: readOptional2(config.handoffDescription) ?? readOptional2(config.description),
|
|
1465
|
+
instructions: readOptional2(config.instructions),
|
|
1466
|
+
isPrimary: config.isPrimary === true || agentKey === configuredPrimaryKey,
|
|
1467
|
+
functionModuleLoaders: functionLoaders
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
if (agents.filter((agent) => agent.isPrimary).length === 0 && agents[0]) {
|
|
1471
|
+
agents[0].isPrimary = true;
|
|
1472
|
+
}
|
|
1473
|
+
if (agents.filter((agent) => agent.isPrimary).length > 1) {
|
|
1474
|
+
let primaryAssigned = false;
|
|
1475
|
+
for (const agent of agents) {
|
|
1476
|
+
if (agent.isPrimary && !primaryAssigned) {
|
|
1477
|
+
primaryAssigned = true;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
agent.isPrimary = false;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return agents;
|
|
1484
|
+
}
|
|
1485
|
+
async function loadAgentModuleConfig(agentKey, baseDirectories, loaderByPath) {
|
|
1486
|
+
for (const baseDirectory of baseDirectories) {
|
|
1487
|
+
const configBase = `${baseDirectory}/${agentKey}/agent.config`;
|
|
1488
|
+
const matchedLoader = buildModuleCandidates(configBase).map((candidate) => loaderByPath.get(candidate)).find(Boolean);
|
|
1489
|
+
if (!matchedLoader) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
try {
|
|
1493
|
+
const imported = await matchedLoader.load();
|
|
1494
|
+
return readAgentModuleConfig(imported);
|
|
1495
|
+
} catch {
|
|
1496
|
+
return {};
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return {};
|
|
1500
|
+
}
|
|
1501
|
+
function readAgentModuleConfig(moduleShape) {
|
|
1502
|
+
const candidate = readRecord(moduleShape.NAVAI_AGENT) ?? readRecord(moduleShape.agent) ?? readRecord(moduleShape.default) ?? {};
|
|
1503
|
+
return {
|
|
1504
|
+
key: readOptionalString(candidate.key),
|
|
1505
|
+
name: readOptionalString(candidate.name),
|
|
1506
|
+
description: readOptionalString(candidate.description),
|
|
1507
|
+
handoffDescription: readOptionalString(candidate.handoffDescription),
|
|
1508
|
+
instructions: readOptionalString(candidate.instructions),
|
|
1509
|
+
isPrimary: candidate.isPrimary === true
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
function readRecord(value) {
|
|
1513
|
+
return value && typeof value === "object" ? value : null;
|
|
1514
|
+
}
|
|
1515
|
+
function readOptionalString(value) {
|
|
1516
|
+
return typeof value === "string" ? readOptional2(value) : void 0;
|
|
1517
|
+
}
|
|
1518
|
+
function resolveAgentBaseDirectories(functionsFolders, defaultFunctionsFolder) {
|
|
1519
|
+
const configuredTokens = functionsFolders.split(",").map((value) => value.trim()).filter(Boolean);
|
|
1520
|
+
const tokens = configuredTokens.length > 0 ? configuredTokens : [defaultFunctionsFolder];
|
|
1521
|
+
return [...new Set(tokens.map(toAgentBaseDirectory).filter(Boolean))];
|
|
1522
|
+
}
|
|
1523
|
+
function toAgentBaseDirectory(input) {
|
|
1524
|
+
const raw = normalizePath(input);
|
|
1525
|
+
if (!raw) {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
const normalized = raw.startsWith("src/") ? raw : `src/${raw}`;
|
|
1529
|
+
if (normalized.includes("*") || /\.[cm]?[jt]s$/.test(normalized)) {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
if (normalized.endsWith("/...")) {
|
|
1533
|
+
return normalized.slice(0, -4).replace(/\/+$/, "") || null;
|
|
1534
|
+
}
|
|
1535
|
+
return normalized.replace(/\/+$/, "") || null;
|
|
1536
|
+
}
|
|
1537
|
+
function extractAgentKeyFromPath(pathValue, baseDirectories, configuredAgents) {
|
|
1538
|
+
const normalized = normalizePath(pathValue);
|
|
1539
|
+
for (const baseDirectory of baseDirectories) {
|
|
1540
|
+
if (!normalized.startsWith(`${baseDirectory}/`)) {
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
const suffix = normalized.slice(baseDirectory.length + 1);
|
|
1544
|
+
const firstSegment = suffix.split("/", 1)[0] ?? "";
|
|
1545
|
+
if (configuredAgents.includes(firstSegment)) {
|
|
1546
|
+
return firstSegment;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return void 0;
|
|
1550
|
+
}
|
|
1551
|
+
function humanizeAgentKey(value) {
|
|
1552
|
+
return value.split(/[_-]+/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
1553
|
+
}
|
|
1554
|
+
function isAgentConfigPath(pathValue) {
|
|
1555
|
+
return /\/agent\.config\.[cm]?[jt]s$/i.test(pathValue);
|
|
1556
|
+
}
|
|
708
1557
|
function globToRegExp(pattern) {
|
|
709
1558
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
710
1559
|
const wildcardSafe = escaped.replace(/\*\*/g, "___DOUBLE_STAR___");
|
|
@@ -715,6 +1564,12 @@ function globToRegExp(pattern) {
|
|
|
715
1564
|
function normalizePath(input) {
|
|
716
1565
|
return input.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^(\.\/)+/, "").replace(/^(\.\.\/)+/, "");
|
|
717
1566
|
}
|
|
1567
|
+
function normalizePathSegment(input) {
|
|
1568
|
+
return normalizePath(input).replace(/\//g, "");
|
|
1569
|
+
}
|
|
1570
|
+
function parseCsvList(input) {
|
|
1571
|
+
return (input ?? "").split(",").map((value) => normalizePathSegment(value)).filter(Boolean);
|
|
1572
|
+
}
|
|
718
1573
|
function readFirstOptionalEnv(env, keys) {
|
|
719
1574
|
if (!env) {
|
|
720
1575
|
return void 0;
|
|
@@ -745,6 +1600,7 @@ function toErrorMessage3(error) {
|
|
|
745
1600
|
// src/useWebVoiceAgent.ts
|
|
746
1601
|
var import_realtime2 = require("@openai/agents/realtime");
|
|
747
1602
|
var import_react = require("react");
|
|
1603
|
+
var DEBUG_PREFIX2 = "[navai debug]";
|
|
748
1604
|
function formatError(error) {
|
|
749
1605
|
if (error instanceof Error) {
|
|
750
1606
|
return error.message;
|
|
@@ -758,6 +1614,13 @@ function emitWarnings(warnings) {
|
|
|
758
1614
|
}
|
|
759
1615
|
}
|
|
760
1616
|
}
|
|
1617
|
+
function debugLog2(message, details) {
|
|
1618
|
+
if (details === void 0) {
|
|
1619
|
+
console.log(`${DEBUG_PREFIX2} ${message}`);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
console.log(`${DEBUG_PREFIX2} ${message}`, details);
|
|
1623
|
+
}
|
|
761
1624
|
function useWebVoiceAgent(options) {
|
|
762
1625
|
const sessionRef = (0, import_react.useRef)(null);
|
|
763
1626
|
const attachedRealtimeSessionRef = (0, import_react.useRef)(null);
|
|
@@ -768,6 +1631,7 @@ function useWebVoiceAgent(options) {
|
|
|
768
1631
|
env: options.env,
|
|
769
1632
|
routesFile: options.routesFile,
|
|
770
1633
|
functionsFolders: options.functionsFolders,
|
|
1634
|
+
agentsFolders: options.agentsFolders,
|
|
771
1635
|
modelOverride: options.modelOverride,
|
|
772
1636
|
defaultRoutesFile: options.defaultRoutesFile,
|
|
773
1637
|
defaultFunctionsFolder: options.defaultFunctionsFolder
|
|
@@ -776,6 +1640,7 @@ function useWebVoiceAgent(options) {
|
|
|
776
1640
|
options.defaultFunctionsFolder,
|
|
777
1641
|
options.defaultRoutes,
|
|
778
1642
|
options.defaultRoutesFile,
|
|
1643
|
+
options.agentsFolders,
|
|
779
1644
|
options.env,
|
|
780
1645
|
options.functionsFolders,
|
|
781
1646
|
options.modelOverride,
|
|
@@ -822,6 +1687,45 @@ function useWebVoiceAgent(options) {
|
|
|
822
1687
|
const attachSessionAudioListeners = (0, import_react.useCallback)(
|
|
823
1688
|
(session) => {
|
|
824
1689
|
detachSessionAudioListeners();
|
|
1690
|
+
session.on("agent_start", (_context, agent, turnInput) => {
|
|
1691
|
+
debugLog2("session agent_start", {
|
|
1692
|
+
agent: agent.name,
|
|
1693
|
+
turnInputCount: Array.isArray(turnInput) ? turnInput.length : 0
|
|
1694
|
+
});
|
|
1695
|
+
});
|
|
1696
|
+
session.on("agent_end", (_context, agent, output) => {
|
|
1697
|
+
debugLog2("session agent_end", {
|
|
1698
|
+
agent: agent.name,
|
|
1699
|
+
output
|
|
1700
|
+
});
|
|
1701
|
+
});
|
|
1702
|
+
session.on("agent_handoff", (_context, fromAgent, toAgent) => {
|
|
1703
|
+
debugLog2("session agent_handoff", {
|
|
1704
|
+
from: fromAgent.name,
|
|
1705
|
+
to: toAgent.name
|
|
1706
|
+
});
|
|
1707
|
+
});
|
|
1708
|
+
session.on("agent_tool_start", (_context, agent, tool2, details) => {
|
|
1709
|
+
debugLog2("session agent_tool_start", {
|
|
1710
|
+
agent: agent.name,
|
|
1711
|
+
tool: tool2.name,
|
|
1712
|
+
toolCall: details.toolCall
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
1715
|
+
session.on("agent_tool_end", (_context, agent, tool2, result, details) => {
|
|
1716
|
+
debugLog2("session agent_tool_end", {
|
|
1717
|
+
agent: agent.name,
|
|
1718
|
+
tool: tool2.name,
|
|
1719
|
+
result,
|
|
1720
|
+
toolCall: details.toolCall
|
|
1721
|
+
});
|
|
1722
|
+
});
|
|
1723
|
+
session.on("history_added", (item) => {
|
|
1724
|
+
debugLog2("session history_added", item);
|
|
1725
|
+
});
|
|
1726
|
+
session.on("error", (sessionError) => {
|
|
1727
|
+
debugLog2("session error", sessionError);
|
|
1728
|
+
});
|
|
825
1729
|
session.on("audio_start", handleSessionAudioStart);
|
|
826
1730
|
session.on("audio_stopped", handleSessionAudioStopped);
|
|
827
1731
|
session.on("audio_interrupted", handleSessionAudioInterrupted);
|
|
@@ -860,6 +1764,17 @@ function useWebVoiceAgent(options) {
|
|
|
860
1764
|
setAgentVoiceStateIfChanged("idle");
|
|
861
1765
|
try {
|
|
862
1766
|
const runtimeConfig = await runtimeConfigPromise;
|
|
1767
|
+
debugLog2("resolved runtime config", {
|
|
1768
|
+
routes: runtimeConfig.routes.map((route) => route.path),
|
|
1769
|
+
functionModules: Object.keys(runtimeConfig.functionModuleLoaders),
|
|
1770
|
+
agents: runtimeConfig.agents.map((agent2) => ({
|
|
1771
|
+
key: agent2.key,
|
|
1772
|
+
name: agent2.name,
|
|
1773
|
+
isPrimary: agent2.isPrimary,
|
|
1774
|
+
functionModules: Object.keys(agent2.functionModuleLoaders)
|
|
1775
|
+
})),
|
|
1776
|
+
warnings: runtimeConfig.warnings
|
|
1777
|
+
});
|
|
863
1778
|
const requestPayload = runtimeConfig.modelOverride ? { model: runtimeConfig.modelOverride } : {};
|
|
864
1779
|
const secretPayload = await backendClient.createClientSecret(requestPayload);
|
|
865
1780
|
const backendFunctionsResult = await backendClient.listFunctions();
|
|
@@ -867,6 +1782,8 @@ function useWebVoiceAgent(options) {
|
|
|
867
1782
|
navigate: options.navigate,
|
|
868
1783
|
routes: runtimeConfig.routes,
|
|
869
1784
|
functionModuleLoaders: runtimeConfig.functionModuleLoaders,
|
|
1785
|
+
agents: runtimeConfig.agents,
|
|
1786
|
+
primaryAgentKey: runtimeConfig.primaryAgentKey,
|
|
870
1787
|
backendFunctions: backendFunctionsResult.functions,
|
|
871
1788
|
executeBackendFunction: backendClient.executeFunction
|
|
872
1789
|
});
|
|
@@ -882,6 +1799,7 @@ function useWebVoiceAgent(options) {
|
|
|
882
1799
|
setStatus("connected");
|
|
883
1800
|
} catch (startError) {
|
|
884
1801
|
const message = formatError(startError);
|
|
1802
|
+
debugLog2("session start failed", { message, error: startError });
|
|
885
1803
|
setError(message);
|
|
886
1804
|
setStatus("error");
|
|
887
1805
|
setAgentVoiceStateIfChanged("idle");
|
|
@@ -912,13 +1830,367 @@ function useWebVoiceAgent(options) {
|
|
|
912
1830
|
stop
|
|
913
1831
|
};
|
|
914
1832
|
}
|
|
1833
|
+
|
|
1834
|
+
// src/orb/index.ts
|
|
1835
|
+
init_Orb();
|
|
1836
|
+
|
|
1837
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
1838
|
+
var import_react5 = require("react");
|
|
1839
|
+
|
|
1840
|
+
// src/orb/dynamic.tsx
|
|
1841
|
+
var import_react4 = require("react");
|
|
1842
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1843
|
+
function dynamic(loader, options = {}) {
|
|
1844
|
+
const { ssr = true, loading: LoadingComponent } = options;
|
|
1845
|
+
const LazyComponent = (0, import_react4.lazy)(async () => {
|
|
1846
|
+
const loaded = await loader();
|
|
1847
|
+
if (typeof loaded === "function") {
|
|
1848
|
+
return { default: loaded };
|
|
1849
|
+
}
|
|
1850
|
+
return loaded;
|
|
1851
|
+
});
|
|
1852
|
+
function DynamicComponent(props) {
|
|
1853
|
+
const [isClientReady, setIsClientReady] = (0, import_react4.useState)(ssr);
|
|
1854
|
+
(0, import_react4.useEffect)(() => {
|
|
1855
|
+
if (!ssr) {
|
|
1856
|
+
setIsClientReady(true);
|
|
1857
|
+
}
|
|
1858
|
+
}, [ssr]);
|
|
1859
|
+
if (!isClientReady) {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
const fallback = LoadingComponent ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingComponent, {}) : null;
|
|
1863
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react4.Suspense, { fallback, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LazyComponent, { ...props }) });
|
|
1864
|
+
}
|
|
1865
|
+
DynamicComponent.displayName = "DynamicComponent";
|
|
1866
|
+
return DynamicComponent;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
1870
|
+
init_styles();
|
|
1871
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1872
|
+
var Orb2 = dynamic(() => Promise.resolve().then(() => (init_Orb(), Orb_exports)), {
|
|
1873
|
+
ssr: false
|
|
1874
|
+
});
|
|
1875
|
+
var ORB_DELAY_MS_MIN = 0;
|
|
1876
|
+
var ORB_DELAY_MS_MAX = 6e4;
|
|
1877
|
+
var DEFAULT_AUTOPLAY_DELAY_MS = 9e3;
|
|
1878
|
+
var DEFAULT_REVEAL_DELAY_MS = 5200;
|
|
1879
|
+
function clampNavaiOrbDelayMs(value, fallback) {
|
|
1880
|
+
const numericValue = Number.isFinite(value) ? value : fallback;
|
|
1881
|
+
return Math.min(ORB_DELAY_MS_MAX, Math.max(ORB_DELAY_MS_MIN, numericValue));
|
|
1882
|
+
}
|
|
1883
|
+
function NavaiHeroOrb({
|
|
1884
|
+
className = "",
|
|
1885
|
+
backgroundColor = "#000000",
|
|
1886
|
+
isAgentSpeaking = false,
|
|
1887
|
+
hoverIntensitySpeaking = 0.66,
|
|
1888
|
+
hoverIntensityIdle = 0.08,
|
|
1889
|
+
revealDelayMs = DEFAULT_REVEAL_DELAY_MS,
|
|
1890
|
+
autoplayDelayMs = DEFAULT_AUTOPLAY_DELAY_MS
|
|
1891
|
+
}) {
|
|
1892
|
+
useNavaiVoiceOrbStyles();
|
|
1893
|
+
const resolvedRevealDelayMs = clampNavaiOrbDelayMs(revealDelayMs, DEFAULT_REVEAL_DELAY_MS);
|
|
1894
|
+
const resolvedAutoplayDelayMs = clampNavaiOrbDelayMs(autoplayDelayMs, DEFAULT_AUTOPLAY_DELAY_MS);
|
|
1895
|
+
const [isOrbReady, setIsOrbReady] = (0, import_react5.useState)(resolvedRevealDelayMs === 0);
|
|
1896
|
+
const [isOrbAutoAnimating, setIsOrbAutoAnimating] = (0, import_react5.useState)(resolvedAutoplayDelayMs === 0);
|
|
1897
|
+
(0, import_react5.useEffect)(() => {
|
|
1898
|
+
if (typeof window === "undefined" || resolvedRevealDelayMs === 0) {
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
const revealOrb = () => setIsOrbReady(true);
|
|
1902
|
+
window.addEventListener("pointerdown", revealOrb, { passive: true, once: true });
|
|
1903
|
+
window.addEventListener("touchstart", revealOrb, { passive: true, once: true });
|
|
1904
|
+
window.addEventListener("keydown", revealOrb, { once: true });
|
|
1905
|
+
const timeoutId = window.setTimeout(revealOrb, resolvedRevealDelayMs);
|
|
1906
|
+
return () => {
|
|
1907
|
+
window.removeEventListener("pointerdown", revealOrb);
|
|
1908
|
+
window.removeEventListener("touchstart", revealOrb);
|
|
1909
|
+
window.removeEventListener("keydown", revealOrb);
|
|
1910
|
+
window.clearTimeout(timeoutId);
|
|
1911
|
+
};
|
|
1912
|
+
}, [resolvedRevealDelayMs]);
|
|
1913
|
+
(0, import_react5.useEffect)(() => {
|
|
1914
|
+
if (typeof window === "undefined" || resolvedAutoplayDelayMs === 0) {
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
let started = false;
|
|
1918
|
+
const startOrbAnimation = () => {
|
|
1919
|
+
if (started) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
started = true;
|
|
1923
|
+
setIsOrbAutoAnimating(true);
|
|
1924
|
+
};
|
|
1925
|
+
if (navigator.userActivation?.hasBeenActive) {
|
|
1926
|
+
startOrbAnimation();
|
|
1927
|
+
}
|
|
1928
|
+
window.addEventListener("pointerdown", startOrbAnimation, { passive: true, once: true });
|
|
1929
|
+
window.addEventListener("touchstart", startOrbAnimation, { passive: true, once: true });
|
|
1930
|
+
window.addEventListener("keydown", startOrbAnimation, { once: true });
|
|
1931
|
+
const timeoutId = window.setTimeout(startOrbAnimation, resolvedAutoplayDelayMs);
|
|
1932
|
+
return () => {
|
|
1933
|
+
window.removeEventListener("pointerdown", startOrbAnimation);
|
|
1934
|
+
window.removeEventListener("touchstart", startOrbAnimation);
|
|
1935
|
+
window.removeEventListener("keydown", startOrbAnimation);
|
|
1936
|
+
window.clearTimeout(timeoutId);
|
|
1937
|
+
};
|
|
1938
|
+
}, [resolvedAutoplayDelayMs]);
|
|
1939
|
+
const orbHoverIntensity = (0, import_react5.useMemo)(() => {
|
|
1940
|
+
return isAgentSpeaking ? hoverIntensitySpeaking : hoverIntensityIdle;
|
|
1941
|
+
}, [hoverIntensityIdle, hoverIntensitySpeaking, isAgentSpeaking]);
|
|
1942
|
+
if (!isOrbReady) {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: ["navai-voice-orb-hero", className].filter(Boolean).join(" "), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1946
|
+
Orb2,
|
|
1947
|
+
{
|
|
1948
|
+
hoverIntensity: orbHoverIntensity,
|
|
1949
|
+
rotateOnHover: true,
|
|
1950
|
+
forceHoverState: isAgentSpeaking,
|
|
1951
|
+
enablePointerHover: false,
|
|
1952
|
+
animate: isAgentSpeaking || isOrbAutoAnimating,
|
|
1953
|
+
backgroundColor
|
|
1954
|
+
}
|
|
1955
|
+
) });
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/orb/NavaiMiniOrbDock.tsx
|
|
1959
|
+
init_styles();
|
|
1960
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1961
|
+
var Orb3 = dynamic(() => Promise.resolve().then(() => (init_Orb(), Orb_exports)), {
|
|
1962
|
+
ssr: false
|
|
1963
|
+
});
|
|
1964
|
+
function NavaiMiniOrbDock({
|
|
1965
|
+
className = "",
|
|
1966
|
+
style,
|
|
1967
|
+
themeMode = "dark",
|
|
1968
|
+
placement = "bottom-right",
|
|
1969
|
+
isActive = false,
|
|
1970
|
+
isConnected = false,
|
|
1971
|
+
isDisabled = false,
|
|
1972
|
+
isAgentSpeaking = false,
|
|
1973
|
+
animateOrb = true,
|
|
1974
|
+
backgroundColor = "#060914",
|
|
1975
|
+
buttonAriaLabel,
|
|
1976
|
+
buttonIcon,
|
|
1977
|
+
buttonType = "button",
|
|
1978
|
+
onButtonClick,
|
|
1979
|
+
statusMessage = "",
|
|
1980
|
+
isError = false,
|
|
1981
|
+
ariaMessage = ""
|
|
1982
|
+
}) {
|
|
1983
|
+
useNavaiVoiceOrbStyles();
|
|
1984
|
+
const dockClassName = ["navai-voice-orb-dock", `is-${placement}`, themeMode === "light" ? "is-light" : "", className].filter(Boolean).join(" ");
|
|
1985
|
+
const shouldHighlightOrb = isAgentSpeaking || isActive;
|
|
1986
|
+
const orbHoverIntensity = isAgentSpeaking ? 0.66 : 0.08;
|
|
1987
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("aside", { className: dockClassName, style, children: [
|
|
1988
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "navai-voice-orb-wrap", children: [
|
|
1989
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: ["navai-voice-orb-surface", shouldHighlightOrb ? "is-highlighted" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1990
|
+
Orb3,
|
|
1991
|
+
{
|
|
1992
|
+
hoverIntensity: orbHoverIntensity,
|
|
1993
|
+
rotateOnHover: true,
|
|
1994
|
+
forceHoverState: isAgentSpeaking,
|
|
1995
|
+
enablePointerHover: false,
|
|
1996
|
+
animate: animateOrb,
|
|
1997
|
+
backgroundColor
|
|
1998
|
+
}
|
|
1999
|
+
) }),
|
|
2000
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: ["navai-voice-orb-button-shell", isConnected ? "is-active" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2001
|
+
"button",
|
|
2002
|
+
{
|
|
2003
|
+
type: buttonType,
|
|
2004
|
+
className: [
|
|
2005
|
+
"navai-voice-orb-button",
|
|
2006
|
+
isConnected ? "is-active" : "",
|
|
2007
|
+
isActive && !isConnected ? "is-connecting" : ""
|
|
2008
|
+
].filter(Boolean).join(" "),
|
|
2009
|
+
onClick: onButtonClick,
|
|
2010
|
+
disabled: isDisabled,
|
|
2011
|
+
"aria-label": buttonAriaLabel,
|
|
2012
|
+
children: buttonIcon
|
|
2013
|
+
}
|
|
2014
|
+
) })
|
|
2015
|
+
] }),
|
|
2016
|
+
statusMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: ["navai-voice-orb-status", isError ? "is-error" : ""].filter(Boolean).join(" "), role: "status", children: statusMessage }) : null,
|
|
2017
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "navai-voice-orb-live", "aria-live": "polite", children: ariaMessage })
|
|
2018
|
+
] });
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
2022
|
+
var import_react7 = require("react");
|
|
2023
|
+
|
|
2024
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
2025
|
+
var import_react6 = require("react");
|
|
2026
|
+
|
|
2027
|
+
// src/orb/NavaiVoiceOrbDockMicIcon.tsx
|
|
2028
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2029
|
+
function NavaiVoiceOrbDockMicIcon({
|
|
2030
|
+
isActive = false,
|
|
2031
|
+
size = 20
|
|
2032
|
+
}) {
|
|
2033
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2034
|
+
"svg",
|
|
2035
|
+
{
|
|
2036
|
+
width: size,
|
|
2037
|
+
height: size,
|
|
2038
|
+
viewBox: "0 0 24 24",
|
|
2039
|
+
fill: "none",
|
|
2040
|
+
stroke: "currentColor",
|
|
2041
|
+
strokeWidth: "2",
|
|
2042
|
+
strokeLinecap: "round",
|
|
2043
|
+
strokeLinejoin: "round",
|
|
2044
|
+
"aria-hidden": "true",
|
|
2045
|
+
className: ["navai-voice-orb-icon", isActive ? "is-pulsing" : ""].filter(Boolean).join(" "),
|
|
2046
|
+
children: [
|
|
2047
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 3a3 3 0 0 0-3 3v6a3 3 0 1 0 6 0V6a3 3 0 0 0-3-3Z" }),
|
|
2048
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M19 10v2a7 7 0 1 1-14 0v-2" }),
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 19v3" })
|
|
2050
|
+
]
|
|
2051
|
+
}
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// src/orb/NavaiVoiceOrbDockSpinnerIcon.tsx
|
|
2056
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2057
|
+
function NavaiVoiceOrbDockSpinnerIcon({
|
|
2058
|
+
size = 20
|
|
2059
|
+
}) {
|
|
2060
|
+
const style = {
|
|
2061
|
+
width: size,
|
|
2062
|
+
height: size
|
|
2063
|
+
};
|
|
2064
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", className: "navai-voice-orb-spinner", style });
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
2068
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2069
|
+
var DEFAULT_MESSAGES = {
|
|
2070
|
+
ariaStart: "Activate NAVAI voice",
|
|
2071
|
+
ariaStop: "Deactivate NAVAI voice",
|
|
2072
|
+
idle: "NAVAI ready to start.",
|
|
2073
|
+
connecting: "Connecting NAVAI voice...",
|
|
2074
|
+
listening: "NAVAI is listening.",
|
|
2075
|
+
speaking: "NAVAI is speaking.",
|
|
2076
|
+
errorPrefix: "NAVAI error"
|
|
2077
|
+
};
|
|
2078
|
+
function resolveNavaiVoiceOrbRuntimeSnapshot(agent) {
|
|
2079
|
+
return {
|
|
2080
|
+
status: agent.status,
|
|
2081
|
+
agentVoiceState: agent.agentVoiceState,
|
|
2082
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
2083
|
+
error: agent.error
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
function resolveStatusMessage(runtimeSnapshot, messages) {
|
|
2087
|
+
if (runtimeSnapshot.error) {
|
|
2088
|
+
return `${messages.errorPrefix}: ${runtimeSnapshot.error}`;
|
|
2089
|
+
}
|
|
2090
|
+
if (runtimeSnapshot.isAgentSpeaking) {
|
|
2091
|
+
return messages.speaking;
|
|
2092
|
+
}
|
|
2093
|
+
if (runtimeSnapshot.status === "connecting") {
|
|
2094
|
+
return messages.connecting;
|
|
2095
|
+
}
|
|
2096
|
+
if (runtimeSnapshot.status === "connected") {
|
|
2097
|
+
return messages.listening;
|
|
2098
|
+
}
|
|
2099
|
+
return messages.idle;
|
|
2100
|
+
}
|
|
2101
|
+
function NavaiVoiceOrbDock({
|
|
2102
|
+
agent,
|
|
2103
|
+
className,
|
|
2104
|
+
style,
|
|
2105
|
+
themeMode = "dark",
|
|
2106
|
+
placement = "bottom-right",
|
|
2107
|
+
backgroundColorLight = "#f4f6fb",
|
|
2108
|
+
backgroundColorDark = "#060914",
|
|
2109
|
+
showStatus = true,
|
|
2110
|
+
messages
|
|
2111
|
+
}) {
|
|
2112
|
+
const resolvedMessages = (0, import_react6.useMemo)(() => ({ ...DEFAULT_MESSAGES, ...messages }), [messages]);
|
|
2113
|
+
const runtimeSnapshot = (0, import_react6.useMemo)(() => resolveNavaiVoiceOrbRuntimeSnapshot(agent), [agent]);
|
|
2114
|
+
const statusMessage = showStatus ? resolveStatusMessage(runtimeSnapshot, resolvedMessages) : "";
|
|
2115
|
+
const isError = runtimeSnapshot.status === "error" || Boolean(runtimeSnapshot.error);
|
|
2116
|
+
const isConnecting = runtimeSnapshot.status === "connecting";
|
|
2117
|
+
const isActive = runtimeSnapshot.status === "connecting" || runtimeSnapshot.status === "connected";
|
|
2118
|
+
const isDisabled = agent.isConnecting;
|
|
2119
|
+
const shouldAnimateOrb = runtimeSnapshot.status !== "error";
|
|
2120
|
+
const handleToggle = (0, import_react6.useCallback)(() => {
|
|
2121
|
+
if (agent.isConnecting) {
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
if (agent.isConnected) {
|
|
2125
|
+
agent.stop();
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
void agent.start();
|
|
2129
|
+
}, [agent]);
|
|
2130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2131
|
+
NavaiMiniOrbDock,
|
|
2132
|
+
{
|
|
2133
|
+
className,
|
|
2134
|
+
style,
|
|
2135
|
+
themeMode,
|
|
2136
|
+
placement,
|
|
2137
|
+
isActive,
|
|
2138
|
+
isConnected: agent.isConnected,
|
|
2139
|
+
isDisabled,
|
|
2140
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
2141
|
+
animateOrb: shouldAnimateOrb,
|
|
2142
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
2143
|
+
buttonAriaLabel: isConnecting ? resolvedMessages.connecting : agent.isConnected ? resolvedMessages.ariaStop : resolvedMessages.ariaStart,
|
|
2144
|
+
buttonIcon: isConnecting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NavaiVoiceOrbDockSpinnerIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NavaiVoiceOrbDockMicIcon, { isActive: agent.isConnected || agent.isAgentSpeaking }),
|
|
2145
|
+
onButtonClick: handleToggle,
|
|
2146
|
+
statusMessage,
|
|
2147
|
+
isError,
|
|
2148
|
+
ariaMessage: statusMessage
|
|
2149
|
+
}
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
2154
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2155
|
+
function NavaiVoiceHeroOrb({
|
|
2156
|
+
agent,
|
|
2157
|
+
themeMode = "dark",
|
|
2158
|
+
backgroundColorLight = "#ffffff",
|
|
2159
|
+
backgroundColorDark = "#000000",
|
|
2160
|
+
onRuntimeSnapshotChange,
|
|
2161
|
+
...orbProps
|
|
2162
|
+
}) {
|
|
2163
|
+
const runtimeSnapshot = resolveNavaiVoiceOrbRuntimeSnapshot(agent);
|
|
2164
|
+
(0, import_react7.useEffect)(() => {
|
|
2165
|
+
if (typeof onRuntimeSnapshotChange === "function") {
|
|
2166
|
+
onRuntimeSnapshotChange(runtimeSnapshot);
|
|
2167
|
+
}
|
|
2168
|
+
}, [onRuntimeSnapshotChange, runtimeSnapshot]);
|
|
2169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2170
|
+
NavaiHeroOrb,
|
|
2171
|
+
{
|
|
2172
|
+
...orbProps,
|
|
2173
|
+
isAgentSpeaking: runtimeSnapshot.isAgentSpeaking,
|
|
2174
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
2175
|
+
className: themeMode === "light" ? "is-light" : ""
|
|
2176
|
+
}
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
915
2179
|
// Annotate the CommonJS export names for ESM import in node:
|
|
916
2180
|
0 && (module.exports = {
|
|
2181
|
+
NavaiHeroOrb,
|
|
2182
|
+
NavaiMiniOrbDock,
|
|
2183
|
+
NavaiVoiceHeroOrb,
|
|
2184
|
+
NavaiVoiceOrbDock,
|
|
2185
|
+
NavaiVoiceOrbDockMicIcon,
|
|
2186
|
+
Orb,
|
|
917
2187
|
buildNavaiAgent,
|
|
2188
|
+
clampNavaiOrbDelayMs,
|
|
918
2189
|
createNavaiBackendClient,
|
|
919
2190
|
getNavaiRoutePromptLines,
|
|
920
2191
|
loadNavaiFunctions,
|
|
921
2192
|
resolveNavaiFrontendRuntimeConfig,
|
|
922
2193
|
resolveNavaiRoute,
|
|
2194
|
+
resolveNavaiVoiceOrbRuntimeSnapshot,
|
|
923
2195
|
useWebVoiceAgent
|
|
924
2196
|
});
|