even-toolkit 1.1.2 → 1.3.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 (82) hide show
  1. package/README.md +74 -0
  2. package/dist/glasses/bridge.d.ts +5 -2
  3. package/dist/glasses/bridge.d.ts.map +1 -1
  4. package/dist/glasses/bridge.js +25 -2
  5. package/dist/glasses/bridge.js.map +1 -1
  6. package/dist/glasses/composer.js +2 -2
  7. package/dist/glasses/layout.d.ts +2 -0
  8. package/dist/glasses/layout.d.ts.map +1 -1
  9. package/dist/glasses/layout.js +4 -0
  10. package/dist/glasses/layout.js.map +1 -1
  11. package/dist/glasses/types.d.ts +23 -0
  12. package/dist/glasses/types.d.ts.map +1 -1
  13. package/dist/glasses/types.js +15 -0
  14. package/dist/glasses/types.js.map +1 -1
  15. package/dist/glasses/useGlasses.d.ts.map +1 -1
  16. package/dist/glasses/useGlasses.js +17 -4
  17. package/dist/glasses/useGlasses.js.map +1 -1
  18. package/dist/stt/debug.d.ts +8 -0
  19. package/dist/stt/debug.d.ts.map +1 -0
  20. package/dist/stt/debug.js +34 -0
  21. package/dist/stt/debug.js.map +1 -0
  22. package/dist/stt/engine.d.ts +9 -6
  23. package/dist/stt/engine.d.ts.map +1 -1
  24. package/dist/stt/engine.js +141 -75
  25. package/dist/stt/engine.js.map +1 -1
  26. package/dist/stt/index.d.ts +1 -0
  27. package/dist/stt/index.d.ts.map +1 -1
  28. package/dist/stt/index.js +1 -0
  29. package/dist/stt/index.js.map +1 -1
  30. package/dist/stt/providers/deepgram.d.ts +1 -1
  31. package/dist/stt/providers/deepgram.d.ts.map +1 -1
  32. package/dist/stt/providers/deepgram.js +24 -9
  33. package/dist/stt/providers/deepgram.js.map +1 -1
  34. package/dist/stt/providers/whisper-api.d.ts.map +1 -1
  35. package/dist/stt/providers/whisper-api.js +75 -4
  36. package/dist/stt/providers/whisper-api.js.map +1 -1
  37. package/dist/stt/react/useSTT.d.ts.map +1 -1
  38. package/dist/stt/react/useSTT.js +44 -11
  39. package/dist/stt/react/useSTT.js.map +1 -1
  40. package/dist/stt/registry.d.ts.map +1 -1
  41. package/dist/stt/registry.js +0 -8
  42. package/dist/stt/registry.js.map +1 -1
  43. package/dist/stt/sources/glass-bridge.d.ts +8 -15
  44. package/dist/stt/sources/glass-bridge.d.ts.map +1 -1
  45. package/dist/stt/sources/glass-bridge.js +66 -9
  46. package/dist/stt/sources/glass-bridge.js.map +1 -1
  47. package/dist/stt/sources/microphone.d.ts.map +1 -1
  48. package/dist/stt/sources/microphone.js +4 -0
  49. package/dist/stt/sources/microphone.js.map +1 -1
  50. package/dist/stt/types.d.ts +7 -3
  51. package/dist/stt/types.d.ts.map +1 -1
  52. package/glasses/bridge.ts +24 -3
  53. package/glasses/composer.ts +2 -2
  54. package/glasses/layout.ts +6 -0
  55. package/glasses/types.ts +28 -0
  56. package/glasses/useGlasses.ts +18 -5
  57. package/package.json +7 -19
  58. package/stt/debug.ts +38 -0
  59. package/stt/engine.ts +158 -83
  60. package/stt/index.ts +1 -0
  61. package/stt/providers/deepgram.ts +26 -9
  62. package/stt/providers/whisper-api.ts +78 -4
  63. package/stt/react/useSTT.ts +45 -11
  64. package/stt/registry.ts +0 -8
  65. package/stt/sources/glass-bridge.ts +69 -25
  66. package/stt/sources/microphone.ts +7 -0
  67. package/stt/types.ts +4 -3
  68. package/dist/stt/providers/web-speech.d.ts +0 -25
  69. package/dist/stt/providers/web-speech.d.ts.map +0 -1
  70. package/dist/stt/providers/web-speech.js +0 -153
  71. package/dist/stt/providers/web-speech.js.map +0 -1
  72. package/dist/stt/providers/whisper-local/provider.d.ts +0 -31
  73. package/dist/stt/providers/whisper-local/provider.d.ts.map +0 -1
  74. package/dist/stt/providers/whisper-local/provider.js +0 -174
  75. package/dist/stt/providers/whisper-local/provider.js.map +0 -1
  76. package/dist/stt/providers/whisper-local/worker.d.ts +0 -2
  77. package/dist/stt/providers/whisper-local/worker.d.ts.map +0 -1
  78. package/dist/stt/providers/whisper-local/worker.js +0 -35
  79. package/dist/stt/providers/whisper-local/worker.js.map +0 -1
  80. package/stt/providers/web-speech.ts +0 -221
  81. package/stt/providers/whisper-local/provider.ts +0 -226
  82. package/stt/providers/whisper-local/worker.ts +0 -40
@@ -7,8 +7,67 @@ import type {
7
7
  STTError,
8
8
  } from '../types';
9
9
  import { float32ToWav } from '../audio/pcm-utils';
10
+ import { sttLog } from '../debug';
11
+
12
+ // Use proxy path to avoid WebView fetch restrictions (CORS/upload limits)
13
+ // The proxy is configured in vite.config.ts: /__whisper → api.openai.com
14
+ const WHISPER_API_URL = '/__whisper/v1/audio/transcriptions';
15
+
16
+ // Whisper hallucinates these on silence/low audio — filter them out
17
+ const HALLUCINATION_PATTERNS = [
18
+ /sottotitoli/i,
19
+ /amara\.org/i,
20
+ /qtss/i,
21
+ /continua\./i,
22
+ /al prossimo episodio/i,
23
+ /prossimo episodio/i,
24
+ /alla prossima/i,
25
+ /a presto/i,
26
+ /thank you for watching/i,
27
+ /thanks for watching/i,
28
+ /please subscribe/i,
29
+ /see you next time/i,
30
+ /see you in the next/i,
31
+ /next episode/i,
32
+ /sous-titres/i,
33
+ /untertitel/i,
34
+ /subtítulos/i,
35
+ /bis zum nächsten/i,
36
+ /字幕/,
37
+ /you$/i,
38
+ /^\s*$/,
39
+ ];
40
+
41
+ /** Common phrases Whisper appends at the end of audio — strip them */
42
+ const TRAILING_HALLUCINATIONS = [
43
+ /\s*al prossimo episodio\.?\s*$/i,
44
+ /\s*alla prossima\.?\s*$/i,
45
+ /\s*a presto\.?\s*$/i,
46
+ /\s*grazie per la visione\.?\s*$/i,
47
+ /\s*thank you for watching\.?\s*$/i,
48
+ /\s*thanks for watching\.?\s*$/i,
49
+ /\s*see you next time\.?\s*$/i,
50
+ /\s*see you in the next episode\.?\s*$/i,
51
+ /\s*please subscribe\.?\s*$/i,
52
+ /\s*bis zum nächsten mal\.?\s*$/i,
53
+ ];
54
+
55
+ /** Check if text is just dots, punctuation, or too short to be real speech */
56
+ function isJunkTranscription(text: string): boolean {
57
+ const cleaned = text.replace(/[\s.,!?;:…\-–—]+/g, '');
58
+ if (cleaned.length === 0) return true;
59
+ if (cleaned.length < 2) return true;
60
+ return HALLUCINATION_PATTERNS.some(p => p.test(text));
61
+ }
10
62
 
11
- const WHISPER_API_URL = 'https://api.openai.com/v1/audio/transcriptions';
63
+ /** Strip trailing hallucination phrases from otherwise valid text */
64
+ function cleanTranscription(text: string): string {
65
+ let result = text;
66
+ for (const pattern of TRAILING_HALLUCINATIONS) {
67
+ result = result.replace(pattern, '');
68
+ }
69
+ return result.trim();
70
+ }
12
71
 
13
72
  export class WhisperApiProvider implements STTProvider {
14
73
  readonly type = 'whisper-api' as const;
@@ -29,11 +88,11 @@ export class WhisperApiProvider implements STTProvider {
29
88
 
30
89
  async init(config: STTProviderConfig): Promise<void> {
31
90
  this.apiKey = config.apiKey ?? '';
32
- this.language = config.language ?? 'en';
91
+ this.language = (config.language ?? 'en').split('-')[0];
33
92
  this.modelId = config.modelId ?? 'whisper-1';
34
93
 
35
94
  if (!this.apiKey) {
36
- const err: STTError = { code: 'not-allowed', message: 'API key is required', provider: this.type };
95
+ const err: STTError = { code: 'not-allowed', message: 'OpenAI API key required — set it in Settings', provider: this.type };
37
96
  this.emitError(err);
38
97
  throw new Error(err.message);
39
98
  }
@@ -63,6 +122,7 @@ export class WhisperApiProvider implements STTProvider {
63
122
 
64
123
  try {
65
124
  const wavBlob = float32ToWav(audio, sampleRate);
125
+ sttLog('whisper-api: sending', (audio.length / sampleRate).toFixed(1), 's audio,', (wavBlob.size / 1024).toFixed(0), 'KB WAV');
66
126
 
67
127
  const formData = new FormData();
68
128
  formData.append('file', wavBlob, 'audio.wav');
@@ -88,8 +148,22 @@ export class WhisperApiProvider implements STTProvider {
88
148
 
89
149
  const json = (await response.json()) as { text: string };
90
150
 
151
+ // Filter and clean Whisper hallucinations
152
+ const rawText = json.text?.trim() ?? '';
153
+ const text = cleanTranscription(rawText);
154
+ if (isJunkTranscription(text)) {
155
+ const transcript: STTTranscript = {
156
+ text: '',
157
+ isFinal: true,
158
+ confidence: 0,
159
+ timestamp: Date.now(),
160
+ };
161
+ this.setState('idle');
162
+ return transcript;
163
+ }
164
+
91
165
  const transcript: STTTranscript = {
92
- text: json.text,
166
+ text,
93
167
  isFinal: true,
94
168
  confidence: 1,
95
169
  timestamp: Date.now(),
@@ -1,17 +1,18 @@
1
1
  import { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import type { UseSTTConfig, UseSTTReturn, STTState, STTError } from '../types';
3
3
  import { STTEngine } from '../engine';
4
+ import { sttLog } from '../debug';
4
5
 
5
6
  export function useSTT(config: UseSTTConfig = {}): UseSTTReturn {
6
7
  const [transcript, setTranscript] = useState('');
7
8
  const [interimTranscript, setInterimTranscript] = useState('');
8
9
  const [isListening, setIsListening] = useState(false);
9
10
  const [isLoading, setIsLoading] = useState(false);
10
- const [loadProgress] = useState(0);
11
11
  const [error, setError] = useState<STTError | null>(null);
12
12
  const [state, setState] = useState<STTState>('idle');
13
13
 
14
14
  const engineRef = useRef<STTEngine | null>(null);
15
+ const busyRef = useRef(false); // prevent start/stop race
15
16
  const configRef = useRef(config);
16
17
  configRef.current = config;
17
18
 
@@ -24,31 +25,50 @@ export function useSTT(config: UseSTTConfig = {}): UseSTTReturn {
24
25
  }, []);
25
26
 
26
27
  const start = useCallback(async () => {
27
- // Dispose previous engine
28
+ if (busyRef.current) {
29
+ sttLog('useSTT: start() blocked — busy');
30
+ return;
31
+ }
32
+ busyRef.current = true;
33
+
28
34
  engineRef.current?.dispose();
29
35
 
30
36
  const cfg = configRef.current;
37
+
38
+ sttLog('useSTT: start()', cfg.provider, 'apiKey?', !!cfg.apiKey);
31
39
  const engine = new STTEngine({
32
- provider: cfg.provider ?? 'web-speech',
40
+ provider: cfg.provider ?? 'whisper-api',
33
41
  source: cfg.source,
34
42
  language: cfg.language,
35
43
  mode: cfg.mode,
36
44
  apiKey: cfg.apiKey,
37
45
  modelId: cfg.modelId,
38
46
  continuous: cfg.continuous,
39
- vad: cfg.vad,
47
+ vad: cfg.vad ?? true,
48
+ chunkIntervalMs: cfg.chunkIntervalMs,
40
49
  fallback: cfg.fallback,
41
50
  });
42
51
 
43
52
  engineRef.current = engine;
44
53
 
45
- // Subscribe to events
54
+ // Accumulate final results for streaming providers
55
+ let accumulated = '';
56
+
46
57
  engine.onTranscript((t) => {
47
58
  if (t.isFinal) {
48
- setTranscript((prev) => (prev ? prev + ' ' + t.text : t.text));
59
+ // Append final to accumulated text
60
+ const clean = t.text.replace(/\.+$/, '').trim();
61
+ if (clean) {
62
+ accumulated = accumulated ? accumulated + ' ' + clean : clean;
63
+ }
64
+ setTranscript(accumulated);
49
65
  setInterimTranscript('');
50
66
  } else {
51
- setInterimTranscript(t.text);
67
+ // Interim: show accumulated + current interim
68
+ const clean = t.text.replace(/\.+$/, '').trim();
69
+ const full = accumulated ? accumulated + ' ' + clean : clean;
70
+ setInterimTranscript(full);
71
+ setTranscript(full);
52
72
  }
53
73
  cfg.onTranscript?.(t.text, t.isFinal);
54
74
  });
@@ -57,29 +77,44 @@ export function useSTT(config: UseSTTConfig = {}): UseSTTReturn {
57
77
  setState(s);
58
78
  setIsListening(s === 'listening');
59
79
  setIsLoading(s === 'loading');
60
- if (s === 'idle') {
80
+ if (s === 'idle' || s === 'error') {
61
81
  setInterimTranscript('');
82
+ busyRef.current = false;
62
83
  }
63
84
  });
64
85
 
65
86
  engine.onError((e) => {
66
87
  setError(e);
88
+ busyRef.current = false;
67
89
  });
68
90
 
69
91
  setError(null);
70
- await engine.start();
92
+ try {
93
+ await engine.start();
94
+ busyRef.current = false;
95
+ } catch {
96
+ busyRef.current = false;
97
+ }
71
98
  }, []);
72
99
 
73
100
  const stop = useCallback(() => {
74
- engineRef.current?.stop();
101
+ sttLog('useSTT: stop()');
102
+ if (!engineRef.current) return;
103
+ // Stop mic immediately, then engine handles final transcription
104
+ engineRef.current.stop();
75
105
  }, []);
76
106
 
77
107
  const abort = useCallback(() => {
108
+ sttLog('useSTT: abort()');
78
109
  engineRef.current?.abort();
110
+ busyRef.current = false;
79
111
  }, []);
80
112
 
81
113
  const reset = useCallback(() => {
114
+ sttLog('useSTT: reset()');
82
115
  engineRef.current?.abort();
116
+ engineRef.current = null;
117
+ busyRef.current = false;
83
118
  setTranscript('');
84
119
  setInterimTranscript('');
85
120
  setError(null);
@@ -102,7 +137,6 @@ export function useSTT(config: UseSTTConfig = {}): UseSTTReturn {
102
137
  interimTranscript,
103
138
  isListening,
104
139
  isLoading,
105
- loadProgress,
106
140
  error,
107
141
  state,
108
142
  start,
package/stt/registry.ts CHANGED
@@ -2,14 +2,6 @@ import type { STTProvider } from './types';
2
2
 
3
3
  export async function createProvider(type: string): Promise<STTProvider> {
4
4
  switch (type) {
5
- case 'web-speech': {
6
- const { WebSpeechProvider } = await import('./providers/web-speech');
7
- return new WebSpeechProvider();
8
- }
9
- case 'whisper-local': {
10
- const { WhisperLocalProvider } = await import('./providers/whisper-local/provider');
11
- return new WhisperLocalProvider();
12
- }
13
5
  case 'whisper-api': {
14
6
  const { WhisperApiProvider } = await import('./providers/whisper-api');
15
7
  return new WhisperApiProvider();
@@ -1,46 +1,65 @@
1
1
  import type { AudioSource } from '../types';
2
- import { uint8ToPcm16, pcm16ToFloat32 } from '../audio/pcm-utils';
2
+ import { sttLog } from '../debug';
3
3
 
4
4
  const GLASS_SAMPLE_RATE = 16000;
5
5
 
6
- export interface GlassBridgeSourceConfig {
7
- /** The EvenHub bridge instance that fires audio events */
8
- bridge: {
9
- onEvent(handler: (event: GlassAudioEvent) => void): void;
10
- };
11
- }
12
-
13
- export interface GlassAudioEvent {
14
- audioEvent?: {
15
- audioPcm?: Uint8Array;
16
- };
17
- }
18
-
19
6
  /**
20
7
  * AudioSource for G2 smart glasses.
21
- * Listens for audio PCM events from the EvenHub SDK bridge
22
- * and converts 16-bit PCM to Float32.
8
+ *
9
+ * Automatically opens/closes the glasses microphone via the EvenHub SDK bridge
10
+ * and listens for PCM audio chunks from `audioEvent.audioPcm`.
11
+ *
12
+ * Usage:
13
+ * const source = new GlassBridgeSource();
14
+ * // It auto-detects the bridge from window.__evenBridge
23
15
  */
24
16
  export class GlassBridgeSource implements AudioSource {
25
- private config: GlassBridgeSourceConfig;
26
17
  private listeners: Array<(pcm: Float32Array, sampleRate: number) => void> = [];
27
18
  private listening = false;
28
-
29
- constructor(config: GlassBridgeSourceConfig) {
30
- this.config = config;
31
- }
19
+ private bridge: any = null;
32
20
 
33
21
  async start(): Promise<void> {
34
22
  if (this.listening) return;
23
+
24
+ // Get bridge from global (set by useGlasses)
25
+ this.bridge = (window as any).__evenBridge ?? null;
26
+ sttLog('GlassBridgeSource.start()', 'bridge found:', !!this.bridge);
27
+
28
+ if (!this.bridge) {
29
+ throw new Error(
30
+ 'Glasses bridge not available. Make sure useGlasses is mounted and __evenBridge is set.'
31
+ );
32
+ }
33
+
35
34
  this.listening = true;
36
35
 
37
- this.config.bridge.onEvent((event: GlassAudioEvent) => {
36
+ // Open the glasses microphone
37
+ try {
38
+ if (this.bridge.rawBridge?.audioControl) {
39
+ sttLog('GlassBridgeSource', 'calling audioControl(true)');
40
+ await this.bridge.rawBridge.audioControl(true);
41
+ sttLog('GlassBridgeSource', 'audioControl(true) succeeded');
42
+ } else if (this.bridge.rawBridge?.callEvenApp) {
43
+ sttLog('GlassBridgeSource', 'calling callEvenApp("audioControl", {isOpen: true})');
44
+ await this.bridge.rawBridge.callEvenApp('audioControl', { isOpen: true });
45
+ sttLog('GlassBridgeSource', 'callEvenApp audioControl succeeded');
46
+ } else {
47
+ sttLog('GlassBridgeSource', 'WARNING: no audioControl method found on bridge');
48
+ }
49
+ } catch (err) {
50
+ sttLog('GlassBridgeSource', 'audioControl(true) error:', err);
51
+ }
52
+
53
+ // Listen for audio PCM events
54
+ this.bridge.onEvent((event: any) => {
38
55
  if (!this.listening) return;
39
- const audioPcm = event.audioEvent?.audioPcm;
56
+ const audioPcm = event?.audioEvent?.audioPcm;
40
57
  if (!audioPcm || audioPcm.length === 0) return;
41
58
 
42
- const pcm16 = uint8ToPcm16(audioPcm);
43
- const float32 = pcm16ToFloat32(pcm16);
59
+ sttLog('GlassBridgeSource', 'got audioPcm chunk, bytes:', audioPcm.length);
60
+
61
+ // Convert Uint8Array (16-bit PCM little-endian) to Float32Array
62
+ const float32 = pcm16ToFloat32(audioPcm);
44
63
 
45
64
  for (const cb of this.listeners) {
46
65
  cb(float32, GLASS_SAMPLE_RATE);
@@ -49,7 +68,21 @@ export class GlassBridgeSource implements AudioSource {
49
68
  }
50
69
 
51
70
  stop(): void {
71
+ sttLog('GlassBridgeSource.stop()');
52
72
  this.listening = false;
73
+
74
+ // Close the glasses microphone
75
+ if (this.bridge) {
76
+ try {
77
+ if (this.bridge.rawBridge?.audioControl) {
78
+ this.bridge.rawBridge.audioControl(false);
79
+ } else if (this.bridge.rawBridge?.callEvenApp) {
80
+ this.bridge.rawBridge.callEvenApp('audioControl', { isOpen: false });
81
+ }
82
+ } catch (err) {
83
+ sttLog('GlassBridgeSource', 'audioControl(false) error:', err);
84
+ }
85
+ }
53
86
  }
54
87
 
55
88
  onAudioData(cb: (pcm: Float32Array, sampleRate: number) => void): () => void {
@@ -65,3 +98,14 @@ export class GlassBridgeSource implements AudioSource {
65
98
  this.listeners.length = 0;
66
99
  }
67
100
  }
101
+
102
+ /** Convert raw bytes (16-bit PCM little-endian) to Float32Array */
103
+ function pcm16ToFloat32(bytes: Uint8Array): Float32Array {
104
+ const samples = bytes.length / 2;
105
+ const float32 = new Float32Array(samples);
106
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
107
+ for (let i = 0; i < samples; i++) {
108
+ float32[i] = view.getInt16(i * 2, true) / 32768;
109
+ }
110
+ return float32;
111
+ }
@@ -15,6 +15,13 @@ export class MicrophoneSource implements AudioSource {
15
15
  private listeners: Array<(pcm: Float32Array, sampleRate: number) => void> = [];
16
16
 
17
17
  async start(): Promise<void> {
18
+ if (!navigator.mediaDevices?.getUserMedia) {
19
+ throw new Error(
20
+ 'getUserMedia not available — likely running in a WebView. ' +
21
+ 'Use glass-bridge source or pass a custom AudioSource instead.'
22
+ );
23
+ }
24
+
18
25
  this.stream = await navigator.mediaDevices.getUserMedia({
19
26
  audio: { sampleRate: DEFAULT_SAMPLE_RATE, channelCount: 1 },
20
27
  });
package/stt/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // ── STT Provider Types ──
2
2
 
3
- export type STTProviderType = 'web-speech' | 'whisper-local' | 'whisper-api' | 'deepgram' | string;
3
+ export type STTProviderType = 'whisper-api' | 'deepgram' | string;
4
4
  export type STTMode = 'streaming' | 'batch';
5
5
  export type STTState = 'idle' | 'loading' | 'listening' | 'processing' | 'error';
6
6
 
@@ -70,6 +70,7 @@ export interface STTEngineConfig {
70
70
  continuous?: boolean;
71
71
  vad?: boolean | { silenceMs?: number; thresholdDb?: number };
72
72
  sampleRate?: number;
73
+ chunkIntervalMs?: number;
73
74
  fallback?: STTProviderType;
74
75
  }
75
76
 
@@ -83,7 +84,8 @@ export interface UseSTTConfig {
83
84
  apiKey?: string;
84
85
  modelId?: string;
85
86
  continuous?: boolean;
86
- vad?: boolean;
87
+ vad?: boolean | { silenceMs?: number; thresholdDb?: number };
88
+ chunkIntervalMs?: number;
87
89
  autoStart?: boolean;
88
90
  fallback?: STTProviderType;
89
91
  onTranscript?: (text: string, isFinal: boolean) => void;
@@ -94,7 +96,6 @@ export interface UseSTTReturn {
94
96
  interimTranscript: string;
95
97
  isListening: boolean;
96
98
  isLoading: boolean;
97
- loadProgress: number;
98
99
  error: STTError | null;
99
100
  state: STTState;
100
101
  start: () => Promise<void>;
@@ -1,25 +0,0 @@
1
- import type { STTProvider, STTProviderConfig, STTMode, STTState, STTTranscript, STTError } from '../types';
2
- export declare class WebSpeechProvider implements STTProvider {
3
- readonly type: "web-speech";
4
- readonly supportedModes: STTMode[];
5
- private _state;
6
- private recognition;
7
- private config;
8
- private stopping;
9
- private transcriptCbs;
10
- private stateCbs;
11
- private errorCbs;
12
- get state(): STTState;
13
- init(config: STTProviderConfig): Promise<void>;
14
- start(): void;
15
- stop(): void;
16
- abort(): void;
17
- dispose(): void;
18
- onTranscript(cb: (t: STTTranscript) => void): () => void;
19
- onStateChange(cb: (s: STTState) => void): () => void;
20
- onError(cb: (e: STTError) => void): () => void;
21
- private setState;
22
- private emitTranscript;
23
- private emitError;
24
- }
25
- //# sourceMappingURL=web-speech.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"web-speech.d.ts","sourceRoot":"","sources":["../../../stt/providers/web-speech.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,QAAQ,EACT,MAAM,UAAU,CAAC;AAuDlB,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,eAAyB;IACtC,QAAQ,CAAC,cAAc,EAAE,OAAO,EAAE,CAAiB;IAEnD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,QAAQ,CAAoC;IAEpD,IAAI,KAAK,IAAI,QAAQ,CAEpB;IAEK,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,KAAK,IAAI,IAAI;IA2Db,IAAI,IAAI,IAAI;IAOZ,KAAK,IAAI,IAAI;IAOb,OAAO,IAAI,IAAI;IAOf,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAOxD,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IAOpD,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IAS9C,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,SAAS;CAGlB"}
@@ -1,153 +0,0 @@
1
- function getSpeechRecognitionCtor() {
2
- if (typeof window === 'undefined')
3
- return null;
4
- const w = window;
5
- return (w.SpeechRecognition ?? w.webkitSpeechRecognition);
6
- }
7
- // ── Provider ──
8
- export class WebSpeechProvider {
9
- constructor() {
10
- this.type = 'web-speech';
11
- this.supportedModes = ['streaming'];
12
- this._state = 'idle';
13
- this.recognition = null;
14
- this.config = {};
15
- this.stopping = false;
16
- this.transcriptCbs = [];
17
- this.stateCbs = [];
18
- this.errorCbs = [];
19
- }
20
- get state() {
21
- return this._state;
22
- }
23
- async init(config) {
24
- const Ctor = getSpeechRecognitionCtor();
25
- if (!Ctor) {
26
- this.emitError({ code: 'unsupported', message: 'SpeechRecognition not available in this browser', provider: this.type });
27
- throw new Error('SpeechRecognition not supported');
28
- }
29
- this.config = config;
30
- }
31
- start() {
32
- const Ctor = getSpeechRecognitionCtor();
33
- if (!Ctor) {
34
- this.emitError({ code: 'unsupported', message: 'SpeechRecognition not available', provider: this.type });
35
- return;
36
- }
37
- // Tear down previous instance if any
38
- if (this.recognition) {
39
- try {
40
- this.recognition.abort();
41
- }
42
- catch { /* ignore */ }
43
- }
44
- this.stopping = false;
45
- const recognition = new Ctor();
46
- recognition.continuous = this.config.continuous ?? true;
47
- recognition.interimResults = true;
48
- recognition.lang = this.config.language ?? 'en-US';
49
- recognition.onstart = () => {
50
- this.setState('listening');
51
- };
52
- recognition.onresult = (event) => {
53
- for (let i = event.resultIndex; i < event.results.length; i++) {
54
- const result = event.results[i];
55
- if (!result?.[0])
56
- continue;
57
- const transcript = {
58
- text: result[0].transcript,
59
- isFinal: result.isFinal,
60
- confidence: result[0].confidence ?? 0,
61
- timestamp: Date.now(),
62
- };
63
- this.emitTranscript(transcript);
64
- }
65
- };
66
- recognition.onerror = (event) => {
67
- // Suppress no-speech and aborted-while-stopping
68
- if (event.error === 'no-speech')
69
- return;
70
- if (event.error === 'aborted' && this.stopping)
71
- return;
72
- const code = mapErrorCode(event.error);
73
- this.emitError({ code, message: event.message || event.error, provider: this.type });
74
- if (code !== 'no-speech') {
75
- this.setState('error');
76
- }
77
- };
78
- recognition.onend = () => {
79
- this.recognition = null;
80
- this.setState('idle');
81
- };
82
- this.recognition = recognition;
83
- recognition.start();
84
- }
85
- stop() {
86
- this.stopping = true;
87
- if (this.recognition) {
88
- this.recognition.stop();
89
- }
90
- }
91
- abort() {
92
- this.stopping = true;
93
- if (this.recognition) {
94
- this.recognition.abort();
95
- }
96
- }
97
- dispose() {
98
- this.abort();
99
- this.transcriptCbs = [];
100
- this.stateCbs = [];
101
- this.errorCbs = [];
102
- }
103
- onTranscript(cb) {
104
- this.transcriptCbs.push(cb);
105
- return () => {
106
- this.transcriptCbs = this.transcriptCbs.filter((c) => c !== cb);
107
- };
108
- }
109
- onStateChange(cb) {
110
- this.stateCbs.push(cb);
111
- return () => {
112
- this.stateCbs = this.stateCbs.filter((c) => c !== cb);
113
- };
114
- }
115
- onError(cb) {
116
- this.errorCbs.push(cb);
117
- return () => {
118
- this.errorCbs = this.errorCbs.filter((c) => c !== cb);
119
- };
120
- }
121
- // ── Private helpers ──
122
- setState(s) {
123
- if (this._state === s)
124
- return;
125
- this._state = s;
126
- for (const cb of this.stateCbs)
127
- cb(s);
128
- }
129
- emitTranscript(t) {
130
- for (const cb of this.transcriptCbs)
131
- cb(t);
132
- }
133
- emitError(e) {
134
- for (const cb of this.errorCbs)
135
- cb(e);
136
- }
137
- }
138
- function mapErrorCode(error) {
139
- switch (error) {
140
- case 'not-allowed':
141
- case 'service-not-allowed':
142
- return 'not-allowed';
143
- case 'no-speech':
144
- return 'no-speech';
145
- case 'network':
146
- return 'network';
147
- case 'aborted':
148
- return 'aborted';
149
- default:
150
- return 'unknown';
151
- }
152
- }
153
- //# sourceMappingURL=web-speech.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"web-speech.js","sourceRoot":"","sources":["../../../stt/providers/web-speech.ts"],"names":[],"mappings":"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"}
@@ -1,31 +0,0 @@
1
- import type { STTProvider, STTProviderConfig, STTMode, STTState, STTTranscript, STTError } from '../../types';
2
- export declare class WhisperLocalProvider implements STTProvider {
3
- readonly type: "whisper-local";
4
- readonly supportedModes: STTMode[];
5
- private _state;
6
- private worker;
7
- private config;
8
- private ready;
9
- private transcriptCbs;
10
- private stateCbs;
11
- private errorCbs;
12
- private pendingResolve;
13
- private pendingReject;
14
- private initResolve;
15
- private initReject;
16
- get state(): STTState;
17
- init(config: STTProviderConfig): Promise<void>;
18
- start(): void;
19
- stop(): void;
20
- abort(): void;
21
- dispose(): void;
22
- transcribe(audio: Float32Array, sampleRate: number): Promise<STTTranscript>;
23
- onTranscript(cb: (t: STTTranscript) => void): () => void;
24
- onStateChange(cb: (s: STTState) => void): () => void;
25
- onError(cb: (e: STTError) => void): () => void;
26
- private handleWorkerMessage;
27
- private setState;
28
- private emitTranscript;
29
- private emitError;
30
- }
31
- //# sourceMappingURL=provider.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../stt/providers/whisper-local/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,QAAQ,EACT,MAAM,aAAa,CAAC;AAkBrB,qBAAa,oBAAqB,YAAW,WAAW;IACtD,QAAQ,CAAC,IAAI,kBAA4B;IACzC,QAAQ,CAAC,cAAc,EAAE,OAAO,EAAE,CAAa;IAE/C,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAS;IAEtB,OAAO,CAAC,aAAa,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,QAAQ,CAAoC;IAEpD,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,UAAU,CAAqC;IAEvD,IAAI,KAAK,IAAI,QAAQ,CAEpB;IAEK,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCpD,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IASb,OAAO,IAAI,IAAI;IAYT,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBjF,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxD,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IAKpD,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI;IAO9C,OAAO,CAAC,mBAAmB;IA0D3B,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,SAAS;CAGlB"}