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/README.md
CHANGED
|
@@ -1534,6 +1534,72 @@ const chat = createChatModel({
|
|
|
1534
1534
|
- Honest limitation: api.anthropic.com does not send CORS headers for browser origins, so from a browser Anthropic must go through your own server route or proxy; point `baseUrl` at it. For production with any provider, prefer a server route that holds the key and set `baseUrl` to it so keys never ship to the browser.
|
|
1535
1535
|
- `createSSE(url, options?)` is a fetch-based event-stream client (`{ status, events, lastEvent, error, connect, disconnect }`): unlike `EventSource` it supports any method and custom headers, parses the full SSE framing (named events, multi-line data, comments, chunk splits), and leaves reconnection manual via `connect()`. SSR-safe: nothing connects until `connect()` (or `autoConnect`) runs on the client.
|
|
1536
1536
|
|
|
1537
|
+
### Voice
|
|
1538
|
+
|
|
1539
|
+
A full voice loop: mic metering, speech-to-text, a voice state machine, canvas waveforms, text-to-speech (browser or cloud), thinking indicators, and a voice-enabled prompt input.
|
|
1540
|
+
|
|
1541
|
+
```tsx
|
|
1542
|
+
import { createVoiceState, createSpeech, createTTS, createPrompt, createChatModel } from "solid-drift"
|
|
1543
|
+
|
|
1544
|
+
const voice = createVoiceState()
|
|
1545
|
+
const chat = createChatModel({ provider: "openai", apiKey: getKey, model: "gpt-4o-mini" })
|
|
1546
|
+
const tts = createTTS()
|
|
1547
|
+
const prompt = createPrompt({
|
|
1548
|
+
onSubmit: async (text) => {
|
|
1549
|
+
voice.toThinking()
|
|
1550
|
+
await chat.send(text)
|
|
1551
|
+
voice.toSpeaking()
|
|
1552
|
+
const msgs = chat.messages()
|
|
1553
|
+
tts.speak(msgs[msgs.length - 1]?.content ?? "")
|
|
1554
|
+
voice.toIdle()
|
|
1555
|
+
},
|
|
1556
|
+
})
|
|
1557
|
+
|
|
1558
|
+
<input
|
|
1559
|
+
value={prompt.value()}
|
|
1560
|
+
onInput={(e) => prompt.setValue(e.currentTarget.value)}
|
|
1561
|
+
onKeyDown={(e) => e.key === "Enter" && prompt.submit()}
|
|
1562
|
+
/>
|
|
1563
|
+
<button onClick={() => { voice.toListening(); prompt.toggleMic(); }}>Mic</button>
|
|
1564
|
+
```
|
|
1565
|
+
|
|
1566
|
+
- `createVoiceState()` is the turn state machine: `state()` is `idle`, `listening`, `thinking`, or `speaking`, with `toIdle`/`toListening`/`toThinking`/`toSpeaking` transitions.
|
|
1567
|
+
- `createMicLevel(options?)` returns `{ level, active, supported, analyser, error, start, stop }`: a 0..1 smoothed RMS meter from `getUserMedia` plus an `AnalyserNode` (call `start()` from a user gesture). The exposed `analyser` wires straight into `createWaveform`.
|
|
1568
|
+
- `createSpeech(options?)` wraps the Web Speech API (`SpeechRecognition` with `webkitSpeechRecognition` fallback): `{ supported, listening, transcript, interim, error, start, stop, reset }`. Final results accumulate into `transcript()`; `continuous` sessions auto-restart if the browser ends them mid-turn.
|
|
1569
|
+
- `createWaveform(canvas, options)` draws the analyser's time-domain wave (`mode: "line"`) or spectrum (`mode: "bars"`) on a canvas, DPR-aware, on the shared clock; under reduced motion it redraws at most every 250ms.
|
|
1570
|
+
- `createTTS(options?)` speaks via `speechSynthesis` by default (`{ supported, speaking, voices, speak, cancel }`, async voice loading, `speak()` cancels the current utterance first) and upgrades to any cloud voice through `provider: { speak(text, { signal }) }`.
|
|
1571
|
+
- `createThinking(options?)` cycles `"Thinking"`, `"Thinking."`, ... through `phrases` at `interval` ms: `{ text, running, start, stop }`.
|
|
1572
|
+
- `createPrompt(options?)` is the voice-enabled input: `{ value, setValue, listening, interim, supported, toggleMic, submit, clear }`. Mic finals are appended to the value as they arrive; `submit()` fires `onSubmit` and clears by default. Everything is SSR-safe: unsupported primitives report `supported: false` and their actions no-op on the server.
|
|
1573
|
+
|
|
1574
|
+
### Mobile hardware
|
|
1575
|
+
|
|
1576
|
+
```tsx
|
|
1577
|
+
import { createBattery, createShare, createScanline } from "solid-drift";
|
|
1578
|
+
|
|
1579
|
+
const battery = createBattery();
|
|
1580
|
+
const share = createShare();
|
|
1581
|
+
const scanline = createScanline({ duration: 1800 });
|
|
1582
|
+
|
|
1583
|
+
scanline.start();
|
|
1584
|
+
// in your scanner viewfinder:
|
|
1585
|
+
// <div class="line" style={{ top: `${scanline.progress() * 100}%` }} />
|
|
1586
|
+
|
|
1587
|
+
<p>Battery: {Math.round(battery.level() * 100)}% {battery.charging() ? "(charging)" : ""}</p>
|
|
1588
|
+
<button onClick={() => share.share({ title: "solid-drift", url: location.href })}>Share</button>
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
- `createBattery()` wraps `navigator.getBattery()`: `{ supported, charging, level, chargingTime, dischargingTime, error }`. Level is 0..1; times are seconds (`Infinity` when unknown). Listeners detach on cleanup.
|
|
1592
|
+
- `createNetwork()` tracks `navigator.onLine` plus the Network Information API: `{ online, effectiveType, downlink, rtt, saveData, supported }`. Updates on `online`/`offline` events and the connection `change` event.
|
|
1593
|
+
- `createWakeLock()` keeps the screen awake: `{ supported, active, error, request, release }`. Re-acquires automatically when the tab becomes visible again if the lock was still wanted.
|
|
1594
|
+
- `createContactPick()` wraps the Contact Picker API: `{ supported, contacts, error, pick }`. `pick({ multiple })` resolves with normalized `{ name, tel, email }` arrays, or an empty array when the user cancels.
|
|
1595
|
+
- `createOTP()` wraps the WebOTP API: `{ supported, code, error, wait, abort }`. `wait({ transport })` resolves with the SMS code (or `null` when aborted). Requires a secure origin and an origin-bound SMS format.
|
|
1596
|
+
- `createShare()` wraps the Web Share API: `{ supported, canShare, error, share }`. `share({ title, text, url, files })` opens the native sheet; user dismissal is not an error.
|
|
1597
|
+
- `createNFC()` wraps Web NFC (Chrome on Android, secure context, needs a user gesture): `{ supported, scanning, message, error, scan, write, abort }`. Scanned tags land in `message()` with decoded `text`/`url` records plus `serialNumber`; `write()` takes a string or `{ records }`.
|
|
1598
|
+
- `createTorch()` drives the camera flashlight: `{ supported, on, error, attach, set, toggle }`. `attach(trackOrStream)` checks the `torch` capability, then `set(true/false)` applies it via `applyConstraints`.
|
|
1599
|
+
- `createGyro()` wraps `deviceorientation`: `{ supported, needsPermission, alpha, beta, gamma, absolute, listening, error, requestPermission, start, stop }`. On iOS, call `requestPermission()` from a tap handler before `start()`; `start()` also requests it if needed.
|
|
1600
|
+
- `createShake(options?)` detects shake gestures from `devicemotion`: `{ supported, needsPermission, listening, shakes, error, requestPermission, start, stop }`. A shake counts when the acceleration delta exceeds `threshold` (default 15 m/s^2), rate-limited by `cooldown` (default 800ms); `onShake` fires per shake.
|
|
1601
|
+
- `createScanline(options?)` is the animated line of a QR/barcode viewfinder: `{ progress, running, start, stop }`. `progress()` sweeps 0..1 on the shared clock (`direction: "down" | "up" | "alternate"`); bind it to the line's position. Under reduced motion it freezes mid-frame. Every primitive is SSR-safe: server renders get `supported: false` and safe no-op actions.
|
|
1602
|
+
|
|
1537
1603
|
### Easings
|
|
1538
1604
|
|
|
1539
1605
|
Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
export interface BatteryState {
|
|
2
|
+
supported: boolean;
|
|
3
|
+
charging: () => boolean;
|
|
4
|
+
level: () => number;
|
|
5
|
+
chargingTime: () => number;
|
|
6
|
+
dischargingTime: () => number;
|
|
7
|
+
error: () => Error | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* createBattery
|
|
11
|
+
*
|
|
12
|
+
* Reactive wrapper around the Battery Status API (`navigator.getBattery`).
|
|
13
|
+
* Tracks charging state, level (0..1), and charge/discharge time in seconds.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createBattery(): BatteryState;
|
|
16
|
+
export interface NetworkState {
|
|
17
|
+
online: () => boolean;
|
|
18
|
+
effectiveType: () => string | undefined;
|
|
19
|
+
downlink: () => number | undefined;
|
|
20
|
+
rtt: () => number | undefined;
|
|
21
|
+
saveData: () => boolean;
|
|
22
|
+
supported: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* createNetwork
|
|
26
|
+
*
|
|
27
|
+
* Reactive network status: `navigator.onLine` plus the Network Information
|
|
28
|
+
* API (`navigator.connection`) for effective type, downlink, RTT, and
|
|
29
|
+
* data-saver preference.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createNetwork(): NetworkState;
|
|
32
|
+
export interface WakeLockState {
|
|
33
|
+
supported: boolean;
|
|
34
|
+
active: () => boolean;
|
|
35
|
+
error: () => Error | null;
|
|
36
|
+
request: () => Promise<void>;
|
|
37
|
+
release: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* createWakeLock
|
|
41
|
+
*
|
|
42
|
+
* Keeps the screen awake with the Screen Wake Lock API. Re-acquires the lock
|
|
43
|
+
* automatically when the tab becomes visible again after a release.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createWakeLock(): WakeLockState;
|
|
46
|
+
export interface PickedContact {
|
|
47
|
+
name: string[];
|
|
48
|
+
tel: string[];
|
|
49
|
+
email: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface ContactPickState {
|
|
52
|
+
supported: boolean;
|
|
53
|
+
contacts: () => PickedContact[];
|
|
54
|
+
error: () => Error | null;
|
|
55
|
+
pick: (options?: {
|
|
56
|
+
multiple?: boolean;
|
|
57
|
+
}) => Promise<PickedContact[]>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* createContactPick
|
|
61
|
+
*
|
|
62
|
+
* Contact Picker API (`navigator.contacts.select`). Resolves with the chosen
|
|
63
|
+
* contacts; empty array when the user cancels.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createContactPick(): ContactPickState;
|
|
66
|
+
export interface OTPState {
|
|
67
|
+
supported: boolean;
|
|
68
|
+
code: () => string | null;
|
|
69
|
+
error: () => Error | null;
|
|
70
|
+
wait: (options?: {
|
|
71
|
+
transport?: string[];
|
|
72
|
+
}) => Promise<string | null>;
|
|
73
|
+
abort: () => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* createOTP
|
|
77
|
+
*
|
|
78
|
+
* WebOTP API: reads a one-time code from an incoming SMS without leaving the
|
|
79
|
+
* page. `wait()` resolves with the code (or null when aborted). Works only on
|
|
80
|
+
* secure origins where the SMS matches the site's origin-bound format.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createOTP(): OTPState;
|
|
83
|
+
export interface ShareState {
|
|
84
|
+
supported: boolean;
|
|
85
|
+
canShare: (data: ShareData) => boolean;
|
|
86
|
+
error: () => Error | null;
|
|
87
|
+
share: (data: ShareData) => Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* createShare
|
|
91
|
+
*
|
|
92
|
+
* Web Share API: opens the native share sheet. `share()` resolves when the
|
|
93
|
+
* user completes or dismisses the sheet (dismissal is not an error).
|
|
94
|
+
*/
|
|
95
|
+
export declare function createShare(): ShareState;
|
|
96
|
+
export interface NFCRecord {
|
|
97
|
+
recordType: string;
|
|
98
|
+
mediaType?: string;
|
|
99
|
+
text?: string;
|
|
100
|
+
url?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface NFCMessage {
|
|
103
|
+
records: NFCRecord[];
|
|
104
|
+
serialNumber: string;
|
|
105
|
+
}
|
|
106
|
+
export interface NFCState {
|
|
107
|
+
supported: boolean;
|
|
108
|
+
scanning: () => boolean;
|
|
109
|
+
message: () => NFCMessage | null;
|
|
110
|
+
error: () => Error | null;
|
|
111
|
+
scan: () => Promise<void>;
|
|
112
|
+
write: (content: string | {
|
|
113
|
+
records: Array<Record<string, unknown>>;
|
|
114
|
+
}) => Promise<void>;
|
|
115
|
+
abort: () => void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* createNFC
|
|
119
|
+
*
|
|
120
|
+
* Web NFC (Chrome on Android, secure context): scan tags with `scan()` and
|
|
121
|
+
* read decoded records from `message`; write text or records with `write()`.
|
|
122
|
+
* Scanning needs a user gesture.
|
|
123
|
+
*/
|
|
124
|
+
export declare function createNFC(): NFCState;
|
|
125
|
+
export interface TorchState {
|
|
126
|
+
supported: () => boolean;
|
|
127
|
+
on: () => boolean;
|
|
128
|
+
error: () => Error | null;
|
|
129
|
+
attach: (input: MediaStreamTrack | MediaStream) => void;
|
|
130
|
+
set: (value: boolean) => Promise<void>;
|
|
131
|
+
toggle: () => Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* createTorch
|
|
135
|
+
*
|
|
136
|
+
* Camera flashlight for devices that expose the `torch` capability.
|
|
137
|
+
* Attach a video track (or stream) from `getUserMedia`, then `set()` or
|
|
138
|
+
* `toggle()` the flashlight.
|
|
139
|
+
*/
|
|
140
|
+
export declare function createTorch(): TorchState;
|
|
141
|
+
export interface GyroState {
|
|
142
|
+
supported: boolean;
|
|
143
|
+
needsPermission: boolean;
|
|
144
|
+
alpha: () => number | null;
|
|
145
|
+
beta: () => number | null;
|
|
146
|
+
gamma: () => number | null;
|
|
147
|
+
absolute: () => boolean;
|
|
148
|
+
listening: () => boolean;
|
|
149
|
+
error: () => Error | null;
|
|
150
|
+
requestPermission: () => Promise<"granted" | "denied">;
|
|
151
|
+
start: () => Promise<void>;
|
|
152
|
+
stop: () => void;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* createGyro
|
|
156
|
+
*
|
|
157
|
+
* Device orientation (alpha/beta/gamma in degrees). On iOS the motion
|
|
158
|
+
* permission prompt must come from a user gesture: call `requestPermission()`
|
|
159
|
+
* from a tap handler, then `start()`.
|
|
160
|
+
*/
|
|
161
|
+
export declare function createGyro(): GyroState;
|
|
162
|
+
export interface ShakeOptions {
|
|
163
|
+
/** Acceleration delta (m/s^2) that counts as a shake. Default 15. */
|
|
164
|
+
threshold?: number;
|
|
165
|
+
/** Minimum milliseconds between shakes. Default 800. */
|
|
166
|
+
cooldown?: number;
|
|
167
|
+
onShake?: () => void;
|
|
168
|
+
}
|
|
169
|
+
export interface ShakeState {
|
|
170
|
+
supported: boolean;
|
|
171
|
+
needsPermission: boolean;
|
|
172
|
+
listening: () => boolean;
|
|
173
|
+
shakes: () => number;
|
|
174
|
+
error: () => Error | null;
|
|
175
|
+
requestPermission: () => Promise<"granted" | "denied">;
|
|
176
|
+
start: () => Promise<void>;
|
|
177
|
+
stop: () => void;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* createShake
|
|
181
|
+
*
|
|
182
|
+
* Shake-to-undo style detection from `devicemotion`. Counts a shake when the
|
|
183
|
+
* acceleration delta exceeds `threshold`, rate-limited by `cooldown`.
|
|
184
|
+
*/
|
|
185
|
+
export declare function createShake(options?: ShakeOptions): ShakeState;
|
|
186
|
+
export interface ScanlineOptions {
|
|
187
|
+
/** Milliseconds per sweep. Default 1800. */
|
|
188
|
+
duration?: number;
|
|
189
|
+
/** Sweep pattern. Default "alternate" (ping-pong). */
|
|
190
|
+
direction?: "down" | "up" | "alternate";
|
|
191
|
+
}
|
|
192
|
+
export interface ScanlineState {
|
|
193
|
+
/** Line position, 0 at the top edge and 1 at the bottom edge. */
|
|
194
|
+
progress: () => number;
|
|
195
|
+
running: () => boolean;
|
|
196
|
+
start: () => void;
|
|
197
|
+
stop: () => void;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* createScanline
|
|
201
|
+
*
|
|
202
|
+
* The animated line of a camera viewfinder (QR/barcode scanning UI). Bind
|
|
203
|
+
* `progress()` to the line position, for example
|
|
204
|
+
* `style={{ top: `${progress() * 100}%` }}`. Runs on the shared clock and
|
|
205
|
+
* freezes mid-frame under reduced motion.
|
|
206
|
+
*/
|
|
207
|
+
export declare function createScanline(options?: ScanlineOptions): ScanlineState;
|