@speechrouter/sdk 0.1.0 → 0.1.1
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 +89 -4
- package/dist/index.cjs +441 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +401 -0
- package/dist/index.js.map +1 -0
- package/dist/listen-DEvmsIUS.d.cts +225 -0
- package/dist/listen-DEvmsIUS.d.ts +225 -0
- package/dist/mic.cjs +78 -0
- package/dist/mic.cjs.map +1 -0
- package/dist/mic.d.cts +23 -0
- package/dist/mic.d.ts +23 -0
- package/dist/mic.js +53 -0
- package/dist/mic.js.map +1 -0
- package/package.json +57 -11
- package/index.cjs +0 -1
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/mic.cjs +0 -1
- package/mic.d.ts +0 -1
- package/mic.js +0 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
type ErrorCode = 'auth_failed' | 'key_revoked' | 'insufficient_credits' | 'rate_limited' | 'concurrency_exceeded' | 'invalid_request' | 'model_not_found' | 'unsupported_capability' | 'unsupported_encoding' | 'payload_too_large' | 'provider_error' | 'provider_timeout' | 'all_providers_failed' | 'audio_timeout' | 'session_expired' | 'internal_error';
|
|
2
|
+
interface Word {
|
|
3
|
+
w: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
conf?: number;
|
|
7
|
+
speaker?: number;
|
|
8
|
+
lang?: string;
|
|
9
|
+
}
|
|
10
|
+
interface SessionOpenEvent {
|
|
11
|
+
type: 'session.open';
|
|
12
|
+
session_id: string;
|
|
13
|
+
model: string;
|
|
14
|
+
encoding?: string;
|
|
15
|
+
sample_rate?: number;
|
|
16
|
+
}
|
|
17
|
+
interface TranscriptEvent {
|
|
18
|
+
type: 'transcript';
|
|
19
|
+
is_final: boolean;
|
|
20
|
+
text: string;
|
|
21
|
+
words?: Word[];
|
|
22
|
+
start?: number;
|
|
23
|
+
end?: number;
|
|
24
|
+
lang?: string;
|
|
25
|
+
/** Present when the session was opened with includeRaw. */
|
|
26
|
+
provider_raw?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
interface SpeechStartedEvent {
|
|
29
|
+
type: 'speech_started';
|
|
30
|
+
at: number;
|
|
31
|
+
}
|
|
32
|
+
interface UtteranceEndEvent {
|
|
33
|
+
type: 'utterance_end';
|
|
34
|
+
at: number;
|
|
35
|
+
}
|
|
36
|
+
interface ProviderSwitchedEvent {
|
|
37
|
+
type: 'provider_switched';
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
resumed_at: number;
|
|
41
|
+
speaker_mapping_preserved: boolean;
|
|
42
|
+
}
|
|
43
|
+
interface TextDeltaEvent {
|
|
44
|
+
type: 'text.delta';
|
|
45
|
+
text: string;
|
|
46
|
+
}
|
|
47
|
+
interface ClearedEvent {
|
|
48
|
+
type: 'cleared';
|
|
49
|
+
last_seq: number;
|
|
50
|
+
}
|
|
51
|
+
interface KeepAliveEvent {
|
|
52
|
+
type: 'keepalive';
|
|
53
|
+
}
|
|
54
|
+
interface DoneEvent {
|
|
55
|
+
type: 'done';
|
|
56
|
+
usage: {
|
|
57
|
+
audio_seconds?: number;
|
|
58
|
+
model?: string;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
interface ErrorEvent {
|
|
63
|
+
type: 'error';
|
|
64
|
+
code: ErrorCode;
|
|
65
|
+
message: string;
|
|
66
|
+
provider?: string;
|
|
67
|
+
recoverable?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Every event the gateway can push during a listen session. */
|
|
70
|
+
type ListenEvent = SessionOpenEvent | TranscriptEvent | SpeechStartedEvent | UtteranceEndEvent | ProviderSwitchedEvent | TextDeltaEvent | ClearedEvent | KeepAliveEvent | DoneEvent | ErrorEvent;
|
|
71
|
+
interface Model {
|
|
72
|
+
slug: string;
|
|
73
|
+
provider: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
modes?: string[];
|
|
77
|
+
pricing?: {
|
|
78
|
+
per_second_usd?: number;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
};
|
|
81
|
+
capabilities?: Record<string, unknown>;
|
|
82
|
+
hipaa_eligible?: boolean;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
interface Transcription {
|
|
86
|
+
text: string;
|
|
87
|
+
}
|
|
88
|
+
interface VerboseTranscription {
|
|
89
|
+
task: string;
|
|
90
|
+
language: string | null;
|
|
91
|
+
duration: number | null;
|
|
92
|
+
text: string;
|
|
93
|
+
words: Array<{
|
|
94
|
+
word: string;
|
|
95
|
+
start: number;
|
|
96
|
+
end: number;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}>;
|
|
99
|
+
model: string;
|
|
100
|
+
provider_raw?: Record<string, unknown>;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Every failure the SDK surfaces is one of these. */
|
|
105
|
+
declare class SpeechRouterError extends Error {
|
|
106
|
+
/** Machine-readable code from the gateway's 16-code error enum, or a
|
|
107
|
+
* client-side code ('connection_failed', 'connection_closed', 'timeout'). */
|
|
108
|
+
readonly code: ErrorCode | 'connection_failed' | 'connection_closed' | 'timeout';
|
|
109
|
+
/** HTTP status for REST calls; undefined for WebSocket errors. */
|
|
110
|
+
readonly status?: number;
|
|
111
|
+
/** Which upstream provider tripped, when the gateway says. */
|
|
112
|
+
readonly provider?: string;
|
|
113
|
+
/** Gateway's hint that retrying the same request may succeed. */
|
|
114
|
+
readonly recoverable: boolean;
|
|
115
|
+
constructor(message: string, opts: {
|
|
116
|
+
code: SpeechRouterError['code'];
|
|
117
|
+
status?: number;
|
|
118
|
+
provider?: string;
|
|
119
|
+
recoverable?: boolean;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface ListenOptions {
|
|
124
|
+
/** Model slug, e.g. "deepgram/nova-3". */
|
|
125
|
+
model: string;
|
|
126
|
+
/** Ordered failover lane, e.g. ["soniox/stt-rt-v5"]. */
|
|
127
|
+
fallbacks?: string[];
|
|
128
|
+
/** PCM encoding of the audio you will send. Default "linear16". */
|
|
129
|
+
encoding?: string;
|
|
130
|
+
/** Sample rate of the audio you will send. Default 16000. */
|
|
131
|
+
sampleRate?: number;
|
|
132
|
+
/** Channel count. Default 1. */
|
|
133
|
+
channels?: number;
|
|
134
|
+
language?: string;
|
|
135
|
+
/** Emit non-final hypotheses. Default true. */
|
|
136
|
+
interimResults?: boolean;
|
|
137
|
+
diarization?: boolean;
|
|
138
|
+
/** Bias recognition toward these terms (when the model supports it). */
|
|
139
|
+
keyterms?: string[];
|
|
140
|
+
/** Attach the untouched provider payload to every transcript. */
|
|
141
|
+
includeRaw?: boolean;
|
|
142
|
+
/** Escape hatch: raw params forwarded to the provider. */
|
|
143
|
+
providerParams?: Record<string, unknown>;
|
|
144
|
+
/** Abort the dial if the socket is not open in this many ms. Default 10000. */
|
|
145
|
+
connectTimeoutMs?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Keep the session alive through silences by sending keepalive frames.
|
|
148
|
+
* true = every 8000 ms, a number = that interval, false = off (default
|
|
149
|
+
* true). Note: an open session bills wall-clock time on session-billed
|
|
150
|
+
* providers — close streams you are done with.
|
|
151
|
+
*/
|
|
152
|
+
keepAlive?: boolean | number;
|
|
153
|
+
}
|
|
154
|
+
type Listener<E> = (event: E) => void;
|
|
155
|
+
interface ListenEventMap {
|
|
156
|
+
/** Socket is open and the gateway accepted the session. */
|
|
157
|
+
open: SessionOpenEvent;
|
|
158
|
+
transcript: TranscriptEvent;
|
|
159
|
+
provider_switched: ProviderSwitchedEvent;
|
|
160
|
+
done: DoneEvent;
|
|
161
|
+
error: SpeechRouterError;
|
|
162
|
+
/** Fired exactly once, after every other event. */
|
|
163
|
+
close: {
|
|
164
|
+
code?: number;
|
|
165
|
+
reason?: string;
|
|
166
|
+
};
|
|
167
|
+
/** Every wire event, untouched — including ones without a named channel. */
|
|
168
|
+
event: ListenEvent;
|
|
169
|
+
}
|
|
170
|
+
declare function buildListenUrl(wsBase: string, opts: ListenOptions, apiKey: string): string;
|
|
171
|
+
/**
|
|
172
|
+
* A live transcription session. Create via `client.listen(...)`, then send
|
|
173
|
+
* PCM with `sendAudio()` and consume events with `on()` or `for await`.
|
|
174
|
+
*/
|
|
175
|
+
declare class ListenStream {
|
|
176
|
+
private url;
|
|
177
|
+
private opts;
|
|
178
|
+
private ws;
|
|
179
|
+
private listeners;
|
|
180
|
+
private sendQueue;
|
|
181
|
+
private iterQueue;
|
|
182
|
+
private iterWaiter;
|
|
183
|
+
private keepAliveTimer;
|
|
184
|
+
private connectTimer;
|
|
185
|
+
private donePromise;
|
|
186
|
+
private resolveDone;
|
|
187
|
+
private rejectDone;
|
|
188
|
+
private doneSettled;
|
|
189
|
+
/** 'connecting' → 'open' → 'closed'; 'finalizing' between finalize() and done. */
|
|
190
|
+
state: 'connecting' | 'open' | 'finalizing' | 'closed';
|
|
191
|
+
/** Set once the gateway confirms the session. */
|
|
192
|
+
session: SessionOpenEvent | null;
|
|
193
|
+
constructor(url: string, opts: ListenOptions);
|
|
194
|
+
on<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void;
|
|
195
|
+
once<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void;
|
|
196
|
+
private emit;
|
|
197
|
+
/** Consume the session as an async stream of wire events. */
|
|
198
|
+
[Symbol.asyncIterator](): AsyncIterator<ListenEvent>;
|
|
199
|
+
private connect;
|
|
200
|
+
private handleMessage;
|
|
201
|
+
private pushIter;
|
|
202
|
+
private startKeepAlive;
|
|
203
|
+
private fail;
|
|
204
|
+
private settleDone;
|
|
205
|
+
private settleDoneWith;
|
|
206
|
+
private teardown;
|
|
207
|
+
/** Send a chunk of PCM audio. Chunks sent before the socket opens are queued. */
|
|
208
|
+
sendAudio(chunk: ArrayBufferLike | ArrayBufferView): void;
|
|
209
|
+
/** Bytes accepted but not yet on the wire — use to pace large sends. */
|
|
210
|
+
get bufferedAmount(): number;
|
|
211
|
+
private sendJson;
|
|
212
|
+
/** Ask the gateway to flush pending audio into a final transcript. */
|
|
213
|
+
finalize(): void;
|
|
214
|
+
/** Resolves with the gateway's usage summary once the session completes. */
|
|
215
|
+
done(): Promise<DoneEvent>;
|
|
216
|
+
/**
|
|
217
|
+
* Graceful shutdown: finalize, wait for the `done` usage event, close the
|
|
218
|
+
* socket. Returns the done event; rejects if the session errored.
|
|
219
|
+
*/
|
|
220
|
+
stop(timeoutMs?: number): Promise<DoneEvent>;
|
|
221
|
+
/** Immediate shutdown. In-flight audio may go untranscribed — prefer stop(). */
|
|
222
|
+
close(): void;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export { type ClearedEvent as C, type DoneEvent as D, type ErrorCode as E, type KeepAliveEvent as K, type ListenOptions as L, type Model as M, type ProviderSwitchedEvent as P, type SessionOpenEvent as S, type Transcription as T, type UtteranceEndEvent as U, type VerboseTranscription as V, type Word as W, ListenStream as a, type ErrorEvent as b, type ListenEvent as c, type ListenEventMap as d, SpeechRouterError as e, type SpeechStartedEvent as f, type TextDeltaEvent as g, type TranscriptEvent as h, buildListenUrl as i };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
type ErrorCode = 'auth_failed' | 'key_revoked' | 'insufficient_credits' | 'rate_limited' | 'concurrency_exceeded' | 'invalid_request' | 'model_not_found' | 'unsupported_capability' | 'unsupported_encoding' | 'payload_too_large' | 'provider_error' | 'provider_timeout' | 'all_providers_failed' | 'audio_timeout' | 'session_expired' | 'internal_error';
|
|
2
|
+
interface Word {
|
|
3
|
+
w: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
conf?: number;
|
|
7
|
+
speaker?: number;
|
|
8
|
+
lang?: string;
|
|
9
|
+
}
|
|
10
|
+
interface SessionOpenEvent {
|
|
11
|
+
type: 'session.open';
|
|
12
|
+
session_id: string;
|
|
13
|
+
model: string;
|
|
14
|
+
encoding?: string;
|
|
15
|
+
sample_rate?: number;
|
|
16
|
+
}
|
|
17
|
+
interface TranscriptEvent {
|
|
18
|
+
type: 'transcript';
|
|
19
|
+
is_final: boolean;
|
|
20
|
+
text: string;
|
|
21
|
+
words?: Word[];
|
|
22
|
+
start?: number;
|
|
23
|
+
end?: number;
|
|
24
|
+
lang?: string;
|
|
25
|
+
/** Present when the session was opened with includeRaw. */
|
|
26
|
+
provider_raw?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
interface SpeechStartedEvent {
|
|
29
|
+
type: 'speech_started';
|
|
30
|
+
at: number;
|
|
31
|
+
}
|
|
32
|
+
interface UtteranceEndEvent {
|
|
33
|
+
type: 'utterance_end';
|
|
34
|
+
at: number;
|
|
35
|
+
}
|
|
36
|
+
interface ProviderSwitchedEvent {
|
|
37
|
+
type: 'provider_switched';
|
|
38
|
+
from: string;
|
|
39
|
+
to: string;
|
|
40
|
+
resumed_at: number;
|
|
41
|
+
speaker_mapping_preserved: boolean;
|
|
42
|
+
}
|
|
43
|
+
interface TextDeltaEvent {
|
|
44
|
+
type: 'text.delta';
|
|
45
|
+
text: string;
|
|
46
|
+
}
|
|
47
|
+
interface ClearedEvent {
|
|
48
|
+
type: 'cleared';
|
|
49
|
+
last_seq: number;
|
|
50
|
+
}
|
|
51
|
+
interface KeepAliveEvent {
|
|
52
|
+
type: 'keepalive';
|
|
53
|
+
}
|
|
54
|
+
interface DoneEvent {
|
|
55
|
+
type: 'done';
|
|
56
|
+
usage: {
|
|
57
|
+
audio_seconds?: number;
|
|
58
|
+
model?: string;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
interface ErrorEvent {
|
|
63
|
+
type: 'error';
|
|
64
|
+
code: ErrorCode;
|
|
65
|
+
message: string;
|
|
66
|
+
provider?: string;
|
|
67
|
+
recoverable?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Every event the gateway can push during a listen session. */
|
|
70
|
+
type ListenEvent = SessionOpenEvent | TranscriptEvent | SpeechStartedEvent | UtteranceEndEvent | ProviderSwitchedEvent | TextDeltaEvent | ClearedEvent | KeepAliveEvent | DoneEvent | ErrorEvent;
|
|
71
|
+
interface Model {
|
|
72
|
+
slug: string;
|
|
73
|
+
provider: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
kind: string;
|
|
76
|
+
modes?: string[];
|
|
77
|
+
pricing?: {
|
|
78
|
+
per_second_usd?: number;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
};
|
|
81
|
+
capabilities?: Record<string, unknown>;
|
|
82
|
+
hipaa_eligible?: boolean;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
interface Transcription {
|
|
86
|
+
text: string;
|
|
87
|
+
}
|
|
88
|
+
interface VerboseTranscription {
|
|
89
|
+
task: string;
|
|
90
|
+
language: string | null;
|
|
91
|
+
duration: number | null;
|
|
92
|
+
text: string;
|
|
93
|
+
words: Array<{
|
|
94
|
+
word: string;
|
|
95
|
+
start: number;
|
|
96
|
+
end: number;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}>;
|
|
99
|
+
model: string;
|
|
100
|
+
provider_raw?: Record<string, unknown>;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Every failure the SDK surfaces is one of these. */
|
|
105
|
+
declare class SpeechRouterError extends Error {
|
|
106
|
+
/** Machine-readable code from the gateway's 16-code error enum, or a
|
|
107
|
+
* client-side code ('connection_failed', 'connection_closed', 'timeout'). */
|
|
108
|
+
readonly code: ErrorCode | 'connection_failed' | 'connection_closed' | 'timeout';
|
|
109
|
+
/** HTTP status for REST calls; undefined for WebSocket errors. */
|
|
110
|
+
readonly status?: number;
|
|
111
|
+
/** Which upstream provider tripped, when the gateway says. */
|
|
112
|
+
readonly provider?: string;
|
|
113
|
+
/** Gateway's hint that retrying the same request may succeed. */
|
|
114
|
+
readonly recoverable: boolean;
|
|
115
|
+
constructor(message: string, opts: {
|
|
116
|
+
code: SpeechRouterError['code'];
|
|
117
|
+
status?: number;
|
|
118
|
+
provider?: string;
|
|
119
|
+
recoverable?: boolean;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface ListenOptions {
|
|
124
|
+
/** Model slug, e.g. "deepgram/nova-3". */
|
|
125
|
+
model: string;
|
|
126
|
+
/** Ordered failover lane, e.g. ["soniox/stt-rt-v5"]. */
|
|
127
|
+
fallbacks?: string[];
|
|
128
|
+
/** PCM encoding of the audio you will send. Default "linear16". */
|
|
129
|
+
encoding?: string;
|
|
130
|
+
/** Sample rate of the audio you will send. Default 16000. */
|
|
131
|
+
sampleRate?: number;
|
|
132
|
+
/** Channel count. Default 1. */
|
|
133
|
+
channels?: number;
|
|
134
|
+
language?: string;
|
|
135
|
+
/** Emit non-final hypotheses. Default true. */
|
|
136
|
+
interimResults?: boolean;
|
|
137
|
+
diarization?: boolean;
|
|
138
|
+
/** Bias recognition toward these terms (when the model supports it). */
|
|
139
|
+
keyterms?: string[];
|
|
140
|
+
/** Attach the untouched provider payload to every transcript. */
|
|
141
|
+
includeRaw?: boolean;
|
|
142
|
+
/** Escape hatch: raw params forwarded to the provider. */
|
|
143
|
+
providerParams?: Record<string, unknown>;
|
|
144
|
+
/** Abort the dial if the socket is not open in this many ms. Default 10000. */
|
|
145
|
+
connectTimeoutMs?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Keep the session alive through silences by sending keepalive frames.
|
|
148
|
+
* true = every 8000 ms, a number = that interval, false = off (default
|
|
149
|
+
* true). Note: an open session bills wall-clock time on session-billed
|
|
150
|
+
* providers — close streams you are done with.
|
|
151
|
+
*/
|
|
152
|
+
keepAlive?: boolean | number;
|
|
153
|
+
}
|
|
154
|
+
type Listener<E> = (event: E) => void;
|
|
155
|
+
interface ListenEventMap {
|
|
156
|
+
/** Socket is open and the gateway accepted the session. */
|
|
157
|
+
open: SessionOpenEvent;
|
|
158
|
+
transcript: TranscriptEvent;
|
|
159
|
+
provider_switched: ProviderSwitchedEvent;
|
|
160
|
+
done: DoneEvent;
|
|
161
|
+
error: SpeechRouterError;
|
|
162
|
+
/** Fired exactly once, after every other event. */
|
|
163
|
+
close: {
|
|
164
|
+
code?: number;
|
|
165
|
+
reason?: string;
|
|
166
|
+
};
|
|
167
|
+
/** Every wire event, untouched — including ones without a named channel. */
|
|
168
|
+
event: ListenEvent;
|
|
169
|
+
}
|
|
170
|
+
declare function buildListenUrl(wsBase: string, opts: ListenOptions, apiKey: string): string;
|
|
171
|
+
/**
|
|
172
|
+
* A live transcription session. Create via `client.listen(...)`, then send
|
|
173
|
+
* PCM with `sendAudio()` and consume events with `on()` or `for await`.
|
|
174
|
+
*/
|
|
175
|
+
declare class ListenStream {
|
|
176
|
+
private url;
|
|
177
|
+
private opts;
|
|
178
|
+
private ws;
|
|
179
|
+
private listeners;
|
|
180
|
+
private sendQueue;
|
|
181
|
+
private iterQueue;
|
|
182
|
+
private iterWaiter;
|
|
183
|
+
private keepAliveTimer;
|
|
184
|
+
private connectTimer;
|
|
185
|
+
private donePromise;
|
|
186
|
+
private resolveDone;
|
|
187
|
+
private rejectDone;
|
|
188
|
+
private doneSettled;
|
|
189
|
+
/** 'connecting' → 'open' → 'closed'; 'finalizing' between finalize() and done. */
|
|
190
|
+
state: 'connecting' | 'open' | 'finalizing' | 'closed';
|
|
191
|
+
/** Set once the gateway confirms the session. */
|
|
192
|
+
session: SessionOpenEvent | null;
|
|
193
|
+
constructor(url: string, opts: ListenOptions);
|
|
194
|
+
on<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void;
|
|
195
|
+
once<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void;
|
|
196
|
+
private emit;
|
|
197
|
+
/** Consume the session as an async stream of wire events. */
|
|
198
|
+
[Symbol.asyncIterator](): AsyncIterator<ListenEvent>;
|
|
199
|
+
private connect;
|
|
200
|
+
private handleMessage;
|
|
201
|
+
private pushIter;
|
|
202
|
+
private startKeepAlive;
|
|
203
|
+
private fail;
|
|
204
|
+
private settleDone;
|
|
205
|
+
private settleDoneWith;
|
|
206
|
+
private teardown;
|
|
207
|
+
/** Send a chunk of PCM audio. Chunks sent before the socket opens are queued. */
|
|
208
|
+
sendAudio(chunk: ArrayBufferLike | ArrayBufferView): void;
|
|
209
|
+
/** Bytes accepted but not yet on the wire — use to pace large sends. */
|
|
210
|
+
get bufferedAmount(): number;
|
|
211
|
+
private sendJson;
|
|
212
|
+
/** Ask the gateway to flush pending audio into a final transcript. */
|
|
213
|
+
finalize(): void;
|
|
214
|
+
/** Resolves with the gateway's usage summary once the session completes. */
|
|
215
|
+
done(): Promise<DoneEvent>;
|
|
216
|
+
/**
|
|
217
|
+
* Graceful shutdown: finalize, wait for the `done` usage event, close the
|
|
218
|
+
* socket. Returns the done event; rejects if the session errored.
|
|
219
|
+
*/
|
|
220
|
+
stop(timeoutMs?: number): Promise<DoneEvent>;
|
|
221
|
+
/** Immediate shutdown. In-flight audio may go untranscribed — prefer stop(). */
|
|
222
|
+
close(): void;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export { type ClearedEvent as C, type DoneEvent as D, type ErrorCode as E, type KeepAliveEvent as K, type ListenOptions as L, type Model as M, type ProviderSwitchedEvent as P, type SessionOpenEvent as S, type Transcription as T, type UtteranceEndEvent as U, type VerboseTranscription as V, type Word as W, ListenStream as a, type ErrorEvent as b, type ListenEvent as c, type ListenEventMap as d, SpeechRouterError as e, type SpeechStartedEvent as f, type TextDeltaEvent as g, type TranscriptEvent as h, buildListenUrl as i };
|
package/dist/mic.cjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/mic.ts
|
|
21
|
+
var mic_exports = {};
|
|
22
|
+
__export(mic_exports, {
|
|
23
|
+
openMicrophone: () => openMicrophone
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(mic_exports);
|
|
26
|
+
async function openMicrophone(stream, opts = {}) {
|
|
27
|
+
const target = opts.sampleRate ?? 16e3;
|
|
28
|
+
const media = await navigator.mediaDevices.getUserMedia({
|
|
29
|
+
audio: {
|
|
30
|
+
echoCancellation: opts.echoCancellation ?? true,
|
|
31
|
+
noiseSuppression: opts.noiseSuppression ?? true,
|
|
32
|
+
channelCount: 1
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const ctx = new AudioContext();
|
|
36
|
+
const source = ctx.createMediaStreamSource(media);
|
|
37
|
+
const processor = ctx.createScriptProcessor(4096, 1, 1);
|
|
38
|
+
const ratio = ctx.sampleRate / target;
|
|
39
|
+
let phase = 0;
|
|
40
|
+
processor.onaudioprocess = (e) => {
|
|
41
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
42
|
+
if (opts.onLevel) {
|
|
43
|
+
let sum = 0;
|
|
44
|
+
for (let i = 0; i < input.length; i++) sum += input[i] * input[i];
|
|
45
|
+
opts.onLevel(Math.sqrt(sum / input.length));
|
|
46
|
+
}
|
|
47
|
+
const frames = [];
|
|
48
|
+
while (phase < input.length - 1) {
|
|
49
|
+
const i = Math.floor(phase);
|
|
50
|
+
const frac = phase - i;
|
|
51
|
+
frames.push(input[i] * (1 - frac) + input[i + 1] * frac);
|
|
52
|
+
phase += ratio;
|
|
53
|
+
}
|
|
54
|
+
phase -= input.length;
|
|
55
|
+
const pcm = new Int16Array(frames.length);
|
|
56
|
+
for (let i = 0; i < frames.length; i++) {
|
|
57
|
+
const s = Math.max(-1, Math.min(1, frames[i]));
|
|
58
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
59
|
+
}
|
|
60
|
+
if (stream.state === "open" || stream.state === "connecting") stream.sendAudio(pcm.buffer);
|
|
61
|
+
};
|
|
62
|
+
source.connect(processor);
|
|
63
|
+
processor.connect(ctx.destination);
|
|
64
|
+
return {
|
|
65
|
+
captureSampleRate: ctx.sampleRate,
|
|
66
|
+
stop() {
|
|
67
|
+
processor.disconnect();
|
|
68
|
+
source.disconnect();
|
|
69
|
+
media.getTracks().forEach((t) => t.stop());
|
|
70
|
+
void ctx.close();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
75
|
+
0 && (module.exports = {
|
|
76
|
+
openMicrophone
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=mic.cjs.map
|
package/dist/mic.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mic.ts"],"sourcesContent":["/* Browser microphone capture → 16 kHz mono PCM, ready for ListenStream.\n *\n * Separate entry point (\"speechrouter/mic\") because it touches\n * getUserMedia/AudioContext — browser-only APIs that React Native and Node\n * bundles must never import. In React Native, capture PCM with a native\n * module (e.g. react-native-live-audio-stream) and feed stream.sendAudio().\n */\n\nimport type { ListenStream } from './listen'\n\nexport interface MicrophoneOptions {\n /** Target sample rate sent to the gateway. Default 16000. */\n sampleRate?: number\n echoCancellation?: boolean\n noiseSuppression?: boolean\n /** Called with the RMS level (0..1) of each captured block — drive a meter. */\n onLevel?: (rms: number) => void\n}\n\nexport interface Microphone {\n /** The rate audio is actually captured at before resampling. */\n readonly captureSampleRate: number\n stop(): void\n}\n\n/**\n * Capture the default microphone and pump it into a listen stream.\n * Browsers ignore requested rates (a 96 kHz interface stays 96 kHz), so\n * audio is linearly resampled with cross-buffer phase continuity.\n */\nexport async function openMicrophone(\n stream: ListenStream,\n opts: MicrophoneOptions = {},\n): Promise<Microphone> {\n const target = opts.sampleRate ?? 16000\n const media = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: opts.echoCancellation ?? true,\n noiseSuppression: opts.noiseSuppression ?? true,\n channelCount: 1,\n },\n })\n const ctx = new AudioContext()\n const source = ctx.createMediaStreamSource(media)\n const processor = ctx.createScriptProcessor(4096, 1, 1)\n const ratio = ctx.sampleRate / target\n let phase = 0\n\n processor.onaudioprocess = (e) => {\n const input = e.inputBuffer.getChannelData(0)\n if (opts.onLevel) {\n let sum = 0\n for (let i = 0; i < input.length; i++) sum += input[i]! * input[i]!\n opts.onLevel(Math.sqrt(sum / input.length))\n }\n const frames: number[] = []\n while (phase < input.length - 1) {\n const i = Math.floor(phase)\n const frac = phase - i\n frames.push(input[i]! * (1 - frac) + input[i + 1]! * frac)\n phase += ratio\n }\n phase -= input.length\n const pcm = new Int16Array(frames.length)\n for (let i = 0; i < frames.length; i++) {\n const s = Math.max(-1, Math.min(1, frames[i]!))\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff\n }\n if (stream.state === 'open' || stream.state === 'connecting') stream.sendAudio(pcm.buffer)\n }\n\n source.connect(processor)\n processor.connect(ctx.destination)\n\n return {\n captureSampleRate: ctx.sampleRate,\n stop() {\n processor.disconnect()\n source.disconnect()\n media.getTracks().forEach((t) => t.stop())\n void ctx.close()\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,eAAsB,eACpB,QACA,OAA0B,CAAC,GACN;AACrB,QAAM,SAAS,KAAK,cAAc;AAClC,QAAM,QAAQ,MAAM,UAAU,aAAa,aAAa;AAAA,IACtD,OAAO;AAAA,MACL,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACD,QAAM,MAAM,IAAI,aAAa;AAC7B,QAAM,SAAS,IAAI,wBAAwB,KAAK;AAChD,QAAM,YAAY,IAAI,sBAAsB,MAAM,GAAG,CAAC;AACtD,QAAM,QAAQ,IAAI,aAAa;AAC/B,MAAI,QAAQ;AAEZ,YAAU,iBAAiB,CAAC,MAAM;AAChC,UAAM,QAAQ,EAAE,YAAY,eAAe,CAAC;AAC5C,QAAI,KAAK,SAAS;AAChB,UAAI,MAAM;AACV,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,QAAO,MAAM,CAAC,IAAK,MAAM,CAAC;AACjE,WAAK,QAAQ,KAAK,KAAK,MAAM,MAAM,MAAM,CAAC;AAAA,IAC5C;AACA,UAAM,SAAmB,CAAC;AAC1B,WAAO,QAAQ,MAAM,SAAS,GAAG;AAC/B,YAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,YAAM,OAAO,QAAQ;AACrB,aAAO,KAAK,MAAM,CAAC,KAAM,IAAI,QAAQ,MAAM,IAAI,CAAC,IAAK,IAAI;AACzD,eAAS;AAAA,IACX;AACA,aAAS,MAAM;AACf,UAAM,MAAM,IAAI,WAAW,OAAO,MAAM;AACxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,CAAE,CAAC;AAC9C,UAAI,CAAC,IAAI,IAAI,IAAI,IAAI,QAAS,IAAI;AAAA,IACpC;AACA,QAAI,OAAO,UAAU,UAAU,OAAO,UAAU,aAAc,QAAO,UAAU,IAAI,MAAM;AAAA,EAC3F;AAEA,SAAO,QAAQ,SAAS;AACxB,YAAU,QAAQ,IAAI,WAAW;AAEjC,SAAO;AAAA,IACL,mBAAmB,IAAI;AAAA,IACvB,OAAO;AACL,gBAAU,WAAW;AACrB,aAAO,WAAW;AAClB,YAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AACzC,WAAK,IAAI,MAAM;AAAA,IACjB;AAAA,EACF;AACF;","names":[]}
|
package/dist/mic.d.cts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { a as ListenStream } from './listen-DEvmsIUS.cjs';
|
|
2
|
+
|
|
3
|
+
interface MicrophoneOptions {
|
|
4
|
+
/** Target sample rate sent to the gateway. Default 16000. */
|
|
5
|
+
sampleRate?: number;
|
|
6
|
+
echoCancellation?: boolean;
|
|
7
|
+
noiseSuppression?: boolean;
|
|
8
|
+
/** Called with the RMS level (0..1) of each captured block — drive a meter. */
|
|
9
|
+
onLevel?: (rms: number) => void;
|
|
10
|
+
}
|
|
11
|
+
interface Microphone {
|
|
12
|
+
/** The rate audio is actually captured at before resampling. */
|
|
13
|
+
readonly captureSampleRate: number;
|
|
14
|
+
stop(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Capture the default microphone and pump it into a listen stream.
|
|
18
|
+
* Browsers ignore requested rates (a 96 kHz interface stays 96 kHz), so
|
|
19
|
+
* audio is linearly resampled with cross-buffer phase continuity.
|
|
20
|
+
*/
|
|
21
|
+
declare function openMicrophone(stream: ListenStream, opts?: MicrophoneOptions): Promise<Microphone>;
|
|
22
|
+
|
|
23
|
+
export { type Microphone, type MicrophoneOptions, openMicrophone };
|
package/dist/mic.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { a as ListenStream } from './listen-DEvmsIUS.js';
|
|
2
|
+
|
|
3
|
+
interface MicrophoneOptions {
|
|
4
|
+
/** Target sample rate sent to the gateway. Default 16000. */
|
|
5
|
+
sampleRate?: number;
|
|
6
|
+
echoCancellation?: boolean;
|
|
7
|
+
noiseSuppression?: boolean;
|
|
8
|
+
/** Called with the RMS level (0..1) of each captured block — drive a meter. */
|
|
9
|
+
onLevel?: (rms: number) => void;
|
|
10
|
+
}
|
|
11
|
+
interface Microphone {
|
|
12
|
+
/** The rate audio is actually captured at before resampling. */
|
|
13
|
+
readonly captureSampleRate: number;
|
|
14
|
+
stop(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Capture the default microphone and pump it into a listen stream.
|
|
18
|
+
* Browsers ignore requested rates (a 96 kHz interface stays 96 kHz), so
|
|
19
|
+
* audio is linearly resampled with cross-buffer phase continuity.
|
|
20
|
+
*/
|
|
21
|
+
declare function openMicrophone(stream: ListenStream, opts?: MicrophoneOptions): Promise<Microphone>;
|
|
22
|
+
|
|
23
|
+
export { type Microphone, type MicrophoneOptions, openMicrophone };
|
package/dist/mic.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/mic.ts
|
|
2
|
+
async function openMicrophone(stream, opts = {}) {
|
|
3
|
+
const target = opts.sampleRate ?? 16e3;
|
|
4
|
+
const media = await navigator.mediaDevices.getUserMedia({
|
|
5
|
+
audio: {
|
|
6
|
+
echoCancellation: opts.echoCancellation ?? true,
|
|
7
|
+
noiseSuppression: opts.noiseSuppression ?? true,
|
|
8
|
+
channelCount: 1
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const ctx = new AudioContext();
|
|
12
|
+
const source = ctx.createMediaStreamSource(media);
|
|
13
|
+
const processor = ctx.createScriptProcessor(4096, 1, 1);
|
|
14
|
+
const ratio = ctx.sampleRate / target;
|
|
15
|
+
let phase = 0;
|
|
16
|
+
processor.onaudioprocess = (e) => {
|
|
17
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
18
|
+
if (opts.onLevel) {
|
|
19
|
+
let sum = 0;
|
|
20
|
+
for (let i = 0; i < input.length; i++) sum += input[i] * input[i];
|
|
21
|
+
opts.onLevel(Math.sqrt(sum / input.length));
|
|
22
|
+
}
|
|
23
|
+
const frames = [];
|
|
24
|
+
while (phase < input.length - 1) {
|
|
25
|
+
const i = Math.floor(phase);
|
|
26
|
+
const frac = phase - i;
|
|
27
|
+
frames.push(input[i] * (1 - frac) + input[i + 1] * frac);
|
|
28
|
+
phase += ratio;
|
|
29
|
+
}
|
|
30
|
+
phase -= input.length;
|
|
31
|
+
const pcm = new Int16Array(frames.length);
|
|
32
|
+
for (let i = 0; i < frames.length; i++) {
|
|
33
|
+
const s = Math.max(-1, Math.min(1, frames[i]));
|
|
34
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
35
|
+
}
|
|
36
|
+
if (stream.state === "open" || stream.state === "connecting") stream.sendAudio(pcm.buffer);
|
|
37
|
+
};
|
|
38
|
+
source.connect(processor);
|
|
39
|
+
processor.connect(ctx.destination);
|
|
40
|
+
return {
|
|
41
|
+
captureSampleRate: ctx.sampleRate,
|
|
42
|
+
stop() {
|
|
43
|
+
processor.disconnect();
|
|
44
|
+
source.disconnect();
|
|
45
|
+
media.getTracks().forEach((t) => t.stop());
|
|
46
|
+
void ctx.close();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
openMicrophone
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=mic.js.map
|
package/dist/mic.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mic.ts"],"sourcesContent":["/* Browser microphone capture → 16 kHz mono PCM, ready for ListenStream.\n *\n * Separate entry point (\"speechrouter/mic\") because it touches\n * getUserMedia/AudioContext — browser-only APIs that React Native and Node\n * bundles must never import. In React Native, capture PCM with a native\n * module (e.g. react-native-live-audio-stream) and feed stream.sendAudio().\n */\n\nimport type { ListenStream } from './listen'\n\nexport interface MicrophoneOptions {\n /** Target sample rate sent to the gateway. Default 16000. */\n sampleRate?: number\n echoCancellation?: boolean\n noiseSuppression?: boolean\n /** Called with the RMS level (0..1) of each captured block — drive a meter. */\n onLevel?: (rms: number) => void\n}\n\nexport interface Microphone {\n /** The rate audio is actually captured at before resampling. */\n readonly captureSampleRate: number\n stop(): void\n}\n\n/**\n * Capture the default microphone and pump it into a listen stream.\n * Browsers ignore requested rates (a 96 kHz interface stays 96 kHz), so\n * audio is linearly resampled with cross-buffer phase continuity.\n */\nexport async function openMicrophone(\n stream: ListenStream,\n opts: MicrophoneOptions = {},\n): Promise<Microphone> {\n const target = opts.sampleRate ?? 16000\n const media = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: opts.echoCancellation ?? true,\n noiseSuppression: opts.noiseSuppression ?? true,\n channelCount: 1,\n },\n })\n const ctx = new AudioContext()\n const source = ctx.createMediaStreamSource(media)\n const processor = ctx.createScriptProcessor(4096, 1, 1)\n const ratio = ctx.sampleRate / target\n let phase = 0\n\n processor.onaudioprocess = (e) => {\n const input = e.inputBuffer.getChannelData(0)\n if (opts.onLevel) {\n let sum = 0\n for (let i = 0; i < input.length; i++) sum += input[i]! * input[i]!\n opts.onLevel(Math.sqrt(sum / input.length))\n }\n const frames: number[] = []\n while (phase < input.length - 1) {\n const i = Math.floor(phase)\n const frac = phase - i\n frames.push(input[i]! * (1 - frac) + input[i + 1]! * frac)\n phase += ratio\n }\n phase -= input.length\n const pcm = new Int16Array(frames.length)\n for (let i = 0; i < frames.length; i++) {\n const s = Math.max(-1, Math.min(1, frames[i]!))\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff\n }\n if (stream.state === 'open' || stream.state === 'connecting') stream.sendAudio(pcm.buffer)\n }\n\n source.connect(processor)\n processor.connect(ctx.destination)\n\n return {\n captureSampleRate: ctx.sampleRate,\n stop() {\n processor.disconnect()\n source.disconnect()\n media.getTracks().forEach((t) => t.stop())\n void ctx.close()\n },\n }\n}\n"],"mappings":";AA8BA,eAAsB,eACpB,QACA,OAA0B,CAAC,GACN;AACrB,QAAM,SAAS,KAAK,cAAc;AAClC,QAAM,QAAQ,MAAM,UAAU,aAAa,aAAa;AAAA,IACtD,OAAO;AAAA,MACL,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AACD,QAAM,MAAM,IAAI,aAAa;AAC7B,QAAM,SAAS,IAAI,wBAAwB,KAAK;AAChD,QAAM,YAAY,IAAI,sBAAsB,MAAM,GAAG,CAAC;AACtD,QAAM,QAAQ,IAAI,aAAa;AAC/B,MAAI,QAAQ;AAEZ,YAAU,iBAAiB,CAAC,MAAM;AAChC,UAAM,QAAQ,EAAE,YAAY,eAAe,CAAC;AAC5C,QAAI,KAAK,SAAS;AAChB,UAAI,MAAM;AACV,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,QAAO,MAAM,CAAC,IAAK,MAAM,CAAC;AACjE,WAAK,QAAQ,KAAK,KAAK,MAAM,MAAM,MAAM,CAAC;AAAA,IAC5C;AACA,UAAM,SAAmB,CAAC;AAC1B,WAAO,QAAQ,MAAM,SAAS,GAAG;AAC/B,YAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,YAAM,OAAO,QAAQ;AACrB,aAAO,KAAK,MAAM,CAAC,KAAM,IAAI,QAAQ,MAAM,IAAI,CAAC,IAAK,IAAI;AACzD,eAAS;AAAA,IACX;AACA,aAAS,MAAM;AACf,UAAM,MAAM,IAAI,WAAW,OAAO,MAAM;AACxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,CAAE,CAAC;AAC9C,UAAI,CAAC,IAAI,IAAI,IAAI,IAAI,QAAS,IAAI;AAAA,IACpC;AACA,QAAI,OAAO,UAAU,UAAU,OAAO,UAAU,aAAc,QAAO,UAAU,IAAI,MAAM;AAAA,EAC3F;AAEA,SAAO,QAAQ,SAAS;AACxB,YAAU,QAAQ,IAAI,WAAW;AAEjC,SAAO;AAAA,IACL,mBAAmB,IAAI;AAAA,IACvB,OAAO;AACL,gBAAU,WAAW;AACrB,aAAO,WAAW;AAClB,YAAM,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AACzC,WAAK,IAAI,MAAM;AAAA,IACjB;AAAA,EACF;AACF;","names":[]}
|