solid-drift 0.24.0 → 0.26.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 +65 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/stream.d.ts +177 -0
- package/dist/stream.js +430 -0
- package/dist/voice.d.ts +292 -0
- package/dist/voice.js +577 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1506,6 +1506,71 @@ const bs = createBottomSheet(() => handle, {
|
|
|
1506
1506
|
- While the pointer is down the sheet tracks 1:1 with light rubber-banding past the fully-open top and the dismissed bottom. On release, the target is the nearest snap to `y + velocity * 0.18`; dismissal happens past the midpoint between the lowest snap and closed, or on a downward flick over 700 px/s. Snap travel uses a spring (`options.spring`, default stiffness 400 / damping 40).
|
|
1507
1507
|
- Bind `ref` to the drag handle when the sheet body scrolls (keeps drag and scroll from fighting), to the sheet root otherwise. The moving element gets `translateY(y())`; the drag target needs `touch-action: none`. Starts dismissed on the server (SSR-safe); under reduced motion it jumps straight to snap targets.
|
|
1508
1508
|
|
|
1509
|
+
### Streaming
|
|
1510
|
+
|
|
1511
|
+
Token-by-token chat over OpenAI, Anthropic, Meta's Llama API, or your own provider, plus a low-level fetch-based SSE client for any event stream.
|
|
1512
|
+
|
|
1513
|
+
```tsx
|
|
1514
|
+
import { createChatModel } from "solid-drift"
|
|
1515
|
+
|
|
1516
|
+
const chat = createChatModel({
|
|
1517
|
+
provider: "openai",
|
|
1518
|
+
apiKey: () => localStorage.getItem("openai_key") ?? "",
|
|
1519
|
+
model: "gpt-4o-mini",
|
|
1520
|
+
system: "You are a concise assistant.",
|
|
1521
|
+
})
|
|
1522
|
+
|
|
1523
|
+
// In your component:
|
|
1524
|
+
<For each={chat.messages()}>
|
|
1525
|
+
{(m) => <div class={m.role}>{m.content}</div>}
|
|
1526
|
+
</For>
|
|
1527
|
+
<button onClick={() => chat.send(input())} disabled={chat.status() === "streaming"}>
|
|
1528
|
+
Send
|
|
1529
|
+
</button>
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
- `createChatModel(options)` returns `{ messages, streamingText, status, error, send, stop, reset }`. `send(content)` appends the user message and streams the reply into a live assistant message, so UI bound to `messages()` renders token by token; `status()` is `idle`, `streaming`, or `error`. `stop()` aborts and keeps the partial reply; `send()` while streaming is ignored.
|
|
1533
|
+
- `provider` is `"openai"`, `"anthropic"`, `"meta"`, or a custom `{ kind: "custom", stream, parseDelta }`. OpenAI and Meta (Llama API via `/compat/v1`, OpenAI-compatible) use Bearer auth and `data:` chunks terminated by `[DONE]`; Anthropic uses `x-api-key` plus `anthropic-version: 2023-06-01`, a top-level `system` prompt, and `content_block_delta` text deltas (required `maxTokens` defaults to 1024).
|
|
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
|
+
- `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
|
+
|
|
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
|
+
|
|
1509
1574
|
### Easings
|
|
1510
1575
|
|
|
1511
1576
|
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.
|
package/dist/index.d.ts
CHANGED
|
@@ -40,3 +40,7 @@ export { createTxLifecycle, createTicker, createMintReveal, createConnectButton,
|
|
|
40
40
|
export type { TxState, TxStatusInput, TxLifecycleOptions, TxLifecycleControls, TickerOptions, TickerControls, MintRevealStatus, MintRevealOptions, MintRevealControls, ConnectButtonOptions, ConnectButtonStatus, ConnectButtonControls, AgentTxState, AgentTxProposal, AgentTxOptions, AgentTxControls, } from "./web3.js";
|
|
41
41
|
export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
|
|
42
42
|
export type { PollStatus, PollOptions, PollControls, ChainInfo, TokenPrice, TokenPriceOptions, PriceChangeOptions, GasPriceOptions, GasPriceData, BalanceOptions, BalanceData, TxReceiptData, TxReceiptOptions, BlockNumberOptions, ChainlinkPriceOptions, NFTMetadata, NFTMetadataOptions, ENSOptions, IdenticonOptions, } from "./web3data.js";
|
|
43
|
+
export { createSSEParser, createSSE, createChatModel, } from "./stream.js";
|
|
44
|
+
export type { SSEEvent, StreamStatus, SSEOptions, SSEControls, ChatMessage, ChatProviderKind, CustomChatProvider, ChatModelOptions, ChatModelControls, } from "./stream.js";
|
|
45
|
+
export { createVoiceState, createMicLevel, createSpeech, createWaveform, createTTS, createThinking, createPrompt, } from "./voice.js";
|
|
46
|
+
export type { VoiceStatus, VoiceStateControls, MicLevelOptions, MicLevelControls, SpeechOptions, SpeechControls, WaveformOptions, WaveformControls, TTSProvider, TTSOptions, TTSControls, ThinkingOptions, ThinkingControls, PromptOptions, PromptControls, } from "./voice.js";
|
package/dist/index.js
CHANGED
|
@@ -36,3 +36,5 @@ export { createDrag, createSwipe, } from "./gesture.js";
|
|
|
36
36
|
export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
|
|
37
37
|
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
|
|
38
38
|
export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
|
|
39
|
+
export { createSSEParser, createSSE, createChatModel, } from "./stream.js";
|
|
40
|
+
export { createVoiceState, createMicLevel, createSpeech, createWaveform, createTTS, createThinking, createPrompt, } from "./voice.js";
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { type Accessor } from "solid-js";
|
|
2
|
+
/** One parsed Server-Sent Event. */
|
|
3
|
+
export interface SSEEvent {
|
|
4
|
+
/** Event name; defaults to "message" when the stream sets none. */
|
|
5
|
+
event: string;
|
|
6
|
+
/** Payload: data lines joined with newlines. */
|
|
7
|
+
data: string;
|
|
8
|
+
/** Last `id:` field seen, if any. */
|
|
9
|
+
id?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Feed raw text chunks through the SSE framing rules and call
|
|
13
|
+
* `onEvent` for each dispatched event. Handles events split across
|
|
14
|
+
* chunk boundaries, multi-line `data:` payloads, and `:` comments.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSSEParser(onEvent: (event: SSEEvent) => void): (chunk: string) => void;
|
|
17
|
+
type FetchFn = typeof fetch;
|
|
18
|
+
/** Connection state of a stream. */
|
|
19
|
+
export type StreamStatus = "idle" | "connecting" | "open" | "closed" | "error";
|
|
20
|
+
export interface SSEOptions {
|
|
21
|
+
/** HTTP method. Default "GET". */
|
|
22
|
+
method?: string;
|
|
23
|
+
/** Extra headers, or a function returning them per connection. */
|
|
24
|
+
headers?: Record<string, string> | (() => Record<string, string>);
|
|
25
|
+
/** JSON-encoded request body (for POST-style SSE endpoints). */
|
|
26
|
+
body?: unknown;
|
|
27
|
+
/** Only surface events with this name. */
|
|
28
|
+
event?: string;
|
|
29
|
+
/** Called for each parsed event (after the name filter). */
|
|
30
|
+
onEvent?: (event: SSEEvent) => void;
|
|
31
|
+
/** Called when the stream opens (first byte accepted). */
|
|
32
|
+
onOpen?: () => void;
|
|
33
|
+
/** Called when the stream ends cleanly. */
|
|
34
|
+
onDone?: () => void;
|
|
35
|
+
/** Called on connection or HTTP errors. */
|
|
36
|
+
onError?: (error: Error) => void;
|
|
37
|
+
/** Connect immediately on creation. Default true. */
|
|
38
|
+
autoConnect?: boolean;
|
|
39
|
+
/** Fetch implementation (for tests or custom transports). */
|
|
40
|
+
fetchFn?: FetchFn;
|
|
41
|
+
}
|
|
42
|
+
export interface SSEControls {
|
|
43
|
+
/** Connection state. */
|
|
44
|
+
status: Accessor<StreamStatus>;
|
|
45
|
+
/** All parsed events received on this connection. */
|
|
46
|
+
events: Accessor<SSEEvent[]>;
|
|
47
|
+
/** The most recent event, if any. */
|
|
48
|
+
lastEvent: Accessor<SSEEvent | null>;
|
|
49
|
+
/** The last error, if any. */
|
|
50
|
+
error: Accessor<Error | null>;
|
|
51
|
+
/** Open (or re-open) the stream. */
|
|
52
|
+
connect: () => void;
|
|
53
|
+
/** Close the stream and abort the request. */
|
|
54
|
+
disconnect: () => void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A fetch-based Server-Sent Events client.
|
|
58
|
+
*
|
|
59
|
+
* Unlike `EventSource`, this works with any HTTP method and custom
|
|
60
|
+
* headers, so it can reach authenticated or POST-style SSE endpoints.
|
|
61
|
+
* There is no automatic reconnection: a dropped stream moves to
|
|
62
|
+
* "closed" (or "error") and `connect()` re-opens it manually.
|
|
63
|
+
*
|
|
64
|
+
* SSR-safe: nothing connects until `connect()` runs (or
|
|
65
|
+
* `autoConnect` fires on the client).
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* const sse = createSSE("https://api.example.com/events", {
|
|
69
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
70
|
+
* onEvent: (ev) => console.log(ev.event, ev.data),
|
|
71
|
+
* });
|
|
72
|
+
* sse.disconnect();
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function createSSE(url: string | (() => string), options?: SSEOptions): SSEControls;
|
|
76
|
+
/** A single chat turn. */
|
|
77
|
+
export interface ChatMessage {
|
|
78
|
+
id: string;
|
|
79
|
+
role: "system" | "user" | "assistant";
|
|
80
|
+
content: string;
|
|
81
|
+
}
|
|
82
|
+
/** Built-in provider kinds. */
|
|
83
|
+
export type ChatProviderKind = "openai" | "anthropic" | "meta";
|
|
84
|
+
/**
|
|
85
|
+
* Custom streaming provider. `stream` opens the request and returns
|
|
86
|
+
* the raw Response; `parseDelta` maps each SSE data payload to text
|
|
87
|
+
* (appended to the reply) or a done flag.
|
|
88
|
+
*/
|
|
89
|
+
export interface CustomChatProvider {
|
|
90
|
+
kind: "custom";
|
|
91
|
+
stream: (messages: ChatMessage[], context: {
|
|
92
|
+
signal: AbortSignal;
|
|
93
|
+
fetchFn: FetchFn;
|
|
94
|
+
}) => Promise<Response>;
|
|
95
|
+
parseDelta: (data: string, event?: string) => {
|
|
96
|
+
text?: string;
|
|
97
|
+
done?: boolean;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export interface ChatModelOptions {
|
|
101
|
+
/**
|
|
102
|
+
* Provider. Built-in kinds:
|
|
103
|
+
* - `"openai"`: chat completions, `data:` chunks, `[DONE]` terminator.
|
|
104
|
+
* - `"anthropic"`: messages API, `content_block_delta` text deltas.
|
|
105
|
+
* Note: api.anthropic.com does not send CORS headers for browser
|
|
106
|
+
* origins, so from a browser you must call it through your own
|
|
107
|
+
* server route or proxy (set `baseUrl` to that proxy).
|
|
108
|
+
* - `"meta"`: Llama API, OpenAI-compatible via /compat/v1.
|
|
109
|
+
* - custom `{ kind: "custom", stream, parseDelta }`.
|
|
110
|
+
*/
|
|
111
|
+
provider: ChatProviderKind | CustomChatProvider;
|
|
112
|
+
/** API key, or a function returning it. Sent as Bearer (openai/meta) or x-api-key (anthropic). */
|
|
113
|
+
apiKey?: string | (() => string | undefined);
|
|
114
|
+
/** Model name, e.g. "gpt-4o-mini", "claude-sonnet-4-20250514". */
|
|
115
|
+
model: string;
|
|
116
|
+
/** Override the API root. Defaults per provider kind. */
|
|
117
|
+
baseUrl?: string;
|
|
118
|
+
/** System prompt, sent as a system message (top-level `system` for anthropic). */
|
|
119
|
+
system?: string;
|
|
120
|
+
/** Sampling temperature, passed through when set. */
|
|
121
|
+
temperature?: number;
|
|
122
|
+
/** Max output tokens. Default 1024. Required by the anthropic API. */
|
|
123
|
+
maxTokens?: number;
|
|
124
|
+
/** Extra headers merged into the request. */
|
|
125
|
+
headers?: Record<string, string>;
|
|
126
|
+
/** Fetch implementation (for tests or custom transports). */
|
|
127
|
+
fetchFn?: FetchFn;
|
|
128
|
+
/** Called with the finished assistant message. */
|
|
129
|
+
onFinish?: (message: ChatMessage) => void;
|
|
130
|
+
/** Called on request or stream errors. */
|
|
131
|
+
onError?: (error: Error) => void;
|
|
132
|
+
}
|
|
133
|
+
export interface ChatModelControls {
|
|
134
|
+
/** Full conversation, including the in-progress reply. */
|
|
135
|
+
messages: Accessor<ChatMessage[]>;
|
|
136
|
+
/** The reply text streamed so far (empty when idle). */
|
|
137
|
+
streamingText: Accessor<string>;
|
|
138
|
+
/** `idle`, `streaming`, or `error`. */
|
|
139
|
+
status: Accessor<"idle" | "streaming" | "error">;
|
|
140
|
+
/** The last error, if any. */
|
|
141
|
+
error: Accessor<Error | null>;
|
|
142
|
+
/** Send a user message and stream the reply. Ignored while streaming. */
|
|
143
|
+
send: (content: string) => Promise<void>;
|
|
144
|
+
/** Abort the in-flight reply, keeping the partial text. */
|
|
145
|
+
stop: () => void;
|
|
146
|
+
/** Clear the conversation and abort any in-flight reply. */
|
|
147
|
+
reset: () => void;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Streaming chat over OpenAI, Anthropic, Meta (Llama API), or a
|
|
151
|
+
* custom provider.
|
|
152
|
+
*
|
|
153
|
+
* `send()` appends the user message, opens the provider stream, and
|
|
154
|
+
* appends text deltas to a live assistant message as they arrive, so
|
|
155
|
+
* UI bound to `messages()` renders the reply token by token. Pairs
|
|
156
|
+
* well with `createTyping` for a typewriter reveal.
|
|
157
|
+
*
|
|
158
|
+
* Keys stay in your hands: pass `apiKey` directly, or a function
|
|
159
|
+
* reading it from your own store. For production, prefer calling
|
|
160
|
+
* through your own server route and pointing `baseUrl` at it so
|
|
161
|
+
* keys never ship to the browser.
|
|
162
|
+
*
|
|
163
|
+
* SSR-safe: nothing connects until `send()` is called.
|
|
164
|
+
*
|
|
165
|
+
* ```ts
|
|
166
|
+
* const chat = createChatModel({
|
|
167
|
+
* provider: "openai",
|
|
168
|
+
* apiKey: () => localStorage.getItem("openai_key") ?? "",
|
|
169
|
+
* model: "gpt-4o-mini",
|
|
170
|
+
* system: "You are a concise assistant.",
|
|
171
|
+
* onFinish: (msg) => console.log("done:", msg.content.length),
|
|
172
|
+
* });
|
|
173
|
+
* await chat.send("What is a signal?");
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function createChatModel(options: ChatModelOptions): ChatModelControls;
|
|
177
|
+
export {};
|