use-voice-control 0.1.0 → 0.1.2

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.
@@ -0,0 +1,276 @@
1
+ # Speech API Integration Guide
2
+
3
+ This document explains how to integrate the new TTS/STT APIs with the existing speech library.
4
+
5
+ ## Overview
6
+
7
+ The speech system now has three layers:
8
+
9
+ 1. **API Layer** (`/app/api/speech/*`) — Server-side endpoints for TTS and STT
10
+ 2. **Client Utilities** (`lib/speech-api.ts`) — High-level functions for frontend use
11
+ 3. **Components** (`components/SpeechInput.tsx`, `components/SpeechSettings.tsx`) — Ready-to-use UI
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌─────────────────────────────────────────┐
17
+ │ React Components (UI Layer) │
18
+ │ ┌────────────────────────────────────┐ │
19
+ │ │ SpeechInput (STT + TTS playback) │ │
20
+ │ │ SpeechSettings (Configuration) │ │
21
+ │ └────────────────────────────────────┘ │
22
+ └──────────────────┬──────────────────────┘
23
+
24
+ ┌──────────────────▼──────────────────────┐
25
+ │ Client Utilities (lib/speech-api) │
26
+ │ ┌────────────────────────────────────┐ │
27
+ │ │ generateSpeechFromText() │ │
28
+ │ │ createAudioURL() │ │
29
+ │ │ speakText() │ │
30
+ │ └────────────────────────────────────┘ │
31
+ └──────────────────┬──────────────────────┘
32
+
33
+ ┌──────────────────▼──────────────────────┐
34
+ │ Next.js API Routes │
35
+ │ ┌────────────────────────────────────┐ │
36
+ │ │ POST /api/speech/tts │ │
37
+ │ │ GET|POST /api/speech/stt │ │
38
+ │ └────────────────────────────────────┘ │
39
+ └──────────────────┬──────────────────────┘
40
+
41
+ ┌──────────────────▼──────────────────────┐
42
+ │ Speech Libraries & Models │
43
+ │ ┌────────────────────────────────────┐ │
44
+ │ │ Kokoro TTS (Node.js via lib) │ │
45
+ │ │ Deepgram TTS (API) │ │
46
+ │ │ Moonshine STT (Browser-side) │ │
47
+ │ └────────────────────────────────────┘ │
48
+ └─────────────────────────────────────────┘
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ### Basic Speech Input (STT only)
54
+
55
+ ```tsx
56
+ 'use client';
57
+
58
+ import { SpeechInput } from '@/components/SpeechInput';
59
+
60
+ export function MyComponent() {
61
+ const handleTranscription = (text: string) => {
62
+ console.log('Transcribed:', text);
63
+ // Do something with the text
64
+ };
65
+
66
+ return (
67
+ <SpeechInput
68
+ onTranscription={handleTranscription}
69
+ />
70
+ );
71
+ }
72
+ ```
73
+
74
+ ### With TTS Feedback
75
+
76
+ ```tsx
77
+ import { SpeechInput } from '@/components/SpeechInput';
78
+
79
+ export function MyComponent() {
80
+ return (
81
+ <SpeechInput
82
+ onTranscription={(text) => console.log(text)}
83
+ enableTTS={true}
84
+ ttsProvider="kokoro"
85
+ ttsVoice="af_heart"
86
+ />
87
+ );
88
+ }
89
+ ```
90
+
91
+ ### With Settings Panel
92
+
93
+ ```tsx
94
+ 'use client';
95
+
96
+ import { useState } from 'react';
97
+ import { SpeechInput } from '@/components/SpeechInput';
98
+ import { SpeechSettings, type SpeechSettings } from '@/components/SpeechSettings';
99
+
100
+ export function MyComponent() {
101
+ const [settings, setSettings] = useState<SpeechSettings>({
102
+ ttsEnabled: true,
103
+ ttsProvider: 'kokoro',
104
+ ttsVoice: 'af_heart',
105
+ sttEnabled: true,
106
+ });
107
+
108
+ return (
109
+ <div className="space-y-4">
110
+ <SpeechSettings onChange={setSettings} />
111
+ <SpeechInput
112
+ onTranscription={(text) => console.log(text)}
113
+ enableTTS={settings.ttsEnabled}
114
+ ttsProvider={settings.ttsProvider}
115
+ ttsVoice={settings.ttsVoice}
116
+ disabled={!settings.sttEnabled}
117
+ />
118
+ </div>
119
+ );
120
+ }
121
+ ```
122
+
123
+ ### Direct API Usage
124
+
125
+ ```tsx
126
+ import { generateSpeechFromText, speakText, createAudioURL } from '@/lib/speech-api';
127
+
128
+ // Generate audio blob
129
+ const audioBlob = await generateSpeechFromText(
130
+ 'Hello, world!',
131
+ 'kokoro',
132
+ 'af_heart'
133
+ );
134
+
135
+ // Create object URL for <audio> element
136
+ const url = await createAudioURL('Hello, world!');
137
+
138
+ // Play audio and wait for completion
139
+ await speakText('Hello, world!', 'kokoro', 'af_heart');
140
+ ```
141
+
142
+ ## API Endpoints
143
+
144
+ ### POST /api/speech/tts
145
+
146
+ Generates speech audio from text.
147
+
148
+ **Request:**
149
+ ```json
150
+ {
151
+ "text": "Hello, world!",
152
+ "provider": "kokoro",
153
+ "voice": "af_heart"
154
+ }
155
+ ```
156
+
157
+ **Response:**
158
+ - 200: Audio data (binary)
159
+ - 400: Invalid text parameter
160
+ - 500: TTS generation failed
161
+
162
+ **Headers:**
163
+ - `Content-Type`: `audio/wav` or `audio/mpeg`
164
+ - `Cache-Control`: `public, max-age=31536000`
165
+
166
+ ### GET /api/speech/stt
167
+
168
+ Returns STT API documentation.
169
+
170
+ **Response:**
171
+ ```json
172
+ {
173
+ "message": "STT (Speech-to-Text) API",
174
+ "description": "Transcription happens client-side using Moonshine.js",
175
+ "models": ["moonshine-small"],
176
+ "note": "For client-side transcription, import SpeechInput component"
177
+ }
178
+ ```
179
+
180
+ ### POST /api/speech/stt
181
+
182
+ Server-side transcription (future implementation).
183
+
184
+ Currently returns 501 Not Implemented.
185
+
186
+ ## Configuration
187
+
188
+ ### Environment Variables
189
+
190
+ No environment variables are required for client-side Moonshine STT.
191
+
192
+ For Deepgram TTS, set:
193
+ ```
194
+ DEEPGRAM_API_KEY=xxx
195
+ ```
196
+
197
+ ### Component Props
198
+
199
+ #### SpeechInput
200
+
201
+ | Prop | Type | Default | Description |
202
+ |------|------|---------|-------------|
203
+ | `onTranscription` | `(text: string) => void` | Required | Callback when transcription is complete |
204
+ | `disabled` | `boolean` | `false` | Disable the microphone button |
205
+ | `enableTTS` | `boolean` | `false` | Auto-play TTS for transcribed text |
206
+ | `ttsProvider` | `'kokoro' \| 'deepgram'` | `'kokoro'` | Which TTS provider to use |
207
+ | `ttsVoice` | `string` | `'af_heart'` | Voice ID for TTS |
208
+
209
+ #### SpeechSettings
210
+
211
+ | Prop | Type | Default | Description |
212
+ |------|------|---------|-------------|
213
+ | `onChange` | `(settings: SpeechSettings) => void` | `undefined` | Callback when settings change |
214
+
215
+ ## Voices
216
+
217
+ ### Kokoro Voices
218
+
219
+ Default provider with natural-sounding voices:
220
+ - Female: `af_heart`, `af_alloy`, `af_aoede`, `af_bella`, `af_jessica`, `af_nicole`, `af_river`, `af_sarah`, `af_sky`
221
+ - Male: `am_adam`, `am_echo`, `am_fable`, `am_fenrir`, `am_liam`, `am_michael`, `am_onyx`
222
+
223
+ ### Deepgram Speakers
224
+
225
+ Premium provider with distinct personalities:
226
+ - `angus`, `asteria`, `arcas`, `orion`, `orpheus`, `athena`, `luna`, `zeus`, `perseus`, `helios`, `hera`, `stella`
227
+
228
+ ## Troubleshooting
229
+
230
+ ### Model Download Issues
231
+
232
+ The first time Moonshine runs, it downloads the model (~150MB). This is cached in the browser.
233
+
234
+ To debug:
235
+ ```tsx
236
+ console.log('Model loading...');
237
+ const Moonshine = await import('@moonshine-ai/moonshine-js');
238
+ console.log('Model loaded');
239
+ ```
240
+
241
+ ### Microphone Permission Denied
242
+
243
+ The browser will prompt for microphone access on first use. If denied:
244
+ 1. Check browser settings
245
+ 2. Ensure the site is running on HTTPS (localhost OK)
246
+ 3. Clear site permissions and retry
247
+
248
+ ### TTS API Failures
249
+
250
+ Check:
251
+ 1. Is `/api/speech/tts` responding? Test with: `curl -X POST http://localhost:3000/api/speech/tts -H "Content-Type: application/json" -d '{"text":"hello"}'`
252
+ 2. Are model dependencies installed? Check `node_modules/@huggingface/transformers`
253
+ 3. Check server logs for errors
254
+
255
+ ### Performance Tips
256
+
257
+ - Moonshine model loads on first STT use; consider preloading with `<link rel="preload">`
258
+ - TTS audio is cached server-side; repeated requests are instant
259
+ - For high-frequency TTS, store generated audio URLs in a client-side cache
260
+
261
+ ## Future Enhancements
262
+
263
+ - [ ] Server-side Moonshine STT as fallback
264
+ - [ ] Streaming TTS (audio chunks as they're generated)
265
+ - [ ] Voice activity detection (VAD) tuning
266
+ - [ ] Audio analytics and error reporting
267
+ - [ ] Multilingual STT/TTS support
268
+
269
+ ## Files
270
+
271
+ - `/app/api/speech/tts/route.ts` — TTS endpoint
272
+ - `/app/api/speech/stt/route.ts` — STT endpoint (documentation only)
273
+ - `/lib/speech-api.ts` — Client utilities
274
+ - `/components/SpeechInput.tsx` — Speech input component
275
+ - `/components/SpeechSettings.tsx` — Configuration component
276
+ - `/lib/speech/*` — Core speech library (Kokoro, Deepgram, types)
@@ -0,0 +1,220 @@
1
+ # TTS Migration Guide
2
+
3
+ ## Changes Summary
4
+
5
+ The voice API has been refactored from a Deepgram-only implementation to a unified speech library supporting both **Kokoro** (default) and **Deepgram** providers.
6
+
7
+ ### What Changed
8
+
9
+ **Before:**
10
+ ```ts
11
+ // app/api/agent/voice/route.ts
12
+ // Hardcoded Deepgram via Cloudflare Workers AI
13
+ const result = await ai.run("@cf/deepgram/aura-1", {
14
+ text,
15
+ speaker: "angus",
16
+ encoding: "mp3",
17
+ });
18
+ ```
19
+
20
+ **After:**
21
+ ```ts
22
+ // lib/speech/index.ts - Unified API
23
+ const result = await generateSpeech({
24
+ text: "Hello world",
25
+ provider: "kokoro", // or "deepgram"
26
+ voice: "af_heart",
27
+ });
28
+ ```
29
+
30
+ ## New Features
31
+
32
+ ### 1. Kokoro Provider (Default)
33
+ - **Faster**: CPU-based inference, no network dependency
34
+ - **Higher Quality**: More natural prosody and intonation
35
+ - **16 Voices**: 9 female, 7 male
36
+ - **WAV Output**: Lossless audio
37
+ - **Model**: `onnx-community/Kokoro-82M-v1.0-ONNX`
38
+
39
+ ### 2. Deepgram Provider (Legacy)
40
+ - **Edge-optimized**: Cloudflare Workers AI
41
+ - **12 Voices**: Aura speakers
42
+ - **MP3 Output**: Smaller file size
43
+ - **Requires**: Cloudflare AI binding
44
+
45
+ ## API Changes
46
+
47
+ ### Request Format
48
+
49
+ **Backward Compatible** - Old requests still work:
50
+ ```json
51
+ {
52
+ "text": "Hello",
53
+ "speaker": "angus"
54
+ }
55
+ ```
56
+
57
+ **New Format** (recommended):
58
+ ```json
59
+ {
60
+ "text": "Hello world",
61
+ "provider": "kokoro",
62
+ "voice": "af_heart"
63
+ }
64
+ ```
65
+
66
+ ### Response
67
+
68
+ **Headers:**
69
+ - `Content-Type`: `audio/wav` (Kokoro) or `audio/mpeg` (Deepgram)
70
+ - `Cache-Control`: `public, max-age=86400`
71
+ - `Content-Disposition`: `inline; filename="speech.wav"`
72
+
73
+ **Body:** Audio buffer
74
+
75
+ ## Breaking Changes
76
+
77
+ ### None!
78
+
79
+ The API maintains backward compatibility:
80
+ - `speaker` field still works (alias for `voice`)
81
+ - Omitting `provider` defaults to Kokoro
82
+ - Old Deepgram requests work by specifying `provider: "deepgram"`
83
+
84
+ ## Migration Path
85
+
86
+ ### For Existing Clients
87
+
88
+ **Option 1: No changes (use Kokoro automatically)**
89
+ ```ts
90
+ // No code changes needed - automatically uses Kokoro
91
+ fetch("/api/agent/voice", {
92
+ method: "POST",
93
+ body: JSON.stringify({ text: "Hello" })
94
+ });
95
+ ```
96
+
97
+ **Option 2: Keep using Deepgram**
98
+ ```ts
99
+ // Explicitly request Deepgram
100
+ fetch("/api/agent/voice", {
101
+ method: "POST",
102
+ body: JSON.stringify({
103
+ text: "Hello",
104
+ provider: "deepgram",
105
+ voice: "angus"
106
+ })
107
+ });
108
+ ```
109
+
110
+ **Option 3: Switch to Kokoro (recommended)**
111
+ ```ts
112
+ fetch("/api/agent/voice", {
113
+ method: "POST",
114
+ body: JSON.stringify({
115
+ text: "Hello",
116
+ provider: "kokoro",
117
+ voice: "af_heart"
118
+ })
119
+ });
120
+ ```
121
+
122
+ ## File Structure
123
+
124
+ ```
125
+ lib/speech/
126
+ ├── index.ts # Main API
127
+ ├── types.ts # Shared types & voice lists
128
+ ├── kokoro.ts # Kokoro provider
129
+ ├── deepgram.ts # Deepgram provider
130
+ ├── README.md # Documentation
131
+ ├── MIGRATION.md # This file
132
+ ├── client-example.tsx # React component example
133
+ └── test.ts # Test script
134
+ ```
135
+
136
+ ## Testing
137
+
138
+ ### Run Test Script
139
+ ```bash
140
+ cd apps/qwksearch-web
141
+ npx tsx lib/speech/test.ts
142
+ # Creates test-output.wav
143
+ ```
144
+
145
+ ### Test API Endpoint
146
+ ```bash
147
+ curl -X POST http://localhost:3000/api/agent/voice \
148
+ -H "Content-Type: application/json" \
149
+ -d '{"text": "Hello from Kokoro", "provider": "kokoro", "voice": "af_heart"}' \
150
+ --output test.wav
151
+ ```
152
+
153
+ ## Performance Comparison
154
+
155
+ | Metric | Kokoro | Deepgram |
156
+ |--------|--------|----------|
157
+ | First request | ~2-3s (model download) | ~500ms |
158
+ | Subsequent | ~100-300ms | ~500ms |
159
+ | Quality | ★★★★★ | ★★★★☆ |
160
+ | File size | Larger (WAV) | Smaller (MP3) |
161
+ | Dependencies | Node CPU | Cloudflare AI |
162
+
163
+ ## Voice Mapping
164
+
165
+ ### Kokoro → Deepgram Equivalents
166
+
167
+ | Use Case | Kokoro | Deepgram |
168
+ |----------|--------|----------|
169
+ | Default female | `af_heart` | `asteria` |
170
+ | Professional female | `af_alloy` | `athena` |
171
+ | Warm female | `af_sarah` | `luna` |
172
+ | Default male | `am_adam` | `angus` |
173
+ | Deep male | `am_echo` | `perseus` |
174
+ | Storyteller | `am_fable` | `orpheus` |
175
+
176
+ ## Troubleshooting
177
+
178
+ ### "Kokoro model loading failed"
179
+ - Check disk space (~82MB needed)
180
+ - Verify network access to Hugging Face
181
+ - Check logs: `console.log` shows model load progress
182
+
183
+ ### "Cloudflare AI binding not available"
184
+ - Expected in local dev without Wrangler
185
+ - Use Kokoro instead: `provider: "kokoro"`
186
+ - For Deepgram, run: `vinext dev`
187
+
188
+ ### WAV files not playing
189
+ - Browser support: All modern browsers support WAV
190
+ - Try converting to MP3: `ffmpeg -i speech.wav speech.mp3`
191
+ - Or use Deepgram provider for native MP3
192
+
193
+ ## Environment Variables
194
+
195
+ None required! Both providers work out-of-the-box:
196
+ - **Kokoro**: Downloads model automatically
197
+ - **Deepgram**: Uses Cloudflare AI binding (in prod)
198
+
199
+ ## Rollback
200
+
201
+ To revert to Deepgram-only:
202
+
203
+ 1. Remove Kokoro dependency:
204
+ ```bash
205
+ bun remove kokoro-js
206
+ ```
207
+
208
+ 2. Restore old route:
209
+ ```bash
210
+ git checkout HEAD -- app/api/agent/voice/route.ts
211
+ ```
212
+
213
+ 3. Delete speech lib:
214
+ ```bash
215
+ rm -rf lib/speech
216
+ ```
217
+
218
+ ## Questions?
219
+
220
+ See `lib/speech/README.md` for detailed documentation.
@@ -0,0 +1,159 @@
1
+ # TTS Quick Start
2
+
3
+ ## 5-Second Summary
4
+
5
+ Text-to-speech with **Kokoro** (default, faster, better quality) or **Deepgram** (Cloudflare AI).
6
+
7
+ ## API Call
8
+
9
+ ```bash
10
+ curl -X POST http://localhost:3000/api/agent/voice \
11
+ -H "Content-Type: application/json" \
12
+ -d '{"text": "Hello world"}' \
13
+ --output speech.wav
14
+ ```
15
+
16
+ ## React Component
17
+
18
+ ```tsx
19
+ import { useState } from "react";
20
+
21
+ export function TextToSpeech() {
22
+ const [audio, setAudio] = useState<string | null>(null);
23
+
24
+ const speak = async (text: string) => {
25
+ const res = await fetch("/api/agent/voice", {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json" },
28
+ body: JSON.stringify({ text }),
29
+ });
30
+
31
+ const blob = await res.blob();
32
+ setAudio(URL.createObjectURL(blob));
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <button onClick={() => speak("Hello world")}>
38
+ Speak
39
+ </button>
40
+ {audio && <audio controls src={audio} autoPlay />}
41
+ </div>
42
+ );
43
+ }
44
+ ```
45
+
46
+ ## Request Body
47
+
48
+ ```ts
49
+ {
50
+ text: string; // Required, max 5000 chars
51
+ provider?: "kokoro" | "deepgram"; // Default: kokoro
52
+ voice?: string; // Default: af_heart
53
+ }
54
+ ```
55
+
56
+ ## Top 5 Voices
57
+
58
+ **Kokoro (default):**
59
+ - `af_heart` - Warm female (default)
60
+ - `af_alloy` - Professional female
61
+ - `am_adam` - Strong male
62
+ - `am_echo` - Deep male
63
+ - `af_sarah` - Friendly female
64
+
65
+ **Deepgram:**
66
+ - `angus` - Default male
67
+ - `asteria` - Default female
68
+ - `luna` - Warm female
69
+ - `perseus` - Deep male
70
+ - `athena` - Professional female
71
+
72
+ ## Switch Provider
73
+
74
+ ```ts
75
+ // Use Deepgram instead
76
+ fetch("/api/agent/voice", {
77
+ method: "POST",
78
+ body: JSON.stringify({
79
+ text: "Hello",
80
+ provider: "deepgram",
81
+ voice: "angus"
82
+ })
83
+ });
84
+ ```
85
+
86
+ ## Full Voice Lists
87
+
88
+ **Kokoro (16 voices):**
89
+ ```ts
90
+ const KOKORO_VOICES = [
91
+ "af_heart", "af_alloy", "af_aoede", "af_bella",
92
+ "af_jessica", "af_nicole", "af_river", "af_sarah", "af_sky",
93
+ "am_adam", "am_echo", "am_fable", "am_fenrir",
94
+ "am_liam", "am_michael", "am_onyx"
95
+ ];
96
+ ```
97
+
98
+ **Deepgram (12 voices):**
99
+ ```ts
100
+ const DEEPGRAM_VOICES = [
101
+ "angus", "asteria", "arcas", "orion", "orpheus", "athena",
102
+ "luna", "zeus", "perseus", "helios", "hera", "stella",
103
+ ];
104
+ ```
105
+
106
+ ## Rate Limits
107
+
108
+ - **Guests**: 10 requests/day
109
+ - **Authenticated**: Unlimited
110
+
111
+ ## Error Handling
112
+
113
+ ```ts
114
+ const res = await fetch("/api/agent/voice", {
115
+ method: "POST",
116
+ body: JSON.stringify({ text: "Hello" })
117
+ });
118
+
119
+ if (!res.ok) {
120
+ const error = await res.json();
121
+ console.error(error.error);
122
+ // "text is required"
123
+ // "Daily TTS limit reached (10/day)"
124
+ // "Cloudflare AI binding not available"
125
+ }
126
+ ```
127
+
128
+ ## Direct Library Usage
129
+
130
+ ```ts
131
+ import { generateSpeech } from "@/lib/speech";
132
+
133
+ const audio = await generateSpeech({
134
+ text: "Hello world",
135
+ voice: "af_heart"
136
+ });
137
+
138
+ // Returns: { audio: ArrayBuffer, contentType: "audio/wav" }
139
+ ```
140
+
141
+ ## Test It
142
+
143
+ ```bash
144
+ # Install dependencies
145
+ cd apps/qwksearch-web
146
+ bun install
147
+
148
+ # Run test script
149
+ npx tsx lib/speech/test.ts
150
+
151
+ # Play output
152
+ test-output.wav
153
+ ```
154
+
155
+ ## Next Steps
156
+
157
+ - 📖 Full docs: [README.md](./README.md)
158
+ - 🔄 Migration: [MIGRATION.md](./MIGRATION.md)
159
+ - 💻 Example: [client-example.tsx](./client-example.tsx)