@absolutejs/voice 0.0.22-beta.494 → 0.0.22-beta.496

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.
@@ -5670,6 +5670,7 @@ var createVoiceSession = (options) => {
5670
5670
  let activeAdapterGeneration = 0;
5671
5671
  let activeTTSTurnId;
5672
5672
  const currentTurnAudio = [];
5673
+ const pendingUserAttachments = [];
5673
5674
  let fallbackAttemptsForCurrentTurn = 0;
5674
5675
  let fallbackReplayAudioMsForCurrentTurn = 0;
5675
5676
  const amdDetector = options.amd;
@@ -7194,7 +7195,9 @@ var createVoiceSession = (options) => {
7194
7195
  audioMs: costEstimate.totalBillableAudioMs
7195
7196
  });
7196
7197
  }
7198
+ const drainedAttachments = pendingUserAttachments.length > 0 ? pendingUserAttachments.splice(0, pendingUserAttachments.length) : undefined;
7197
7199
  const turn = {
7200
+ attachments: drainedAttachments,
7198
7201
  committedAt: Date.now(),
7199
7202
  id: createId(),
7200
7203
  text: finalText,
@@ -7488,6 +7491,9 @@ var createVoiceSession = (options) => {
7488
7491
  };
7489
7492
  const api = {
7490
7493
  id: options.id,
7494
+ attachUserMedia: async (attachment) => {
7495
+ pendingUserAttachments.push(attachment);
7496
+ },
7491
7497
  close: async (reason) => {
7492
7498
  await runSerial("api.close", async () => {
7493
7499
  const disposition = reason === "silence-timeout" ? "silence-timeout" : "closed";
package/dist/types.d.ts CHANGED
@@ -220,6 +220,7 @@ export type VoiceTurnRecord<TResult = unknown> = {
220
220
  quality?: VoiceTranscriptQuality;
221
221
  transcripts: Transcript[];
222
222
  assistantText?: string;
223
+ attachments?: import("./agent").VoiceAgentMessageAttachment[];
223
224
  committedAt: number;
224
225
  result?: TResult;
225
226
  };
@@ -436,6 +437,7 @@ export type VoiceSessionHandle<TContext = unknown, TSession extends VoiceSession
436
437
  id: string;
437
438
  connect: (socket: VoiceSocket) => Promise<void>;
438
439
  receiveAudio: (audio: AudioChunk) => Promise<void>;
440
+ attachUserMedia: (attachment: import("./agent").VoiceAgentMessageAttachment) => Promise<void>;
439
441
  commitTurn: (reason?: VoiceEndOfTurnEvent["reason"]) => Promise<void>;
440
442
  disconnect: (event?: VoiceCloseEvent) => Promise<void>;
441
443
  complete: (result?: TResult) => Promise<void>;
@@ -0,0 +1,77 @@
1
+ import { type PropType } from "vue";
2
+ import type { VoiceControllerOptions } 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 declare const VoiceWidget: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
24
+ controllerOptions: {
25
+ default: () => {};
26
+ type: PropType<VoiceControllerOptions>;
27
+ };
28
+ labels: {
29
+ default: () => {};
30
+ type: PropType<VoiceWidgetLabels>;
31
+ };
32
+ path: {
33
+ default: string;
34
+ type: StringConstructor;
35
+ };
36
+ theme: {
37
+ default: () => {};
38
+ type: PropType<VoiceWidgetTheme>;
39
+ };
40
+ title: {
41
+ default: string;
42
+ type: StringConstructor;
43
+ };
44
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
45
+ [key: string]: any;
46
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
47
+ error: (_message: string) => true;
48
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
49
+ controllerOptions: {
50
+ default: () => {};
51
+ type: PropType<VoiceControllerOptions>;
52
+ };
53
+ labels: {
54
+ default: () => {};
55
+ type: PropType<VoiceWidgetLabels>;
56
+ };
57
+ path: {
58
+ default: string;
59
+ type: StringConstructor;
60
+ };
61
+ theme: {
62
+ default: () => {};
63
+ type: PropType<VoiceWidgetTheme>;
64
+ };
65
+ title: {
66
+ default: string;
67
+ type: StringConstructor;
68
+ };
69
+ }>> & Readonly<{
70
+ onError?: ((_message: string) => any) | undefined;
71
+ }>, {
72
+ title: string;
73
+ path: string;
74
+ theme: VoiceWidgetTheme;
75
+ controllerOptions: VoiceControllerOptions;
76
+ labels: VoiceWidgetLabels;
77
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -31,6 +31,8 @@ export { useVoiceDeliveryRuntime } from "./useVoiceDeliveryRuntime";
31
31
  export { useVoiceCampaignDialerProof } from "./useVoiceCampaignDialerProof";
32
32
  export { useVoiceStream } from "./useVoiceStream";
33
33
  export { useVoiceController } from "./useVoiceController";
34
+ export { VoiceWidget } from "./VoiceWidget";
35
+ export type { VoiceWidgetLabels, VoiceWidgetTheme, } from "./VoiceWidget";
34
36
  export { useVoiceProviderStatus } from "./useVoiceProviderStatus";
35
37
  export { useVoiceProviderCapabilities } from "./useVoiceProviderCapabilities";
36
38
  export { useVoiceProviderContracts } from "./useVoiceProviderContracts";
package/dist/vue/index.js CHANGED
@@ -11867,6 +11867,221 @@ function useVoiceController(path, options = {}) {
11867
11867
  turns
11868
11868
  };
11869
11869
  }
11870
+ // src/vue/VoiceWidget.ts
11871
+ import { computed as computed10, defineComponent as defineComponent18, h as h18 } from "vue";
11872
+
11873
+ // src/agentState.ts
11874
+ var deriveVoiceAgentUIState = (input) => {
11875
+ if (!input.isConnected) {
11876
+ return "idle";
11877
+ }
11878
+ if (input.isPlaying) {
11879
+ return "speaking";
11880
+ }
11881
+ if (input.isRecording && input.hasActivePartial) {
11882
+ return "listening";
11883
+ }
11884
+ if (input.isRecording) {
11885
+ return "listening";
11886
+ }
11887
+ if (input.lastTranscriptAt && !input.lastAssistantAt) {
11888
+ return "thinking";
11889
+ }
11890
+ if (input.lastTranscriptAt && input.lastAssistantAt && input.lastTranscriptAt > input.lastAssistantAt) {
11891
+ return "thinking";
11892
+ }
11893
+ return "idle";
11894
+ };
11895
+ var describeVoiceAgentUIState = (state) => {
11896
+ switch (state) {
11897
+ case "idle":
11898
+ return "Idle";
11899
+ case "listening":
11900
+ return "Listening";
11901
+ case "speaking":
11902
+ return "Speaking";
11903
+ case "thinking":
11904
+ return "Thinking";
11905
+ }
11906
+ };
11907
+ var voiceAgentUIStateOrder = [
11908
+ "idle",
11909
+ "listening",
11910
+ "thinking",
11911
+ "speaking"
11912
+ ];
11913
+
11914
+ // src/vue/VoiceWidget.ts
11915
+ var DEFAULT_THEME = {
11916
+ accent: "#3b82f6",
11917
+ background: "#0f172a",
11918
+ errorAccent: "#ef4444",
11919
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
11920
+ foreground: "#f8fafc",
11921
+ radius: 16
11922
+ };
11923
+ var DEFAULT_LABELS = {
11924
+ callEnded: "Call ended",
11925
+ connecting: "Connecting\u2026",
11926
+ endCall: "End call",
11927
+ idle: "Idle",
11928
+ listening: "Listening",
11929
+ mute: "Mute",
11930
+ speaking: "Speaking",
11931
+ startCall: "Start call",
11932
+ thinking: "Thinking",
11933
+ unmute: "Unmute"
11934
+ };
11935
+ var resolveRadius = (radius) => typeof radius === "number" ? `${radius}px` : radius;
11936
+ var VoiceWidget = defineComponent18({
11937
+ name: "VoiceWidget",
11938
+ props: {
11939
+ controllerOptions: {
11940
+ default: () => ({}),
11941
+ type: Object
11942
+ },
11943
+ labels: {
11944
+ default: () => ({}),
11945
+ type: Object
11946
+ },
11947
+ path: { default: "/voice", type: String },
11948
+ theme: {
11949
+ default: () => ({}),
11950
+ type: Object
11951
+ },
11952
+ title: { default: "Voice", type: String }
11953
+ },
11954
+ emits: { error: (_message) => true },
11955
+ setup(props, { emit }) {
11956
+ const controller = useVoiceController(props.path, props.controllerOptions);
11957
+ const theme = computed10(() => ({ ...DEFAULT_THEME, ...props.theme }));
11958
+ const labels = computed10(() => ({ ...DEFAULT_LABELS, ...props.labels }));
11959
+ const agentState = computed10(() => {
11960
+ const lastAssistantAt = controller.assistantAudio.value.at(-1)?.receivedAt;
11961
+ const lastTranscriptAt = controller.turns.value.at(-1)?.committedAt;
11962
+ return deriveVoiceAgentUIState({
11963
+ hasActivePartial: controller.partial.value.length > 0,
11964
+ isConnected: controller.isConnected.value,
11965
+ isPlaying: false,
11966
+ isRecording: controller.isRecording.value,
11967
+ lastAssistantAt,
11968
+ lastTranscriptAt
11969
+ });
11970
+ });
11971
+ return () => {
11972
+ const t = theme.value;
11973
+ const l = labels.value;
11974
+ if (controller.error.value) {
11975
+ emit("error", controller.error.value);
11976
+ }
11977
+ const stateLabel = controller.error.value ? "Error" : !controller.isConnected.value && controller.status.value !== "idle" ? l.connecting : controller.status.value === "completed" ? l.callEnded : agentState.value === "listening" ? l.listening : agentState.value === "speaking" ? l.speaking : agentState.value === "thinking" ? l.thinking : l.idle;
11978
+ const showStart = !controller.isRecording.value && controller.status.value !== "completed";
11979
+ return h18("div", {
11980
+ "aria-live": "polite",
11981
+ "data-agent-state": agentState.value,
11982
+ role: "region",
11983
+ style: {
11984
+ background: t.background,
11985
+ borderRadius: resolveRadius(t.radius),
11986
+ color: t.foreground,
11987
+ fontFamily: t.fontFamily,
11988
+ minWidth: "240px",
11989
+ padding: "20px 22px"
11990
+ }
11991
+ }, [
11992
+ h18("div", {
11993
+ style: {
11994
+ alignItems: "center",
11995
+ display: "flex",
11996
+ gap: "10px",
11997
+ marginBottom: "12px"
11998
+ }
11999
+ }, [
12000
+ h18("span", {
12001
+ "aria-hidden": "true",
12002
+ style: {
12003
+ background: controller.error.value ? t.errorAccent : agentState.value === "idle" ? "rgba(148,163,184,0.6)" : t.accent,
12004
+ borderRadius: "50%",
12005
+ height: "10px",
12006
+ width: "10px"
12007
+ }
12008
+ }),
12009
+ h18("strong", { style: { fontSize: "15px" } }, props.title),
12010
+ h18("span", {
12011
+ style: {
12012
+ fontSize: "13px",
12013
+ marginLeft: "auto",
12014
+ opacity: "0.7"
12015
+ }
12016
+ }, stateLabel)
12017
+ ]),
12018
+ controller.partial.value ? h18("p", {
12019
+ style: {
12020
+ fontSize: "13px",
12021
+ margin: "8px 0 12px",
12022
+ opacity: "0.85",
12023
+ wordBreak: "break-word"
12024
+ }
12025
+ }, `\u201C${controller.partial.value}\u201D`) : null,
12026
+ h18("div", { style: { display: "flex", gap: "10px" } }, [
12027
+ showStart ? h18("button", {
12028
+ onClick: () => {
12029
+ controller.startRecording();
12030
+ },
12031
+ style: {
12032
+ background: t.accent,
12033
+ border: "none",
12034
+ borderRadius: "12px",
12035
+ color: t.foreground,
12036
+ cursor: "pointer",
12037
+ fontSize: "14px",
12038
+ fontWeight: "500",
12039
+ padding: "10px 14px"
12040
+ },
12041
+ type: "button"
12042
+ }, l.startCall) : null,
12043
+ controller.isRecording.value ? h18("button", {
12044
+ onClick: () => controller.stopRecording(),
12045
+ style: {
12046
+ background: "transparent",
12047
+ border: "1px solid rgba(255,255,255,0.18)",
12048
+ borderRadius: "12px",
12049
+ color: t.foreground,
12050
+ cursor: "pointer",
12051
+ fontSize: "14px",
12052
+ fontWeight: "500",
12053
+ padding: "10px 14px"
12054
+ },
12055
+ type: "button"
12056
+ }, l.mute) : null,
12057
+ controller.isConnected.value ? h18("button", {
12058
+ onClick: () => {
12059
+ controller.close();
12060
+ },
12061
+ style: {
12062
+ background: t.errorAccent,
12063
+ border: "none",
12064
+ borderRadius: "12px",
12065
+ color: t.foreground,
12066
+ cursor: "pointer",
12067
+ fontSize: "14px",
12068
+ fontWeight: "500",
12069
+ padding: "10px 14px"
12070
+ },
12071
+ type: "button"
12072
+ }, l.endCall) : null
12073
+ ]),
12074
+ controller.error.value ? h18("p", {
12075
+ style: {
12076
+ color: t.errorAccent,
12077
+ fontSize: "12px",
12078
+ marginTop: "12px"
12079
+ }
12080
+ }, controller.error.value) : null
12081
+ ]);
12082
+ };
12083
+ }
12084
+ });
11870
12085
  // src/vue/useVoiceTraceTimeline.ts
11871
12086
  import { onUnmounted as onUnmounted23, ref as ref18, shallowRef as shallowRef22 } from "vue";
11872
12087
  function useVoiceTraceTimeline(path = "/api/voice-traces", options = {}) {
@@ -12036,6 +12251,7 @@ export {
12036
12251
  useVoiceCampaignDialerProof,
12037
12252
  useVoiceCallDebugger,
12038
12253
  useVoiceAgentSquadStatus,
12254
+ VoiceWidget,
12039
12255
  VoiceTurnQuality,
12040
12256
  VoiceTurnLatency,
12041
12257
  VoiceSessionSnapshot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.494",
3
+ "version": "0.0.22-beta.496",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",