@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.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Orb,
|
|
3
|
+
useNavaiVoiceOrbStyles
|
|
4
|
+
} from "./chunk-KBBRQQLK.js";
|
|
5
|
+
|
|
1
6
|
// src/agent.ts
|
|
2
7
|
import { RealtimeAgent, tool } from "@openai/agents/realtime";
|
|
3
8
|
import { z } from "zod";
|
|
@@ -242,27 +247,33 @@ function getNavaiRoutePromptLines(routes = []) {
|
|
|
242
247
|
// src/agent.ts
|
|
243
248
|
var RESERVED_TOOL_NAMES = /* @__PURE__ */ new Set(["navigate_to", "execute_app_function"]);
|
|
244
249
|
var TOOL_NAME_REGEXP = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
250
|
+
var DEBUG_PREFIX = "[navai debug]";
|
|
245
251
|
function toErrorMessage2(error) {
|
|
246
252
|
return error instanceof Error ? error.message : String(error);
|
|
247
253
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
254
|
+
function debugLog(message, details) {
|
|
255
|
+
if (details === void 0) {
|
|
256
|
+
console.log(`${DEBUG_PREFIX} ${message}`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
console.log(`${DEBUG_PREFIX} ${message}`, details);
|
|
260
|
+
}
|
|
261
|
+
function normalizeBackendFunctions(backendFunctions, functionsRegistry, warnings) {
|
|
251
262
|
const backendFunctionsByName = /* @__PURE__ */ new Map();
|
|
252
263
|
const backendFunctionsOrdered = [];
|
|
253
|
-
for (const backendFunction of
|
|
264
|
+
for (const backendFunction of backendFunctions ?? []) {
|
|
254
265
|
const name = backendFunction.name.trim().toLowerCase();
|
|
255
266
|
if (!name) {
|
|
256
267
|
continue;
|
|
257
268
|
}
|
|
258
269
|
if (functionsRegistry.byName.has(name)) {
|
|
259
|
-
|
|
270
|
+
warnings.push(
|
|
260
271
|
`[navai] Ignored backend function "${backendFunction.name}": name conflicts with a frontend function.`
|
|
261
272
|
);
|
|
262
273
|
continue;
|
|
263
274
|
}
|
|
264
275
|
if (backendFunctionsByName.has(name)) {
|
|
265
|
-
|
|
276
|
+
warnings.push(`[navai] Ignored duplicated backend function "${backendFunction.name}".`);
|
|
266
277
|
continue;
|
|
267
278
|
}
|
|
268
279
|
const normalizedDefinition = {
|
|
@@ -272,94 +283,119 @@ async function buildNavaiAgent(options) {
|
|
|
272
283
|
backendFunctionsByName.set(name, normalizedDefinition);
|
|
273
284
|
backendFunctionsOrdered.push(normalizedDefinition);
|
|
274
285
|
}
|
|
286
|
+
return backendFunctionsOrdered;
|
|
287
|
+
}
|
|
288
|
+
function createExecuteAppFunction(input) {
|
|
289
|
+
const backendFunctionsByName = new Map(input.backendFunctions.map((item) => [item.name, item]));
|
|
275
290
|
const availableFunctionNames = [
|
|
276
|
-
...functionsRegistry.ordered.map((item) => item.name),
|
|
277
|
-
...
|
|
291
|
+
...input.functionsRegistry.ordered.map((item) => item.name),
|
|
292
|
+
...input.backendFunctions.map((item) => item.name)
|
|
278
293
|
];
|
|
279
|
-
const aliasWarnings = [];
|
|
280
|
-
const directFunctionToolNames = [...new Set(availableFunctionNames)].map((name) => name.trim().toLowerCase()).filter((name) => {
|
|
281
|
-
if (!name) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
if (RESERVED_TOOL_NAMES.has(name)) {
|
|
285
|
-
aliasWarnings.push(
|
|
286
|
-
`[navai] Function "${name}" is available only via execute_app_function because its name conflicts with a built-in tool.`
|
|
287
|
-
);
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
if (!TOOL_NAME_REGEXP.test(name)) {
|
|
291
|
-
aliasWarnings.push(
|
|
292
|
-
`[navai] Function "${name}" is available only via execute_app_function because its name is not a valid tool id.`
|
|
293
|
-
);
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
return true;
|
|
297
|
-
});
|
|
298
294
|
const executeAppFunction = async (requestedName, payload) => {
|
|
299
295
|
const requested = requestedName.trim().toLowerCase();
|
|
300
|
-
|
|
296
|
+
debugLog("execute_app_function called", { requestedName, requested, payload });
|
|
297
|
+
const frontendDefinition = input.functionsRegistry.byName.get(requested);
|
|
301
298
|
if (frontendDefinition) {
|
|
302
299
|
try {
|
|
303
|
-
|
|
304
|
-
|
|
300
|
+
debugLog("executing frontend function", {
|
|
301
|
+
functionName: frontendDefinition.name,
|
|
302
|
+
source: frontendDefinition.source
|
|
303
|
+
});
|
|
304
|
+
const result = await frontendDefinition.run(payload ?? {}, input.context);
|
|
305
|
+
const response = {
|
|
306
|
+
ok: true,
|
|
307
|
+
function_name: frontendDefinition.name,
|
|
308
|
+
source: frontendDefinition.source,
|
|
309
|
+
result
|
|
310
|
+
};
|
|
311
|
+
debugLog("frontend function completed", response);
|
|
312
|
+
return response;
|
|
305
313
|
} catch (error) {
|
|
306
|
-
|
|
314
|
+
const failure = {
|
|
307
315
|
ok: false,
|
|
308
316
|
function_name: frontendDefinition.name,
|
|
309
317
|
error: "Function execution failed.",
|
|
310
318
|
details: toErrorMessage2(error)
|
|
311
319
|
};
|
|
320
|
+
debugLog("frontend function failed", failure);
|
|
321
|
+
return failure;
|
|
312
322
|
}
|
|
313
323
|
}
|
|
314
324
|
const backendDefinition = backendFunctionsByName.get(requested);
|
|
315
325
|
if (!backendDefinition) {
|
|
316
|
-
|
|
326
|
+
const failure = {
|
|
317
327
|
ok: false,
|
|
318
328
|
error: "Unknown or disallowed function.",
|
|
319
329
|
available_functions: availableFunctionNames
|
|
320
330
|
};
|
|
331
|
+
debugLog("execute_app_function rejected unknown function", failure);
|
|
332
|
+
return failure;
|
|
321
333
|
}
|
|
322
|
-
if (!
|
|
323
|
-
|
|
334
|
+
if (!input.executeBackendFunction) {
|
|
335
|
+
const failure = {
|
|
324
336
|
ok: false,
|
|
325
337
|
function_name: backendDefinition.name,
|
|
326
338
|
error: "Backend function execution is not configured."
|
|
327
339
|
};
|
|
340
|
+
debugLog("backend function execution unavailable", failure);
|
|
341
|
+
return failure;
|
|
328
342
|
}
|
|
329
343
|
try {
|
|
330
|
-
|
|
344
|
+
debugLog("executing backend function", {
|
|
345
|
+
functionName: backendDefinition.name,
|
|
346
|
+
source: backendDefinition.source ?? "backend"
|
|
347
|
+
});
|
|
348
|
+
const result = await input.executeBackendFunction({
|
|
331
349
|
functionName: backendDefinition.name,
|
|
332
350
|
payload: payload ?? null
|
|
333
351
|
});
|
|
334
|
-
|
|
352
|
+
const response = {
|
|
335
353
|
ok: true,
|
|
336
354
|
function_name: backendDefinition.name,
|
|
337
355
|
source: backendDefinition.source ?? "backend",
|
|
338
356
|
result
|
|
339
357
|
};
|
|
358
|
+
debugLog("backend function completed", response);
|
|
359
|
+
return response;
|
|
340
360
|
} catch (error) {
|
|
341
|
-
|
|
361
|
+
const failure = {
|
|
342
362
|
ok: false,
|
|
343
363
|
function_name: backendDefinition.name,
|
|
344
364
|
error: "Function execution failed.",
|
|
345
365
|
details: toErrorMessage2(error)
|
|
346
366
|
};
|
|
367
|
+
debugLog("backend function failed", failure);
|
|
368
|
+
return failure;
|
|
347
369
|
}
|
|
348
370
|
};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
return {
|
|
372
|
+
availableFunctionNames,
|
|
373
|
+
executeAppFunction
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function createFunctionTools(input) {
|
|
377
|
+
const aliasWarnings = [];
|
|
378
|
+
const availableFunctionNames = [
|
|
379
|
+
...input.functionsRegistry.ordered.map((item) => item.name),
|
|
380
|
+
...input.backendFunctions.map((item) => item.name)
|
|
381
|
+
];
|
|
382
|
+
const directFunctionToolNames = input.includeDirectAliases === false ? [] : [...new Set(availableFunctionNames)].map((name) => name.trim().toLowerCase()).filter((name) => {
|
|
383
|
+
if (!name) {
|
|
384
|
+
return false;
|
|
362
385
|
}
|
|
386
|
+
if (RESERVED_TOOL_NAMES.has(name)) {
|
|
387
|
+
aliasWarnings.push(
|
|
388
|
+
`[navai] Function "${name}" is available only via execute_app_function because its name conflicts with a built-in tool.`
|
|
389
|
+
);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
if (!TOOL_NAME_REGEXP.test(name)) {
|
|
393
|
+
aliasWarnings.push(
|
|
394
|
+
`[navai] Function "${name}" is available only via execute_app_function because its name is not a valid tool id.`
|
|
395
|
+
);
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
return true;
|
|
363
399
|
});
|
|
364
400
|
const executeFunctionTool = tool({
|
|
365
401
|
name: "execute_app_function",
|
|
@@ -370,36 +406,146 @@ async function buildNavaiAgent(options) {
|
|
|
370
406
|
"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."
|
|
371
407
|
)
|
|
372
408
|
}),
|
|
373
|
-
execute: async ({ function_name, payload }) => await executeAppFunction(function_name, payload)
|
|
409
|
+
execute: async ({ function_name, payload }) => await input.executeAppFunction(function_name, payload)
|
|
374
410
|
});
|
|
375
411
|
const directFunctionTools = directFunctionToolNames.map(
|
|
376
412
|
(functionName) => tool({
|
|
377
413
|
name: functionName,
|
|
378
414
|
description: `Direct alias for execute_app_function("${functionName}").`,
|
|
379
415
|
parameters: z.object({
|
|
380
|
-
payload: z.record(z.string(), z.unknown()).nullable().
|
|
416
|
+
payload: z.record(z.string(), z.unknown()).nullable().describe(
|
|
381
417
|
"Payload object. Optional. Use payload.args as array for function args, payload.constructorArgs for class constructors, payload.methodArgs for class methods."
|
|
382
418
|
)
|
|
383
419
|
}),
|
|
384
|
-
execute: async ({ payload }) => await executeAppFunction(functionName, payload ?? null)
|
|
420
|
+
execute: async ({ payload }) => await input.executeAppFunction(functionName, payload ?? null)
|
|
385
421
|
})
|
|
386
422
|
);
|
|
387
|
-
|
|
388
|
-
|
|
423
|
+
return {
|
|
424
|
+
aliasWarnings,
|
|
425
|
+
availableFunctionNames,
|
|
426
|
+
executeFunctionTool,
|
|
427
|
+
directFunctionTools
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function buildFunctionLines(functionsRegistry, backendFunctions) {
|
|
431
|
+
return functionsRegistry.ordered.length + backendFunctions.length > 0 ? [
|
|
389
432
|
...functionsRegistry.ordered.map((item) => `- ${item.name}: ${item.description}`),
|
|
390
|
-
...
|
|
391
|
-
(item) => `- ${item.name}: ${item.description ?? "Execute backend function."}`
|
|
392
|
-
)
|
|
433
|
+
...backendFunctions.map((item) => `- ${item.name}: ${item.description ?? "Execute backend function."}`)
|
|
393
434
|
] : ["- none"];
|
|
435
|
+
}
|
|
436
|
+
async function buildNavaiAgent(options) {
|
|
437
|
+
const aggregatedWarnings = [];
|
|
438
|
+
const configuredAgents = (options.agents ?? []).filter(
|
|
439
|
+
(agent2) => Object.keys(agent2.functionModuleLoaders ?? {}).length > 0
|
|
440
|
+
);
|
|
441
|
+
const primaryAgentConfig = configuredAgents.find((agent2) => agent2.key === options.primaryAgentKey) ?? configuredAgents.find((agent2) => agent2.isPrimary) ?? configuredAgents[0];
|
|
442
|
+
const primaryFunctionLoaders = primaryAgentConfig?.functionModuleLoaders ?? options.functionModuleLoaders ?? {};
|
|
443
|
+
const functionsRegistry = await loadNavaiFunctions(primaryFunctionLoaders);
|
|
444
|
+
const backendFunctionsOrdered = normalizeBackendFunctions(options.backendFunctions, functionsRegistry, aggregatedWarnings);
|
|
445
|
+
const primaryExecutionSurface = createExecuteAppFunction({
|
|
446
|
+
functionsRegistry,
|
|
447
|
+
backendFunctions: backendFunctionsOrdered,
|
|
448
|
+
executeBackendFunction: options.executeBackendFunction,
|
|
449
|
+
context: options
|
|
450
|
+
});
|
|
451
|
+
const primaryFunctionTools = createFunctionTools({
|
|
452
|
+
functionsRegistry,
|
|
453
|
+
backendFunctions: backendFunctionsOrdered,
|
|
454
|
+
executeAppFunction: primaryExecutionSurface.executeAppFunction
|
|
455
|
+
});
|
|
456
|
+
aggregatedWarnings.push(...functionsRegistry.warnings, ...primaryFunctionTools.aliasWarnings);
|
|
457
|
+
const navigateTool = tool({
|
|
458
|
+
name: "navigate_to",
|
|
459
|
+
description: "Navigate to an allowed route in the current app.",
|
|
460
|
+
parameters: z.object({
|
|
461
|
+
target: z.string().min(1).describe("Route name or route path. Example: perfil, ajustes, /profile, /settings")
|
|
462
|
+
}),
|
|
463
|
+
execute: async ({ target }) => {
|
|
464
|
+
debugLog("navigate_to called", { target });
|
|
465
|
+
const path = resolveNavaiRoute(target, options.routes);
|
|
466
|
+
if (!path) {
|
|
467
|
+
const failure = { ok: false, error: "Unknown or disallowed route." };
|
|
468
|
+
debugLog("navigate_to rejected", failure);
|
|
469
|
+
return failure;
|
|
470
|
+
}
|
|
471
|
+
options.navigate(path);
|
|
472
|
+
const response = { ok: true, path };
|
|
473
|
+
debugLog("navigate_to completed", response);
|
|
474
|
+
return response;
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
const routeLines = getNavaiRoutePromptLines(options.routes);
|
|
478
|
+
const functionLines = buildFunctionLines(functionsRegistry, backendFunctionsOrdered);
|
|
479
|
+
const specialistAgents = [];
|
|
480
|
+
const specialistLines = [];
|
|
481
|
+
for (const runtimeAgent of configuredAgents) {
|
|
482
|
+
if (primaryAgentConfig && runtimeAgent.key === primaryAgentConfig.key) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const specialistRegistry = await loadNavaiFunctions(runtimeAgent.functionModuleLoaders);
|
|
486
|
+
const specialistWarnings = [...specialistRegistry.warnings];
|
|
487
|
+
const specialistBackendFunctions = normalizeBackendFunctions(
|
|
488
|
+
options.backendFunctions,
|
|
489
|
+
specialistRegistry,
|
|
490
|
+
specialistWarnings
|
|
491
|
+
);
|
|
492
|
+
const specialistExecutionSurface = createExecuteAppFunction({
|
|
493
|
+
functionsRegistry: specialistRegistry,
|
|
494
|
+
backendFunctions: specialistBackendFunctions,
|
|
495
|
+
executeBackendFunction: options.executeBackendFunction,
|
|
496
|
+
context: options
|
|
497
|
+
});
|
|
498
|
+
const specialistFunctionTools = createFunctionTools({
|
|
499
|
+
functionsRegistry: specialistRegistry,
|
|
500
|
+
backendFunctions: specialistBackendFunctions,
|
|
501
|
+
executeAppFunction: specialistExecutionSurface.executeAppFunction,
|
|
502
|
+
includeDirectAliases: false
|
|
503
|
+
});
|
|
504
|
+
specialistWarnings.push(...specialistFunctionTools.aliasWarnings);
|
|
505
|
+
aggregatedWarnings.push(...specialistWarnings);
|
|
506
|
+
const specialistInstructions = [
|
|
507
|
+
runtimeAgent.instructions ?? `You are the ${runtimeAgent.name} specialist agent for this web app.`,
|
|
508
|
+
"Allowed app functions:",
|
|
509
|
+
...buildFunctionLines(specialistRegistry, specialistBackendFunctions),
|
|
510
|
+
"Rules:",
|
|
511
|
+
"- Always use execute_app_function for app actions.",
|
|
512
|
+
"- When no arguments are needed, call execute_app_function with payload set to null.",
|
|
513
|
+
"- Use only the functions available to this specialist agent.",
|
|
514
|
+
"- Do not navigate unless one of your allowed functions explicitly does so.",
|
|
515
|
+
"- Return a concise result to the main NAVAI agent."
|
|
516
|
+
].join("\n");
|
|
517
|
+
debugLog("creating specialist agent", {
|
|
518
|
+
key: runtimeAgent.key,
|
|
519
|
+
name: runtimeAgent.name,
|
|
520
|
+
functions: [
|
|
521
|
+
...specialistRegistry.ordered.map((item) => item.name),
|
|
522
|
+
...specialistBackendFunctions.map((item) => item.name)
|
|
523
|
+
]
|
|
524
|
+
});
|
|
525
|
+
const specialistAgent = new RealtimeAgent({
|
|
526
|
+
name: runtimeAgent.name,
|
|
527
|
+
handoffDescription: runtimeAgent.handoffDescription ?? runtimeAgent.description ?? `Delegate specialist work to ${runtimeAgent.name}.`,
|
|
528
|
+
instructions: specialistInstructions,
|
|
529
|
+
tools: [specialistFunctionTools.executeFunctionTool]
|
|
530
|
+
});
|
|
531
|
+
specialistAgents.push(specialistAgent);
|
|
532
|
+
specialistLines.push(
|
|
533
|
+
`- ${runtimeAgent.name}: ${runtimeAgent.description ?? runtimeAgent.handoffDescription ?? "Specialist agent available by delegation."}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
394
536
|
const instructions = [
|
|
395
|
-
options.baseInstructions ?? "You are
|
|
537
|
+
primaryAgentConfig?.instructions ?? options.baseInstructions ?? "You are the main NAVAI voice agent embedded in a web app.",
|
|
396
538
|
"Allowed routes:",
|
|
397
539
|
...routeLines,
|
|
398
540
|
"Allowed app functions:",
|
|
399
541
|
...functionLines,
|
|
542
|
+
"Available specialist agents:",
|
|
543
|
+
...specialistLines.length > 0 ? specialistLines : ["- none"],
|
|
400
544
|
"Rules:",
|
|
401
545
|
"- If user asks to go/open a section, always call navigate_to.",
|
|
402
|
-
"- If user asks to run an internal action, call execute_app_function or the matching direct function tool.",
|
|
546
|
+
"- If user asks to run an internal action that belongs to you, call execute_app_function or the matching direct function tool.",
|
|
547
|
+
"- If the task clearly belongs to a specialist agent, hand off to that specialist agent.",
|
|
548
|
+
"- Food recommendations, fast food, hamburgers, pizza, tacos, snacks, and meal suggestions belong to the food specialist.",
|
|
403
549
|
"- Always include payload in execute_app_function. Use null when no arguments are needed.",
|
|
404
550
|
"- For execute_app_function, pass arguments using payload.args (array).",
|
|
405
551
|
"- For class methods, pass payload.constructorArgs and payload.methodArgs.",
|
|
@@ -407,11 +553,23 @@ async function buildNavaiAgent(options) {
|
|
|
407
553
|
"- If destination/action is unclear, ask a brief clarifying question."
|
|
408
554
|
].join("\n");
|
|
409
555
|
const agent = new RealtimeAgent({
|
|
410
|
-
name: options.agentName ?? "Navai Voice Agent",
|
|
556
|
+
name: primaryAgentConfig?.name ?? options.agentName ?? "Navai Voice Agent",
|
|
411
557
|
instructions,
|
|
412
|
-
|
|
558
|
+
handoffs: specialistAgents,
|
|
559
|
+
tools: [
|
|
560
|
+
navigateTool,
|
|
561
|
+
primaryFunctionTools.executeFunctionTool,
|
|
562
|
+
...primaryFunctionTools.directFunctionTools
|
|
563
|
+
]
|
|
564
|
+
});
|
|
565
|
+
debugLog("created primary agent", {
|
|
566
|
+
name: primaryAgentConfig?.name ?? options.agentName ?? "Navai Voice Agent",
|
|
567
|
+
primaryAgentKey: primaryAgentConfig?.key ?? null,
|
|
568
|
+
directFunctions: functionsRegistry.ordered.map((item) => item.name),
|
|
569
|
+
backendFunctions: backendFunctionsOrdered.map((item) => item.name),
|
|
570
|
+
specialistDelegates: configuredAgents.filter((runtimeAgent) => runtimeAgent.key !== primaryAgentConfig?.key).map((runtimeAgent) => runtimeAgent.key)
|
|
413
571
|
});
|
|
414
|
-
return { agent, warnings:
|
|
572
|
+
return { agent, warnings: aggregatedWarnings };
|
|
415
573
|
}
|
|
416
574
|
|
|
417
575
|
// src/backend.ts
|
|
@@ -534,6 +692,7 @@ function createNavaiBackendClient(options = {}) {
|
|
|
534
692
|
// src/runtime.ts
|
|
535
693
|
var ROUTES_ENV_KEYS = ["NAVAI_ROUTES_FILE"];
|
|
536
694
|
var FUNCTIONS_ENV_KEYS = ["NAVAI_FUNCTIONS_FOLDERS"];
|
|
695
|
+
var AGENTS_ENV_KEYS = ["NAVAI_AGENTS_FOLDERS"];
|
|
537
696
|
var MODEL_ENV_KEYS = ["NAVAI_REALTIME_MODEL"];
|
|
538
697
|
async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
539
698
|
const warnings = [];
|
|
@@ -543,6 +702,7 @@ async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
|
543
702
|
const defaultFunctionsFolder = options.defaultFunctionsFolder ?? "src/ai/functions-modules";
|
|
544
703
|
const routesFile = readOptional2(options.routesFile) ?? readFirstOptionalEnv(options.env, ROUTES_ENV_KEYS) ?? defaultRoutesFile;
|
|
545
704
|
const functionsFolders = readOptional2(options.functionsFolders) ?? readFirstOptionalEnv(options.env, FUNCTIONS_ENV_KEYS) ?? defaultFunctionsFolder;
|
|
705
|
+
const agentsFolders = readOptional2(options.agentsFolders) ?? readFirstOptionalEnv(options.env, AGENTS_ENV_KEYS);
|
|
546
706
|
const modelOverride = readOptional2(options.modelOverride) ?? readFirstOptionalEnv(options.env, MODEL_ENV_KEYS);
|
|
547
707
|
const routes = await resolveRoutes({
|
|
548
708
|
routesFile,
|
|
@@ -554,12 +714,23 @@ async function resolveNavaiFrontendRuntimeConfig(options) {
|
|
|
554
714
|
const functionModuleLoaders = resolveFunctionModuleLoaders({
|
|
555
715
|
indexedLoaders,
|
|
556
716
|
functionsFolders,
|
|
717
|
+
agentsFolders,
|
|
557
718
|
defaultFunctionsFolder,
|
|
558
719
|
warnings
|
|
559
720
|
});
|
|
721
|
+
const agents = await resolveRuntimeAgents({
|
|
722
|
+
indexedLoaders,
|
|
723
|
+
functionModuleLoaders,
|
|
724
|
+
functionsFolders,
|
|
725
|
+
agentsFolders,
|
|
726
|
+
defaultFunctionsFolder
|
|
727
|
+
});
|
|
728
|
+
const primaryAgentKey = agents.find((agent) => agent.isPrimary)?.key;
|
|
560
729
|
return {
|
|
561
730
|
routes,
|
|
562
731
|
functionModuleLoaders,
|
|
732
|
+
agents,
|
|
733
|
+
primaryAgentKey,
|
|
563
734
|
modelOverride,
|
|
564
735
|
warnings
|
|
565
736
|
};
|
|
@@ -595,10 +766,11 @@ async function resolveRoutes(input) {
|
|
|
595
766
|
}
|
|
596
767
|
function resolveFunctionModuleLoaders(input) {
|
|
597
768
|
const configuredTokens = input.functionsFolders.split(",").map((value) => value.trim()).filter(Boolean);
|
|
769
|
+
const agentFolders = parseCsvList(input.agentsFolders);
|
|
598
770
|
const tokens = configuredTokens.length > 0 ? configuredTokens : [input.defaultFunctionsFolder];
|
|
599
|
-
const matchers = tokens.map((token) => createPathMatcher(token));
|
|
771
|
+
const matchers = tokens.map((token) => createPathMatcher(token, agentFolders));
|
|
600
772
|
const matchedEntries = input.indexedLoaders.filter(
|
|
601
|
-
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && matchers.some((matcher) => matcher(entry.normalizedPath))
|
|
773
|
+
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && !isAgentConfigPath(entry.normalizedPath) && matchers.some((matcher) => matcher(entry.normalizedPath))
|
|
602
774
|
);
|
|
603
775
|
if (matchedEntries.length > 0) {
|
|
604
776
|
return Object.fromEntries(matchedEntries.map((entry) => [entry.rawPath, entry.load]));
|
|
@@ -608,9 +780,9 @@ function resolveFunctionModuleLoaders(input) {
|
|
|
608
780
|
`[navai] NAVAI_FUNCTIONS_FOLDERS did not match any module: "${input.functionsFolders}". Falling back to "${input.defaultFunctionsFolder}".`
|
|
609
781
|
);
|
|
610
782
|
}
|
|
611
|
-
const
|
|
783
|
+
const fallbackMatcherWithAgents = createPathMatcher(input.defaultFunctionsFolder, agentFolders);
|
|
612
784
|
const fallbackEntries = input.indexedLoaders.filter(
|
|
613
|
-
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") &&
|
|
785
|
+
(entry) => !entry.normalizedPath.endsWith(".d.ts") && !entry.normalizedPath.startsWith("src/node_modules/") && !isAgentConfigPath(entry.normalizedPath) && fallbackMatcherWithAgents(entry.normalizedPath)
|
|
614
786
|
);
|
|
615
787
|
return Object.fromEntries(fallbackEntries.map((entry) => [entry.rawPath, entry.load]));
|
|
616
788
|
}
|
|
@@ -653,7 +825,7 @@ function buildModuleCandidates(inputPath) {
|
|
|
653
825
|
}
|
|
654
826
|
return [srcPrefixed, `${srcPrefixed}.ts`, `${srcPrefixed}.js`, `${srcPrefixed}/index.ts`, `${srcPrefixed}/index.js`];
|
|
655
827
|
}
|
|
656
|
-
function createPathMatcher(input) {
|
|
828
|
+
function createPathMatcher(input, agentFolders = []) {
|
|
657
829
|
const raw = normalizePath(input);
|
|
658
830
|
if (!raw) {
|
|
659
831
|
return () => false;
|
|
@@ -671,8 +843,141 @@ function createPathMatcher(input) {
|
|
|
671
843
|
return (path) => path === normalized;
|
|
672
844
|
}
|
|
673
845
|
const base = normalized.replace(/\/+$/, "");
|
|
846
|
+
const normalizedAgents = agentFolders.map(normalizePathSegment).filter(Boolean);
|
|
847
|
+
if (normalizedAgents.length > 0) {
|
|
848
|
+
return (path) => {
|
|
849
|
+
if (!path.startsWith(`${base}/`)) {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
const suffix = path.slice(base.length + 1);
|
|
853
|
+
const firstSegment = suffix.split("/", 1)[0] ?? "";
|
|
854
|
+
return normalizedAgents.includes(firstSegment);
|
|
855
|
+
};
|
|
856
|
+
}
|
|
674
857
|
return (path) => path === base || path.startsWith(`${base}/`);
|
|
675
858
|
}
|
|
859
|
+
async function resolveRuntimeAgents(input) {
|
|
860
|
+
const configuredAgents = parseCsvList(input.agentsFolders);
|
|
861
|
+
if (configuredAgents.length === 0) {
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
864
|
+
const loaderByPath = new Map(input.indexedLoaders.map((entry) => [entry.normalizedPath, entry]));
|
|
865
|
+
const baseDirectories = resolveAgentBaseDirectories(input.functionsFolders, input.defaultFunctionsFolder);
|
|
866
|
+
const groupedLoaders = /* @__PURE__ */ new Map();
|
|
867
|
+
for (const [rawPath, load] of Object.entries(input.functionModuleLoaders)) {
|
|
868
|
+
const agentKey = extractAgentKeyFromPath(rawPath, baseDirectories, configuredAgents);
|
|
869
|
+
if (!agentKey) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const current = groupedLoaders.get(agentKey) ?? {};
|
|
873
|
+
current[rawPath] = load;
|
|
874
|
+
groupedLoaders.set(agentKey, current);
|
|
875
|
+
}
|
|
876
|
+
const configuredPrimaryKey = configuredAgents[0];
|
|
877
|
+
const agents = [];
|
|
878
|
+
for (const agentKey of configuredAgents) {
|
|
879
|
+
const functionLoaders = groupedLoaders.get(agentKey);
|
|
880
|
+
if (!functionLoaders || Object.keys(functionLoaders).length === 0) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const config = await loadAgentModuleConfig(agentKey, baseDirectories, loaderByPath);
|
|
884
|
+
agents.push({
|
|
885
|
+
key: config.key?.trim() || agentKey,
|
|
886
|
+
name: readOptional2(config.name) ?? humanizeAgentKey(agentKey),
|
|
887
|
+
description: readOptional2(config.description),
|
|
888
|
+
handoffDescription: readOptional2(config.handoffDescription) ?? readOptional2(config.description),
|
|
889
|
+
instructions: readOptional2(config.instructions),
|
|
890
|
+
isPrimary: config.isPrimary === true || agentKey === configuredPrimaryKey,
|
|
891
|
+
functionModuleLoaders: functionLoaders
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (agents.filter((agent) => agent.isPrimary).length === 0 && agents[0]) {
|
|
895
|
+
agents[0].isPrimary = true;
|
|
896
|
+
}
|
|
897
|
+
if (agents.filter((agent) => agent.isPrimary).length > 1) {
|
|
898
|
+
let primaryAssigned = false;
|
|
899
|
+
for (const agent of agents) {
|
|
900
|
+
if (agent.isPrimary && !primaryAssigned) {
|
|
901
|
+
primaryAssigned = true;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
agent.isPrimary = false;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return agents;
|
|
908
|
+
}
|
|
909
|
+
async function loadAgentModuleConfig(agentKey, baseDirectories, loaderByPath) {
|
|
910
|
+
for (const baseDirectory of baseDirectories) {
|
|
911
|
+
const configBase = `${baseDirectory}/${agentKey}/agent.config`;
|
|
912
|
+
const matchedLoader = buildModuleCandidates(configBase).map((candidate) => loaderByPath.get(candidate)).find(Boolean);
|
|
913
|
+
if (!matchedLoader) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
const imported = await matchedLoader.load();
|
|
918
|
+
return readAgentModuleConfig(imported);
|
|
919
|
+
} catch {
|
|
920
|
+
return {};
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return {};
|
|
924
|
+
}
|
|
925
|
+
function readAgentModuleConfig(moduleShape) {
|
|
926
|
+
const candidate = readRecord(moduleShape.NAVAI_AGENT) ?? readRecord(moduleShape.agent) ?? readRecord(moduleShape.default) ?? {};
|
|
927
|
+
return {
|
|
928
|
+
key: readOptionalString(candidate.key),
|
|
929
|
+
name: readOptionalString(candidate.name),
|
|
930
|
+
description: readOptionalString(candidate.description),
|
|
931
|
+
handoffDescription: readOptionalString(candidate.handoffDescription),
|
|
932
|
+
instructions: readOptionalString(candidate.instructions),
|
|
933
|
+
isPrimary: candidate.isPrimary === true
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function readRecord(value) {
|
|
937
|
+
return value && typeof value === "object" ? value : null;
|
|
938
|
+
}
|
|
939
|
+
function readOptionalString(value) {
|
|
940
|
+
return typeof value === "string" ? readOptional2(value) : void 0;
|
|
941
|
+
}
|
|
942
|
+
function resolveAgentBaseDirectories(functionsFolders, defaultFunctionsFolder) {
|
|
943
|
+
const configuredTokens = functionsFolders.split(",").map((value) => value.trim()).filter(Boolean);
|
|
944
|
+
const tokens = configuredTokens.length > 0 ? configuredTokens : [defaultFunctionsFolder];
|
|
945
|
+
return [...new Set(tokens.map(toAgentBaseDirectory).filter(Boolean))];
|
|
946
|
+
}
|
|
947
|
+
function toAgentBaseDirectory(input) {
|
|
948
|
+
const raw = normalizePath(input);
|
|
949
|
+
if (!raw) {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
const normalized = raw.startsWith("src/") ? raw : `src/${raw}`;
|
|
953
|
+
if (normalized.includes("*") || /\.[cm]?[jt]s$/.test(normalized)) {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
if (normalized.endsWith("/...")) {
|
|
957
|
+
return normalized.slice(0, -4).replace(/\/+$/, "") || null;
|
|
958
|
+
}
|
|
959
|
+
return normalized.replace(/\/+$/, "") || null;
|
|
960
|
+
}
|
|
961
|
+
function extractAgentKeyFromPath(pathValue, baseDirectories, configuredAgents) {
|
|
962
|
+
const normalized = normalizePath(pathValue);
|
|
963
|
+
for (const baseDirectory of baseDirectories) {
|
|
964
|
+
if (!normalized.startsWith(`${baseDirectory}/`)) {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
const suffix = normalized.slice(baseDirectory.length + 1);
|
|
968
|
+
const firstSegment = suffix.split("/", 1)[0] ?? "";
|
|
969
|
+
if (configuredAgents.includes(firstSegment)) {
|
|
970
|
+
return firstSegment;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
975
|
+
function humanizeAgentKey(value) {
|
|
976
|
+
return value.split(/[_-]+/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
977
|
+
}
|
|
978
|
+
function isAgentConfigPath(pathValue) {
|
|
979
|
+
return /\/agent\.config\.[cm]?[jt]s$/i.test(pathValue);
|
|
980
|
+
}
|
|
676
981
|
function globToRegExp(pattern) {
|
|
677
982
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
678
983
|
const wildcardSafe = escaped.replace(/\*\*/g, "___DOUBLE_STAR___");
|
|
@@ -683,6 +988,12 @@ function globToRegExp(pattern) {
|
|
|
683
988
|
function normalizePath(input) {
|
|
684
989
|
return input.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^(\.\/)+/, "").replace(/^(\.\.\/)+/, "");
|
|
685
990
|
}
|
|
991
|
+
function normalizePathSegment(input) {
|
|
992
|
+
return normalizePath(input).replace(/\//g, "");
|
|
993
|
+
}
|
|
994
|
+
function parseCsvList(input) {
|
|
995
|
+
return (input ?? "").split(",").map((value) => normalizePathSegment(value)).filter(Boolean);
|
|
996
|
+
}
|
|
686
997
|
function readFirstOptionalEnv(env, keys) {
|
|
687
998
|
if (!env) {
|
|
688
999
|
return void 0;
|
|
@@ -713,6 +1024,7 @@ function toErrorMessage3(error) {
|
|
|
713
1024
|
// src/useWebVoiceAgent.ts
|
|
714
1025
|
import { RealtimeSession } from "@openai/agents/realtime";
|
|
715
1026
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
1027
|
+
var DEBUG_PREFIX2 = "[navai debug]";
|
|
716
1028
|
function formatError(error) {
|
|
717
1029
|
if (error instanceof Error) {
|
|
718
1030
|
return error.message;
|
|
@@ -726,6 +1038,13 @@ function emitWarnings(warnings) {
|
|
|
726
1038
|
}
|
|
727
1039
|
}
|
|
728
1040
|
}
|
|
1041
|
+
function debugLog2(message, details) {
|
|
1042
|
+
if (details === void 0) {
|
|
1043
|
+
console.log(`${DEBUG_PREFIX2} ${message}`);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
console.log(`${DEBUG_PREFIX2} ${message}`, details);
|
|
1047
|
+
}
|
|
729
1048
|
function useWebVoiceAgent(options) {
|
|
730
1049
|
const sessionRef = useRef(null);
|
|
731
1050
|
const attachedRealtimeSessionRef = useRef(null);
|
|
@@ -736,6 +1055,7 @@ function useWebVoiceAgent(options) {
|
|
|
736
1055
|
env: options.env,
|
|
737
1056
|
routesFile: options.routesFile,
|
|
738
1057
|
functionsFolders: options.functionsFolders,
|
|
1058
|
+
agentsFolders: options.agentsFolders,
|
|
739
1059
|
modelOverride: options.modelOverride,
|
|
740
1060
|
defaultRoutesFile: options.defaultRoutesFile,
|
|
741
1061
|
defaultFunctionsFolder: options.defaultFunctionsFolder
|
|
@@ -744,6 +1064,7 @@ function useWebVoiceAgent(options) {
|
|
|
744
1064
|
options.defaultFunctionsFolder,
|
|
745
1065
|
options.defaultRoutes,
|
|
746
1066
|
options.defaultRoutesFile,
|
|
1067
|
+
options.agentsFolders,
|
|
747
1068
|
options.env,
|
|
748
1069
|
options.functionsFolders,
|
|
749
1070
|
options.modelOverride,
|
|
@@ -790,6 +1111,45 @@ function useWebVoiceAgent(options) {
|
|
|
790
1111
|
const attachSessionAudioListeners = useCallback(
|
|
791
1112
|
(session) => {
|
|
792
1113
|
detachSessionAudioListeners();
|
|
1114
|
+
session.on("agent_start", (_context, agent, turnInput) => {
|
|
1115
|
+
debugLog2("session agent_start", {
|
|
1116
|
+
agent: agent.name,
|
|
1117
|
+
turnInputCount: Array.isArray(turnInput) ? turnInput.length : 0
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
session.on("agent_end", (_context, agent, output) => {
|
|
1121
|
+
debugLog2("session agent_end", {
|
|
1122
|
+
agent: agent.name,
|
|
1123
|
+
output
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
session.on("agent_handoff", (_context, fromAgent, toAgent) => {
|
|
1127
|
+
debugLog2("session agent_handoff", {
|
|
1128
|
+
from: fromAgent.name,
|
|
1129
|
+
to: toAgent.name
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
session.on("agent_tool_start", (_context, agent, tool2, details) => {
|
|
1133
|
+
debugLog2("session agent_tool_start", {
|
|
1134
|
+
agent: agent.name,
|
|
1135
|
+
tool: tool2.name,
|
|
1136
|
+
toolCall: details.toolCall
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
1139
|
+
session.on("agent_tool_end", (_context, agent, tool2, result, details) => {
|
|
1140
|
+
debugLog2("session agent_tool_end", {
|
|
1141
|
+
agent: agent.name,
|
|
1142
|
+
tool: tool2.name,
|
|
1143
|
+
result,
|
|
1144
|
+
toolCall: details.toolCall
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
session.on("history_added", (item) => {
|
|
1148
|
+
debugLog2("session history_added", item);
|
|
1149
|
+
});
|
|
1150
|
+
session.on("error", (sessionError) => {
|
|
1151
|
+
debugLog2("session error", sessionError);
|
|
1152
|
+
});
|
|
793
1153
|
session.on("audio_start", handleSessionAudioStart);
|
|
794
1154
|
session.on("audio_stopped", handleSessionAudioStopped);
|
|
795
1155
|
session.on("audio_interrupted", handleSessionAudioInterrupted);
|
|
@@ -828,6 +1188,17 @@ function useWebVoiceAgent(options) {
|
|
|
828
1188
|
setAgentVoiceStateIfChanged("idle");
|
|
829
1189
|
try {
|
|
830
1190
|
const runtimeConfig = await runtimeConfigPromise;
|
|
1191
|
+
debugLog2("resolved runtime config", {
|
|
1192
|
+
routes: runtimeConfig.routes.map((route) => route.path),
|
|
1193
|
+
functionModules: Object.keys(runtimeConfig.functionModuleLoaders),
|
|
1194
|
+
agents: runtimeConfig.agents.map((agent2) => ({
|
|
1195
|
+
key: agent2.key,
|
|
1196
|
+
name: agent2.name,
|
|
1197
|
+
isPrimary: agent2.isPrimary,
|
|
1198
|
+
functionModules: Object.keys(agent2.functionModuleLoaders)
|
|
1199
|
+
})),
|
|
1200
|
+
warnings: runtimeConfig.warnings
|
|
1201
|
+
});
|
|
831
1202
|
const requestPayload = runtimeConfig.modelOverride ? { model: runtimeConfig.modelOverride } : {};
|
|
832
1203
|
const secretPayload = await backendClient.createClientSecret(requestPayload);
|
|
833
1204
|
const backendFunctionsResult = await backendClient.listFunctions();
|
|
@@ -835,6 +1206,8 @@ function useWebVoiceAgent(options) {
|
|
|
835
1206
|
navigate: options.navigate,
|
|
836
1207
|
routes: runtimeConfig.routes,
|
|
837
1208
|
functionModuleLoaders: runtimeConfig.functionModuleLoaders,
|
|
1209
|
+
agents: runtimeConfig.agents,
|
|
1210
|
+
primaryAgentKey: runtimeConfig.primaryAgentKey,
|
|
838
1211
|
backendFunctions: backendFunctionsResult.functions,
|
|
839
1212
|
executeBackendFunction: backendClient.executeFunction
|
|
840
1213
|
});
|
|
@@ -850,6 +1223,7 @@ function useWebVoiceAgent(options) {
|
|
|
850
1223
|
setStatus("connected");
|
|
851
1224
|
} catch (startError) {
|
|
852
1225
|
const message = formatError(startError);
|
|
1226
|
+
debugLog2("session start failed", { message, error: startError });
|
|
853
1227
|
setError(message);
|
|
854
1228
|
setStatus("error");
|
|
855
1229
|
setAgentVoiceStateIfChanged("idle");
|
|
@@ -880,12 +1254,361 @@ function useWebVoiceAgent(options) {
|
|
|
880
1254
|
stop
|
|
881
1255
|
};
|
|
882
1256
|
}
|
|
1257
|
+
|
|
1258
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
1259
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
|
|
1260
|
+
|
|
1261
|
+
// src/orb/dynamic.tsx
|
|
1262
|
+
import { lazy, Suspense, useEffect as useEffect2, useState as useState2 } from "react";
|
|
1263
|
+
import { jsx } from "react/jsx-runtime";
|
|
1264
|
+
function dynamic(loader, options = {}) {
|
|
1265
|
+
const { ssr = true, loading: LoadingComponent } = options;
|
|
1266
|
+
const LazyComponent = lazy(async () => {
|
|
1267
|
+
const loaded = await loader();
|
|
1268
|
+
if (typeof loaded === "function") {
|
|
1269
|
+
return { default: loaded };
|
|
1270
|
+
}
|
|
1271
|
+
return loaded;
|
|
1272
|
+
});
|
|
1273
|
+
function DynamicComponent(props) {
|
|
1274
|
+
const [isClientReady, setIsClientReady] = useState2(ssr);
|
|
1275
|
+
useEffect2(() => {
|
|
1276
|
+
if (!ssr) {
|
|
1277
|
+
setIsClientReady(true);
|
|
1278
|
+
}
|
|
1279
|
+
}, [ssr]);
|
|
1280
|
+
if (!isClientReady) {
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
const fallback = LoadingComponent ? /* @__PURE__ */ jsx(LoadingComponent, {}) : null;
|
|
1284
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback, children: /* @__PURE__ */ jsx(LazyComponent, { ...props }) });
|
|
1285
|
+
}
|
|
1286
|
+
DynamicComponent.displayName = "DynamicComponent";
|
|
1287
|
+
return DynamicComponent;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/orb/NavaiHeroOrb.tsx
|
|
1291
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1292
|
+
var Orb2 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
|
|
1293
|
+
ssr: false
|
|
1294
|
+
});
|
|
1295
|
+
var ORB_DELAY_MS_MIN = 0;
|
|
1296
|
+
var ORB_DELAY_MS_MAX = 6e4;
|
|
1297
|
+
var DEFAULT_AUTOPLAY_DELAY_MS = 9e3;
|
|
1298
|
+
var DEFAULT_REVEAL_DELAY_MS = 5200;
|
|
1299
|
+
function clampNavaiOrbDelayMs(value, fallback) {
|
|
1300
|
+
const numericValue = Number.isFinite(value) ? value : fallback;
|
|
1301
|
+
return Math.min(ORB_DELAY_MS_MAX, Math.max(ORB_DELAY_MS_MIN, numericValue));
|
|
1302
|
+
}
|
|
1303
|
+
function NavaiHeroOrb({
|
|
1304
|
+
className = "",
|
|
1305
|
+
backgroundColor = "#000000",
|
|
1306
|
+
isAgentSpeaking = false,
|
|
1307
|
+
hoverIntensitySpeaking = 0.66,
|
|
1308
|
+
hoverIntensityIdle = 0.08,
|
|
1309
|
+
revealDelayMs = DEFAULT_REVEAL_DELAY_MS,
|
|
1310
|
+
autoplayDelayMs = DEFAULT_AUTOPLAY_DELAY_MS
|
|
1311
|
+
}) {
|
|
1312
|
+
useNavaiVoiceOrbStyles();
|
|
1313
|
+
const resolvedRevealDelayMs = clampNavaiOrbDelayMs(revealDelayMs, DEFAULT_REVEAL_DELAY_MS);
|
|
1314
|
+
const resolvedAutoplayDelayMs = clampNavaiOrbDelayMs(autoplayDelayMs, DEFAULT_AUTOPLAY_DELAY_MS);
|
|
1315
|
+
const [isOrbReady, setIsOrbReady] = useState3(resolvedRevealDelayMs === 0);
|
|
1316
|
+
const [isOrbAutoAnimating, setIsOrbAutoAnimating] = useState3(resolvedAutoplayDelayMs === 0);
|
|
1317
|
+
useEffect3(() => {
|
|
1318
|
+
if (typeof window === "undefined" || resolvedRevealDelayMs === 0) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const revealOrb = () => setIsOrbReady(true);
|
|
1322
|
+
window.addEventListener("pointerdown", revealOrb, { passive: true, once: true });
|
|
1323
|
+
window.addEventListener("touchstart", revealOrb, { passive: true, once: true });
|
|
1324
|
+
window.addEventListener("keydown", revealOrb, { once: true });
|
|
1325
|
+
const timeoutId = window.setTimeout(revealOrb, resolvedRevealDelayMs);
|
|
1326
|
+
return () => {
|
|
1327
|
+
window.removeEventListener("pointerdown", revealOrb);
|
|
1328
|
+
window.removeEventListener("touchstart", revealOrb);
|
|
1329
|
+
window.removeEventListener("keydown", revealOrb);
|
|
1330
|
+
window.clearTimeout(timeoutId);
|
|
1331
|
+
};
|
|
1332
|
+
}, [resolvedRevealDelayMs]);
|
|
1333
|
+
useEffect3(() => {
|
|
1334
|
+
if (typeof window === "undefined" || resolvedAutoplayDelayMs === 0) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
let started = false;
|
|
1338
|
+
const startOrbAnimation = () => {
|
|
1339
|
+
if (started) {
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
started = true;
|
|
1343
|
+
setIsOrbAutoAnimating(true);
|
|
1344
|
+
};
|
|
1345
|
+
if (navigator.userActivation?.hasBeenActive) {
|
|
1346
|
+
startOrbAnimation();
|
|
1347
|
+
}
|
|
1348
|
+
window.addEventListener("pointerdown", startOrbAnimation, { passive: true, once: true });
|
|
1349
|
+
window.addEventListener("touchstart", startOrbAnimation, { passive: true, once: true });
|
|
1350
|
+
window.addEventListener("keydown", startOrbAnimation, { once: true });
|
|
1351
|
+
const timeoutId = window.setTimeout(startOrbAnimation, resolvedAutoplayDelayMs);
|
|
1352
|
+
return () => {
|
|
1353
|
+
window.removeEventListener("pointerdown", startOrbAnimation);
|
|
1354
|
+
window.removeEventListener("touchstart", startOrbAnimation);
|
|
1355
|
+
window.removeEventListener("keydown", startOrbAnimation);
|
|
1356
|
+
window.clearTimeout(timeoutId);
|
|
1357
|
+
};
|
|
1358
|
+
}, [resolvedAutoplayDelayMs]);
|
|
1359
|
+
const orbHoverIntensity = useMemo2(() => {
|
|
1360
|
+
return isAgentSpeaking ? hoverIntensitySpeaking : hoverIntensityIdle;
|
|
1361
|
+
}, [hoverIntensityIdle, hoverIntensitySpeaking, isAgentSpeaking]);
|
|
1362
|
+
if (!isOrbReady) {
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
return /* @__PURE__ */ jsx2("div", { className: ["navai-voice-orb-hero", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx2(
|
|
1366
|
+
Orb2,
|
|
1367
|
+
{
|
|
1368
|
+
hoverIntensity: orbHoverIntensity,
|
|
1369
|
+
rotateOnHover: true,
|
|
1370
|
+
forceHoverState: isAgentSpeaking,
|
|
1371
|
+
enablePointerHover: false,
|
|
1372
|
+
animate: isAgentSpeaking || isOrbAutoAnimating,
|
|
1373
|
+
backgroundColor
|
|
1374
|
+
}
|
|
1375
|
+
) });
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/orb/NavaiMiniOrbDock.tsx
|
|
1379
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1380
|
+
var Orb3 = dynamic(() => import("./Orb-B4OSC3XR.js"), {
|
|
1381
|
+
ssr: false
|
|
1382
|
+
});
|
|
1383
|
+
function NavaiMiniOrbDock({
|
|
1384
|
+
className = "",
|
|
1385
|
+
style,
|
|
1386
|
+
themeMode = "dark",
|
|
1387
|
+
placement = "bottom-right",
|
|
1388
|
+
isActive = false,
|
|
1389
|
+
isConnected = false,
|
|
1390
|
+
isDisabled = false,
|
|
1391
|
+
isAgentSpeaking = false,
|
|
1392
|
+
animateOrb = true,
|
|
1393
|
+
backgroundColor = "#060914",
|
|
1394
|
+
buttonAriaLabel,
|
|
1395
|
+
buttonIcon,
|
|
1396
|
+
buttonType = "button",
|
|
1397
|
+
onButtonClick,
|
|
1398
|
+
statusMessage = "",
|
|
1399
|
+
isError = false,
|
|
1400
|
+
ariaMessage = ""
|
|
1401
|
+
}) {
|
|
1402
|
+
useNavaiVoiceOrbStyles();
|
|
1403
|
+
const dockClassName = ["navai-voice-orb-dock", `is-${placement}`, themeMode === "light" ? "is-light" : "", className].filter(Boolean).join(" ");
|
|
1404
|
+
const shouldHighlightOrb = isAgentSpeaking || isActive;
|
|
1405
|
+
const orbHoverIntensity = isAgentSpeaking ? 0.66 : 0.08;
|
|
1406
|
+
return /* @__PURE__ */ jsxs("aside", { className: dockClassName, style, children: [
|
|
1407
|
+
/* @__PURE__ */ jsxs("div", { className: "navai-voice-orb-wrap", children: [
|
|
1408
|
+
/* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-surface", shouldHighlightOrb ? "is-highlighted" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
|
|
1409
|
+
Orb3,
|
|
1410
|
+
{
|
|
1411
|
+
hoverIntensity: orbHoverIntensity,
|
|
1412
|
+
rotateOnHover: true,
|
|
1413
|
+
forceHoverState: isAgentSpeaking,
|
|
1414
|
+
enablePointerHover: false,
|
|
1415
|
+
animate: animateOrb,
|
|
1416
|
+
backgroundColor
|
|
1417
|
+
}
|
|
1418
|
+
) }),
|
|
1419
|
+
/* @__PURE__ */ jsx3("div", { className: ["navai-voice-orb-button-shell", isConnected ? "is-active" : ""].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx3(
|
|
1420
|
+
"button",
|
|
1421
|
+
{
|
|
1422
|
+
type: buttonType,
|
|
1423
|
+
className: [
|
|
1424
|
+
"navai-voice-orb-button",
|
|
1425
|
+
isConnected ? "is-active" : "",
|
|
1426
|
+
isActive && !isConnected ? "is-connecting" : ""
|
|
1427
|
+
].filter(Boolean).join(" "),
|
|
1428
|
+
onClick: onButtonClick,
|
|
1429
|
+
disabled: isDisabled,
|
|
1430
|
+
"aria-label": buttonAriaLabel,
|
|
1431
|
+
children: buttonIcon
|
|
1432
|
+
}
|
|
1433
|
+
) })
|
|
1434
|
+
] }),
|
|
1435
|
+
statusMessage ? /* @__PURE__ */ jsx3("p", { className: ["navai-voice-orb-status", isError ? "is-error" : ""].filter(Boolean).join(" "), role: "status", children: statusMessage }) : null,
|
|
1436
|
+
/* @__PURE__ */ jsx3("span", { className: "navai-voice-orb-live", "aria-live": "polite", children: ariaMessage })
|
|
1437
|
+
] });
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
1441
|
+
import { useEffect as useEffect4 } from "react";
|
|
1442
|
+
|
|
1443
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
1444
|
+
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
1445
|
+
|
|
1446
|
+
// src/orb/NavaiVoiceOrbDockMicIcon.tsx
|
|
1447
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1448
|
+
function NavaiVoiceOrbDockMicIcon({
|
|
1449
|
+
isActive = false,
|
|
1450
|
+
size = 20
|
|
1451
|
+
}) {
|
|
1452
|
+
return /* @__PURE__ */ jsxs2(
|
|
1453
|
+
"svg",
|
|
1454
|
+
{
|
|
1455
|
+
width: size,
|
|
1456
|
+
height: size,
|
|
1457
|
+
viewBox: "0 0 24 24",
|
|
1458
|
+
fill: "none",
|
|
1459
|
+
stroke: "currentColor",
|
|
1460
|
+
strokeWidth: "2",
|
|
1461
|
+
strokeLinecap: "round",
|
|
1462
|
+
strokeLinejoin: "round",
|
|
1463
|
+
"aria-hidden": "true",
|
|
1464
|
+
className: ["navai-voice-orb-icon", isActive ? "is-pulsing" : ""].filter(Boolean).join(" "),
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 3a3 3 0 0 0-3 3v6a3 3 0 1 0 6 0V6a3 3 0 0 0-3-3Z" }),
|
|
1467
|
+
/* @__PURE__ */ jsx4("path", { d: "M19 10v2a7 7 0 1 1-14 0v-2" }),
|
|
1468
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 19v3" })
|
|
1469
|
+
]
|
|
1470
|
+
}
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/orb/NavaiVoiceOrbDockSpinnerIcon.tsx
|
|
1475
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1476
|
+
function NavaiVoiceOrbDockSpinnerIcon({
|
|
1477
|
+
size = 20
|
|
1478
|
+
}) {
|
|
1479
|
+
const style = {
|
|
1480
|
+
width: size,
|
|
1481
|
+
height: size
|
|
1482
|
+
};
|
|
1483
|
+
return /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", className: "navai-voice-orb-spinner", style });
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/orb/NavaiVoiceOrbDock.tsx
|
|
1487
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1488
|
+
var DEFAULT_MESSAGES = {
|
|
1489
|
+
ariaStart: "Activate NAVAI voice",
|
|
1490
|
+
ariaStop: "Deactivate NAVAI voice",
|
|
1491
|
+
idle: "NAVAI ready to start.",
|
|
1492
|
+
connecting: "Connecting NAVAI voice...",
|
|
1493
|
+
listening: "NAVAI is listening.",
|
|
1494
|
+
speaking: "NAVAI is speaking.",
|
|
1495
|
+
errorPrefix: "NAVAI error"
|
|
1496
|
+
};
|
|
1497
|
+
function resolveNavaiVoiceOrbRuntimeSnapshot(agent) {
|
|
1498
|
+
return {
|
|
1499
|
+
status: agent.status,
|
|
1500
|
+
agentVoiceState: agent.agentVoiceState,
|
|
1501
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
1502
|
+
error: agent.error
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
function resolveStatusMessage(runtimeSnapshot, messages) {
|
|
1506
|
+
if (runtimeSnapshot.error) {
|
|
1507
|
+
return `${messages.errorPrefix}: ${runtimeSnapshot.error}`;
|
|
1508
|
+
}
|
|
1509
|
+
if (runtimeSnapshot.isAgentSpeaking) {
|
|
1510
|
+
return messages.speaking;
|
|
1511
|
+
}
|
|
1512
|
+
if (runtimeSnapshot.status === "connecting") {
|
|
1513
|
+
return messages.connecting;
|
|
1514
|
+
}
|
|
1515
|
+
if (runtimeSnapshot.status === "connected") {
|
|
1516
|
+
return messages.listening;
|
|
1517
|
+
}
|
|
1518
|
+
return messages.idle;
|
|
1519
|
+
}
|
|
1520
|
+
function NavaiVoiceOrbDock({
|
|
1521
|
+
agent,
|
|
1522
|
+
className,
|
|
1523
|
+
style,
|
|
1524
|
+
themeMode = "dark",
|
|
1525
|
+
placement = "bottom-right",
|
|
1526
|
+
backgroundColorLight = "#f4f6fb",
|
|
1527
|
+
backgroundColorDark = "#060914",
|
|
1528
|
+
showStatus = true,
|
|
1529
|
+
messages
|
|
1530
|
+
}) {
|
|
1531
|
+
const resolvedMessages = useMemo3(() => ({ ...DEFAULT_MESSAGES, ...messages }), [messages]);
|
|
1532
|
+
const runtimeSnapshot = useMemo3(() => resolveNavaiVoiceOrbRuntimeSnapshot(agent), [agent]);
|
|
1533
|
+
const statusMessage = showStatus ? resolveStatusMessage(runtimeSnapshot, resolvedMessages) : "";
|
|
1534
|
+
const isError = runtimeSnapshot.status === "error" || Boolean(runtimeSnapshot.error);
|
|
1535
|
+
const isConnecting = runtimeSnapshot.status === "connecting";
|
|
1536
|
+
const isActive = runtimeSnapshot.status === "connecting" || runtimeSnapshot.status === "connected";
|
|
1537
|
+
const isDisabled = agent.isConnecting;
|
|
1538
|
+
const shouldAnimateOrb = runtimeSnapshot.status !== "error";
|
|
1539
|
+
const handleToggle = useCallback2(() => {
|
|
1540
|
+
if (agent.isConnecting) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (agent.isConnected) {
|
|
1544
|
+
agent.stop();
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
void agent.start();
|
|
1548
|
+
}, [agent]);
|
|
1549
|
+
return /* @__PURE__ */ jsx6(
|
|
1550
|
+
NavaiMiniOrbDock,
|
|
1551
|
+
{
|
|
1552
|
+
className,
|
|
1553
|
+
style,
|
|
1554
|
+
themeMode,
|
|
1555
|
+
placement,
|
|
1556
|
+
isActive,
|
|
1557
|
+
isConnected: agent.isConnected,
|
|
1558
|
+
isDisabled,
|
|
1559
|
+
isAgentSpeaking: agent.isAgentSpeaking,
|
|
1560
|
+
animateOrb: shouldAnimateOrb,
|
|
1561
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
1562
|
+
buttonAriaLabel: isConnecting ? resolvedMessages.connecting : agent.isConnected ? resolvedMessages.ariaStop : resolvedMessages.ariaStart,
|
|
1563
|
+
buttonIcon: isConnecting ? /* @__PURE__ */ jsx6(NavaiVoiceOrbDockSpinnerIcon, {}) : /* @__PURE__ */ jsx6(NavaiVoiceOrbDockMicIcon, { isActive: agent.isConnected || agent.isAgentSpeaking }),
|
|
1564
|
+
onButtonClick: handleToggle,
|
|
1565
|
+
statusMessage,
|
|
1566
|
+
isError,
|
|
1567
|
+
ariaMessage: statusMessage
|
|
1568
|
+
}
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// src/orb/NavaiVoiceHeroOrb.tsx
|
|
1573
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1574
|
+
function NavaiVoiceHeroOrb({
|
|
1575
|
+
agent,
|
|
1576
|
+
themeMode = "dark",
|
|
1577
|
+
backgroundColorLight = "#ffffff",
|
|
1578
|
+
backgroundColorDark = "#000000",
|
|
1579
|
+
onRuntimeSnapshotChange,
|
|
1580
|
+
...orbProps
|
|
1581
|
+
}) {
|
|
1582
|
+
const runtimeSnapshot = resolveNavaiVoiceOrbRuntimeSnapshot(agent);
|
|
1583
|
+
useEffect4(() => {
|
|
1584
|
+
if (typeof onRuntimeSnapshotChange === "function") {
|
|
1585
|
+
onRuntimeSnapshotChange(runtimeSnapshot);
|
|
1586
|
+
}
|
|
1587
|
+
}, [onRuntimeSnapshotChange, runtimeSnapshot]);
|
|
1588
|
+
return /* @__PURE__ */ jsx7(
|
|
1589
|
+
NavaiHeroOrb,
|
|
1590
|
+
{
|
|
1591
|
+
...orbProps,
|
|
1592
|
+
isAgentSpeaking: runtimeSnapshot.isAgentSpeaking,
|
|
1593
|
+
backgroundColor: themeMode === "light" ? backgroundColorLight : backgroundColorDark,
|
|
1594
|
+
className: themeMode === "light" ? "is-light" : ""
|
|
1595
|
+
}
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
883
1598
|
export {
|
|
1599
|
+
NavaiHeroOrb,
|
|
1600
|
+
NavaiMiniOrbDock,
|
|
1601
|
+
NavaiVoiceHeroOrb,
|
|
1602
|
+
NavaiVoiceOrbDock,
|
|
1603
|
+
NavaiVoiceOrbDockMicIcon,
|
|
1604
|
+
Orb,
|
|
884
1605
|
buildNavaiAgent,
|
|
1606
|
+
clampNavaiOrbDelayMs,
|
|
885
1607
|
createNavaiBackendClient,
|
|
886
1608
|
getNavaiRoutePromptLines,
|
|
887
1609
|
loadNavaiFunctions,
|
|
888
1610
|
resolveNavaiFrontendRuntimeConfig,
|
|
889
1611
|
resolveNavaiRoute,
|
|
1612
|
+
resolveNavaiVoiceOrbRuntimeSnapshot,
|
|
890
1613
|
useWebVoiceAgent
|
|
891
1614
|
};
|