@bobfrankston/msger 0.1.74 → 0.1.75
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/KNOWN-BUGS.md +121 -0
- package/MSGER-API-SUMMARY.md +162 -0
- package/MSGER-API.md +376 -0
- package/README.md +93 -0
- package/SESSION-2025-11-06.md +191 -0
- package/SESSION-NOTES.md +678 -0
- package/clihandler.d.ts.map +1 -1
- package/clihandler.js +62 -2
- package/clihandler.js.map +1 -1
- package/clihandler.ts +60 -2
- package/icon.png +0 -0
- package/icon1.png +0 -0
- package/msger-native/Cargo.toml +1 -0
- package/msger-native/bin/msgernative.exe +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Breadcrumbs +12 -118
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/BrowserMetrics/{BrowserMetrics-690552AF-DCD4.pma → BrowserMetrics-690B9AD3-657C.pma} +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/BrowserMetrics/{BrowserMetrics-69055373-F88C.pma → BrowserMetrics-690BA05A-501C.pma} +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/BrowsingTopicsState +1 -1
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/data_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/{BrowserMetrics/BrowserMetrics-69055587-A65C.pma → Default/Cache/Cache_Data/data_2} +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/data_3 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000001 +383 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000002 +1091 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000003 +2153 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000004 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000005 +626 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/f_000006 +393 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Cache/Cache_Data/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/01241693cfdc32b9_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/0ba1eea781f3552c_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/323aa210eebefe2c_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/4608446ac118e77a_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/6938205dc2f77841_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/6de12299dc89e5f3_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/8f403c112eaa455b_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/9a3aceb491137f07_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/aedb266cbaf9c28f_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/ca526fdda86d0b9d_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/f5d11d783c9fdf69_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Code Cache/js/index-dir/the-real-index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Collections/collectionsSQLite +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Collections/collectionsSQLite-journal +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/DIPS +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/DawnGraphiteCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/DawnGraphiteCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/DawnWebGPUCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/DawnWebGPUCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Extension State/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Favicons +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/GPUCache/data_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/GPUCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/GPUCache/data_2 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/GPUCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/History +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Local Storage/leveldb/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/MediaDeviceSalts +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/MediaDeviceSalts-journal +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Network/Network Persistent State +1 -1
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Network/TransportSecurity +1 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Preferences +1 -1
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/CacheStorage/14e849fa8522d406112ea607cf7fd6342b71b987/249ee9af-c3df-4a86-89a8-2c51f3370ee0/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/CacheStorage/14e849fa8522d406112ea607cf7fd6342b71b987/249ee9af-c3df-4a86-89a8-2c51f3370ee0/index-dir/the-real-index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/CacheStorage/14e849fa8522d406112ea607cf7fd6342b71b987/index.txt +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/CURRENT +1 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/LOCK +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/LOG +3 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/LOG.old +3 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/Database/MANIFEST-000001 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/ScriptCache/2cc80dabc69f58b6_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/ScriptCache/2cc80dabc69f58b6_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/ScriptCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Service Worker/ScriptCache/index-dir/the-real-index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Session Storage/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Session Storage/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Site Characteristics Database/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/favorites_diagnostic.log +27 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/000003.log +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG.old +3 -3
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/data_0 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/data_3 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/f_000003 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/f_000004 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GrShaderCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GraphiteDawnCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/GraphiteDawnCache/index +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/Local State +1 -1
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/RevisitationBloomfilter +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/ShaderCache/data_1 +0 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/ShaderCache/index +0 -0
- package/msger-native/src/main.rs +343 -37
- package/msger-native/src/template.html +103 -28
- package/msger-storage-demo.html +290 -0
- package/msger.code-workspace +3 -0
- package/msgerdefs/README.md +122 -0
- package/msgerdefs/msgerdefs.d.ts +322 -0
- package/msgerdefs/msgerdefs.d.ts.map +1 -0
- package/msgerdefs/msgerdefs.js +110 -0
- package/msgerdefs/msgerdefs.js.map +1 -0
- package/msgerdefs/msgerdefs.ts +427 -0
- package/msgerdefs/package.json +38 -0
- package/msgerdefs/samples.html +431 -0
- package/msgerdefs/test1.cmd +1 -0
- package/msgerdefs/tsconfig.json +17 -0
- package/msgernative-linux-x64 +0 -0
- package/package.json +5 -1
- package/shower.d.ts +2 -0
- package/shower.d.ts.map +1 -1
- package/shower.js +17 -0
- package/shower.js.map +1 -1
- package/shower.ts +24 -0
- package/test-data-persistence.html +315 -0
- package/test-htmlfrom.html +29 -0
- package/test-ipc-reach.html +113 -0
- package/test-msger-api.html +120 -0
- package/test-msger-functions.html +325 -0
- package/msger-native/bin/msgernative.exe.WebView2/EBWebView/BrowserMetrics/BrowserMetrics-69055419-C8A0.pma +0 -0
|
Binary file
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
// Voice Controller Module - Handles wake word detection and speech recognition
|
|
2
|
+
// @ts-ignore - Module resolution for browser ES modules
|
|
3
|
+
import { TensorFlowWakeWord, TENSORFLOW_WAKE_WORD_CONFIGS } from './tensorflow-wake-word-simple.js';
|
|
4
|
+
export class VoiceController {
|
|
5
|
+
constructor(config, events = {}) {
|
|
6
|
+
this.isListening = false;
|
|
7
|
+
this.speechRecognition = null;
|
|
8
|
+
this.tensorflowModel = null;
|
|
9
|
+
this.audioContext = null;
|
|
10
|
+
this.mediaStream = null;
|
|
11
|
+
this.isInitialized = false;
|
|
12
|
+
this.currentState = 'idle';
|
|
13
|
+
this.wakeWordBuffer = [];
|
|
14
|
+
this.speechBuffer = [];
|
|
15
|
+
this.lastCommandTime = 0;
|
|
16
|
+
this.commandCooldown = 2000;
|
|
17
|
+
this.selectedMicrophoneId = null;
|
|
18
|
+
this.lastWakePhrase = '';
|
|
19
|
+
this.wakePhraseTimeout = 0;
|
|
20
|
+
this.consecutiveErrors = 0;
|
|
21
|
+
this.lastErrorType = '';
|
|
22
|
+
this.backoffDelay = 0;
|
|
23
|
+
this.maxConsecutiveErrors = 5;
|
|
24
|
+
this.isMicrophoneDead = false;
|
|
25
|
+
// Use null coalescing for cleaner backward compatibility
|
|
26
|
+
this.config = {
|
|
27
|
+
...config,
|
|
28
|
+
timeoutMs: config.timeoutMs ?? config.commandTimeoutMs ?? 5000,
|
|
29
|
+
useTensorFlow: config.useTensorFlow ?? config.enableTensorFlow ?? false,
|
|
30
|
+
useWebSpeech: config.useWebSpeech ?? config.enableWebSpeech ?? true,
|
|
31
|
+
wakeWords: config.wakeWords ?? (config.wakeWord ? [config.wakeWord] : ['Computer']),
|
|
32
|
+
tabletMode: config.tabletMode ?? this.detectTablet()
|
|
33
|
+
};
|
|
34
|
+
this.events = events;
|
|
35
|
+
this.selectedMicrophoneId = localStorage.getItem('wallclock-selected-microphone');
|
|
36
|
+
}
|
|
37
|
+
detectTablet() {
|
|
38
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
39
|
+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
40
|
+
const isTabletUA = /tablet|ipad|android(?!.*mobile)/i.test(userAgent);
|
|
41
|
+
const isSmallScreen = window.innerWidth <= 1024; // Typical tablet breakpoint
|
|
42
|
+
return isTouchDevice && (isTabletUA || isSmallScreen);
|
|
43
|
+
}
|
|
44
|
+
isNativeWakePhrase(transcript) {
|
|
45
|
+
// Only match complete wake phrases to avoid false matches
|
|
46
|
+
const wakePatterns = [
|
|
47
|
+
/\b(go|hey)\s+(timer|time)\b/i, // "go timer" or "hey timer" ONLY
|
|
48
|
+
/\b(go|hey)\s*timer\b/i, // Handle missing space: "gotimer" or "heytimer"
|
|
49
|
+
/\b(go|hey)\s+(timer|time)r?\b/i, // Handle "timers" or slight variations
|
|
50
|
+
];
|
|
51
|
+
const isMatch = wakePatterns.some(pattern => pattern.test(transcript));
|
|
52
|
+
console.log(`[VoiceController] Wake phrase check: "${transcript}" -> ${isMatch ? 'MATCHED' : 'NO MATCH'}`);
|
|
53
|
+
return isMatch;
|
|
54
|
+
}
|
|
55
|
+
async initialize() {
|
|
56
|
+
try {
|
|
57
|
+
this.log('Initializing voice controller...');
|
|
58
|
+
this.log(`Tablet mode: ${this.config.tabletMode}`);
|
|
59
|
+
this.log(`Using TensorFlow: ${this.config.useTensorFlow}`);
|
|
60
|
+
// For non-tablets, prefer native speech recognition
|
|
61
|
+
if (!this.config.tabletMode) {
|
|
62
|
+
this.config.useTensorFlow = false;
|
|
63
|
+
this.config.useWebSpeech = true;
|
|
64
|
+
this.log('Desktop detected: Using native speech recognition');
|
|
65
|
+
}
|
|
66
|
+
// Request microphone permission first
|
|
67
|
+
await this.requestMicrophonePermission();
|
|
68
|
+
// Initialize audio context
|
|
69
|
+
await this.initializeAudioContext();
|
|
70
|
+
// Initialize recognition engines based on config
|
|
71
|
+
if (this.config.useTensorFlow) {
|
|
72
|
+
await this.initializeTensorFlow();
|
|
73
|
+
}
|
|
74
|
+
if (this.config.useWebSpeech) {
|
|
75
|
+
this.initializeWebSpeech();
|
|
76
|
+
}
|
|
77
|
+
this.isInitialized = true;
|
|
78
|
+
this.events.statusChanged?.('Initialized successfully');
|
|
79
|
+
this.log('Voice controller initialized successfully');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.log(`Failed to initialize voice controller: ${error.message}`);
|
|
83
|
+
this.events.error?.(`Initialization failed: ${error.message}`);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async getAvailableMicrophones() {
|
|
88
|
+
try {
|
|
89
|
+
this.log('Getting available microphones...');
|
|
90
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
91
|
+
this.log(`Total devices found: ${devices.length}`);
|
|
92
|
+
const microphones = devices
|
|
93
|
+
.filter(device => device.kind === 'audioinput')
|
|
94
|
+
.map(device => ({
|
|
95
|
+
deviceId: device.deviceId,
|
|
96
|
+
label: device.label || `Microphone ${device.deviceId.substr(0, 8)}`
|
|
97
|
+
}));
|
|
98
|
+
this.log(`Found ${microphones.length} microphones:`);
|
|
99
|
+
microphones.forEach(mic => {
|
|
100
|
+
this.log(` - ${mic.label} (${mic.deviceId})`);
|
|
101
|
+
});
|
|
102
|
+
return microphones;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
this.log(`Failed to enumerate microphones: ${error.message}`);
|
|
106
|
+
console.error('[VOICE] Microphone enumeration error:', error);
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
setSelectedMicrophone(deviceId) {
|
|
111
|
+
this.selectedMicrophoneId = deviceId;
|
|
112
|
+
if (deviceId) {
|
|
113
|
+
localStorage.setItem('wallclock-selected-microphone', deviceId);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
localStorage.removeItem('wallclock-selected-microphone');
|
|
117
|
+
}
|
|
118
|
+
this.log(`Selected microphone: ${deviceId || 'default'}`);
|
|
119
|
+
}
|
|
120
|
+
getMicrophoneConstraints() {
|
|
121
|
+
const constraints = {
|
|
122
|
+
echoCancellation: true,
|
|
123
|
+
noiseSuppression: true,
|
|
124
|
+
autoGainControl: true,
|
|
125
|
+
sampleRate: this.config.mobileOptimized ? 16000 : 44100,
|
|
126
|
+
channelCount: 1
|
|
127
|
+
};
|
|
128
|
+
// Use optional chaining
|
|
129
|
+
if (this.selectedMicrophoneId) {
|
|
130
|
+
constraints.deviceId = { exact: this.selectedMicrophoneId };
|
|
131
|
+
}
|
|
132
|
+
return constraints;
|
|
133
|
+
}
|
|
134
|
+
async requestMicrophonePermission() {
|
|
135
|
+
try {
|
|
136
|
+
const constraints = {
|
|
137
|
+
audio: this.getMicrophoneConstraints()
|
|
138
|
+
};
|
|
139
|
+
this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
140
|
+
this.log('Microphone permission granted');
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error.name === 'NotAllowedError') {
|
|
144
|
+
throw new Error('Microphone permission denied');
|
|
145
|
+
}
|
|
146
|
+
else if (error.name === 'NotFoundError') {
|
|
147
|
+
throw new Error('No microphone found');
|
|
148
|
+
}
|
|
149
|
+
else if (error.name === 'OverconstrainedError') {
|
|
150
|
+
// Retry with default device if selected device fails
|
|
151
|
+
if (this.selectedMicrophoneId) {
|
|
152
|
+
this.log('Selected microphone failed, trying default...');
|
|
153
|
+
this.setSelectedMicrophone('');
|
|
154
|
+
return this.requestMicrophonePermission();
|
|
155
|
+
}
|
|
156
|
+
throw new Error('Microphone constraints not supported');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new Error(`Microphone access failed: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async initializeAudioContext() {
|
|
164
|
+
try {
|
|
165
|
+
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
166
|
+
if (this.audioContext.state === 'suspended') {
|
|
167
|
+
await this.audioContext.resume();
|
|
168
|
+
}
|
|
169
|
+
this.log('Audio context initialized');
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
throw new Error(`Failed to initialize audio context: ${error.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async initializeTensorFlow() {
|
|
176
|
+
try {
|
|
177
|
+
this.log('Initializing TensorFlow.js wake word detection...');
|
|
178
|
+
const tfConfig = this.config.tensorFlowConfig ?? TENSORFLOW_WAKE_WORD_CONFIGS.GO_WAKE_WORD;
|
|
179
|
+
const tfEvents = {
|
|
180
|
+
onWakeWordDetected: (word, confidence) => {
|
|
181
|
+
this.log(`🎯 TensorFlow wake word detected: "${word}" (${confidence.toFixed(3)})`);
|
|
182
|
+
this.events.wakeWordDetected?.();
|
|
183
|
+
},
|
|
184
|
+
onListeningStarted: () => {
|
|
185
|
+
this.log('TensorFlow wake word listening started');
|
|
186
|
+
},
|
|
187
|
+
onListeningStopped: () => {
|
|
188
|
+
this.log('TensorFlow wake word listening stopped');
|
|
189
|
+
},
|
|
190
|
+
onError: (error) => {
|
|
191
|
+
this.log(`TensorFlow error: ${error.message}`);
|
|
192
|
+
this.events.error?.(error.message);
|
|
193
|
+
},
|
|
194
|
+
onLog: (message) => {
|
|
195
|
+
this.events.onLog?.(message);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
this.tensorflowModel = new TensorFlowWakeWord(tfConfig, tfEvents);
|
|
199
|
+
await this.tensorflowModel.initialize();
|
|
200
|
+
await this.tensorflowModel.startListening();
|
|
201
|
+
this.log('TensorFlow wake word detection active');
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.log(`TensorFlow initialization failed: ${error.message ?? error}`);
|
|
205
|
+
this.log(`TensorFlow error type: ${error.constructor.name}`);
|
|
206
|
+
this.events.error?.(error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
handleSpeechResult(event) {
|
|
210
|
+
console.log('[VoiceController] handleSpeechResult called', event);
|
|
211
|
+
console.log(`[VoiceController] Current state: ${this.currentState}`);
|
|
212
|
+
if (!event.results || event.results.length === 0) {
|
|
213
|
+
console.log('[VoiceController] No results in event');
|
|
214
|
+
this.log('Speech result received but no results available');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const result = event.results[event.results.length - 1];
|
|
218
|
+
console.log('[VoiceController] Result:', result);
|
|
219
|
+
if (!result || result.length === 0) {
|
|
220
|
+
console.log('[VoiceController] No alternatives in result');
|
|
221
|
+
this.log('Speech result received but no alternatives available');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Web Speech API structure: result[0] contains the transcript and confidence
|
|
225
|
+
const transcript = result[0].transcript?.trim().toLowerCase() || '';
|
|
226
|
+
const confidence = result[0].confidence ?? 1.0; // Default to 1.0 if not provided
|
|
227
|
+
console.log(`[VoiceController] Transcript: "${transcript}", Confidence: ${confidence}`);
|
|
228
|
+
console.log(`[VoiceController] Is final: ${result.isFinal}`);
|
|
229
|
+
if (transcript) {
|
|
230
|
+
this.log(`Speech result: "${transcript}" (confidence: ${confidence.toFixed(2)}, final: ${result.isFinal})`);
|
|
231
|
+
}
|
|
232
|
+
// For command listening, wait longer before considering a result "final" to allow compound phrases
|
|
233
|
+
if (this.currentState === 'command-listening') {
|
|
234
|
+
// Only process truly final results during command listening to avoid cutting off compound phrases
|
|
235
|
+
if (!result.isFinal) {
|
|
236
|
+
return; // Wait for the final result
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (result.isFinal && confidence >= this.config.confidenceThreshold) {
|
|
240
|
+
console.log(`[VoiceController] Processing final result, currentState: ${this.currentState}`);
|
|
241
|
+
// Native wake phrase detection (desktop mode)
|
|
242
|
+
if (!this.config.tabletMode) {
|
|
243
|
+
// If we're already in command-listening mode, treat everything as a command
|
|
244
|
+
if (this.currentState === 'command-listening') {
|
|
245
|
+
// Check if this is the same wake phrase we just detected (ignore it)
|
|
246
|
+
if (transcript === this.lastWakePhrase && Date.now() < this.wakePhraseTimeout) {
|
|
247
|
+
console.log(`[VoiceController] Ignoring repeated wake phrase in command mode: "${transcript}"`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
console.log(`[VoiceController] In command-listening mode, sending as command: "${transcript}"`);
|
|
251
|
+
this.events.commandReceived?.(transcript, confidence);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Otherwise check for wake phrases
|
|
255
|
+
if (this.isNativeWakePhrase(transcript)) {
|
|
256
|
+
console.log(`[VoiceController] Native wake phrase detected: "${transcript}"`);
|
|
257
|
+
// Check if it's just a wake phrase or includes command
|
|
258
|
+
const hasCommand = /\b(go|hey)\s*(?:timer|time)s?\s+(?:\d|\w+)/i.test(transcript);
|
|
259
|
+
if (hasCommand) {
|
|
260
|
+
console.log('[VoiceController] Complete command detected, sending to commandReceived');
|
|
261
|
+
// Complete command in one phrase
|
|
262
|
+
this.events.commandReceived?.(transcript, confidence);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
console.log('[VoiceController] Just wake phrase detected, triggering wakeWordDetected');
|
|
266
|
+
// Store the wake phrase and set timeout to ignore it if it comes through again
|
|
267
|
+
this.lastWakePhrase = transcript;
|
|
268
|
+
this.wakePhraseTimeout = Date.now() + 2000; // Ignore for 2 seconds
|
|
269
|
+
// Just wake phrase, trigger wake word detection
|
|
270
|
+
this.events.wakeWordDetected?.();
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Traditional two-stage mode (tablet mode)
|
|
277
|
+
// If we're in command-listening mode, send the transcript as a command
|
|
278
|
+
if (this.currentState === 'command-listening') {
|
|
279
|
+
console.log(`[VoiceController] Sending command: "${transcript}"`);
|
|
280
|
+
this.events.commandReceived?.(transcript, confidence);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Check for wake words only if not in command-listening mode
|
|
284
|
+
const containsWakeWord = this.config.wakeWords.some(word => transcript.includes(word.toLowerCase()));
|
|
285
|
+
if (containsWakeWord) {
|
|
286
|
+
this.events.wakeWordDetected?.();
|
|
287
|
+
const command = this.extractCommand(transcript);
|
|
288
|
+
if (command) {
|
|
289
|
+
this.events.commandReceived?.(command, confidence);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
destroy() {
|
|
297
|
+
this.stopListening();
|
|
298
|
+
this.mediaStream?.getTracks().forEach(track => track.stop());
|
|
299
|
+
this.mediaStream = null;
|
|
300
|
+
if (this.audioContext?.state !== 'closed') {
|
|
301
|
+
this.audioContext?.close();
|
|
302
|
+
this.audioContext = null;
|
|
303
|
+
}
|
|
304
|
+
this.speechRecognition = null;
|
|
305
|
+
this.tensorflowModel = null;
|
|
306
|
+
this.isInitialized = false;
|
|
307
|
+
this.log('Voice controller destroyed');
|
|
308
|
+
}
|
|
309
|
+
getWakeWordLabel() {
|
|
310
|
+
return this.config.wakeWords?.[0] ?? 'Computer';
|
|
311
|
+
}
|
|
312
|
+
isWebSpeechListening() {
|
|
313
|
+
return this.isListening && this.speechRecognition !== null;
|
|
314
|
+
}
|
|
315
|
+
// Add missing methods that clock.ts expects
|
|
316
|
+
getSelectedMicrophoneId() {
|
|
317
|
+
return this.selectedMicrophoneId ?? '';
|
|
318
|
+
}
|
|
319
|
+
hasActiveWakeWordDetection() {
|
|
320
|
+
return this.isListening && this.currentState === 'wake-listening';
|
|
321
|
+
}
|
|
322
|
+
startCommandListening() {
|
|
323
|
+
console.log('[VoiceController] startCommandListening called');
|
|
324
|
+
console.log('[VoiceController] Previous state:', this.currentState);
|
|
325
|
+
console.log('[VoiceController] isListening:', this.isListening);
|
|
326
|
+
console.log('[VoiceController] speechRecognition exists:', !!this.speechRecognition);
|
|
327
|
+
this.currentState = 'command-listening';
|
|
328
|
+
console.log('[VoiceController] Switched to command-listening mode');
|
|
329
|
+
if (!this.isListening && this.speechRecognition) {
|
|
330
|
+
// Speech recognition not running - start it
|
|
331
|
+
try {
|
|
332
|
+
this.isListening = true;
|
|
333
|
+
this.speechRecognition.start();
|
|
334
|
+
this.log('Started listening for commands');
|
|
335
|
+
console.log('[VoiceController] Speech recognition started for commands');
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
this.log(`Failed to start command listening: ${error.message}`);
|
|
339
|
+
console.error('[VoiceController] Failed to start speech recognition:', error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else if (this.isListening) {
|
|
343
|
+
// Speech recognition already running from wake word detection
|
|
344
|
+
// Just fire the listeningStarted event since we're now in command mode
|
|
345
|
+
this.log('Speech recognition already running, switching to command mode');
|
|
346
|
+
console.log('[VoiceController] Speech recognition already active, firing listeningStarted event');
|
|
347
|
+
this.events.listeningStarted?.();
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.log('[VoiceController] No speech recognition available');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
shutdown() {
|
|
354
|
+
this.destroy();
|
|
355
|
+
}
|
|
356
|
+
async logAvailableMicrophones() {
|
|
357
|
+
const microphones = await this.getAvailableMicrophones();
|
|
358
|
+
this.log('Available microphones:');
|
|
359
|
+
microphones.forEach((mic, index) => {
|
|
360
|
+
const selected = mic.deviceId === this.selectedMicrophoneId ? ' (SELECTED)' : '';
|
|
361
|
+
this.log(` ${index + 1}. ${mic.label}${selected}`);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// Add missing Web Speech initialization and handlers
|
|
365
|
+
initializeWebSpeech() {
|
|
366
|
+
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
|
367
|
+
this.log('Web Speech API not supported');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const SpeechRecognition = window.SpeechRecognition ?? window.webkitSpeechRecognition;
|
|
371
|
+
this.speechRecognition = new SpeechRecognition();
|
|
372
|
+
// Configure for native wake phrases (no TensorFlow needed)
|
|
373
|
+
if (!this.config.tabletMode) {
|
|
374
|
+
this.speechRecognition.continuous = true; // Always listening for wake phrases
|
|
375
|
+
this.speechRecognition.interimResults = true; // Enable interim results for better responsiveness
|
|
376
|
+
this.speechRecognition.maxAlternatives = 3; // More alternatives for better matching
|
|
377
|
+
this.speechRecognition.lang = 'en-US';
|
|
378
|
+
this.log('Configured for native wake phrase detection');
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
// Traditional two-stage mode for tablets
|
|
382
|
+
this.speechRecognition.continuous = false;
|
|
383
|
+
this.speechRecognition.interimResults = true;
|
|
384
|
+
this.speechRecognition.maxAlternatives = 1;
|
|
385
|
+
this.speechRecognition.lang = 'en-US';
|
|
386
|
+
this.log('Configured for two-stage recognition');
|
|
387
|
+
}
|
|
388
|
+
this.speechRecognition.onstart = () => {
|
|
389
|
+
this.log(`Speech recognition started - currentState: ${this.currentState}`);
|
|
390
|
+
// Only fire listeningStarted event when in command-listening mode
|
|
391
|
+
// Don't fire it when just waiting for wake words
|
|
392
|
+
if (this.currentState === 'command-listening') {
|
|
393
|
+
this.log('Firing listeningStarted event');
|
|
394
|
+
this.events.listeningStarted?.();
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
this.log('Not firing listeningStarted - state is not command-listening');
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
this.speechRecognition.onresult = (event) => {
|
|
401
|
+
this.handleSpeechResult(event);
|
|
402
|
+
};
|
|
403
|
+
this.speechRecognition.onerror = (event) => {
|
|
404
|
+
this.handleSpeechError(event);
|
|
405
|
+
};
|
|
406
|
+
this.speechRecognition.onend = () => {
|
|
407
|
+
const willAutoRestart = this.currentState === 'wake-listening' && !this.isMicrophoneDead;
|
|
408
|
+
if (willAutoRestart) {
|
|
409
|
+
// Don't fire listeningEnded or log for natural restarts - this is normal behavior
|
|
410
|
+
this.isListening = false;
|
|
411
|
+
// Apply backoff delay if we have consecutive errors
|
|
412
|
+
const restartDelay = this.backoffDelay > 0 ? this.backoffDelay : 100;
|
|
413
|
+
setTimeout(() => {
|
|
414
|
+
if (!this.isListening && this.currentState === 'wake-listening' && !this.isMicrophoneDead) {
|
|
415
|
+
this.startListening();
|
|
416
|
+
}
|
|
417
|
+
}, restartDelay);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Only fire listeningEnded when actually stopping (not auto-restarting)
|
|
421
|
+
this.log(`Speech recognition ended - currentState: ${this.currentState}, micDead: ${this.isMicrophoneDead}`);
|
|
422
|
+
this.events.listeningEnded?.();
|
|
423
|
+
this.isListening = false;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
this.log('Web Speech API initialized');
|
|
427
|
+
}
|
|
428
|
+
extractCommand(transcript) {
|
|
429
|
+
let command = transcript;
|
|
430
|
+
this.config.wakeWords.forEach(wakeWord => {
|
|
431
|
+
const regex = new RegExp(wakeWord.toLowerCase(), 'gi');
|
|
432
|
+
command = command.replace(regex, '').trim();
|
|
433
|
+
});
|
|
434
|
+
return command;
|
|
435
|
+
}
|
|
436
|
+
handleSpeechError(event) {
|
|
437
|
+
const error = event.error;
|
|
438
|
+
// Track consecutive errors for backoff
|
|
439
|
+
if (error === this.lastErrorType) {
|
|
440
|
+
this.consecutiveErrors++;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.consecutiveErrors = 1;
|
|
444
|
+
this.lastErrorType = error;
|
|
445
|
+
}
|
|
446
|
+
// Log error with consecutive count
|
|
447
|
+
this.log(`Speech recognition error: ${error} (consecutive: ${this.consecutiveErrors})`);
|
|
448
|
+
// Check for microphone death - "aborted" errors in rapid succession
|
|
449
|
+
if (error === 'aborted' && this.consecutiveErrors >= this.maxConsecutiveErrors) {
|
|
450
|
+
this.isMicrophoneDead = true;
|
|
451
|
+
this.isListening = false;
|
|
452
|
+
this.events.error?.('Microphone unavailable - too many consecutive errors. Please check system permissions.');
|
|
453
|
+
this.log('MICROPHONE DEAD: Stopping restart attempts');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
// Calculate exponential backoff delay
|
|
457
|
+
if (this.consecutiveErrors > 1) {
|
|
458
|
+
this.backoffDelay = Math.min(1000 * Math.pow(2, this.consecutiveErrors - 1), 30000); // Max 30 seconds
|
|
459
|
+
this.log(`Applying backoff delay: ${this.backoffDelay}ms`);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
this.backoffDelay = 0;
|
|
463
|
+
}
|
|
464
|
+
if (error === 'no-speech') {
|
|
465
|
+
this.log('No speech detected');
|
|
466
|
+
this.consecutiveErrors = 0; // Reset on benign errors
|
|
467
|
+
}
|
|
468
|
+
else if (error === 'network') {
|
|
469
|
+
this.events.error?.('Network error during speech recognition');
|
|
470
|
+
}
|
|
471
|
+
else if (error === 'not-allowed') {
|
|
472
|
+
this.isMicrophoneDead = true;
|
|
473
|
+
this.isListening = false;
|
|
474
|
+
this.events.error?.('Microphone permission denied');
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
this.events.error?.(`Speech recognition error: ${error}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async startListening() {
|
|
481
|
+
if (!this.isInitialized) {
|
|
482
|
+
throw new Error('Voice controller not initialized');
|
|
483
|
+
}
|
|
484
|
+
// Don't restart if microphone is dead
|
|
485
|
+
if (this.isMicrophoneDead) {
|
|
486
|
+
this.log('Microphone is dead - not attempting to restart');
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (this.isListening) {
|
|
490
|
+
this.log('Already listening');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
this.isListening = true;
|
|
495
|
+
this.currentState = 'wake-listening';
|
|
496
|
+
this.speechRecognition?.start();
|
|
497
|
+
// Reset error tracking on successful start
|
|
498
|
+
this.consecutiveErrors = 0;
|
|
499
|
+
this.lastErrorType = '';
|
|
500
|
+
this.backoffDelay = 0;
|
|
501
|
+
this.log('Started listening for wake words');
|
|
502
|
+
this.events.statusChanged?.('Listening for wake words...');
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
this.isListening = false;
|
|
506
|
+
this.log(`Failed to start listening: ${error.message}`);
|
|
507
|
+
this.events.error?.(`Failed to start listening: ${error.message}`);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
stopListening() {
|
|
512
|
+
if (!this.isListening) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
this.isListening = false;
|
|
516
|
+
this.currentState = 'idle';
|
|
517
|
+
this.speechRecognition?.stop();
|
|
518
|
+
this.log('Stopped listening');
|
|
519
|
+
this.events.statusChanged?.('Stopped listening');
|
|
520
|
+
}
|
|
521
|
+
updateConfig(newConfig) {
|
|
522
|
+
this.config = { ...this.config, ...newConfig };
|
|
523
|
+
this.log('Configuration updated');
|
|
524
|
+
}
|
|
525
|
+
getState() {
|
|
526
|
+
return this.currentState;
|
|
527
|
+
}
|
|
528
|
+
isActive() {
|
|
529
|
+
return this.isListening;
|
|
530
|
+
}
|
|
531
|
+
log(message) {
|
|
532
|
+
const logMessage = typeof message === 'string' ? message : message.message;
|
|
533
|
+
if (this.config.debugMode) {
|
|
534
|
+
console.log(`[VoiceController] ${logMessage}`);
|
|
535
|
+
}
|
|
536
|
+
this.events.onLog?.(logMessage);
|
|
537
|
+
}
|
|
538
|
+
checkBrowserCompatibility() {
|
|
539
|
+
try {
|
|
540
|
+
// Check for WebAssembly support
|
|
541
|
+
if (typeof WebAssembly === 'undefined') {
|
|
542
|
+
this.log('WebAssembly not supported');
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
// Check for Web Workers support
|
|
546
|
+
if (typeof Worker === 'undefined') {
|
|
547
|
+
this.log('Web Workers not supported');
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
// Check for Promises support
|
|
551
|
+
if (typeof Promise === 'undefined') {
|
|
552
|
+
this.log('Promises not supported');
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
// Check for Web Audio API
|
|
556
|
+
if (typeof window !== 'undefined' && !window.AudioContext && !window.webkitAudioContext) {
|
|
557
|
+
this.log('Web Audio API not supported');
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
// Check for navigator and getUserMedia
|
|
561
|
+
if (typeof navigator === 'undefined' || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
562
|
+
this.log('MediaDevices API not supported');
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
this.log('Browser compatibility check passed');
|
|
566
|
+
return true;
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
this.log(`Compatibility check failed: ${error.message}`);
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Default configurations for easy setup
|
|
575
|
+
export const DEFAULT_VOICE_CONFIG = {
|
|
576
|
+
accessKey: "uurGHRfD6i555eNVTcSIAi6ciiYgq9EiELQMzgCckAA0XU3DiE3aww==",
|
|
577
|
+
wakeWords: ['Computer'],
|
|
578
|
+
timeoutMs: 15000,
|
|
579
|
+
useWebSpeech: true,
|
|
580
|
+
useTensorFlow: false, // Disabled by default - use native approach
|
|
581
|
+
enableAIFallback: false,
|
|
582
|
+
aiService: 'openai',
|
|
583
|
+
confidenceThreshold: 0.0,
|
|
584
|
+
mobileOptimized: true,
|
|
585
|
+
tabletMode: false, // Auto-detected in constructor
|
|
586
|
+
debugMode: true
|
|
587
|
+
};
|
|
588
|
+
// Alternative configurations for experimentation
|
|
589
|
+
export const VOICE_CONFIGS = {
|
|
590
|
+
TENSORFLOW_ONLY: {
|
|
591
|
+
...DEFAULT_VOICE_CONFIG,
|
|
592
|
+
useTensorFlow: true,
|
|
593
|
+
useWebSpeech: true,
|
|
594
|
+
wakeWords: ['go'],
|
|
595
|
+
tensorFlowConfig: TENSORFLOW_WAKE_WORD_CONFIGS.GO_OPTIMIZED
|
|
596
|
+
},
|
|
597
|
+
// Tablet-optimized configuration for better mobile device performance
|
|
598
|
+
TENSORFLOW_TABLET: {
|
|
599
|
+
...DEFAULT_VOICE_CONFIG,
|
|
600
|
+
useTensorFlow: true,
|
|
601
|
+
useWebSpeech: true,
|
|
602
|
+
wakeWords: ['go'],
|
|
603
|
+
timeoutMs: 20000, // Longer timeout for tablets (20 seconds vs default 15)
|
|
604
|
+
tensorFlowConfig: TENSORFLOW_WAKE_WORD_CONFIGS.TABLET_OPTIMIZED
|
|
605
|
+
},
|
|
606
|
+
TENSORFLOW_GO_STOP: {
|
|
607
|
+
...DEFAULT_VOICE_CONFIG,
|
|
608
|
+
useTensorFlow: true,
|
|
609
|
+
useWebSpeech: true,
|
|
610
|
+
wakeWords: ['go', 'stop'],
|
|
611
|
+
tensorFlowConfig: TENSORFLOW_WAKE_WORD_CONFIGS.GO_STOP_WAKE_WORDS
|
|
612
|
+
},
|
|
613
|
+
WEB_SPEECH_ONLY: {
|
|
614
|
+
...DEFAULT_VOICE_CONFIG,
|
|
615
|
+
wakeWords: ['timer'], // Will match "hey timer" or "go timer"
|
|
616
|
+
useTensorFlow: false
|
|
617
|
+
},
|
|
618
|
+
TENSORFLOW_NUMBERS: {
|
|
619
|
+
...DEFAULT_VOICE_CONFIG,
|
|
620
|
+
useTensorFlow: true,
|
|
621
|
+
useWebSpeech: true,
|
|
622
|
+
wakeWords: ['three', 'five'],
|
|
623
|
+
tensorFlowConfig: TENSORFLOW_WAKE_WORD_CONFIGS.NUMBER_WAKE_WORDS
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
//# sourceMappingURL=voice-controller.js.map
|