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.
- package/README.md +1 -1
- package/dist/core/deepgram.d.ts +5 -0
- package/dist/core/kokoro.d.ts +5 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/types/types.d.ts +17 -0
- package/package.json +7 -11
- package/speech/core/KokoroTTS.js +91 -0
- package/speech/core/deepgram.ts +56 -0
- package/speech/core/kokoro.js +93 -0
- package/speech/core/kokoro.ts +81 -0
- package/speech/docs/ARCHITECTURE.md +349 -0
- package/speech/docs/HUGGINGFACE_MIGRATION.md +172 -0
- package/speech/docs/INTEGRATION.md +276 -0
- package/speech/docs/MIGRATION.md +220 -0
- package/speech/docs/QUICKSTART.md +159 -0
- package/speech/docs/README.md +167 -0
- package/speech/index.ts +54 -0
- package/speech/legacy/conversation.js +150 -0
- package/speech/legacy/main.js +56 -0
- package/speech/legacy/stt.js +161 -0
- package/speech/legacy/tts.js +56 -0
- package/speech/legacy/worker.js +71 -0
- package/speech/types/types.ts +34 -0
- package/speech/ui/AudioPlayer.js +84 -0
- package/speech/ui/ui.js +109 -0
- package/speech/ui/voice-selector.js +77 -0
- package/speech/ui/voices.js +259 -0
- package/speech/utils/audio-utils.js +82 -0
- package/speech/utils/phonemize.js +198 -0
- package/speech/utils/semantic-split.js +107 -0
- package/speech/utils/sentence-detector.js +89 -0
- package/readme.md +0 -1
|
@@ -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)
|