@absolutejs/voice 0.0.22-beta.493 → 0.0.22-beta.495

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.
@@ -0,0 +1,18 @@
1
+ import type { VoiceControllerOptions } from "../types";
2
+ import { type VoiceWidgetLabels, type VoiceWidgetTheme, type VoiceWidgetViewModel } from "../client/voiceWidgetView";
3
+ export type CreateVoiceWidgetServiceOptions = VoiceControllerOptions & {
4
+ labels?: VoiceWidgetLabels;
5
+ theme?: VoiceWidgetTheme;
6
+ title?: string;
7
+ };
8
+ export declare class VoiceWidgetService {
9
+ connect<TResult = unknown>(path: string, options?: CreateVoiceWidgetServiceOptions): {
10
+ close: () => void;
11
+ endCall: () => void;
12
+ getHTML: () => string;
13
+ mute: () => void;
14
+ startCall: () => Promise<void>;
15
+ unmute: () => Promise<void>;
16
+ viewModel: import("@angular/core").Signal<VoiceWidgetViewModel>;
17
+ };
18
+ }
@@ -0,0 +1,50 @@
1
+ import type { VoiceAgentUIState } from "../agentState";
2
+ import type { VoiceControllerState } from "../types";
3
+ export type VoiceWidgetTheme = {
4
+ accent?: string;
5
+ background?: string;
6
+ errorAccent?: string;
7
+ fontFamily?: string;
8
+ foreground?: string;
9
+ radius?: number | string;
10
+ };
11
+ export type VoiceWidgetLabels = {
12
+ callEnded?: string;
13
+ connecting?: string;
14
+ endCall?: string;
15
+ idle?: string;
16
+ listening?: string;
17
+ mute?: string;
18
+ speaking?: string;
19
+ startCall?: string;
20
+ thinking?: string;
21
+ unmute?: string;
22
+ };
23
+ export type VoiceWidgetViewModelInput = {
24
+ labels?: VoiceWidgetLabels;
25
+ state: Pick<VoiceControllerState, "assistantAudio" | "error" | "isConnected" | "isRecording" | "partial" | "status" | "turns">;
26
+ theme?: VoiceWidgetTheme;
27
+ title?: string;
28
+ };
29
+ export type VoiceWidgetViewModel = {
30
+ agentState: VoiceAgentUIState;
31
+ classes: {
32
+ container: string;
33
+ dot: string;
34
+ };
35
+ controls: {
36
+ canEnd: boolean;
37
+ canMute: boolean;
38
+ canStart: boolean;
39
+ };
40
+ errorMessage?: string;
41
+ labels: Required<VoiceWidgetLabels>;
42
+ partial?: string;
43
+ statusLabel: string;
44
+ theme: Required<VoiceWidgetTheme>;
45
+ title: string;
46
+ };
47
+ export declare const DEFAULT_VOICE_WIDGET_THEME: Required<VoiceWidgetTheme>;
48
+ export declare const DEFAULT_VOICE_WIDGET_LABELS: Required<VoiceWidgetLabels>;
49
+ export declare const createVoiceWidgetViewModel: (input: VoiceWidgetViewModelInput) => VoiceWidgetViewModel;
50
+ export declare const renderVoiceWidgetHTML: (model: VoiceWidgetViewModel) => string;
@@ -0,0 +1,13 @@
1
+ import type { VoiceControllerOptions } from "../types";
2
+ import { type VoiceWidgetLabels, type VoiceWidgetTheme, type VoiceWidgetViewModel } from "../client/voiceWidgetView";
3
+ export type { VoiceWidgetLabels, VoiceWidgetTheme, VoiceWidgetViewModel };
4
+ export type VoiceWidgetProps = {
5
+ className?: string;
6
+ controllerOptions?: VoiceControllerOptions;
7
+ labels?: VoiceWidgetLabels;
8
+ onError?: (error: string) => void;
9
+ path?: string;
10
+ theme?: VoiceWidgetTheme;
11
+ title?: string;
12
+ };
13
+ export declare const VoiceWidget: ({ className, controllerOptions, labels, onError, path, theme, title, }: VoiceWidgetProps) => import("react/jsx-runtime").JSX.Element;
@@ -35,6 +35,8 @@ export { useVoiceDeliveryRuntime } from "./useVoiceDeliveryRuntime";
35
35
  export { useVoiceCampaignDialerProof } from "./useVoiceCampaignDialerProof";
36
36
  export { useVoiceStream } from "./useVoiceStream";
37
37
  export { useVoiceController } from "./useVoiceController";
38
+ export { VoiceWidget } from "./VoiceWidget";
39
+ export type { VoiceWidgetLabels, VoiceWidgetProps, VoiceWidgetTheme, } from "./VoiceWidget";
38
40
  export { useVoiceProviderStatus } from "./useVoiceProviderStatus";
39
41
  export { useVoiceProviderCapabilities } from "./useVoiceProviderCapabilities";
40
42
  export { useVoiceProviderContracts } from "./useVoiceProviderContracts";
@@ -12450,8 +12450,296 @@ var useVoiceController = (path, options = {}) => {
12450
12450
  toggleRecording: () => controller.toggleRecording()
12451
12451
  };
12452
12452
  };
12453
+ // src/react/VoiceWidget.tsx
12454
+ import { useMemo, useRef as useRef26 } from "react";
12455
+
12456
+ // src/agentState.ts
12457
+ var deriveVoiceAgentUIState = (input) => {
12458
+ if (!input.isConnected) {
12459
+ return "idle";
12460
+ }
12461
+ if (input.isPlaying) {
12462
+ return "speaking";
12463
+ }
12464
+ if (input.isRecording && input.hasActivePartial) {
12465
+ return "listening";
12466
+ }
12467
+ if (input.isRecording) {
12468
+ return "listening";
12469
+ }
12470
+ if (input.lastTranscriptAt && !input.lastAssistantAt) {
12471
+ return "thinking";
12472
+ }
12473
+ if (input.lastTranscriptAt && input.lastAssistantAt && input.lastTranscriptAt > input.lastAssistantAt) {
12474
+ return "thinking";
12475
+ }
12476
+ return "idle";
12477
+ };
12478
+ var describeVoiceAgentUIState = (state) => {
12479
+ switch (state) {
12480
+ case "idle":
12481
+ return "Idle";
12482
+ case "listening":
12483
+ return "Listening";
12484
+ case "speaking":
12485
+ return "Speaking";
12486
+ case "thinking":
12487
+ return "Thinking";
12488
+ }
12489
+ };
12490
+ var voiceAgentUIStateOrder = [
12491
+ "idle",
12492
+ "listening",
12493
+ "thinking",
12494
+ "speaking"
12495
+ ];
12496
+
12497
+ // src/client/voiceWidgetView.ts
12498
+ var DEFAULT_VOICE_WIDGET_THEME = {
12499
+ accent: "#3b82f6",
12500
+ background: "#0f172a",
12501
+ errorAccent: "#ef4444",
12502
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
12503
+ foreground: "#f8fafc",
12504
+ radius: 16
12505
+ };
12506
+ var DEFAULT_VOICE_WIDGET_LABELS = {
12507
+ callEnded: "Call ended",
12508
+ connecting: "Connecting\u2026",
12509
+ endCall: "End call",
12510
+ idle: "Idle",
12511
+ listening: "Listening",
12512
+ mute: "Mute",
12513
+ speaking: "Speaking",
12514
+ startCall: "Start call",
12515
+ thinking: "Thinking",
12516
+ unmute: "Unmute"
12517
+ };
12518
+ var stateLabel = (state, labels) => {
12519
+ switch (state) {
12520
+ case "listening":
12521
+ return labels.listening;
12522
+ case "speaking":
12523
+ return labels.speaking;
12524
+ case "thinking":
12525
+ return labels.thinking;
12526
+ case "idle":
12527
+ return labels.idle;
12528
+ }
12529
+ };
12530
+ var createVoiceWidgetViewModel = (input) => {
12531
+ const theme = { ...DEFAULT_VOICE_WIDGET_THEME, ...input.theme };
12532
+ const labels = { ...DEFAULT_VOICE_WIDGET_LABELS, ...input.labels };
12533
+ const lastAssistantAt = input.state.assistantAudio.at(-1)?.receivedAt;
12534
+ const lastTranscriptAt = input.state.turns.at(-1)?.committedAt;
12535
+ const agentState = deriveVoiceAgentUIState({
12536
+ hasActivePartial: input.state.partial.length > 0,
12537
+ isConnected: input.state.isConnected,
12538
+ isPlaying: false,
12539
+ isRecording: input.state.isRecording,
12540
+ lastAssistantAt,
12541
+ lastTranscriptAt
12542
+ });
12543
+ const connecting = !input.state.isConnected && input.state.status !== "idle" && !input.state.error;
12544
+ const statusLabel2 = input.state.error ? "Error" : connecting ? labels.connecting : input.state.status === "completed" ? labels.callEnded : stateLabel(agentState, labels);
12545
+ return {
12546
+ agentState,
12547
+ classes: {
12548
+ container: `absolute-voice-widget absolute-voice-widget--${agentState}`,
12549
+ dot: `absolute-voice-widget__dot${input.state.error ? " absolute-voice-widget__dot--error" : ""}`
12550
+ },
12551
+ controls: {
12552
+ canEnd: input.state.isConnected,
12553
+ canMute: input.state.isRecording,
12554
+ canStart: !input.state.isRecording && input.state.status !== "completed"
12555
+ },
12556
+ errorMessage: input.state.error ?? undefined,
12557
+ labels,
12558
+ partial: input.state.partial || undefined,
12559
+ statusLabel: statusLabel2,
12560
+ theme,
12561
+ title: input.title ?? "Voice"
12562
+ };
12563
+ };
12564
+ var escapeHtml27 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
12565
+ var resolveRadius = (radius) => typeof radius === "number" ? `${radius}px` : radius;
12566
+ var renderVoiceWidgetHTML = (model) => {
12567
+ const t = model.theme;
12568
+ const containerStyle = `background:${t.background};border-radius:${resolveRadius(t.radius)};color:${t.foreground};font-family:${t.fontFamily};min-width:240px;padding:20px 22px;`;
12569
+ const dotStyle = `background:${model.errorMessage ? t.errorAccent : model.agentState === "idle" ? "rgba(148,163,184,0.6)" : t.accent};border-radius:50%;height:10px;width:10px;`;
12570
+ const buttons = [];
12571
+ if (model.controls.canStart) {
12572
+ buttons.push(`<button type="button" data-action="start" style="background:${t.accent};border:none;border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml27(model.labels.startCall)}</button>`);
12573
+ }
12574
+ if (model.controls.canMute) {
12575
+ buttons.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml27(model.labels.mute)}</button>`);
12576
+ }
12577
+ if (model.controls.canEnd) {
12578
+ buttons.push(`<button type="button" data-action="end" style="background:${t.errorAccent};border:none;border-radius:12px;color:${t.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${escapeHtml27(model.labels.endCall)}</button>`);
12579
+ }
12580
+ return `<div role="region" aria-live="polite" data-agent-state="${model.agentState}" class="${escapeHtml27(model.classes.container)}" style="${containerStyle}">
12581
+ <div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
12582
+ <span aria-hidden="true" class="${escapeHtml27(model.classes.dot)}" style="${dotStyle}"></span>
12583
+ <strong style="font-size:15px;">${escapeHtml27(model.title)}</strong>
12584
+ <span style="font-size:13px;margin-left:auto;opacity:0.7;">${escapeHtml27(model.statusLabel)}</span>
12585
+ </div>
12586
+ ${model.partial ? `<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">\u201C${escapeHtml27(model.partial)}\u201D</p>` : ""}
12587
+ <div style="display:flex;gap:10px;">${buttons.join("")}</div>
12588
+ ${model.errorMessage ? `<p style="color:${t.errorAccent};font-size:12px;margin-top:12px;">${escapeHtml27(model.errorMessage)}</p>` : ""}
12589
+ </div>`;
12590
+ };
12591
+
12592
+ // src/react/VoiceWidget.tsx
12593
+ import { jsxDEV as jsxDEV22 } from "react/jsx-dev-runtime";
12594
+ var resolveRadius2 = (radius) => typeof radius === "number" ? `${radius}px` : radius;
12595
+ var buttonStyle = (variant, theme) => ({
12596
+ background: variant === "primary" ? theme.accent : variant === "danger" ? theme.errorAccent : "transparent",
12597
+ border: variant === "secondary" ? `1px solid rgba(255,255,255,0.18)` : "none",
12598
+ borderRadius: 12,
12599
+ color: theme.foreground,
12600
+ cursor: "pointer",
12601
+ fontSize: 14,
12602
+ fontWeight: 500,
12603
+ padding: "10px 14px"
12604
+ });
12605
+ var VoiceWidget = ({
12606
+ className,
12607
+ controllerOptions,
12608
+ labels,
12609
+ onError,
12610
+ path = "/voice",
12611
+ theme,
12612
+ title
12613
+ }) => {
12614
+ const lastErrorRef = useRef26(null);
12615
+ const controller = useVoiceController(path, controllerOptions);
12616
+ if (controller.error && controller.error !== lastErrorRef.current) {
12617
+ lastErrorRef.current = controller.error;
12618
+ onError?.(controller.error);
12619
+ }
12620
+ const model = useMemo(() => createVoiceWidgetViewModel({
12621
+ labels,
12622
+ state: {
12623
+ assistantAudio: controller.assistantAudio,
12624
+ error: controller.error,
12625
+ isConnected: controller.isConnected,
12626
+ isRecording: controller.isRecording,
12627
+ partial: controller.partial,
12628
+ status: controller.status,
12629
+ turns: controller.turns
12630
+ },
12631
+ theme,
12632
+ title
12633
+ }), [
12634
+ controller.assistantAudio,
12635
+ controller.error,
12636
+ controller.isConnected,
12637
+ controller.isRecording,
12638
+ controller.partial,
12639
+ controller.status,
12640
+ controller.turns,
12641
+ labels,
12642
+ theme,
12643
+ title
12644
+ ]);
12645
+ return /* @__PURE__ */ jsxDEV22("div", {
12646
+ "aria-live": "polite",
12647
+ className: className ?? model.classes.container,
12648
+ "data-agent-state": model.agentState,
12649
+ role: "region",
12650
+ style: {
12651
+ background: model.theme.background,
12652
+ borderRadius: resolveRadius2(model.theme.radius),
12653
+ color: model.theme.foreground,
12654
+ fontFamily: model.theme.fontFamily,
12655
+ minWidth: 240,
12656
+ padding: "20px 22px"
12657
+ },
12658
+ children: [
12659
+ /* @__PURE__ */ jsxDEV22("div", {
12660
+ style: {
12661
+ alignItems: "center",
12662
+ display: "flex",
12663
+ gap: 10,
12664
+ marginBottom: 12
12665
+ },
12666
+ children: [
12667
+ /* @__PURE__ */ jsxDEV22("span", {
12668
+ "aria-hidden": "true",
12669
+ style: {
12670
+ background: model.errorMessage ? model.theme.errorAccent : model.agentState === "idle" ? "rgba(148, 163, 184, 0.6)" : model.theme.accent,
12671
+ borderRadius: "50%",
12672
+ height: 10,
12673
+ width: 10
12674
+ }
12675
+ }, undefined, false, undefined, this),
12676
+ /* @__PURE__ */ jsxDEV22("strong", {
12677
+ style: { fontSize: 15 },
12678
+ children: model.title
12679
+ }, undefined, false, undefined, this),
12680
+ /* @__PURE__ */ jsxDEV22("span", {
12681
+ style: {
12682
+ fontSize: 13,
12683
+ marginLeft: "auto",
12684
+ opacity: 0.7
12685
+ },
12686
+ children: model.statusLabel
12687
+ }, undefined, false, undefined, this)
12688
+ ]
12689
+ }, undefined, true, undefined, this),
12690
+ model.partial ? /* @__PURE__ */ jsxDEV22("p", {
12691
+ style: {
12692
+ fontSize: 13,
12693
+ margin: "8px 0 12px",
12694
+ opacity: 0.85,
12695
+ wordBreak: "break-word"
12696
+ },
12697
+ children: [
12698
+ "\u201C",
12699
+ model.partial,
12700
+ "\u201D"
12701
+ ]
12702
+ }, undefined, true, undefined, this) : null,
12703
+ /* @__PURE__ */ jsxDEV22("div", {
12704
+ style: { display: "flex", gap: 10 },
12705
+ children: [
12706
+ model.controls.canStart ? /* @__PURE__ */ jsxDEV22("button", {
12707
+ onClick: () => {
12708
+ controller.startRecording();
12709
+ },
12710
+ style: buttonStyle("primary", model.theme),
12711
+ type: "button",
12712
+ children: model.labels.startCall
12713
+ }, undefined, false, undefined, this) : null,
12714
+ model.controls.canMute ? /* @__PURE__ */ jsxDEV22("button", {
12715
+ onClick: () => controller.stopRecording(),
12716
+ style: buttonStyle("secondary", model.theme),
12717
+ type: "button",
12718
+ children: model.labels.mute
12719
+ }, undefined, false, undefined, this) : null,
12720
+ model.controls.canEnd ? /* @__PURE__ */ jsxDEV22("button", {
12721
+ onClick: () => {
12722
+ controller.close();
12723
+ },
12724
+ style: buttonStyle("danger", model.theme),
12725
+ type: "button",
12726
+ children: model.labels.endCall
12727
+ }, undefined, false, undefined, this) : null
12728
+ ]
12729
+ }, undefined, true, undefined, this),
12730
+ model.errorMessage ? /* @__PURE__ */ jsxDEV22("p", {
12731
+ style: {
12732
+ color: model.theme.errorAccent,
12733
+ fontSize: 12,
12734
+ marginTop: 12
12735
+ },
12736
+ children: model.errorMessage
12737
+ }, undefined, false, undefined, this) : null
12738
+ ]
12739
+ }, undefined, true, undefined, this);
12740
+ };
12453
12741
  // src/react/useVoiceWorkflowStatus.tsx
12454
- import { useEffect as useEffect26, useRef as useRef26, useSyncExternalStore as useSyncExternalStore26 } from "react";
12742
+ import { useEffect as useEffect26, useRef as useRef27, useSyncExternalStore as useSyncExternalStore26 } from "react";
12455
12743
 
12456
12744
  // src/client/workflowStatus.ts
12457
12745
  var fetchVoiceWorkflowStatus = async (path = "/evals/scenarios/json", options = {}) => {
@@ -12534,7 +12822,7 @@ var createVoiceWorkflowStatusStore = (path = "/evals/scenarios/json", options =
12534
12822
 
12535
12823
  // src/react/useVoiceWorkflowStatus.tsx
12536
12824
  var useVoiceWorkflowStatus = (path = "/evals/scenarios/json", options = {}) => {
12537
- const storeRef = useRef26(null);
12825
+ const storeRef = useRef27(null);
12538
12826
  if (!storeRef.current) {
12539
12827
  storeRef.current = createVoiceWorkflowStatusStore(path, options);
12540
12828
  }
@@ -12575,6 +12863,7 @@ export {
12575
12863
  useVoiceCampaignDialerProof,
12576
12864
  useVoiceCallDebugger,
12577
12865
  useVoiceAgentSquadStatus,
12866
+ VoiceWidget,
12578
12867
  VoiceTurnQuality,
12579
12868
  VoiceTurnLatency,
12580
12869
  VoiceTraceTimeline,
@@ -0,0 +1,19 @@
1
+ import type { VoiceControllerOptions } from "../types";
2
+ import { type VoiceWidgetLabels, type VoiceWidgetTheme, type VoiceWidgetViewModel } from "../client/voiceWidgetView";
3
+ export type CreateVoiceWidgetOptions = VoiceControllerOptions & {
4
+ labels?: VoiceWidgetLabels;
5
+ theme?: VoiceWidgetTheme;
6
+ title?: string;
7
+ };
8
+ export declare const createVoiceWidget: <TResult = unknown>(path: string, options?: CreateVoiceWidgetOptions) => {
9
+ close: () => void;
10
+ endCall: () => void;
11
+ getHTML: () => string;
12
+ getSnapshot: () => import("..").VoiceControllerState<TResult>;
13
+ getViewModel: () => VoiceWidgetViewModel;
14
+ mute: () => void;
15
+ startCall: () => Promise<void>;
16
+ subscribe: (subscriber: () => void) => () => void;
17
+ unmute: () => Promise<void>;
18
+ };
19
+ export type { VoiceWidgetLabels, VoiceWidgetTheme, VoiceWidgetViewModel, } from "../client/voiceWidgetView";
@@ -1,4 +1,6 @@
1
1
  export { createVoiceCampaignDialerProof } from "./createVoiceCampaignDialerProof";
2
+ export { createVoiceWidget } from "./createVoiceWidget";
3
+ export type { CreateVoiceWidgetOptions, VoiceWidgetLabels, VoiceWidgetTheme, VoiceWidgetViewModel, } from "./createVoiceWidget";
2
4
  export { createVoiceDeliveryRuntime } from "./createVoiceDeliveryRuntime";
3
5
  export { createVoiceOpsActionCenter } from "./createVoiceOpsActionCenter";
4
6
  export { createVoiceLiveOps } from "./createVoiceLiveOps";