@olares/lares-mobile 0.15.32 → 0.15.34
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 +24 -11
- package/src/desktop/DesktopShell.vue +1 -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.34",
|
|
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.34",
|
|
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
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
</button>
|
|
29
29
|
</li>
|
|
30
30
|
<li
|
|
31
|
-
v-for="{ row, index } in shown(block)"
|
|
31
|
+
v-for="{ row, index, inProcess } in shown(block)"
|
|
32
32
|
:key="row.id || row.key || index"
|
|
33
33
|
class="lares-log__item"
|
|
34
34
|
:data-type="row.type"
|
|
35
|
+
:data-process="inProcess ? 'true' : undefined"
|
|
35
36
|
:data-pending="row.pending ? 'true' : 'false'"
|
|
36
37
|
>
|
|
37
38
|
<div v-if="row.type === 'user'" class="lares-msg" data-role="user">{{ row.text }}</div>
|
|
@@ -77,13 +78,16 @@
|
|
|
77
78
|
<div class="lares-md" v-html="renderMarkdown(row.text)" />
|
|
78
79
|
<p v-if="row.voiceSummary" class="lares-voice-summary">{{ row.voiceSummary }}</p>
|
|
79
80
|
</template>
|
|
80
|
-
<div v-if="hasReadout(row)" role="status" class="lares-message-readout">
|
|
81
|
+
<div v-if="!inProcess && hasReadout(row)" role="status" class="lares-message-readout">
|
|
81
82
|
<span>{{ speechLabel }}</span>
|
|
82
83
|
<button type="button" class="lares-message-stop" @click="readout.stop()">{{ t(readout.status === 'error' ? 'readout.dismiss' : 'readout.stop') }}</button>
|
|
83
84
|
</div>
|
|
84
|
-
<
|
|
85
|
+
<div v-else-if="!inProcess && ownsPlayback(row)" role="status" class="lares-message-readout">
|
|
86
|
+
<span>{{ t(playbackChrome.status) }}</span>
|
|
87
|
+
<button type="button" class="lares-message-stop" @click="$emit('stop-speaking')">{{ t(playbackChrome.stopLabel) }}</button>
|
|
88
|
+
</div>
|
|
85
89
|
<LaresMessageActions
|
|
86
|
-
v-if="!row.pending"
|
|
90
|
+
v-if="!inProcess && !row.pending"
|
|
87
91
|
:text="row.text"
|
|
88
92
|
:reaction="reactions[actionKey(index)] || ''"
|
|
89
93
|
:metrics="row.metrics || null"
|
|
@@ -111,6 +115,7 @@
|
|
|
111
115
|
|
|
112
116
|
<script>
|
|
113
117
|
import { readoutState } from "@olares/lares-core/voice/speech";
|
|
118
|
+
import { voicePlaybackChrome } from "@olares/lares-core/voice/presentation";
|
|
114
119
|
import { partitionPreviews } from "@olares/lares-core/files/preview-groups";
|
|
115
120
|
import { parseAskUserQuestions } from "@olares/lares-core/larepass/questions";
|
|
116
121
|
import { hidesRunningClock, processFoldLabel, transcriptBlocks } from "@olares/lares-core/larepass/process-fold";
|
|
@@ -159,6 +164,7 @@ export default {
|
|
|
159
164
|
},
|
|
160
165
|
computed: {
|
|
161
166
|
speechLabel() { return this.t(`readout.${this.readout?.status}`); },
|
|
167
|
+
playbackChrome() { return voicePlaybackChrome(this.voiceView); },
|
|
162
168
|
runningText() {
|
|
163
169
|
return runningLabel(this.runningSince, this.t, this.now);
|
|
164
170
|
},
|
|
@@ -197,11 +203,12 @@ export default {
|
|
|
197
203
|
const latest = this.items.findLast(item => item.type === "assistant" && item.turn === this.readout.turn);
|
|
198
204
|
return latest && (row.id || row.key) === (latest.id || latest.key);
|
|
199
205
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
206
|
+
ownsPlayback(row) {
|
|
207
|
+
if (!this.playbackChrome || row.type !== "assistant") return false;
|
|
208
|
+
const voiceId = this.voiceView?.snapshot?.voiceId;
|
|
209
|
+
const matched = voiceId && this.items.findLast((item) => item.type === "assistant" && (item.voiceId || item.voicePlayback?.voiceId) === voiceId);
|
|
210
|
+
const target = matched || this.items.findLast((item) => item.type === "assistant");
|
|
211
|
+
return Boolean(target) && (row.id || row.key) === (target.id || target.key);
|
|
205
212
|
},
|
|
206
213
|
syncClock() {
|
|
207
214
|
clearInterval(this.clock);
|
|
@@ -215,8 +222,9 @@ export default {
|
|
|
215
222
|
renderMarkdown,
|
|
216
223
|
thinkSummary,
|
|
217
224
|
shown(block) {
|
|
218
|
-
if (block.kind
|
|
219
|
-
return [
|
|
225
|
+
if (block.kind !== "fold") return [block];
|
|
226
|
+
if (!this.processOpen(block)) return [];
|
|
227
|
+
return block.members.map((member) => ({ ...member, inProcess: true }));
|
|
220
228
|
},
|
|
221
229
|
processOpen(block) {
|
|
222
230
|
return !block.foldable || Boolean(this.openFolds[block.key]);
|
|
@@ -481,6 +489,11 @@ export default {
|
|
|
481
489
|
margin-top: 8px;
|
|
482
490
|
}
|
|
483
491
|
|
|
492
|
+
.lares-log__item[data-process="true"] {
|
|
493
|
+
margin-top: 0;
|
|
494
|
+
gap: 0;
|
|
495
|
+
}
|
|
496
|
+
|
|
484
497
|
.lares-msg[data-role="user"] {
|
|
485
498
|
align-self: flex-end;
|
|
486
499
|
max-width: 86%;
|
|
@@ -226,6 +226,7 @@ export default {
|
|
|
226
226
|
.desktop-shell :deep(.lares-log > *) { box-sizing:border-box; width:var(--lares-column); padding-inline:var(--lares-gutter); }
|
|
227
227
|
.desktop-shell :deep(.lares-log__item[data-type="user"]) { margin-top:18px; }
|
|
228
228
|
.desktop-shell :deep(.lares-log__item[data-type="assistant"]) { margin-top:12px; }
|
|
229
|
+
.desktop-shell :deep(.lares-log__item[data-process="true"]) { margin-top:0; }
|
|
229
230
|
.desktop-shell :deep(.lares-msg[data-role="user"]) { max-width:68%; border-radius:16px; padding:10px 14px; }
|
|
230
231
|
.desktop-shell :deep(.lares-md) { font-size:15px; line-height:1.65; }
|
|
231
232
|
@media (max-width:760px) {
|