@actrun_ai/tastekit-voice 0.1.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/dist/__tests__/elevenlabs-smoke.d.ts +7 -0
- package/dist/__tests__/elevenlabs-smoke.d.ts.map +1 -0
- package/dist/__tests__/elevenlabs-smoke.js +157 -0
- package/dist/__tests__/elevenlabs-smoke.js.map +1 -0
- package/dist/audio/player.d.ts +27 -0
- package/dist/audio/player.d.ts.map +1 -0
- package/dist/audio/player.js +69 -0
- package/dist/audio/player.js.map +1 -0
- package/dist/audio/recorder.d.ts +22 -0
- package/dist/audio/recorder.d.ts.map +1 -0
- package/dist/audio/recorder.js +45 -0
- package/dist/audio/recorder.js.map +1 -0
- package/dist/config.d.ts +99 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/resolve.d.ts +16 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +77 -0
- package/dist/resolve.js.map +1 -0
- package/dist/stt/elevenlabs-stt.d.ts +25 -0
- package/dist/stt/elevenlabs-stt.d.ts.map +1 -0
- package/dist/stt/elevenlabs-stt.js +140 -0
- package/dist/stt/elevenlabs-stt.js.map +1 -0
- package/dist/stt/provider.d.ts +25 -0
- package/dist/stt/provider.d.ts.map +1 -0
- package/dist/stt/provider.js +8 -0
- package/dist/stt/provider.js.map +1 -0
- package/dist/stt/whisper-stt.d.ts +27 -0
- package/dist/stt/whisper-stt.d.ts.map +1 -0
- package/dist/stt/whisper-stt.js +95 -0
- package/dist/stt/whisper-stt.js.map +1 -0
- package/dist/tts/elevenlabs-tts.d.ts +24 -0
- package/dist/tts/elevenlabs-tts.d.ts.map +1 -0
- package/dist/tts/elevenlabs-tts.js +67 -0
- package/dist/tts/elevenlabs-tts.js.map +1 -0
- package/dist/tts/piper-tts.d.ts +23 -0
- package/dist/tts/piper-tts.d.ts.map +1 -0
- package/dist/tts/piper-tts.js +59 -0
- package/dist/tts/piper-tts.js.map +1 -0
- package/dist/tts/provider.d.ts +17 -0
- package/dist/tts/provider.d.ts.map +1 -0
- package/dist/tts/provider.js +8 -0
- package/dist/tts/provider.js.map +1 -0
- package/dist/voice-io.d.ts +48 -0
- package/dist/voice-io.d.ts.map +1 -0
- package/dist/voice-io.js +110 -0
- package/dist/voice-io.js.map +1 -0
- package/package.json +47 -0
package/dist/voice-io.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { MicRecorder } from './audio/recorder.js';
|
|
2
|
+
import { AudioPlayer } from './audio/player.js';
|
|
3
|
+
/**
|
|
4
|
+
* Voice I/O orchestrator for TasteKit onboarding.
|
|
5
|
+
*
|
|
6
|
+
* Composes STT + TTS + mic recorder + audio player into the two
|
|
7
|
+
* callbacks the Interviewer expects: getUserInput() and onInterviewerMessage().
|
|
8
|
+
*
|
|
9
|
+
* Drop-in replacement for the text-based inquirer callbacks.
|
|
10
|
+
*/
|
|
11
|
+
export class VoiceIO {
|
|
12
|
+
stt;
|
|
13
|
+
tts;
|
|
14
|
+
recorder;
|
|
15
|
+
player;
|
|
16
|
+
options;
|
|
17
|
+
constructor(stt, tts, recorder, player, options) {
|
|
18
|
+
this.stt = stt;
|
|
19
|
+
this.tts = tts;
|
|
20
|
+
this.recorder = recorder ?? new MicRecorder();
|
|
21
|
+
this.player = player ?? new AudioPlayer();
|
|
22
|
+
this.options = {
|
|
23
|
+
showTranscription: options?.showTranscription ?? true,
|
|
24
|
+
onPartial: options?.onPartial ?? (() => { }),
|
|
25
|
+
onFinalTranscript: options?.onFinalTranscript ?? (() => { }),
|
|
26
|
+
printText: options?.printText ?? true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Drop-in replacement for Interviewer's getUserInput callback.
|
|
31
|
+
* Records from mic → runs STT → returns transcribed text.
|
|
32
|
+
*/
|
|
33
|
+
async getUserInput() {
|
|
34
|
+
if (this.options.showTranscription) {
|
|
35
|
+
process.stdout.write('\n 🎤 Listening... (speak now)\n');
|
|
36
|
+
}
|
|
37
|
+
// Start recording
|
|
38
|
+
const audioStream = this.recorder.start();
|
|
39
|
+
// Create a controlled audio iterator that we can stop
|
|
40
|
+
let stopRecording = false;
|
|
41
|
+
const controlledAudio = this.createControlledStream(audioStream, () => stopRecording);
|
|
42
|
+
let finalText = '';
|
|
43
|
+
// Run STT on the audio stream
|
|
44
|
+
for await (const event of this.stt.transcribe(controlledAudio)) {
|
|
45
|
+
if (event.type === 'partial') {
|
|
46
|
+
if (this.options.showTranscription) {
|
|
47
|
+
// Overwrite the current line with partial transcript
|
|
48
|
+
process.stdout.write(`\r 📝 ${event.text}${''.padEnd(20)}`);
|
|
49
|
+
}
|
|
50
|
+
this.options.onPartial(event.text);
|
|
51
|
+
}
|
|
52
|
+
else if (event.type === 'final') {
|
|
53
|
+
finalText = event.text;
|
|
54
|
+
stopRecording = true;
|
|
55
|
+
this.recorder.stop();
|
|
56
|
+
if (this.options.showTranscription) {
|
|
57
|
+
// Clear the partial and show final
|
|
58
|
+
process.stdout.write(`\r ✅ ${finalText}${''.padEnd(20)}\n`);
|
|
59
|
+
}
|
|
60
|
+
this.options.onFinalTranscript(finalText);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Ensure recorder is stopped
|
|
65
|
+
this.recorder.stop();
|
|
66
|
+
if (!finalText) {
|
|
67
|
+
throw new Error('No speech detected');
|
|
68
|
+
}
|
|
69
|
+
return finalText;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Drop-in replacement for Interviewer's onInterviewerMessage callback.
|
|
73
|
+
* Prints text AND plays it as audio via TTS.
|
|
74
|
+
*/
|
|
75
|
+
async onInterviewerMessage(message) {
|
|
76
|
+
// Always print to console so user can read along
|
|
77
|
+
if (this.options.printText) {
|
|
78
|
+
process.stdout.write(`\n 🔊 ${message}\n`);
|
|
79
|
+
}
|
|
80
|
+
// Synthesize and play audio
|
|
81
|
+
try {
|
|
82
|
+
const audioChunks = await this.tts.synthesize(message);
|
|
83
|
+
await this.player.playStream(audioChunks, { sampleRate: 22050 });
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
// TTS failure is non-fatal — text was already printed
|
|
87
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
88
|
+
process.stderr.write(` ⚠ TTS playback failed: ${msg}\n`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Clean up all resources. */
|
|
92
|
+
async dispose() {
|
|
93
|
+
this.recorder.stop();
|
|
94
|
+
this.player.stop();
|
|
95
|
+
await this.stt.dispose?.();
|
|
96
|
+
await this.tts.dispose?.();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Wrap an async iterable so it stops yielding when a condition is met.
|
|
100
|
+
* This lets us stop the mic recording when STT returns a final transcript.
|
|
101
|
+
*/
|
|
102
|
+
async *createControlledStream(source, shouldStop) {
|
|
103
|
+
for await (const chunk of source) {
|
|
104
|
+
if (shouldStop())
|
|
105
|
+
break;
|
|
106
|
+
yield chunk;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=voice-io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-io.js","sourceRoot":"","sources":["../src/voice-io.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@actrun_ai/tastekit-voice",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Voice I/O layer for TasteKit onboarding — ElevenLabs, Whisper, Piper",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "TasteKit Contributors",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/tastekit/tastekit.git",
|
|
11
|
+
"directory": "packages/voice"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"lint": "eslint --ignore-pattern dist/ --ignore-pattern __tests__/ .",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"zod": "^3.23.0",
|
|
34
|
+
"ws": "^8.16.0"
|
|
35
|
+
},
|
|
36
|
+
"optionalDependencies": {
|
|
37
|
+
"node-record-lpcm16": "^1.0.1",
|
|
38
|
+
"speaker": "^0.5.5",
|
|
39
|
+
"play-sound": "^1.1.6"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"@types/ws": "^8.5.0",
|
|
44
|
+
"typescript": "^5.6.0",
|
|
45
|
+
"vitest": "^2.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|