@absolutejs/voice 0.0.2 → 0.0.4

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.md CHANGED
@@ -51,6 +51,56 @@ const app = new Elysia()
51
51
 
52
52
  `createVoiceMemoryStore()` is dev-only. Real deployments should provide a shared store backed by Redis, Postgres, or equivalent.
53
53
 
54
+ ## HTMX
55
+
56
+ Voice now mirrors the AI plugin's HTMX pattern with plugin-owned renderers and a plugin-owned fragment route.
57
+
58
+ ```ts
59
+ import { voice, createVoiceMemoryStore } from '@absolutejs/voice';
60
+
61
+ app.use(
62
+ voice({
63
+ path: '/voice/intake',
64
+ htmx: {
65
+ render: {
66
+ result: ({ result }) =>
67
+ result
68
+ ? `<pre>${JSON.stringify(result, null, 2)}</pre>`
69
+ : '<p>No structured result yet.</p>'
70
+ }
71
+ },
72
+ onComplete: async () => {},
73
+ onTurn: async ({ turn }) => ({
74
+ assistantText: `You said: ${turn.text}`
75
+ }),
76
+ session: createVoiceMemoryStore(),
77
+ stt: deepgram({
78
+ apiKey: process.env.DEEPGRAM_API_KEY!,
79
+ model: 'nova-3'
80
+ })
81
+ })
82
+ );
83
+ ```
84
+
85
+ The plugin exposes `GET /voice/intake/htmx/session?sessionId=...` by default. That route returns HTMX out-of-band fragments for:
86
+
87
+ - metrics
88
+ - status
89
+ - committed turns
90
+ - assistant replies
91
+ - structured result
92
+
93
+ On the client, bind the browser voice stream to a hidden HTMX refresh element:
94
+
95
+ ```ts
96
+ import { bindVoiceHTMX, createVoiceStream } from '@absolutejs/voice/client';
97
+
98
+ const voice = createVoiceStream('/voice/intake');
99
+ bindVoiceHTMX(voice, { element: '#voice-htmx-sync' });
100
+ ```
101
+
102
+ That keeps HTMX pages declarative without inventing custom fragment endpoints for core voice session UI.
103
+
54
104
  ## Adapter Contract
55
105
 
56
106
  Adapters normalize vendor behavior into a core event model so the plugin never branches on vendor names.
@@ -366,6 +366,7 @@ var createVoiceStreamStore = () => {
366
366
  state = {
367
367
  ...state,
368
368
  error: null,
369
+ isConnected: action.status === "active",
369
370
  sessionId: action.sessionId,
370
371
  status: action.status
371
372
  };
@@ -408,7 +409,6 @@ var createVoiceStream = (path, options = {}) => {
408
409
  notify();
409
410
  }
410
411
  });
411
- store.dispatch({ type: "connected" });
412
412
  return {
413
413
  close() {
414
414
  unsubscribeConnection();
@@ -8,7 +8,7 @@ export declare class VoiceStreamService {
8
8
  isConnected: import("@angular/core").Signal<boolean>;
9
9
  partial: import("@angular/core").Signal<string>;
10
10
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
11
- sessionId: import("@angular/core").Signal<string>;
11
+ sessionId: import("@angular/core").Signal<string | null>;
12
12
  status: import("@angular/core").Signal<import("..").VoiceSessionStatus | "idle">;
13
13
  turns: import("@angular/core").Signal<VoiceTurnRecord<TResult>[]>;
14
14
  };
@@ -1,14 +1,2 @@
1
- import type { VoiceConnectionOptions } from '../types';
2
- export declare const createVoiceStream: <TResult = unknown>(path: string, options?: VoiceConnectionOptions) => {
3
- close(): void;
4
- endTurn(): void;
5
- readonly error: string | null;
6
- readonly isConnected: boolean;
7
- readonly partial: string;
8
- readonly sessionId: string;
9
- readonly status: import("..").VoiceSessionStatus | "idle";
10
- readonly turns: import("..").VoiceTurnRecord<TResult>[];
11
- readonly assistantTexts: string[];
12
- sendAudio(audio: Uint8Array | ArrayBuffer): void;
13
- subscribe(subscriber: () => void): () => void;
14
- };
1
+ import type { VoiceConnectionOptions, VoiceStream } from '../types';
2
+ export declare const createVoiceStream: <TResult = unknown>(path: string, options?: VoiceConnectionOptions) => VoiceStream<TResult>;
@@ -0,0 +1,2 @@
1
+ import type { VoiceHTMXBindingOptions, VoiceStream } from '../types';
2
+ export declare const bindVoiceHTMX: <TResult = unknown>(stream: VoiceStream<TResult>, options: VoiceHTMXBindingOptions) => () => void;
@@ -1,3 +1,4 @@
1
1
  export { createVoiceConnection } from './connection';
2
2
  export { createVoiceStream } from './createVoiceStream';
3
+ export { bindVoiceHTMX } from './htmx';
3
4
  export { createMicrophoneCapture } from './microphone';
@@ -362,6 +362,7 @@ var createVoiceStreamStore = () => {
362
362
  state = {
363
363
  ...state,
364
364
  error: null,
365
+ isConnected: action.status === "active",
365
366
  sessionId: action.sessionId,
366
367
  status: action.status
367
368
  };
@@ -404,7 +405,6 @@ var createVoiceStream = (path, options = {}) => {
404
405
  notify();
405
406
  }
406
407
  });
407
- store.dispatch({ type: "connected" });
408
408
  return {
409
409
  close() {
410
410
  unsubscribeConnection();
@@ -447,6 +447,53 @@ var createVoiceStream = (path, options = {}) => {
447
447
  }
448
448
  };
449
449
  };
450
+ // src/client/htmx.ts
451
+ var DEFAULT_EVENT_NAME = "voice-refresh";
452
+ var DEFAULT_QUERY_PARAM = "sessionId";
453
+ var resolveElement = (input) => {
454
+ if (typeof input !== "string") {
455
+ return input;
456
+ }
457
+ return document.querySelector(input);
458
+ };
459
+ var buildRoute = (element, route, queryParam, sessionId) => {
460
+ const baseRoute = route ?? element.getAttribute("hx-get") ?? "";
461
+ if (!baseRoute) {
462
+ return "";
463
+ }
464
+ const url = new URL(baseRoute, window.location.origin);
465
+ if (sessionId) {
466
+ url.searchParams.set(queryParam, sessionId);
467
+ } else {
468
+ url.searchParams.delete(queryParam);
469
+ }
470
+ return `${url.pathname}${url.search}${url.hash}`;
471
+ };
472
+ var bindVoiceHTMX = (stream, options) => {
473
+ if (typeof window === "undefined" || typeof document === "undefined") {
474
+ return () => {};
475
+ }
476
+ const element = resolveElement(options.element);
477
+ if (!element) {
478
+ return () => {};
479
+ }
480
+ const eventName = options.eventName ?? DEFAULT_EVENT_NAME;
481
+ const queryParam = options.sessionQueryParam ?? DEFAULT_QUERY_PARAM;
482
+ const sync = () => {
483
+ const htmxWindow = window;
484
+ const nextRoute = buildRoute(element, options.route, queryParam, stream.sessionId);
485
+ if (nextRoute) {
486
+ element.setAttribute("hx-get", nextRoute);
487
+ }
488
+ htmxWindow.htmx?.process?.(element);
489
+ htmxWindow.htmx?.trigger?.(element, eventName);
490
+ };
491
+ const unsubscribe = stream.subscribe(sync);
492
+ sync();
493
+ return () => {
494
+ unsubscribe();
495
+ };
496
+ };
450
497
  // src/client/microphone.ts
451
498
  var clampSample = (value) => Math.max(-1, Math.min(1, value));
452
499
  var floatTo16BitPCM = (input) => {
@@ -517,5 +564,6 @@ var createMicrophoneCapture = (options) => {
517
564
  export {
518
565
  createVoiceStream,
519
566
  createVoiceConnection,
520
- createMicrophoneCapture
567
+ createMicrophoneCapture,
568
+ bindVoiceHTMX
521
569
  };
package/dist/htmx.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { VoiceHTMXRenderConfig, VoiceHTMXRenderInput, VoiceHTMXTargets, VoiceSessionRecord } from './types';
2
+ type ResolvedVoiceHTMXRenderConfig<TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Required<VoiceHTMXRenderConfig<TSession, TResult>>;
3
+ export declare const resolveVoiceHTMXTargets: (custom?: Partial<VoiceHTMXTargets>) => VoiceHTMXTargets;
4
+ export declare const resolveVoiceHTMXRenderers: <TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(custom?: VoiceHTMXRenderConfig<TSession, TResult>) => ResolvedVoiceHTMXRenderConfig<TSession, TResult>;
5
+ export declare const buildVoiceHTMXResponse: <TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(input: VoiceHTMXRenderInput<TResult, TSession>, renderers: ResolvedVoiceHTMXRenderConfig<TSession, TResult>, targets: VoiceHTMXTargets) => string;
6
+ export {};
package/dist/index.js CHANGED
@@ -72,6 +72,124 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
72
72
  // src/plugin.ts
73
73
  import { Elysia } from "elysia";
74
74
 
75
+ // src/htmx.ts
76
+ var DEFAULT_HTMX_TARGETS = {
77
+ assistant: "voice-htmx-assistant",
78
+ metrics: "voice-htmx-metrics",
79
+ result: "voice-htmx-result",
80
+ status: "voice-htmx-status",
81
+ turns: "voice-htmx-turns"
82
+ };
83
+ var escapeHtml = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
84
+ var stringifyResult = (result) => {
85
+ if (result === undefined) {
86
+ return "";
87
+ }
88
+ if (typeof result === "string") {
89
+ return result;
90
+ }
91
+ try {
92
+ return JSON.stringify(result, null, 2);
93
+ } catch {
94
+ return String(result);
95
+ }
96
+ };
97
+ var defaultEmptyState = (kind) => {
98
+ switch (kind) {
99
+ case "assistant":
100
+ return '<p class="empty-copy">No assistant messages yet.</p>';
101
+ case "metrics":
102
+ return '<p class="empty-copy">No active voice session yet.</p>';
103
+ case "result":
104
+ return '<p class="empty-copy">No structured result yet.</p>';
105
+ case "status":
106
+ return '<p class="empty-copy">Voice session is idle.</p>';
107
+ case "turns":
108
+ return '<p class="empty-copy">No turns committed yet.</p>';
109
+ }
110
+ };
111
+ var defaultMetrics = (input) => {
112
+ if (!input.sessionId) {
113
+ return defaultEmptyState("metrics");
114
+ }
115
+ return [
116
+ '<div class="voice-metric">',
117
+ '<span class="voice-metric-label">Session</span>',
118
+ `<span class="voice-metric-value">${escapeHtml(input.sessionId)}</span>`,
119
+ "</div>",
120
+ '<div class="voice-metric">',
121
+ '<span class="voice-metric-label">Status</span>',
122
+ `<span class="voice-metric-value">${escapeHtml(input.status)}</span>`,
123
+ "</div>",
124
+ '<div class="voice-metric">',
125
+ '<span class="voice-metric-label">Committed turns</span>',
126
+ `<span class="voice-metric-value">${String(input.turnCount)}</span>`,
127
+ "</div>"
128
+ ].join("");
129
+ };
130
+ var defaultStatus = (input) => [
131
+ '<div class="status-row">',
132
+ '<span class="label">Voice status</span>',
133
+ `<span class="value">${escapeHtml(input.status)}</span>`,
134
+ "</div>",
135
+ '<div class="status-row">',
136
+ '<span class="label">Partial transcript</span>',
137
+ `<span class="value">${escapeHtml(input.partial || "No live partial")}</span>`,
138
+ "</div>"
139
+ ].join("");
140
+ var renderTurn = (turn) => [
141
+ '<article class="voice-turn">',
142
+ '<div class="voice-turn-header">',
143
+ `<strong>${escapeHtml(turn.text)}</strong>`,
144
+ `<span>${new Date(turn.committedAt).toLocaleString("en-US", {
145
+ dateStyle: "medium",
146
+ timeStyle: "short"
147
+ })}</span>`,
148
+ "</div>",
149
+ turn.assistantText ? [
150
+ '<div class="voice-assistant-label">Assistant</div>',
151
+ `<p class="voice-turn-text">${escapeHtml(turn.assistantText)}</p>`
152
+ ].join("") : "",
153
+ "</article>"
154
+ ].join("");
155
+ var defaultTurns = (input) => input.turns.length === 0 ? defaultEmptyState("turns") : input.turns.map((turn) => renderTurn(turn)).join("");
156
+ var defaultAssistant = (input) => input.assistantTexts.length === 0 ? defaultEmptyState("assistant") : input.assistantTexts.map((text, index) => [
157
+ '<article class="voice-assistant-item">',
158
+ `<div class="voice-assistant-label">Reply ${String(index + 1)}</div>`,
159
+ `<p class="voice-turn-text">${escapeHtml(text)}</p>`,
160
+ "</article>"
161
+ ].join("")).join("");
162
+ var defaultResult = (input) => {
163
+ if (input.result === undefined) {
164
+ return defaultEmptyState("result");
165
+ }
166
+ return [
167
+ '<pre class="voice-code"><code>',
168
+ escapeHtml(stringifyResult(input.result)),
169
+ "</code></pre>"
170
+ ].join("");
171
+ };
172
+ var resolveVoiceHTMXTargets = (custom) => ({
173
+ ...DEFAULT_HTMX_TARGETS,
174
+ ...custom
175
+ });
176
+ var resolveVoiceHTMXRenderers = (custom) => ({
177
+ assistant: custom?.assistant ?? defaultAssistant,
178
+ emptyState: custom?.emptyState ?? defaultEmptyState,
179
+ metrics: custom?.metrics ?? defaultMetrics,
180
+ result: custom?.result ?? defaultResult,
181
+ status: custom?.status ?? defaultStatus,
182
+ turns: custom?.turns ?? defaultTurns
183
+ });
184
+ var renderOob = (id, html) => `<div id="${escapeHtml(id)}" hx-swap-oob="innerHTML">${html}</div>`;
185
+ var buildVoiceHTMXResponse = (input, renderers, targets) => [
186
+ renderOob(targets.metrics, renderers.metrics(input)),
187
+ renderOob(targets.status, renderers.status(input)),
188
+ renderOob(targets.turns, renderers.turns(input)),
189
+ renderOob(targets.assistant, renderers.assistant(input)),
190
+ renderOob(targets.result, renderers.result(input))
191
+ ].join("");
192
+
75
193
  // src/logger.ts
76
194
  var noop = () => {};
77
195
  var createNoopLogger = () => ({
@@ -560,6 +678,33 @@ var voice = (config) => {
560
678
  logger: resolveLogger(config.logger),
561
679
  socketSessions: new WeakMap
562
680
  };
681
+ const htmxConfig = typeof config.htmx === "object" ? config.htmx : undefined;
682
+ const htmxRoute = htmxConfig?.route ?? `${config.path}/htmx/session`;
683
+ const htmxRenderers = resolveVoiceHTMXRenderers(htmxConfig?.render);
684
+ const htmxTargets = resolveVoiceHTMXTargets(htmxConfig?.targets);
685
+ const htmxRoutes = () => {
686
+ if (!config.htmx) {
687
+ return new Elysia;
688
+ }
689
+ return new Elysia().get(htmxRoute, async ({ query }) => {
690
+ const sessionId = typeof query.sessionId === "string" && query.sessionId.trim() ? query.sessionId.trim() : undefined;
691
+ const session = sessionId ? await config.session.get(sessionId) : undefined;
692
+ const result = session?.turns.toReversed().find((turn) => turn.result !== undefined)?.result;
693
+ const turns = session?.turns ?? [];
694
+ return new Response(buildVoiceHTMXResponse({
695
+ assistantTexts: session?.turns.flatMap((turn) => turn.assistantText ? [turn.assistantText] : []) ?? [],
696
+ partial: session?.currentTurn.partialText ?? "",
697
+ result,
698
+ session,
699
+ sessionId,
700
+ status: session?.status ?? "idle",
701
+ turnCount: turns.length,
702
+ turns
703
+ }, htmxRenderers, htmxTargets), {
704
+ headers: { "Content-Type": "text/html; charset=utf-8" }
705
+ });
706
+ });
707
+ };
563
708
  return new Elysia({ name: "absolutejs-voice" }).ws(config.path, {
564
709
  close: async (ws, code, reason) => {
565
710
  const sessionId = runtime.socketSessions.get(ws);
@@ -661,7 +806,7 @@ var voice = (config) => {
661
806
  runtime.activeSessions.set(sessionId, session);
662
807
  await session.connect(createSocketAdapter(ws));
663
808
  }
664
- });
809
+ }).use(htmxRoutes());
665
810
  };
666
811
  // src/memoryStore.ts
667
812
  var createVoiceMemoryStore = () => {
package/dist/plugin.d.ts CHANGED
@@ -25,7 +25,29 @@ export declare const voice: <TContext = unknown, TSession extends VoiceSessionRe
25
25
  response: {};
26
26
  };
27
27
  };
28
- }, {
28
+ } | ({
29
+ [x: string]: {
30
+ subscribe: {
31
+ body: unknown;
32
+ params: {};
33
+ query: unknown;
34
+ headers: unknown;
35
+ response: {};
36
+ };
37
+ };
38
+ } & {
39
+ [x: string]: {
40
+ get: {
41
+ body: unknown;
42
+ params: {};
43
+ query: unknown;
44
+ headers: unknown;
45
+ response: {
46
+ 200: Response;
47
+ };
48
+ };
49
+ };
50
+ }), {
29
51
  derive: {};
30
52
  resolve: {};
31
53
  schema: {};
@@ -37,4 +59,10 @@ export declare const voice: <TContext = unknown, TSession extends VoiceSessionRe
37
59
  schema: {};
38
60
  standaloneSchema: {};
39
61
  response: {};
62
+ } & {
63
+ derive: {};
64
+ resolve: {};
65
+ schema: {};
66
+ standaloneSchema: {};
67
+ response: {};
40
68
  }>;
@@ -366,6 +366,7 @@ var createVoiceStreamStore = () => {
366
366
  state = {
367
367
  ...state,
368
368
  error: null,
369
+ isConnected: action.status === "active",
369
370
  sessionId: action.sessionId,
370
371
  status: action.status
371
372
  };
@@ -408,7 +409,6 @@ var createVoiceStream = (path, options = {}) => {
408
409
  notify();
409
410
  }
410
411
  });
411
- store.dispatch({ type: "connected" });
412
412
  return {
413
413
  close() {
414
414
  unsubscribeConnection();
@@ -7,7 +7,7 @@ export declare const useVoiceStream: <TResult = unknown>(path: string, options?:
7
7
  error: string | null;
8
8
  isConnected: boolean;
9
9
  partial: string;
10
- sessionId: string;
10
+ sessionId: string | null;
11
11
  status: import("..").VoiceSessionStatus | "idle";
12
12
  turns: import("..").VoiceTurnRecord<TResult>[];
13
13
  };
@@ -1,14 +1,2 @@
1
1
  import type { VoiceConnectionOptions } from '../types';
2
- export declare const createVoiceStream: <TResult = unknown>(path: string, options?: VoiceConnectionOptions) => {
3
- close(): void;
4
- endTurn(): void;
5
- readonly error: string | null;
6
- readonly isConnected: boolean;
7
- readonly partial: string;
8
- readonly sessionId: string;
9
- readonly status: import("..").VoiceSessionStatus | "idle";
10
- readonly turns: import("..").VoiceTurnRecord<TResult>[];
11
- readonly assistantTexts: string[];
12
- sendAudio(audio: Uint8Array | ArrayBuffer): void;
13
- subscribe(subscriber: () => void): () => void;
14
- };
2
+ export declare const createVoiceStream: <TResult = unknown>(path: string, options?: VoiceConnectionOptions) => import("..").VoiceStream<TResult>;
@@ -363,6 +363,7 @@ var createVoiceStreamStore = () => {
363
363
  state = {
364
364
  ...state,
365
365
  error: null,
366
+ isConnected: action.status === "active",
366
367
  sessionId: action.sessionId,
367
368
  status: action.status
368
369
  };
@@ -405,7 +406,6 @@ var createVoiceStream = (path, options = {}) => {
405
406
  notify();
406
407
  }
407
408
  });
408
- store.dispatch({ type: "connected" });
409
409
  return {
410
410
  close() {
411
411
  unsubscribeConnection();
package/dist/types.d.ts CHANGED
@@ -208,6 +208,7 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
208
208
  silenceMs?: number;
209
209
  };
210
210
  logger?: VoiceLogger;
211
+ htmx?: boolean | VoiceHTMXConfig<TSession, TResult>;
211
212
  } & VoiceRouteConfig<TContext, TSession, TResult>;
212
213
  export type CreateVoiceSessionOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
213
214
  id: string;
@@ -280,6 +281,36 @@ export type VoiceConnectionOptions = {
280
281
  pingInterval?: number;
281
282
  sessionId?: string;
282
283
  };
284
+ export type VoiceHTMXRenderInput<TResult = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
285
+ assistantTexts: string[];
286
+ partial: string;
287
+ result?: TResult;
288
+ session?: TSession;
289
+ sessionId?: string;
290
+ status: VoiceSessionStatus | 'idle';
291
+ turnCount: number;
292
+ turns: VoiceTurnRecord<TResult>[];
293
+ };
294
+ export type VoiceHTMXRenderConfig<TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
295
+ metrics?: (input: VoiceHTMXRenderInput<TResult, TSession>) => string;
296
+ status?: (input: VoiceHTMXRenderInput<TResult, TSession>) => string;
297
+ turns?: (input: VoiceHTMXRenderInput<TResult, TSession>) => string;
298
+ assistant?: (input: VoiceHTMXRenderInput<TResult, TSession>) => string;
299
+ result?: (input: VoiceHTMXRenderInput<TResult, TSession>) => string;
300
+ emptyState?: (kind: keyof VoiceHTMXTargets, input: VoiceHTMXRenderInput<TResult, TSession>) => string;
301
+ };
302
+ export type VoiceHTMXTargets = {
303
+ assistant: string;
304
+ metrics: string;
305
+ result: string;
306
+ status: string;
307
+ turns: string;
308
+ };
309
+ export type VoiceHTMXConfig<TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
310
+ render?: VoiceHTMXRenderConfig<TSession, TResult>;
311
+ route?: string;
312
+ targets?: Partial<VoiceHTMXTargets>;
313
+ };
283
314
  export type VoiceStreamState<TResult = unknown> = {
284
315
  sessionId: string | null;
285
316
  status: VoiceSessionStatus | 'idle';
@@ -289,6 +320,25 @@ export type VoiceStreamState<TResult = unknown> = {
289
320
  error: string | null;
290
321
  isConnected: boolean;
291
322
  };
323
+ export type VoiceStream<TResult = unknown> = {
324
+ close: () => void;
325
+ endTurn: () => void;
326
+ error: string | null;
327
+ isConnected: boolean;
328
+ partial: string;
329
+ sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
330
+ sessionId: string | null;
331
+ status: VoiceSessionStatus | 'idle';
332
+ subscribe: (subscriber: () => void) => () => void;
333
+ turns: VoiceTurnRecord<TResult>[];
334
+ assistantTexts: string[];
335
+ };
336
+ export type VoiceHTMXBindingOptions = {
337
+ element: Element | string;
338
+ eventName?: string;
339
+ route?: string;
340
+ sessionQueryParam?: string;
341
+ };
292
342
  export type VoiceStoreAction<TResult = unknown> = {
293
343
  type: 'session';
294
344
  sessionId: string;
package/dist/vue/index.js CHANGED
@@ -366,6 +366,7 @@ var createVoiceStreamStore = () => {
366
366
  state = {
367
367
  ...state,
368
368
  error: null,
369
+ isConnected: action.status === "active",
369
370
  sessionId: action.sessionId,
370
371
  status: action.status
371
372
  };
@@ -408,7 +409,6 @@ var createVoiceStream = (path, options = {}) => {
408
409
  notify();
409
410
  }
410
411
  });
411
- store.dispatch({ type: "connected" });
412
412
  return {
413
413
  close() {
414
414
  unsubscribeConnection();
@@ -7,7 +7,7 @@ export declare const useVoiceStream: <TResult = unknown>(path: string, options?:
7
7
  isConnected: import("vue").Ref<boolean, boolean>;
8
8
  partial: import("vue").Ref<string, string>;
9
9
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
10
- sessionId: import("vue").Ref<string, string>;
10
+ sessionId: import("vue").Ref<string | null, string | null>;
11
11
  status: import("vue").Ref<import("..").VoiceSessionStatus | "idle", import("..").VoiceSessionStatus | "idle">;
12
12
  turns: import("vue").ShallowRef<VoiceTurnRecord<TResult>[], VoiceTurnRecord<TResult>[]>;
13
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",