@luckydraw/cumulus 0.30.24 → 0.30.26

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/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.30.25
4
+ - Restored tap-to-interject on the voice mode mic indicator
5
+
6
+
3
7
  ## v0.30.23
4
- - Voice replies now finish playing fully before the session ends
5
8
 
9
+ - Voice replies now finish playing fully before the session ends
6
10
 
7
11
  ## v0.30.22
8
12
 
@@ -1282,7 +1282,11 @@
1282
1282
  ' display: flex; align-items: center; justify-content: center;',
1283
1283
  ' transition: all 0.3s ease;',
1284
1284
  ' margin-bottom: 24px;',
1285
+ ' cursor: pointer;',
1286
+ ' user-select: none;',
1287
+ ' -webkit-tap-highlight-color: transparent;',
1285
1288
  '}',
1289
+ '.cumulus-voice-indicator:active { transform: scale(0.95); }',
1286
1290
  '.cumulus-voice-overlay.listening .cumulus-voice-indicator {',
1287
1291
  ' background: rgba(0, 102, 204, 0.2);',
1288
1292
  ' border: 2px solid #0066cc;',
@@ -4374,6 +4378,7 @@
4374
4378
  audioQueue: [], // queue of Float32Array PCM chunks
4375
4379
  audioPlaying: false, // currently playing audio
4376
4380
  audioDone: false, // server signaled all audio sent
4381
+ micRestartTimer: null, // pending mic restart, cancellable when PCM resumes
4377
4382
  // Voice loading state for Safari compatibility
4378
4383
  voicesLoaded: false,
4379
4384
  speakQueue: [],
@@ -4398,6 +4403,24 @@
4398
4403
  indicator.appendChild(indicatorIcon);
4399
4404
  overlay.appendChild(indicator);
4400
4405
 
4406
+ // Tap-to-interject: clicking the indicator stops TTS and starts listening
4407
+ // immediately, regardless of phase. Cancels any pending mic-restart timer
4408
+ // so the manual action wins over the scheduled auto-restart.
4409
+ indicator.addEventListener('click', function () {
4410
+ if (!state.active) return;
4411
+ if (state.micRestartTimer) {
4412
+ clearTimeout(state.micRestartTimer);
4413
+ state.micRestartTimer = null;
4414
+ }
4415
+ if (state.phase === 'speaking') {
4416
+ stopSpeaking();
4417
+ }
4418
+ if (state.phase !== 'listening') {
4419
+ setPhase('listening');
4420
+ startRecognition();
4421
+ }
4422
+ });
4423
+
4401
4424
  var transcriptEl = document.createElement('div');
4402
4425
  transcriptEl.className = 'cumulus-voice-transcript';
4403
4426
  overlay.appendChild(transcriptEl);
@@ -4501,9 +4524,7 @@
4501
4524
  } else {
4502
4525
  // No speech detected — restart listening
4503
4526
  setPhase('idle');
4504
- setTimeout(function () {
4505
- if (state.active) startRecognition();
4506
- }, 300);
4527
+ scheduleMicRestart(300);
4507
4528
  }
4508
4529
  };
4509
4530
 
@@ -4513,15 +4534,11 @@
4513
4534
  // Normal — just restart
4514
4535
  if (state.active) {
4515
4536
  setPhase('idle');
4516
- setTimeout(function () {
4517
- if (state.active) startRecognition();
4518
- }, 500);
4537
+ scheduleMicRestart(500);
4519
4538
  }
4520
4539
  } else {
4521
4540
  label.textContent = 'Mic error: ' + event.error;
4522
- setTimeout(function () {
4523
- if (state.active) startRecognition();
4524
- }, 1000);
4541
+ scheduleMicRestart(1000);
4525
4542
  }
4526
4543
  };
4527
4544
 
@@ -4700,12 +4717,18 @@
4700
4717
  state.speaking = false;
4701
4718
  if (state.active) {
4702
4719
  setPhase('idle');
4703
- setTimeout(function () {
4704
- if (state.active) startRecognition();
4705
- }, 300);
4720
+ scheduleMicRestart(300);
4706
4721
  }
4707
4722
  }
4708
4723
 
4724
+ function scheduleMicRestart(delay) {
4725
+ if (state.micRestartTimer) clearTimeout(state.micRestartTimer);
4726
+ state.micRestartTimer = setTimeout(function () {
4727
+ state.micRestartTimer = null;
4728
+ if (state.active && state.phase !== 'speaking') startRecognition();
4729
+ }, delay);
4730
+ }
4731
+
4709
4732
  // ── Server-side TTS (Web Audio API playback) ──
4710
4733
  function ensureAudioCtx() {
4711
4734
  if (!state.audioCtx) {
@@ -4721,7 +4744,31 @@
4721
4744
  }
4722
4745
 
4723
4746
  function queuePCMAudio(arrayBuffer) {
4724
- if (!state.active || state.phase === 'listening') return;
4747
+ if (!state.active) return;
4748
+
4749
+ // Server is sending audio — it always wins over the mic. If the widget
4750
+ // transitioned to listening (or back to idle) prematurely, recover by
4751
+ // killing recognition and resuming the speaking phase. Without this,
4752
+ // any subsequent PCM is silently dropped and the mic gets stuck in an
4753
+ // idle/listen flicker loop.
4754
+ if (state.phase !== 'speaking') {
4755
+ console.log('[Voice] PCM arrived in phase=' + state.phase + ', recovering to speaking');
4756
+ if (state.recognition) {
4757
+ try {
4758
+ state.recognition.abort();
4759
+ } catch (e) {}
4760
+ }
4761
+ clearWatchdog();
4762
+ // Cancel any pending mic-restart timer so it can't override us
4763
+ if (state.micRestartTimer) {
4764
+ clearTimeout(state.micRestartTimer);
4765
+ state.micRestartTimer = null;
4766
+ }
4767
+ // Reset audioDone — server is still sending, so a previously-seen
4768
+ // voice_audio_done signal must have been spurious (or stale from a
4769
+ // prior turn). Wait for the next legitimate one.
4770
+ state.audioDone = false;
4771
+ }
4725
4772
 
4726
4773
  // Strip 4-byte "VPCM" header
4727
4774
  var pcmData = new Int16Array(arrayBuffer, 4);
@@ -4785,9 +4832,7 @@
4785
4832
  state.audioDone = false;
4786
4833
  if (state.active) {
4787
4834
  setPhase('idle');
4788
- setTimeout(function () {
4789
- if (state.active) startRecognition();
4790
- }, 300);
4835
+ scheduleMicRestart(300);
4791
4836
  }
4792
4837
  }
4793
4838
 
@@ -4849,6 +4894,10 @@
4849
4894
  clearTimeout(state.voiceLoadTimer);
4850
4895
  state.voiceLoadTimer = null;
4851
4896
  }
4897
+ if (state.micRestartTimer) {
4898
+ clearTimeout(state.micRestartTimer);
4899
+ state.micRestartTimer = null;
4900
+ }
4852
4901
  state.speakQueue.length = 0;
4853
4902
  state.voicesLoaded = false;
4854
4903
  state.waitingForServerTTS = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckydraw/cumulus",
3
- "version": "0.30.24",
3
+ "version": "0.30.26",
4
4
  "description": "RLM-based CLI chat wrapper for Claude with external history context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",