@olares/lares-mobile 0.15.28 → 0.15.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olares/lares-mobile",
3
- "version": "0.15.28",
3
+ "version": "0.15.30",
4
4
  "description": "Lares chat component for LarePass",
5
5
  "type": "module",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "vue": "^3.2.0"
29
29
  },
30
30
  "dependencies": {
31
- "@olares/lares-core": "0.15.28",
31
+ "@olares/lares-core": "0.15.30",
32
32
  "markdown-it": "^14.3.0"
33
33
  }
34
34
  }
package/src/App.vue CHANGED
@@ -730,8 +730,11 @@ export default {
730
730
  this.items = snap.items;
731
731
  this.running = snap.running;
732
732
  this.runningSince = snap.runningSince || 0;
733
- if (this.voiceCall) this.reader.stop();
734
- const target = this.voiceSpeak && !this.voiceCall && !document.hidden ? speakable(utterance, this.voiceStreaming) : null;
733
+ // A paused call is not speaking. Only an open call owns the reply;
734
+ // leaving it set after the keyboard returns is how later answers stay silent.
735
+ const voiceSpeaking = this.voiceCall?.connection === "open";
736
+ if (voiceSpeaking) this.reader.stop();
737
+ const target = this.voiceSpeak && !voiceSpeaking && !document.hidden ? speakable(utterance, this.voiceStreaming) : null;
735
738
  if (target) this.reader.follow({ ...target, sessionId: snap.sessionId, source: { sessionId: snap.sessionId, turn: target.turn, step: target.step, attemptId: target.attemptId }, key: `${snap.sessionId}:${target.key}` });
736
739
  this.failed = snap.failed;
737
740
  this.error = snap.error;
@@ -1546,11 +1549,9 @@ export default {
1546
1549
  const text = this.draft.trim();
1547
1550
  if (!this.canSend) return;
1548
1551
  this.player?.unlock();
1549
- const continuous = this.voicePanelState?.mode === "continuous";
1550
- if (this.voiceSpeak && continuous) {
1551
- try { await this.voicePanel.openReadout(); }
1552
- catch (error) { this.voiceError = error?.code || error?.message || "voice_failed"; }
1553
- } else if (this.voiceCall?.connection === "open") await this.voicePanel.pause();
1552
+ // A typed reply is the chat's. An open voice call would keep the reader
1553
+ // quiet and then not speak this text, so pause it and let the reader take the answer.
1554
+ if (this.voiceCall?.connection === "open") await this.voicePanel.pause();
1554
1555
  this.reader.stop();
1555
1556
  const sessionId = this.sessionId;
1556
1557
  const images = this.draftImages.filter((row) => draftImageStatus(row) === "ready");
@@ -93,6 +93,10 @@
93
93
  </button>
94
94
  </figure>
95
95
  </div>
96
+ <div v-if="playbackChrome" class="lares-composer__playback" role="status">
97
+ <span>{{ t(playbackChrome.status) }}</span>
98
+ <button type="button" @click="stopPlayback">{{ t(playbackChrome.stopLabel) }}</button>
99
+ </div>
96
100
  <div class="lares-composer__line" :data-expanded="expanded ? 'true' : 'false'">
97
101
  <button
98
102
  type="button"
@@ -170,7 +174,7 @@
170
174
 
171
175
  <script>
172
176
  import { messageFromCode } from "@olares/lares-core/i18n/t";
173
- import { voiceCallFailure } from "@olares/lares-core/voice/presentation";
177
+ import { voiceCallFailure, voicePlaybackChrome } from "@olares/lares-core/voice/presentation";
174
178
  import LaresVoiceInput from "./VoiceInput.vue";
175
179
  import {
176
180
  REFERENCE_DEBOUNCE_MS,
@@ -252,6 +256,9 @@ export default {
252
256
  callFailure() {
253
257
  return this.voiceView?.inputActive ? voiceCallFailure(this.voiceView) : null;
254
258
  },
259
+ playbackChrome() {
260
+ return voicePlaybackChrome(this.voiceView);
261
+ },
255
262
  callFailureText() {
256
263
  return this.callFailure ? messageFromCode(this.t, this.callFailure.code, "error.voice_failed") : "";
257
264
  },
@@ -284,6 +291,9 @@ export default {
284
291
  window.removeEventListener("pointerdown", this.onTokenPointer, true);
285
292
  },
286
293
  methods: {
294
+ stopPlayback() {
295
+ this.$emit("voice-action", { action: "stopPlayback" });
296
+ },
287
297
  onInput(event) {
288
298
  const value = event.target.value;
289
299
  this.$emit("update:draft", value);
@@ -703,6 +713,34 @@ export default {
703
713
  stroke-linecap: round;
704
714
  }
705
715
 
716
+ .lares-composer__playback {
717
+ display: flex;
718
+ align-items: center;
719
+ justify-content: space-between;
720
+ gap: 12px;
721
+ padding: 4px 4px 8px;
722
+ color: var(--q-ink-2);
723
+ font-size: 13px;
724
+ line-height: 20px;
725
+ }
726
+
727
+ .lares-composer__playback button {
728
+ flex: none;
729
+ min-height: 32px;
730
+ padding: 4px 12px;
731
+ border: 1px solid var(--q-separator);
732
+ border-radius: 999px;
733
+ background: transparent;
734
+ color: var(--q-ink-1);
735
+ font: inherit;
736
+ font-size: 13px;
737
+ }
738
+
739
+ .lares-composer__playback button:focus-visible {
740
+ outline: 2px solid var(--q-blue-default, #3377ff);
741
+ outline-offset: 2px;
742
+ }
743
+
706
744
  .lares-composer__line {
707
745
  display: flex;
708
746
  width: 100%;