@olares/lares-mobile 0.15.24 → 0.15.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/package.json +2 -2
- package/src/App.vue +18 -4
- package/src/chat/ChatBar.vue +4 -0
- package/src/chat/Transcript.vue +11 -12
- package/src/chat/VoiceInput.vue +0 -5
- package/src/desktop/DesktopQueue.vue +1 -3
- package/src/desktop/DesktopShell.vue +1 -0
- package/src/host.js +2 -0
- package/src/mobile/MobileShell.vue +27 -1
- package/src/motion.css +16 -0
- package/src/settings/AgentSettings.vue +206 -36
- package/src/settings/Select.vue +7 -1
- package/src/settings/Sheet.vue +19 -0
- package/src/settings/TtsSettings.vue +260 -39
- package/src/settings/VoiceKeys.vue +267 -0
- package/src/settings/VoicePreferences.vue +80 -32
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div v-if="view.value" class="lares-voice-preferences">
|
|
3
|
-
<label
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
</select>
|
|
3
|
+
<label v-if="device === 'desktop'" class="lares-voice-preferences__field">
|
|
4
|
+
<span>{{ t('preferences.dictationStyle') }}</span>
|
|
5
|
+
<LaresSelect :value="view.value.config.dictationPreset" :sections="presetSections" :disabled="guardView.selecting || Boolean(view.pending.dictationPreset)" @select="choosePreset" />
|
|
7
6
|
</label>
|
|
7
|
+
<LaresSettingRow
|
|
8
|
+
v-else
|
|
9
|
+
:label="t('preferences.dictationStyle')"
|
|
10
|
+
:value="t(`dictation.${view.value.config.dictationPreset}`)"
|
|
11
|
+
chevron
|
|
12
|
+
boxed
|
|
13
|
+
:disabled="guardView.selecting || Boolean(view.pending.dictationPreset)"
|
|
14
|
+
@click="sheet = 'preset'"
|
|
15
|
+
/>
|
|
8
16
|
<p v-if="view.errors.dictationPreset" role="alert">{{ t('settings.saveFailed') }}</p>
|
|
9
17
|
<p v-if="view.value.config.dictationPreset === 'raw'">{{ t('dictation.noLlm') }}</p>
|
|
10
18
|
<div v-else class="lares-voice-preferences__editor">
|
|
11
|
-
<label
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
<option v-for="item in models" :key="item.id" :value="item.id" :disabled="item.disabled">{{ item.label }}</option>
|
|
15
|
-
</select>
|
|
19
|
+
<label v-if="device === 'desktop'" class="lares-voice-preferences__field">
|
|
20
|
+
<span>{{ t('dictation.model') }}</span>
|
|
21
|
+
<LaresSelect :value="profile.model" :sections="modelSections" :disabled="profilePending" @select="chooseModel" />
|
|
16
22
|
</label>
|
|
17
|
-
<
|
|
23
|
+
<LaresSettingRow
|
|
24
|
+
v-else
|
|
25
|
+
:label="t('dictation.model')"
|
|
26
|
+
:value="modelLabel"
|
|
27
|
+
chevron
|
|
28
|
+
boxed
|
|
29
|
+
:disabled="profilePending"
|
|
30
|
+
@click="sheet = 'model'"
|
|
31
|
+
/>
|
|
32
|
+
<label class="lares-voice-preferences__field">
|
|
33
|
+
<span>{{ t('dictation.instructions') }}</span>
|
|
18
34
|
<textarea ref="instructions" :value="profile.instructions" :disabled="profilePending" rows="6" maxlength="8000" @input="preferences.edit(view.value.config.dictationPreset, { instructions: $event.target.value })" />
|
|
19
35
|
</label>
|
|
20
36
|
<div class="lares-voice-preferences__actions">
|
|
21
37
|
<button type="button" :disabled="profilePending" @click="preferences.restore(view.value.config.dictationPreset)">{{ t('dictation.reset') }}</button>
|
|
22
38
|
<template v-if="preferences.dirty(view.value.config.dictationPreset)">
|
|
23
39
|
<button type="button" :disabled="profilePending" @click="preferences.discard([view.value.config.dictationPreset])">{{ t('preferences.cancel') }}</button>
|
|
24
|
-
<button type="button" :disabled="profilePending" @click="saveProfile">{{ t(profilePending ? 'preferences.saving' : 'preferences.save') }}</button>
|
|
40
|
+
<button type="button" class="is-primary" :disabled="profilePending" @click="saveProfile">{{ t(profilePending ? 'preferences.saving' : 'preferences.save') }}</button>
|
|
25
41
|
</template>
|
|
26
42
|
</div>
|
|
27
43
|
<p v-if="view.errors[`profile:${view.value.config.dictationPreset}`]" role="alert">{{ t('settings.saveFailed') }}</p>
|
|
28
44
|
</div>
|
|
29
|
-
<TtsSettings :preferences="preferences" :view="view" :t="t" :fetcher="ttsFetch" />
|
|
45
|
+
<TtsSettings :preferences="preferences" :view="view" :t="t" :fetcher="ttsFetch" :device="device" />
|
|
30
46
|
<section v-if="device === 'desktop'" class="lares-voice-preferences__shortcuts">
|
|
31
47
|
<h3>{{ t('preferences.shortcuts') }}</h3>
|
|
32
48
|
<p>{{ t('preferences.shortcutScope') }}</p>
|
|
@@ -50,9 +66,19 @@
|
|
|
50
66
|
<p v-if="guardView.error" role="alert">{{ t('settings.saveFailed') }}</p>
|
|
51
67
|
<button type="button" :disabled="guardView.saving" @click="guard.answer('continue')">{{ t('preferences.continue') }}</button>
|
|
52
68
|
<button type="button" :disabled="guardView.saving" @click="guard.answer('discard')">{{ t('preferences.discard') }}</button>
|
|
53
|
-
<button type="button" :disabled="guardView.saving" @click="guard.answer('save')">{{ t(guardView.saving ? 'preferences.saving' : 'preferences.save') }}</button>
|
|
69
|
+
<button type="button" class="is-primary" :disabled="guardView.saving" @click="guard.answer('save')">{{ t(guardView.saving ? 'preferences.saving' : 'preferences.save') }}</button>
|
|
54
70
|
</div>
|
|
55
71
|
</LaresSheet>
|
|
72
|
+
<LaresSheet v-if="device !== 'desktop'" :open="Boolean(sheet)" :title="sheetTitle" @close="sheet = ''">
|
|
73
|
+
<LaresSettingRow
|
|
74
|
+
v-for="item in sheetItems"
|
|
75
|
+
:key="item.id || 'none'"
|
|
76
|
+
:label="item.label"
|
|
77
|
+
:checked="item.id === sheetCurrent"
|
|
78
|
+
:disabled="item.disabled || (sheet === 'preset' ? guardView.selecting : profilePending)"
|
|
79
|
+
@click="chooseSheet(item)"
|
|
80
|
+
/>
|
|
81
|
+
</LaresSheet>
|
|
56
82
|
</div>
|
|
57
83
|
<p v-else role="status">{{ t(view.loadError ? 'settings.loadFailed' : 'settings.loading') }}</p>
|
|
58
84
|
</template>
|
|
@@ -63,10 +89,12 @@ import { createVoicePreferences, createPreferencesGuard } from "@olares/lares-co
|
|
|
63
89
|
import { createVoiceShortcutRecorder, formatVoiceShortcut } from "@olares/lares-core/voice/shortcuts";
|
|
64
90
|
import { DICTATION_PRESETS } from "@olares/lares-core/voice/dictation";
|
|
65
91
|
import LaresSheet from "./Sheet.vue";
|
|
92
|
+
import LaresSettingRow from "./SettingRow.vue";
|
|
93
|
+
import LaresSelect from "./Select.vue";
|
|
66
94
|
import TtsSettings from "./TtsSettings.vue";
|
|
67
95
|
|
|
68
96
|
export default {
|
|
69
|
-
components: { LaresSheet, TtsSettings },
|
|
97
|
+
components: { LaresSheet, LaresSettingRow, LaresSelect, TtsSettings },
|
|
70
98
|
props: { ttsFetch: { type: Function, required: true }, settings: { type: Object, required: true }, snapshot: { type: Object, default: null }, device: { type: String, default: "mobile" }, t: { type: Function, required: true }, bindBeforeLeave: { type: Function, default: undefined } },
|
|
71
99
|
expose: ["beforeLeave", "reload", "patch"],
|
|
72
100
|
emits: ["change"],
|
|
@@ -76,10 +104,16 @@ export default {
|
|
|
76
104
|
if (this.snapshot) preferences.accept(toRaw(this.snapshot));
|
|
77
105
|
const guard = markRaw(createPreferencesGuard(preferences));
|
|
78
106
|
const recorder = markRaw(createVoiceShortcutRecorder({ current: () => preferences.getSnapshot().value.config.dictationShortcuts, save: (id, key) => preferences.saveShortcut(id, key) }));
|
|
79
|
-
return { preferences, guard, recorder, view: preferences.getSnapshot(), guardView: guard.getSnapshot(), shortcut: recorder.getSnapshot(), bindings: markRaw({}), presets: DICTATION_PRESETS, shortcutIds: ["default", ...DICTATION_PRESETS] };
|
|
107
|
+
return { preferences, guard, recorder, view: preferences.getSnapshot(), guardView: guard.getSnapshot(), shortcut: recorder.getSnapshot(), bindings: markRaw({}), presets: DICTATION_PRESETS, shortcutIds: ["default", ...DICTATION_PRESETS], sheet: "" };
|
|
80
108
|
},
|
|
81
109
|
computed: {
|
|
82
110
|
profile() { return this.view.value && this.view.value.config.dictationPreset !== "raw" ? this.preferences.profile(this.view.value.config.dictationPreset) : null; },
|
|
111
|
+
presetSections() { return [{ items: this.presets.map(id => ({ id, label: this.t(`dictation.${id}`) })) }]; },
|
|
112
|
+
modelSections() { return [{ items: [{ id: "", label: this.t("settings.model.none") }, ...this.models.map(item => ({ id: item.id, label: item.label, disabled: item.disabled }))] }]; },
|
|
113
|
+
modelLabel() { return this.modelSections[0].items.find(item => item.id === (this.profile?.model || ""))?.label || this.t("settings.model.none"); },
|
|
114
|
+
sheetTitle() { return this.t(this.sheet === "model" ? "dictation.model" : "preferences.dictationStyle"); },
|
|
115
|
+
sheetItems() { return this.sheet === "model" ? this.modelSections[0].items : this.presetSections[0].items; },
|
|
116
|
+
sheetCurrent() { return this.sheet === "model" ? (this.profile?.model || "") : this.view.value?.config.dictationPreset; },
|
|
83
117
|
profilePending() { return this.guardView.selecting || Boolean(this.view.pending[`profile:${this.view.value.config.dictationPreset}`]); },
|
|
84
118
|
models() {
|
|
85
119
|
const available = this.view.value?.chatModels || [];
|
|
@@ -117,10 +151,14 @@ export default {
|
|
|
117
151
|
beforeLeave() { return this.guard.request(); },
|
|
118
152
|
async patch(partial) { if (!this.view.value) await this.preferences.load(); return this.preferences.patch(partial); },
|
|
119
153
|
reload() { return this.preferences.load(true); },
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
choosePreset(item) { void this.guard.selectPreset(item.id).catch(() => {}); },
|
|
155
|
+
chooseModel(item) { if (!item.disabled) this.preferences.edit(this.view.value.config.dictationPreset, { model: item.id }); },
|
|
156
|
+
chooseSheet(item) {
|
|
157
|
+
if (item.disabled) return;
|
|
158
|
+
const sheet = this.sheet;
|
|
159
|
+
this.sheet = "";
|
|
160
|
+
if (sheet === "model") this.chooseModel(item);
|
|
161
|
+
else this.choosePreset(item);
|
|
124
162
|
},
|
|
125
163
|
async saveProfile() { try { await this.preferences.saveProfile(this.view.value.config.dictationPreset); this.$refs.instructions?.focus(); } catch { /* The shared store retains the draft and error. */ } },
|
|
126
164
|
shortcutTitle(id) { return this.t(id === "default" ? "dictation.shortcut" : `dictation.${id}`); },
|
|
@@ -135,19 +173,29 @@ export default {
|
|
|
135
173
|
</script>
|
|
136
174
|
|
|
137
175
|
<style scoped>
|
|
138
|
-
.lares-voice-preferences { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
139
|
-
.lares-voice-preferences h3 { margin:
|
|
140
|
-
.lares-voice-preferences p { margin: 0;
|
|
176
|
+
.lares-voice-preferences { display: flex; flex-direction: column; gap: 12px; min-width: 0; color: var(--q-ink-1); font-size: 15px; line-height: 22px; }
|
|
177
|
+
.lares-voice-preferences h3 { margin: 4px 0 0; color: var(--q-ink-3); font-size: 14px; font-weight: 500; line-height: 18px; }
|
|
178
|
+
.lares-voice-preferences p { margin: 0; color: var(--q-ink-3); font-size: 15px; line-height: 22px; }
|
|
141
179
|
.lares-voice-preferences [role="alert"] { color: var(--q-orange-default); }
|
|
142
|
-
.lares-voice-
|
|
143
|
-
.lares-voice-
|
|
180
|
+
.lares-voice-preferences__field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
|
181
|
+
.lares-voice-preferences__field > span { color: var(--q-ink-2); }
|
|
182
|
+
.lares-voice-preferences__row { display: flex; gap: 12px; align-items: center; justify-content: space-between; min-height: 56px; padding: 12px 0; border-bottom: 1px solid var(--q-separator); }
|
|
183
|
+
.lares-voice-preferences__row > span { flex: 1; min-width: 0; font-size: 16px; line-height: 22px; }
|
|
144
184
|
.lares-voice-preferences__actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
|
145
|
-
.lares-voice-
|
|
146
|
-
.lares-voice-
|
|
147
|
-
.lares-voice-
|
|
148
|
-
.lares-voice-
|
|
149
|
-
.lares-voice-
|
|
150
|
-
.lares-voice-
|
|
151
|
-
.lares-voice-
|
|
152
|
-
.lares-voice-
|
|
185
|
+
.lares-voice-preferences__actions button,
|
|
186
|
+
.lares-voice-preferences__question button,
|
|
187
|
+
.lares-voice-preferences__shortcuts button { min-height: 36px; border: 1px solid var(--q-separator); border-radius: 12px; padding: 0 14px; background: transparent; color: inherit; font: inherit; font-size: 15px; line-height: 20px; cursor: pointer; }
|
|
188
|
+
.lares-voice-preferences__actions button:disabled,
|
|
189
|
+
.lares-voice-preferences__question button:disabled,
|
|
190
|
+
.lares-voice-preferences__shortcuts button:disabled { opacity: .45; cursor: default; }
|
|
191
|
+
.lares-voice-preferences button.is-primary { border: 0; background: var(--q-blue-alpha); color: var(--q-blue-default); font-weight: 500; }
|
|
192
|
+
.lares-voice-preferences button[data-recording="true"] { outline: 2px solid var(--q-blue-default); }
|
|
193
|
+
.lares-voice-preferences__actions button:focus-visible,
|
|
194
|
+
.lares-voice-preferences__question button:focus-visible,
|
|
195
|
+
.lares-voice-preferences__shortcuts button:focus-visible,
|
|
196
|
+
.lares-voice-preferences textarea:focus-visible { outline: 2px solid var(--q-blue-default); outline-offset: 2px; }
|
|
197
|
+
.lares-voice-preferences__editor, .lares-voice-preferences__shortcuts, .lares-voice-preferences__question { display: flex; flex-direction: column; gap: 12px; }
|
|
198
|
+
.lares-voice-preferences textarea { box-sizing: border-box; width: 100%; min-height: 144px; padding: 12px 16px; resize: vertical; color: inherit; background: transparent; border: 1px solid var(--q-separator); border-radius: 16px; font: inherit; font-size: 16px; line-height: 1.5; }
|
|
199
|
+
.lares-voice-preferences__question { padding: 4px 20px 12px; }
|
|
200
|
+
.lares-voice-preferences__question button { width: 100%; }
|
|
153
201
|
</style>
|