1import"./core-base-C6U1gwfh.js";import{V as i}from"./voiceAiLimits-B2BIt8Ot.js";import"./vendor-core-PeJPZ6eL.js";import"./vendor-firebase-BoNTAgzP.js";const t=new class{constructor(){this.mediaRecorder=null,this.audioChunks=[],this.isRecording=!1,this.useNativeRecognition=!1,this.recognition=null,this.lastNativeTranscript=null,this._charLimitStopping=!1,this._charLimitNotified=!1,this.onPromptCharLimitReached=null,this.transcriptionServerUrl="http://localhost:8000/transcribe",this.healthUrl="http://localhost:8000/health"}async checkServerHealth(){try{return(await fetch(this.healthUrl,{signal:AbortSignal.timeout(2e3)})).ok}catch{return!1}}async startRecording(){if(this.isRecording)return;const i=await this.checkServerHealth(),t=window.SpeechRecognition||window.webkitSpeechRecognition;if(!i&&t)return this.startNativeRecognition();try{const i=await navigator.mediaDevices.getUserMedia({audio:!0}),t=MediaRecorder.isTypeSupported("audio/webm")?"audio/webm":MediaRecorder.isTypeSupported("audio/ogg")?"audio/ogg":"audio/wav";return this.mediaRecorder=new MediaRecorder(i,{mimeType:t}),this.audioChunks=[],this.mediaRecorder.ondataavailable=i=>{i.data.size>0&&this.audioChunks.push(i.data)},this.mediaRecorder.start(),this.isRecording=!0,this.useNativeRecognition=!1,!0}catch(e){throw console.error("[VoiceService] Microphone access denied:",e),e}}startNativeRecognition(){const t=window.SpeechRecognition||window.webkitSpeechRecognition;return!!t&&(this.recognition=new t,this.recognition.lang="en-US",this.recognition.interimResults=!0,this.recognition.continuous=!0,this.recognition.maxAlternatives=1,this._charLimitStopping=!1,this._charLimitNotified=!1,this.lastNativeTranscript="",new Promise((t,e)=>{this.recognition.onstart=()=>{this.isRecording=!0,this.useNativeRecognition=!0,t(!0)},this.recognition.onerror=i=>{this._charLimitStopping&&"aborted"===i.error||(console.error("[VoiceService] Native recognition error:",i.error),this.isRecording=!1,e(new Error(i.error)))},this.recognition.onresult=t=>{let e="";for(let i=t.resultIndex;i<t.results.length;i++)e+=t.results[i][0].transcript;const r=e.trim();if(r.length>=i)return this.lastNativeTranscript=r.slice(0,i),void this._stopNativeForCharLimit();this.lastNativeTranscript=r},this.recognition.onend=()=>{const i=this.isRecording;this.isRecording=!1,this._charLimitStopping=!1,i&&!this._charLimitNotified&&window.BillVoiceAIState&&"function"==typeof window.BillVoiceAIState.stopRecording&&window.BillVoiceAIState.stopRecording()},this.recognition.start()}))}_stopNativeForCharLimit(){if(this.isRecording&&!this._charLimitStopping){this._charLimitStopping=!0,this.isRecording=!1,this._charLimitNotified||(this._charLimitNotified=!0,"function"==typeof this.onPromptCharLimitReached&&this.onPromptCharLimitReached(i));try{this.recognition.stop()}catch(t){console.warn("[VoiceService] stop after char limit:",t)}}}stopRecording(){return this.useNativeRecognition&&this.recognition?new Promise(i=>{const t=this.lastNativeTranscript;if(this.lastNativeTranscript=null,!this.isRecording)return i({text:t});const e=this.recognition.onend;this.recognition.onend=()=>{e&&e(),i({text:t||this.lastNativeTranscript}),this.lastNativeTranscript=null},this.recognition.stop()}):new Promise((t,e)=>{if(!this.mediaRecorder||!this.isRecording)return t(null);this.mediaRecorder.onstop=async()=>{try{const e=new Blob(this.audioChunks,{type:this.mediaRecorder.mimeType});if(this.audioChunks=[],this.isRecording=!1,e.size<1e3)return console.warn("[VoiceService] Recording too short"),t(null);const r=await this.transcribe(e);if(r&&"object"==typeof r&&r.text){let e=String(r.text).trim(),o=!1;e.length>i&&(e=e.slice(0,i),o=!0),t({text:e,trimmed:o})}else if("string"==typeof r){let e=r.trim(),o=!1;e.length>i&&(e=e.slice(0,i),o=!0),t({text:e,trimmed:o})}else t(r)}catch(r){e(r)}},this.mediaRecorder.stop(),this.mediaRecorder.stream.getTracks().forEach(i=>i.stop())})}async transcribe(i){try{const t=new FormData;t.append("file",i,"recording.webm");const e=await fetch(this.transcriptionServerUrl,{method:"POST",body:t,signal:AbortSignal.timeout(15e3)});if(!e.ok)throw new Error("Offline or Server Error");return await e.json()}catch(t){throw console.error("[VoiceService] Transcription failed:",t),t}}};window.BillVoiceAIState=new class{constructor(){this.currentState="Hidden",this.listeners=[],this.isTyping=!1,this.isRecording=!1,this.idleTimer=null,this.idleTimeoutMs=15e3,"undefined"!=typeof window&&window.addEventListener("click",()=>
1{"Hidden"!==this.currentState&&"Peeking"!==this.currentState&&this.resetIdleTimer()})}resetIdleTimer(){this.idleTimer&&clearTimeout(this.idleTimer),["Hidden","Peeking","Verification","Speaking","Listening"].includes(this.currentState)||(this.idleTimer=setTimeout(()=>{"Hidden"===this.currentState||this.isTyping||this.isRecording||this.transition("Peeking")},this.idleTimeoutMs))}subscribe(i){this.listeners.push(i)}notify(){this.listeners.forEach(i=>i(this.currentState))}transition(i){var t;return!(!(null==(t={Hidden:["Peeking","Idle"],Peeking:["Idle","Hidden"],Idle:["Listening","Speaking","Hidden","Verification","Peeking"],Listening:["Idle","Speaking","Verification","Peeking"],Speaking:["Idle","Verification","Hidden"],Verification:["Idle","Hidden"]}[this.currentState])?void 0:t.includes(i))&&"Hidden"!==i)&&(this.currentState=i,"Verification"===i&&window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.open(),this.notify(),this.resetIdleTimer(),!0)}startTyping(){return!this.isRecording&&(this.isTyping=!0,this.resetIdleTimer(),!0)}stopTyping(){this.isTyping=!1,this.resetIdleTimer()}async startRecording(){if(this.isTyping||this.isRecording)return!1;t.onPromptCharLimitReached=i=>{const t=`You've reached the ${i}-character limit for this voice command.`;window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.appendMessage(t,"system"),this.isRecording&&this.stopRecording()};try{return await t.startRecording(),this.isRecording=!0,this.transition("Listening"),!0}catch(i){return t.onPromptCharLimitReached=null,console.error("[BillVoiceAIState] Failed to start recording",i),window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.appendMessage("Microphone access denied or error occurred.","system"),!1}}async stopRecording(){if(this.isRecording||t.isRecording){t.onPromptCharLimitReached=null,this.isRecording=!1,this.transition("Speaking");try{const e=await t.stopRecording();e&&e.text?(e.trimmed&&window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.appendMessage(`Recording trimmed to ${i} characters.`,"system"),window.BillVoiceAIChatPopup&&await window.BillVoiceAIChatPopup.handleCommand(e.text)):e&&e.message&&window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.appendMessage(e.message,"ai")}catch(e){console.error("[BillVoiceAIState] Transcription error",e),window.BillVoiceAIChatPopup&&window.BillVoiceAIChatPopup.appendMessage("Offline: Local transcription service unavailable.","ai")}finally{"Speaking"!==this.currentState&&"Listening"!==this.currentState||this.transition("Idle")}}}};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.