Updates
Rolling release / release (push) Successful in 3m14s

This commit is contained in:
2026-09-11 19:12:28 -04:00
parent 8c17112fa8
commit 7604cded2c
13 changed files with 411 additions and 52 deletions
+21 -4
View File
@@ -22,7 +22,7 @@ export function fastCommand(text) { return FAST_COMMANDS.get(String(text || '').
export class VoiceLoop extends EventEmitter {
constructor({ daemon, capture = new PipeWireCapture(), playback = new PipeWirePlayback(), wake = new WakeEngine(), vad = new VadSegmenter(), asr, tts, cooldownMs = POST_PLAYBACK_COOLDOWN_MS, now = () => Date.now() } = {}) {
super(); this.daemon = daemon; this.capture = capture; this.playback = playback; this.wake = wake; this.vad = vad; this.asr = asr; this.tts = tts; this.cooldownMs = cooldownMs; this.now = now;
this.isSpeaking = false; this.cooldownUntil = 0; this.running = false; this.ptt = false; this._speechQueue = Promise.resolve(); this.metrics = new VoiceMetrics();
this.isSpeaking = false; this.cooldownUntil = 0; this.running = false; this.ptt = false; this._generation = 0; this._speechQueue = Promise.resolve(); this.metrics = new VoiceMetrics();
capture.on('audio', (chunk) => this.pushAudio(chunk));
capture.on('error', (error) => this.emit('error', error));
wake.on('wake', (phrase) => this.wakeHeard(phrase));
@@ -58,12 +58,29 @@ export class VoiceLoop extends EventEmitter {
if (command === 'screen') { await this.daemon?.ask?.('What is on my screen?'); return; }
await this.daemon?.ask?.(text);
}
interrupt() {
this._generation += 1;
this.playback.stop();
this.isSpeaking = false;
this.wake.resume();
}
_releaseSpeaking() {
this.isSpeaking = false;
this.wake.resume();
if (this.daemon?.state === 'SPEAKING') { this.daemon.voice?.finishSpeaking?.(); this.daemon.setState?.('LISTENING'); }
}
async speak(text) {
if (!isMeaningfulTranscript(text) || !this.tts?.speak) return;
const generation = this._generation || 0;
if (!isMeaningfulTranscript(text) || !this.tts?.speak) { this._releaseSpeaking(); return; }
this.metrics.reply();
const sentences = String(text).match(/[^.!?]+[.!?]+|[^.!?]+$/g)?.map((part) => part.trim()).filter(isMeaningfulTranscript) || [];
this._speechQueue = this._speechQueue.then(async () => {
for (const sentence of sentences) await this.speakSentence(sentence);
if (!sentences.length) { this._releaseSpeaking(); return; }
this._speechQueue = this._speechQueue.catch(() => {}).then(async () => {
if (generation !== this._generation) return;
for (const sentence of sentences) {
if (generation !== this._generation) return;
await this.speakSentence(sentence);
}
});
return this._speechQueue;
}