@olares/lares-mobile 0.15.32 → 0.15.33
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 +15 -8
- package/src/chat/Composer.vue +4 -39
- package/src/chat/Transcript.vue +12 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olares/lares-mobile",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.33",
|
|
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.33",
|
|
32
32
|
"markdown-it": "^14.3.0"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/App.vue
CHANGED
|
@@ -1327,20 +1327,27 @@ export default {
|
|
|
1327
1327
|
const view = this.voicePanel.getSnapshot();
|
|
1328
1328
|
if (view.busy) return;
|
|
1329
1329
|
if (view.visible && view.mode === "single") { if (view.snapshot?.capture === "recording") await this.voicePanel.finish().catch(() => {}); return; }
|
|
1330
|
-
await this.voicePanel.pause().catch(() => {});
|
|
1331
1330
|
await this.voicePanel.open({ mode: "single", sessionId: this.sessionId || null, preset: preset && preset !== "default" ? preset : undefined, configSnapshot: this.voiceSettings.config }).catch(() => {});
|
|
1332
1331
|
},
|
|
1333
|
-
|
|
1332
|
+
onHoldStart() {
|
|
1334
1333
|
this.voicePanel.arm();
|
|
1335
1334
|
if (this.voicePanelState?.mode === "single" && this.voicePanelState.raw) {
|
|
1336
|
-
|
|
1337
|
-
return;
|
|
1335
|
+
return this.voicePanel.open({ mode: "single", sessionId: this.sessionId || null });
|
|
1338
1336
|
}
|
|
1337
|
+
const settings = this.voiceSettings;
|
|
1338
|
+
if (!settings) return this.openVoiceFromSettings();
|
|
1339
|
+
this.voiceDefaultMode = settings.config.mobileMode;
|
|
1340
|
+
const available = laresVoiceAvailability(settings, !this.modelBlocked);
|
|
1341
|
+
if (!(this.voiceDefaultMode === "single" ? available.dictation : available.conversation)) { this.voiceError = "voice_model_unavailable"; return; }
|
|
1342
|
+
return this.voicePanel.open({ mode: this.voiceDefaultMode, sessionId: this.sessionId || null, configSnapshot: settings.config });
|
|
1343
|
+
},
|
|
1344
|
+
async openVoiceFromSettings() {
|
|
1339
1345
|
const settings = await this.runtime.settings.voice(false, { diagnostics: false });
|
|
1346
|
+
this.voiceSettings = markRaw(settings);
|
|
1340
1347
|
this.voiceDefaultMode = settings.config.mobileMode;
|
|
1341
1348
|
const available = laresVoiceAvailability(settings, !this.modelBlocked);
|
|
1342
1349
|
if (!(this.voiceDefaultMode === "single" ? available.dictation : available.conversation)) { this.voiceError = "voice_model_unavailable"; return; }
|
|
1343
|
-
|
|
1350
|
+
return this.voicePanel.open({ mode: this.voiceDefaultMode, sessionId: this.sessionId || null, configSnapshot: settings.config });
|
|
1344
1351
|
},
|
|
1345
1352
|
onVoiceAction({ action, value }) {
|
|
1346
1353
|
if (action === "start" || action === "end" || action === "close") this.speakNotice = "";
|
|
@@ -1350,7 +1357,7 @@ export default {
|
|
|
1350
1357
|
if (["holdStart", "holdEnd", "finish", "start", "close", "end"].includes(action)) void Promise.resolve(this.voicePanel[action](value)).catch(() => {});
|
|
1351
1358
|
},
|
|
1352
1359
|
runVoicePanel(action) { void this.voicePanel[action]().catch(() => {}); },
|
|
1353
|
-
async createMobileVoiceConversation({ mode, onTranscript, onDevice, config }) {
|
|
1360
|
+
async createMobileVoiceConversation({ mode, onTranscript, onDevice, config, preparation }) {
|
|
1354
1361
|
config = laresVoiceConfig(config, mode);
|
|
1355
1362
|
const settings = { config };
|
|
1356
1363
|
const continuous = mode === "continuous";
|
|
@@ -1362,7 +1369,7 @@ export default {
|
|
|
1362
1369
|
return createUnifiedConversation({ request: (path, options) => this.runtime.voiceRequest(path, options), socket: (ticket) => this.runtime.voiceSocket(ticket),
|
|
1363
1370
|
device: captureSource("mobile", config.device), readAloud: continuous && config.speak, preset: config.preset, presentationSession: true,
|
|
1364
1371
|
endpointSilenceMs: config.endpointSilenceMs, idleTimeoutMs: config.idleTimeoutMs, recoveryTimeoutMs: config.recoveryTimeoutMs,
|
|
1365
|
-
onTranscript: continuous ? null : onTranscript, mode: () => resolveSubmitMode(this.running, false, true, this.busyEnter),
|
|
1372
|
+
onTranscript: continuous ? null : onTranscript, mode: () => resolveSubmitMode(this.running, false, true, this.busyEnter), preparation,
|
|
1366
1373
|
transcribe: (blob, signal) => this.runtime.transcribe(blob, config.language, signal, config.model) });
|
|
1367
1374
|
}
|
|
1368
1375
|
let conversation;
|
|
@@ -1384,7 +1391,7 @@ export default {
|
|
|
1384
1391
|
readAloud: continuous && settings.config.speak,
|
|
1385
1392
|
preset: settings.config.preset, backend: settings.config.backend, endpointSilenceMs: settings.config.endpointSilenceMs, idleTimeoutMs: settings.config.idleTimeoutMs, recoveryTimeoutMs: settings.config.recoveryTimeoutMs,
|
|
1386
1393
|
presentationSession: true,
|
|
1387
|
-
onTranscript: continuous ? null : onTranscript,
|
|
1394
|
+
onTranscript: continuous ? null : onTranscript, preparation,
|
|
1388
1395
|
mode: () => resolveSubmitMode(this.running, false, true, this.busyEnter),
|
|
1389
1396
|
playback: { get canPause() { return reader.canPause; }, get key() { return reader.key; }, pause: () => reader.pause(), resume: () => reader.resume(), stop: () => { reader.stop(); this.player.stop(); } },
|
|
1390
1397
|
onReply: (reply) => { const value = speakable(reply, config.streaming === true); if (value) reader.follow(value); },
|
package/src/chat/Composer.vue
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
<p>{{ t("settings.readAloudSyncFailed") }}</p>
|
|
10
10
|
<button type="button" @click="$emit('voice-action', { action: 'retryReadAloud' })">{{ t("dictation.retry") }}</button>
|
|
11
11
|
</div>
|
|
12
|
+
<div v-else-if="voiceError && !voiceView?.inputActive" class="lares-composer__alert" role="alert">
|
|
13
|
+
<p>{{ voiceError }}</p>
|
|
14
|
+
</div>
|
|
12
15
|
<div
|
|
13
16
|
class="lares-composer__card"
|
|
14
17
|
data-composer-card
|
|
@@ -93,10 +96,6 @@
|
|
|
93
96
|
</button>
|
|
94
97
|
</figure>
|
|
95
98
|
</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>
|
|
100
99
|
<div class="lares-composer__line" :data-expanded="expanded ? 'true' : 'false'">
|
|
101
100
|
<button
|
|
102
101
|
type="button"
|
|
@@ -174,7 +173,7 @@
|
|
|
174
173
|
|
|
175
174
|
<script>
|
|
176
175
|
import { messageFromCode } from "@olares/lares-core/i18n/t";
|
|
177
|
-
import { voiceCallFailure
|
|
176
|
+
import { voiceCallFailure } from "@olares/lares-core/voice/presentation";
|
|
178
177
|
import LaresVoiceInput from "./VoiceInput.vue";
|
|
179
178
|
import {
|
|
180
179
|
REFERENCE_DEBOUNCE_MS,
|
|
@@ -256,9 +255,6 @@ export default {
|
|
|
256
255
|
callFailure() {
|
|
257
256
|
return this.voiceView?.inputActive ? voiceCallFailure(this.voiceView) : null;
|
|
258
257
|
},
|
|
259
|
-
playbackChrome() {
|
|
260
|
-
return voicePlaybackChrome(this.voiceView);
|
|
261
|
-
},
|
|
262
258
|
callFailureText() {
|
|
263
259
|
return this.callFailure ? messageFromCode(this.t, this.callFailure.code, "error.voice_failed") : "";
|
|
264
260
|
},
|
|
@@ -291,9 +287,6 @@ export default {
|
|
|
291
287
|
window.removeEventListener("pointerdown", this.onTokenPointer, true);
|
|
292
288
|
},
|
|
293
289
|
methods: {
|
|
294
|
-
stopPlayback() {
|
|
295
|
-
this.$emit("voice-action", { action: "stopPlayback" });
|
|
296
|
-
},
|
|
297
290
|
onInput(event) {
|
|
298
291
|
const value = event.target.value;
|
|
299
292
|
this.$emit("update:draft", value);
|
|
@@ -713,34 +706,6 @@ export default {
|
|
|
713
706
|
stroke-linecap: round;
|
|
714
707
|
}
|
|
715
708
|
|
|
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
|
-
|
|
744
709
|
.lares-composer__line {
|
|
745
710
|
display: flex;
|
|
746
711
|
width: 100%;
|
package/src/chat/Transcript.vue
CHANGED
|
@@ -81,7 +81,10 @@
|
|
|
81
81
|
<span>{{ speechLabel }}</span>
|
|
82
82
|
<button type="button" class="lares-message-stop" @click="readout.stop()">{{ t(readout.status === 'error' ? 'readout.dismiss' : 'readout.stop') }}</button>
|
|
83
83
|
</div>
|
|
84
|
-
<
|
|
84
|
+
<div v-else-if="ownsPlayback(row)" role="status" class="lares-message-readout">
|
|
85
|
+
<span>{{ t(playbackChrome.status) }}</span>
|
|
86
|
+
<button type="button" class="lares-message-stop" @click="$emit('stop-speaking')">{{ t(playbackChrome.stopLabel) }}</button>
|
|
87
|
+
</div>
|
|
85
88
|
<LaresMessageActions
|
|
86
89
|
v-if="!row.pending"
|
|
87
90
|
:text="row.text"
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
|
|
112
115
|
<script>
|
|
113
116
|
import { readoutState } from "@olares/lares-core/voice/speech";
|
|
117
|
+
import { voicePlaybackChrome } from "@olares/lares-core/voice/presentation";
|
|
114
118
|
import { partitionPreviews } from "@olares/lares-core/files/preview-groups";
|
|
115
119
|
import { parseAskUserQuestions } from "@olares/lares-core/larepass/questions";
|
|
116
120
|
import { hidesRunningClock, processFoldLabel, transcriptBlocks } from "@olares/lares-core/larepass/process-fold";
|
|
@@ -159,6 +163,7 @@ export default {
|
|
|
159
163
|
},
|
|
160
164
|
computed: {
|
|
161
165
|
speechLabel() { return this.t(`readout.${this.readout?.status}`); },
|
|
166
|
+
playbackChrome() { return voicePlaybackChrome(this.voiceView); },
|
|
162
167
|
runningText() {
|
|
163
168
|
return runningLabel(this.runningSince, this.t, this.now);
|
|
164
169
|
},
|
|
@@ -197,11 +202,12 @@ export default {
|
|
|
197
202
|
const latest = this.items.findLast(item => item.type === "assistant" && item.turn === this.readout.turn);
|
|
198
203
|
return latest && (row.id || row.key) === (latest.id || latest.key);
|
|
199
204
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
+
ownsPlayback(row) {
|
|
206
|
+
if (!this.playbackChrome || row.type !== "assistant") return false;
|
|
207
|
+
const voiceId = this.voiceView?.snapshot?.voiceId;
|
|
208
|
+
const matched = voiceId && this.items.findLast((item) => item.type === "assistant" && (item.voiceId || item.voicePlayback?.voiceId) === voiceId);
|
|
209
|
+
const target = matched || this.items.findLast((item) => item.type === "assistant");
|
|
210
|
+
return Boolean(target) && (row.id || row.key) === (target.id || target.key);
|
|
205
211
|
},
|
|
206
212
|
syncClock() {
|
|
207
213
|
clearInterval(this.clock);
|