@absolutejs/voice 0.0.3 → 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 +50 -0
- package/dist/angular/voice-stream.service.d.ts +1 -1
- package/dist/client/createVoiceStream.d.ts +2 -14
- package/dist/client/htmx.d.ts +2 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +49 -1
- package/dist/htmx.d.ts +6 -0
- package/dist/index.js +146 -1
- package/dist/plugin.d.ts +29 -1
- package/dist/react/useVoiceStream.d.ts +1 -1
- package/dist/svelte/createVoiceStream.d.ts +1 -13
- package/dist/types.d.ts +50 -0
- package/dist/vue/useVoiceStream.d.ts +1 -1
- package/package.json +1 -1
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.
|
|
@@ -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>;
|
package/dist/client/index.d.ts
CHANGED
package/dist/client/index.js
CHANGED
|
@@ -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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
}>;
|
|
@@ -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>;
|
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;
|
|
@@ -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
|
};
|