@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.
Files changed (53) hide show
  1. package/dist/__tests__/elevenlabs-smoke.d.ts +7 -0
  2. package/dist/__tests__/elevenlabs-smoke.d.ts.map +1 -0
  3. package/dist/__tests__/elevenlabs-smoke.js +157 -0
  4. package/dist/__tests__/elevenlabs-smoke.js.map +1 -0
  5. package/dist/audio/player.d.ts +27 -0
  6. package/dist/audio/player.d.ts.map +1 -0
  7. package/dist/audio/player.js +69 -0
  8. package/dist/audio/player.js.map +1 -0
  9. package/dist/audio/recorder.d.ts +22 -0
  10. package/dist/audio/recorder.d.ts.map +1 -0
  11. package/dist/audio/recorder.js +45 -0
  12. package/dist/audio/recorder.js.map +1 -0
  13. package/dist/config.d.ts +99 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +36 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/index.d.ts +16 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +14 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/resolve.d.ts +16 -0
  22. package/dist/resolve.d.ts.map +1 -0
  23. package/dist/resolve.js +77 -0
  24. package/dist/resolve.js.map +1 -0
  25. package/dist/stt/elevenlabs-stt.d.ts +25 -0
  26. package/dist/stt/elevenlabs-stt.d.ts.map +1 -0
  27. package/dist/stt/elevenlabs-stt.js +140 -0
  28. package/dist/stt/elevenlabs-stt.js.map +1 -0
  29. package/dist/stt/provider.d.ts +25 -0
  30. package/dist/stt/provider.d.ts.map +1 -0
  31. package/dist/stt/provider.js +8 -0
  32. package/dist/stt/provider.js.map +1 -0
  33. package/dist/stt/whisper-stt.d.ts +27 -0
  34. package/dist/stt/whisper-stt.d.ts.map +1 -0
  35. package/dist/stt/whisper-stt.js +95 -0
  36. package/dist/stt/whisper-stt.js.map +1 -0
  37. package/dist/tts/elevenlabs-tts.d.ts +24 -0
  38. package/dist/tts/elevenlabs-tts.d.ts.map +1 -0
  39. package/dist/tts/elevenlabs-tts.js +67 -0
  40. package/dist/tts/elevenlabs-tts.js.map +1 -0
  41. package/dist/tts/piper-tts.d.ts +23 -0
  42. package/dist/tts/piper-tts.d.ts.map +1 -0
  43. package/dist/tts/piper-tts.js +59 -0
  44. package/dist/tts/piper-tts.js.map +1 -0
  45. package/dist/tts/provider.d.ts +17 -0
  46. package/dist/tts/provider.d.ts.map +1 -0
  47. package/dist/tts/provider.js +8 -0
  48. package/dist/tts/provider.js.map +1 -0
  49. package/dist/voice-io.d.ts +48 -0
  50. package/dist/voice-io.d.ts.map +1 -0
  51. package/dist/voice-io.js +110 -0
  52. package/dist/voice-io.js.map +1 -0
  53. package/package.json +47 -0
@@ -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
+ }