@olares/lares-mobile 0.15.29 → 0.15.31
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 +2 -2
- package/src/App.vue +33 -2
- package/src/chat/Composer.vue +2 -9
- package/src/settings/TtsSettings.vue +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olares/lares-mobile",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.31",
|
|
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.
|
|
31
|
+
"@olares/lares-core": "0.15.31",
|
|
32
32
|
"markdown-it": "^14.3.0"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/App.vue
CHANGED
|
@@ -127,6 +127,7 @@ import { createVoiceConversation } from "@olares/lares-core/voice/conversation";
|
|
|
127
127
|
import { createUnifiedConversation } from "@olares/lares-core/voice/unified-conversation";
|
|
128
128
|
import { createAnswerPlayer } from "./chat/playback.js";
|
|
129
129
|
import { createReplySpeaker, createSentencePlayer, readoutState, speakable } from "@olares/lares-core/voice/speech";
|
|
130
|
+
import { createScreenWakeLock } from "@olares/lares-core/voice/wake-lock";
|
|
130
131
|
import { prepareBrowserCapture } from "@olares/lares-core/voice/browser-capture";
|
|
131
132
|
import { playAudioStream, createStreamingCapture } from "@olares/lares-core/voice/stream-client";
|
|
132
133
|
import { voiceHeaders, wavBlob } from "@olares/lares-core/voice/client";
|
|
@@ -561,6 +562,9 @@ export default {
|
|
|
561
562
|
this.intake = new FileIntake((file, sessionId, options) => this.runtime.upload(file, options, sessionId));
|
|
562
563
|
this.imageUploadControllers = new Map();
|
|
563
564
|
this.player = createAnswerPlayer();
|
|
565
|
+
// A locked screen suspends the WebView and cuts a long answer short; hold
|
|
566
|
+
// the screen for as long as a readout is actually playing.
|
|
567
|
+
this.readoutWake = createScreenWakeLock();
|
|
564
568
|
this.voicePanel = createVoicePanel({
|
|
565
569
|
armOutput: () => this.player?.unlock(),
|
|
566
570
|
create: (options) => this.createMobileVoiceConversation(options),
|
|
@@ -607,6 +611,7 @@ export default {
|
|
|
607
611
|
halt: () => this.player?.stop(),
|
|
608
612
|
silence: (source) => void this.runtime.interruptSpeech?.(source),
|
|
609
613
|
fail: (err) => this.showSpeakProblem(err),
|
|
614
|
+
onState: (active) => this.setReadoutAwake(active),
|
|
610
615
|
});
|
|
611
616
|
|
|
612
617
|
this.bindIntake(this.sessionId);
|
|
@@ -624,12 +629,30 @@ export default {
|
|
|
624
629
|
this.conversationUnsub = subscribeConversationSettings((section) => {
|
|
625
630
|
this.busyEnter = section.busyEnter;
|
|
626
631
|
});
|
|
632
|
+
this.voiceReadoutPaused = false;
|
|
627
633
|
this.voiceVisibilityHandler = () => {
|
|
628
|
-
if (document.hidden) {
|
|
634
|
+
if (document.hidden) {
|
|
635
|
+
// A locked screen or an app switch is a pause, not the end of the
|
|
636
|
+
// answer: stopping here marks the utterance read and it never resumes.
|
|
637
|
+
if (this.reader.canPause && this.reader.pause()) this.voiceReadoutPaused = true;
|
|
638
|
+
void this.voicePanel.pause().catch(() => {});
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (this.readoutActive) void this.readoutWake?.hold();
|
|
642
|
+
if (this.voiceReadoutPaused) { this.voiceReadoutPaused = false; this.reader.resume(); }
|
|
643
|
+
};
|
|
644
|
+
this.voicePageHideHandler = () => {
|
|
645
|
+
if (this.reader.canPause && this.reader.pause()) this.voiceReadoutPaused = true;
|
|
646
|
+
void this.voicePanel.pause().catch(() => {});
|
|
647
|
+
};
|
|
648
|
+
this.voicePageShowHandler = () => {
|
|
649
|
+
if (this.voiceReadoutPaused) { this.voiceReadoutPaused = false; this.reader.resume(); }
|
|
629
650
|
};
|
|
630
651
|
this.voiceOfflineHandler = () => { this.reader.stop(); void this.voicePanel.pause().catch(() => {}); };
|
|
631
652
|
window.addEventListener("offline", this.voiceOfflineHandler);
|
|
632
653
|
document.addEventListener("visibilitychange", this.voiceVisibilityHandler);
|
|
654
|
+
window.addEventListener("pagehide", this.voicePageHideHandler);
|
|
655
|
+
window.addEventListener("pageshow", this.voicePageShowHandler);
|
|
633
656
|
window.addEventListener("keydown", this.onVoiceShortcut, true);
|
|
634
657
|
this.voiceClock = setInterval(() => {
|
|
635
658
|
if (this.voicePhase === "recording") this.voiceElapsed += 1;
|
|
@@ -647,6 +670,9 @@ export default {
|
|
|
647
670
|
beforeUnmount() {
|
|
648
671
|
window.removeEventListener("offline", this.voiceOfflineHandler);
|
|
649
672
|
document.removeEventListener("visibilitychange", this.voiceVisibilityHandler);
|
|
673
|
+
window.removeEventListener("pagehide", this.voicePageHideHandler);
|
|
674
|
+
window.removeEventListener("pageshow", this.voicePageShowHandler);
|
|
675
|
+
this.readoutWake?.release();
|
|
650
676
|
window.removeEventListener("keydown", this.onVoiceShortcut, true);
|
|
651
677
|
clearInterval(this.voiceClock);
|
|
652
678
|
this.captureLog();
|
|
@@ -1340,7 +1366,7 @@ export default {
|
|
|
1340
1366
|
send: (value) => conversation.speak(value),
|
|
1341
1367
|
play: (sentence) => sentence.bluetooth === "played" ? Promise.resolve() : this.player.play(wavBlob(sentence.audio)),
|
|
1342
1368
|
halt: () => this.player.stop(), silence: () => {},
|
|
1343
|
-
onState: (playing) => conversation?.playbackState(playing),
|
|
1369
|
+
onState: (playing) => { conversation?.playbackState(playing); this.setReadoutAwake(playing); },
|
|
1344
1370
|
onOutput: (value) => readoutState.publish(value?.status === "error" ? null : value),
|
|
1345
1371
|
fail: (error) => { void conversation.controller.disconnect(error?.code || error?.message || "voice_playback_failed"); },
|
|
1346
1372
|
});
|
|
@@ -1368,6 +1394,11 @@ export default {
|
|
|
1368
1394
|
onError: (error) => { this.voiceError = error.message; },
|
|
1369
1395
|
});
|
|
1370
1396
|
},
|
|
1397
|
+
setReadoutAwake(active) {
|
|
1398
|
+
this.readoutActive = Boolean(active);
|
|
1399
|
+
if (this.readoutActive) void this.readoutWake?.hold();
|
|
1400
|
+
else this.readoutWake?.release();
|
|
1401
|
+
},
|
|
1371
1402
|
showSpeakProblem(err) {
|
|
1372
1403
|
this.speakNotice = speakProblemText(this.t, err);
|
|
1373
1404
|
},
|
package/src/chat/Composer.vue
CHANGED
|
@@ -175,7 +175,6 @@
|
|
|
175
175
|
<script>
|
|
176
176
|
import { messageFromCode } from "@olares/lares-core/i18n/t";
|
|
177
177
|
import { voiceCallFailure, voicePlaybackChrome } from "@olares/lares-core/voice/presentation";
|
|
178
|
-
import { readoutState } from "@olares/lares-core/voice/speech";
|
|
179
178
|
import LaresVoiceInput from "./VoiceInput.vue";
|
|
180
179
|
import {
|
|
181
180
|
REFERENCE_DEBOUNCE_MS,
|
|
@@ -219,8 +218,6 @@ export default {
|
|
|
219
218
|
],
|
|
220
219
|
data() {
|
|
221
220
|
return {
|
|
222
|
-
readout: null,
|
|
223
|
-
readoutUnsub: null,
|
|
224
221
|
inputMultiline: false,
|
|
225
222
|
inputWidth: 0,
|
|
226
223
|
inputResizeObserver: null,
|
|
@@ -260,7 +257,7 @@ export default {
|
|
|
260
257
|
return this.voiceView?.inputActive ? voiceCallFailure(this.voiceView) : null;
|
|
261
258
|
},
|
|
262
259
|
playbackChrome() {
|
|
263
|
-
return voicePlaybackChrome(this.voiceView
|
|
260
|
+
return voicePlaybackChrome(this.voiceView);
|
|
264
261
|
},
|
|
265
262
|
callFailureText() {
|
|
266
263
|
return this.callFailure ? messageFromCode(this.t, this.callFailure.code, "error.voice_failed") : "";
|
|
@@ -279,8 +276,6 @@ export default {
|
|
|
279
276
|
},
|
|
280
277
|
},
|
|
281
278
|
mounted() {
|
|
282
|
-
this.readout = readoutState.getSnapshot();
|
|
283
|
-
this.readoutUnsub = readoutState.subscribe(() => { this.readout = readoutState.getSnapshot(); });
|
|
284
279
|
this.autosize();
|
|
285
280
|
this.inputResizeObserver = new ResizeObserver(([entry]) => {
|
|
286
281
|
const width = entry.contentRect.width;
|
|
@@ -291,15 +286,13 @@ export default {
|
|
|
291
286
|
this.inputResizeObserver.observe(this.$refs.input);
|
|
292
287
|
},
|
|
293
288
|
beforeUnmount() {
|
|
294
|
-
this.readoutUnsub?.();
|
|
295
289
|
this.inputResizeObserver?.disconnect();
|
|
296
290
|
window.clearTimeout(this.referenceTimer);
|
|
297
291
|
window.removeEventListener("pointerdown", this.onTokenPointer, true);
|
|
298
292
|
},
|
|
299
293
|
methods: {
|
|
300
294
|
stopPlayback() {
|
|
301
|
-
|
|
302
|
-
else this.$emit("voice-action", { action: "stopPlayback" });
|
|
295
|
+
this.$emit("voice-action", { action: "stopPlayback" });
|
|
303
296
|
},
|
|
304
297
|
onInput(event) {
|
|
305
298
|
const value = event.target.value;
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
<p v-if="view.errors.ttsModel" class="lares-tts__alert" role="alert">{{ t('settings.saveFailed') }}</p>
|
|
18
18
|
<p v-if="!state.model" class="lares-tts__hint">{{ t('preferences.modelsMissing') }}</p>
|
|
19
19
|
<p v-else-if="state.loading" class="lares-tts__hint" role="status">{{ t('tts.loading') }}</p>
|
|
20
|
+
<div v-if="state.stale" class="lares-tts__status" role="status">
|
|
21
|
+
<p>{{ t('tts.stale') }}</p>
|
|
22
|
+
<button type="button" @click="controller.load()">{{ t('dictation.retry') }}</button>
|
|
23
|
+
</div>
|
|
20
24
|
<div v-else-if="state.error" class="lares-tts__status" role="alert">
|
|
21
25
|
<p>{{ t('tts.loadFailed') }}</p>
|
|
22
26
|
<button type="button" @click="controller.load()">{{ t('dictation.retry') }}</button>
|