solid-drift 0.25.0 → 0.27.0
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 +66 -0
- package/dist/hardware.d.ts +207 -0
- package/dist/hardware.js +631 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/voice.d.ts +292 -0
- package/dist/voice.js +577 -0
- package/package.json +1 -1
package/dist/voice.d.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { type Accessor } from "solid-js";
|
|
2
|
+
type MaybeElement = () => Element | null | undefined;
|
|
3
|
+
/** Phase of a voice interaction. */
|
|
4
|
+
export type VoiceStatus = "idle" | "listening" | "thinking" | "speaking";
|
|
5
|
+
export interface VoiceStateControls {
|
|
6
|
+
/** Current phase. */
|
|
7
|
+
state: Accessor<VoiceStatus>;
|
|
8
|
+
toIdle: () => void;
|
|
9
|
+
toListening: () => void;
|
|
10
|
+
toThinking: () => void;
|
|
11
|
+
toSpeaking: () => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The state machine behind a voice assistant turn: the user speaks
|
|
15
|
+
* (`listening`), the app works (`thinking`), the app replies
|
|
16
|
+
* (`speaking`), then back to `idle`. Wire the phases to
|
|
17
|
+
* `createSpeech` (listening), your model call (thinking), and
|
|
18
|
+
* `createTTS` (speaking).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const voice = createVoiceState();
|
|
22
|
+
* voice.toListening(); // mic open
|
|
23
|
+
* // ... transcript arrives
|
|
24
|
+
* voice.toThinking(); // calling the model
|
|
25
|
+
* // ... reply ready
|
|
26
|
+
* voice.toSpeaking(); // TTS playing
|
|
27
|
+
* voice.toIdle();
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function createVoiceState(): VoiceStateControls;
|
|
31
|
+
export interface MicLevelOptions {
|
|
32
|
+
/** Analyser FFT size. Default 512. */
|
|
33
|
+
fftSize?: number;
|
|
34
|
+
/** EMA smoothing factor 0..1 (higher = smoother). Default 0.7. */
|
|
35
|
+
smoothing?: number;
|
|
36
|
+
/** Gain applied to the RMS before clamping to 0..1. Default 3. */
|
|
37
|
+
gain?: number;
|
|
38
|
+
/** Use an existing stream instead of requesting the microphone. */
|
|
39
|
+
stream?: MediaStream;
|
|
40
|
+
/** Called with each smoothed level. */
|
|
41
|
+
onLevel?: (level: number) => void;
|
|
42
|
+
}
|
|
43
|
+
export interface MicLevelControls {
|
|
44
|
+
/** Smoothed volume 0..1. */
|
|
45
|
+
level: Accessor<number>;
|
|
46
|
+
/** Whether the meter is running. */
|
|
47
|
+
active: Accessor<boolean>;
|
|
48
|
+
/** False on the server or without WebAudio/getUserMedia. */
|
|
49
|
+
supported: boolean;
|
|
50
|
+
/** The analyser node, for wiring into `createWaveform`. */
|
|
51
|
+
analyser: Accessor<AnalyserNode | null>;
|
|
52
|
+
/** The last error, if any. */
|
|
53
|
+
error: Accessor<Error | null>;
|
|
54
|
+
/** Request the mic (call from a user gesture) and start metering. */
|
|
55
|
+
start: () => Promise<void>;
|
|
56
|
+
/** Stop metering and release the microphone. */
|
|
57
|
+
stop: () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A live microphone volume meter: `getUserMedia` plus an
|
|
61
|
+
* `AnalyserNode`, smoothed to a 0..1 level signal.
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* const mic = createMicLevel();
|
|
65
|
+
* // in a click handler:
|
|
66
|
+
* await mic.start();
|
|
67
|
+
* <div style={{ width: `${mic.level() * 100}%` }} />
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* SSR-safe: `supported` is false without WebAudio/getUserMedia and
|
|
71
|
+
* `start()` rejects there.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createMicLevel(options?: MicLevelOptions): MicLevelControls;
|
|
74
|
+
export interface SpeechOptions {
|
|
75
|
+
/** BCP-47 language tag. Default the browser language. */
|
|
76
|
+
lang?: string;
|
|
77
|
+
/** Keep listening across utterances. Default false. */
|
|
78
|
+
continuous?: boolean;
|
|
79
|
+
/** Emit interim (non-final) results. Default true. */
|
|
80
|
+
interimResults?: boolean;
|
|
81
|
+
/** Called per result: the result text and whether it is final. */
|
|
82
|
+
onResult?: (transcript: string, isFinal: boolean) => void;
|
|
83
|
+
/** Called on recognition errors. */
|
|
84
|
+
onError?: (error: Error) => void;
|
|
85
|
+
}
|
|
86
|
+
export interface SpeechControls {
|
|
87
|
+
/** False on the server or without the Web Speech API. */
|
|
88
|
+
supported: boolean;
|
|
89
|
+
/** Whether recognition is running. */
|
|
90
|
+
listening: Accessor<boolean>;
|
|
91
|
+
/** Accumulated final transcript. */
|
|
92
|
+
transcript: Accessor<string>;
|
|
93
|
+
/** Latest interim transcript (empty when the last result was final). */
|
|
94
|
+
interim: Accessor<string>;
|
|
95
|
+
/** The last error, if any. */
|
|
96
|
+
error: Accessor<Error | null>;
|
|
97
|
+
/** Start listening (call from a user gesture). */
|
|
98
|
+
start: () => void;
|
|
99
|
+
/** Stop listening. */
|
|
100
|
+
stop: () => void;
|
|
101
|
+
/** Clear the accumulated transcript. */
|
|
102
|
+
reset: () => void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Speech-to-text via the Web Speech API (`SpeechRecognition` with
|
|
106
|
+
* `webkitSpeechRecognition` fallback).
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* const speech = createSpeech({ lang: "en-US" });
|
|
110
|
+
* // in a click handler:
|
|
111
|
+
* speech.start();
|
|
112
|
+
* <p>{speech.transcript()}<span class="dim">{speech.interim()}</span></p>
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* SSR-safe: `supported` is false without the API and `start()` is a
|
|
116
|
+
* no-op there. Note the API itself is a browser/cloud service with
|
|
117
|
+
* per-browser availability and limits.
|
|
118
|
+
*/
|
|
119
|
+
export declare function createSpeech(options?: SpeechOptions): SpeechControls;
|
|
120
|
+
export interface WaveformOptions {
|
|
121
|
+
/** Analyser node to draw (wire to `createMicLevel().analyser`). */
|
|
122
|
+
analyser: () => AnalyserNode | null;
|
|
123
|
+
/** Stroke color. Default "#3e6c99". */
|
|
124
|
+
color?: string;
|
|
125
|
+
/** Line width in px. Default 2. */
|
|
126
|
+
lineWidth?: number;
|
|
127
|
+
/** `"line"` draws the time-domain wave, `"bars"` the spectrum. Default `"line"`. */
|
|
128
|
+
mode?: "line" | "bars";
|
|
129
|
+
/** Background fill, or null for a transparent clear. Default null. */
|
|
130
|
+
background?: string | null;
|
|
131
|
+
/** Pause rendering. Default true. */
|
|
132
|
+
enabled?: boolean | Accessor<boolean>;
|
|
133
|
+
}
|
|
134
|
+
export interface WaveformControls {
|
|
135
|
+
/** Whether the render loop is running. */
|
|
136
|
+
active: Accessor<boolean>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A canvas waveform renderer driven by an `AnalyserNode`.
|
|
140
|
+
*
|
|
141
|
+
* ```tsx
|
|
142
|
+
* const mic = createMicLevel();
|
|
143
|
+
* createWaveform(() => canvas, { analyser: mic.analyser, mode: "bars" });
|
|
144
|
+
* <canvas ref={canvas} style={{ width: "100%", height: "64px" }} />
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* The canvas is sized to its CSS box times the device pixel ratio.
|
|
148
|
+
* Under reduced motion it redraws at most every 250ms. SSR-safe:
|
|
149
|
+
* nothing renders without a canvas.
|
|
150
|
+
*/
|
|
151
|
+
export declare function createWaveform(canvas: MaybeElement, options: WaveformOptions): WaveformControls;
|
|
152
|
+
/** Cloud TTS upgrade: implement `speak` with your provider. */
|
|
153
|
+
export interface TTSProvider {
|
|
154
|
+
speak: (text: string, opts: {
|
|
155
|
+
signal: AbortSignal;
|
|
156
|
+
}) => Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
export interface TTSOptions {
|
|
159
|
+
/**
|
|
160
|
+
* `"browser"` uses `speechSynthesis`; pass a `TTSProvider` to
|
|
161
|
+
* upgrade to a cloud voice (fetch the audio in `speak`).
|
|
162
|
+
* Default `"browser"`.
|
|
163
|
+
*/
|
|
164
|
+
provider?: "browser" | TTSProvider;
|
|
165
|
+
/** BCP-47 language tag. Default the browser language. */
|
|
166
|
+
lang?: string;
|
|
167
|
+
/** 0.1..10. Default 1. */
|
|
168
|
+
rate?: number;
|
|
169
|
+
/** 0..2. Default 1. */
|
|
170
|
+
pitch?: number;
|
|
171
|
+
/** 0..1. Default 1. */
|
|
172
|
+
volume?: number;
|
|
173
|
+
/**
|
|
174
|
+
* Pick the voice: a voice name, or a function choosing from the
|
|
175
|
+
* available voices.
|
|
176
|
+
*/
|
|
177
|
+
voice?: string | ((voices: SpeechSynthesisVoice[]) => SpeechSynthesisVoice | undefined);
|
|
178
|
+
onStart?: () => void;
|
|
179
|
+
onEnd?: () => void;
|
|
180
|
+
onError?: (error: Error) => void;
|
|
181
|
+
}
|
|
182
|
+
export interface TTSControls {
|
|
183
|
+
/** False on the server or without `speechSynthesis` (browser provider). */
|
|
184
|
+
supported: boolean;
|
|
185
|
+
/** Whether speech is currently playing. */
|
|
186
|
+
speaking: Accessor<boolean>;
|
|
187
|
+
/** Available browser voices (loads asynchronously). */
|
|
188
|
+
voices: Accessor<SpeechSynthesisVoice[]>;
|
|
189
|
+
/** Speak the text (cancels anything currently playing). */
|
|
190
|
+
speak: (text: string) => void;
|
|
191
|
+
/** Stop playback. */
|
|
192
|
+
cancel: () => void;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Text-to-speech with `speechSynthesis` by default and a clean
|
|
196
|
+
* upgrade path to cloud voices.
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* const tts = createTTS({ rate: 1.05 });
|
|
200
|
+
* tts.speak("Your report is ready.");
|
|
201
|
+
*
|
|
202
|
+
* // Cloud upgrade:
|
|
203
|
+
* const tts = createTTS({
|
|
204
|
+
* provider: {
|
|
205
|
+
* speak: async (text, { signal }) => {
|
|
206
|
+
* const res = await fetch("/api/tts", {
|
|
207
|
+
* method: "POST",
|
|
208
|
+
* body: JSON.stringify({ text }),
|
|
209
|
+
* signal,
|
|
210
|
+
* });
|
|
211
|
+
* await playAudioBlob(await res.blob(), signal);
|
|
212
|
+
* },
|
|
213
|
+
* },
|
|
214
|
+
* });
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* SSR-safe: `supported` is false without `speechSynthesis` and
|
|
218
|
+
* `speak()` is a no-op there.
|
|
219
|
+
*/
|
|
220
|
+
export declare function createTTS(options?: TTSOptions): TTSControls;
|
|
221
|
+
export interface ThinkingOptions {
|
|
222
|
+
/** Phrases to cycle through. Default `["Thinking"]`. */
|
|
223
|
+
phrases?: string[];
|
|
224
|
+
/** Time per step in ms. Default 400. */
|
|
225
|
+
interval?: number;
|
|
226
|
+
}
|
|
227
|
+
export interface ThinkingControls {
|
|
228
|
+
/** Current label, e.g. `"Thinking.."`. Empty when stopped. */
|
|
229
|
+
text: Accessor<string>;
|
|
230
|
+
/** Whether the indicator is running. */
|
|
231
|
+
running: Accessor<boolean>;
|
|
232
|
+
/** Start cycling. */
|
|
233
|
+
start: () => void;
|
|
234
|
+
/** Stop and clear the label. */
|
|
235
|
+
stop: () => void;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* An animated "thinking" indicator for voice reply latency: cycles
|
|
239
|
+
* trailing dots, then moves through phrases.
|
|
240
|
+
*
|
|
241
|
+
* ```ts
|
|
242
|
+
* const thinking = createThinking({ phrases: ["Thinking", "Searching"] });
|
|
243
|
+
* thinking.start();
|
|
244
|
+
* <p>{thinking.text()}</p> // "Thinking", "Thinking.", "Thinking..", ...
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export declare function createThinking(options?: ThinkingOptions): ThinkingControls;
|
|
248
|
+
export interface PromptOptions {
|
|
249
|
+
/** Called with the submitted text. */
|
|
250
|
+
onSubmit?: (text: string) => void;
|
|
251
|
+
/** Clear the input after submit. Default true. */
|
|
252
|
+
clearOnSubmit?: boolean;
|
|
253
|
+
/** Language for dictation. */
|
|
254
|
+
lang?: string;
|
|
255
|
+
}
|
|
256
|
+
export interface PromptControls {
|
|
257
|
+
/** Current input text (typed or dictated). */
|
|
258
|
+
value: Accessor<string>;
|
|
259
|
+
/** Set the input text. */
|
|
260
|
+
setValue: (value: string) => void;
|
|
261
|
+
/** Whether the mic is listening. */
|
|
262
|
+
listening: Accessor<boolean>;
|
|
263
|
+
/** Latest interim dictation. */
|
|
264
|
+
interim: Accessor<string>;
|
|
265
|
+
/** False on the server or without the Web Speech API. */
|
|
266
|
+
supported: boolean;
|
|
267
|
+
/** Toggle mic dictation. */
|
|
268
|
+
toggleMic: () => void;
|
|
269
|
+
/** Submit the current text. */
|
|
270
|
+
submit: () => void;
|
|
271
|
+
/** Clear the input. */
|
|
272
|
+
clear: () => void;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A voice-enabled prompt input: a text value plus mic dictation,
|
|
276
|
+
* built for chat boxes and voice assistants. Dictated finals are
|
|
277
|
+
* appended to the value as they arrive; pairs with `createChatModel`
|
|
278
|
+
* for a full voice loop.
|
|
279
|
+
*
|
|
280
|
+
* ```tsx
|
|
281
|
+
* const prompt = createPrompt({ onSubmit: (t) => chat.send(t) });
|
|
282
|
+
* <input
|
|
283
|
+
* value={prompt.value()}
|
|
284
|
+
* onInput={(e) => prompt.setValue(e.currentTarget.value)}
|
|
285
|
+
* onKeyDown={(e) => e.key === "Enter" && prompt.submit()}
|
|
286
|
+
* placeholder={prompt.listening() ? prompt.interim() || "Listening..." : "Ask anything"}
|
|
287
|
+
* />
|
|
288
|
+
* <button onClick={() => prompt.toggleMic()}>Mic</button>
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
export declare function createPrompt(options?: PromptOptions): PromptControls;
|
|
292
|
+
export {};
|