@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/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
- async function buildNavaiAgent(options) {
281
- const functionsRegistry = await loadNavaiFunctions(options.functionModuleLoaders ?? {});
282
- const backendWarnings = [];
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 options.backendFunctions ?? []) {
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
- backendWarnings.push(
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
- backendWarnings.push(`[navai] Ignored duplicated backend function "${backendFunction.name}".`);
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
- ...backendFunctionsOrdered.map((item) => item.name)
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
- const frontendDefinition = functionsRegistry.byName.get(requested);
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
- const result = await frontendDefinition.run(payload ?? {}, options);
336
- return { ok: true, function_name: frontendDefinition.name, source: frontendDefinition.source, result };
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
- return {
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
- return {
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 (!options.executeBackendFunction) {
355
- return {
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
- const result = await options.executeBackendFunction({
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
- return {
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
- return {
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
- const navigateTool = (0, import_realtime.tool)({
382
- name: "navigate_to",
383
- description: "Navigate to an allowed route in the current app.",
384
- parameters: import_zod.z.object({
385
- target: import_zod.z.string().min(1).describe("Route name or route path. Example: perfil, ajustes, /profile, /settings")
386
- }),
387
- execute: async ({ target }) => {
388
- const path = resolveNavaiRoute(target, options.routes);
389
- if (!path) {
390
- return { ok: false, error: "Unknown or disallowed route." };
391
- }
392
- options.navigate(path);
393
- return { ok: true, path };
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().optional().describe(
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
- const routeLines = getNavaiRoutePromptLines(options.routes);
420
- const functionLines = functionsRegistry.ordered.length + backendFunctionsOrdered.length > 0 ? [
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
- ...backendFunctionsOrdered.map(
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 a voice assistant embedded in a web app.",
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
- tools: [navigateTool, executeFunctionTool, ...directFunctionTools]
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: [...functionsRegistry.warnings, ...backendWarnings, ...aliasWarnings] };
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 fallbackMatcher = createPathMatcher(input.defaultFunctionsFolder);
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/") && fallbackMatcher(entry.normalizedPath)
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
  });