@olares/lares-mobile 0.15.33 → 0.15.35

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.33",
3
+ "version": "0.15.35",
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.33",
31
+ "@olares/lares-core": "0.15.35",
32
32
  "markdown-it": "^14.3.0"
33
33
  }
34
34
  }
package/src/App.vue CHANGED
@@ -102,6 +102,7 @@
102
102
  @pick-command="pickCommand"
103
103
  @toggle-speak="toggleSpeak"
104
104
  @dismiss-speak="speakNotice = ''"
105
+ @answer-approval="answerApproval"
105
106
  />
106
107
  <LaresVoicePanel v-if="voicePanelState" :view="voicePanelState" :t="t"
107
108
  @close="runVoicePanel('close')" @edit="voicePanel.edit($event)" @preset="voicePanel.selectPreset($event, true).catch(() => {})"
@@ -154,6 +155,7 @@ import {
154
155
  effortSwitchSelection,
155
156
  } from "@olares/lares-core/router/session-model";
156
157
  import { messageFromCode } from "@olares/lares-core/i18n/t";
158
+ import { presentApproval } from "@olares/lares-core/larepass/approval";
157
159
  import { parseAskUserQuestions, singleSelectAnswer } from "@olares/lares-core/larepass/questions";
158
160
  import { applyCommand } from "@olares/lares-core/larepass/commands";
159
161
  import { permissionOption, presetDescription, presetLabel } from "@olares/lares-core/larepass/permissions";
@@ -785,7 +787,7 @@ export default {
785
787
  this.queue = snap.queue ?? [];
786
788
  this.question = snap.question || null;
787
789
  if (!this.question) this.questionBusy = false;
788
- this.approval = snap.approval || null;
790
+ this.approval = presentApproval(snap.approval, snap.items, this.locale);
789
791
  if (!this.approval) this.approvalBusy = false;
790
792
  if (this.pendingUser && snap.items.some((row) => row.type === "user" && row.text === this.pendingUser)) {
791
793
  this.pendingUser = "";
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <section
3
+ class="lares-approval"
4
+ :aria-busy="busy ? 'true' : 'false'"
5
+ @keydown="onKeydown"
6
+ >
7
+ <div class="lares-approval__strip">
8
+ <span aria-hidden="true" />
9
+ {{ t("approval.waiting") }}
10
+ </div>
11
+ <div
12
+ class="lares-approval__body"
13
+ tabindex="0"
14
+ role="group"
15
+ :aria-label="t('approval.detail')"
16
+ >
17
+ <p class="lares-approval__headline">{{ headline }}</p>
18
+ <p v-if="approval.command" class="lares-approval__command">{{ approval.command }}</p>
19
+ </div>
20
+ <div class="lares-approval__actions">
21
+ <button type="button" :disabled="busy" @click="$emit('answer', 'rejected')">
22
+ {{ t("approval.reject") }}
23
+ </button>
24
+ <button type="button" :disabled="busy" @click="$emit('answer', 'allowed-once')">
25
+ {{ t("approval.allowOnce") }}
26
+ </button>
27
+ </div>
28
+ </section>
29
+ </template>
30
+
31
+ <script>
32
+ export default {
33
+ name: "LaresApprovalCard",
34
+ props: {
35
+ approval: { type: Object, required: true },
36
+ busy: { type: Boolean, default: false },
37
+ t: { type: Function, required: true },
38
+ },
39
+ emits: ["answer"],
40
+ computed: {
41
+ headline() {
42
+ return this.approval.reason || this.t("approval.escalation", { toolName: this.approval.toolName });
43
+ },
44
+ },
45
+ methods: {
46
+ onKeydown(event) {
47
+ const target = event.target;
48
+ if (event.defaultPrevented || this.busy) return;
49
+ if (event.key !== "Enter" && event.key !== "Escape") return;
50
+ if (target?.closest?.("input, textarea, select, [contenteditable='true']")) return;
51
+ if (event.key === "Enter" && target?.closest?.("button, a[href], [role='button']")) return;
52
+ if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey || event.repeat) return;
53
+ event.preventDefault();
54
+ this.$emit("answer", event.key === "Enter" ? "allowed-once" : "rejected");
55
+ },
56
+ },
57
+ };
58
+ </script>
59
+
60
+ <style scoped>
61
+ .lares-approval {
62
+ overflow: hidden;
63
+ border: 1px solid var(--q-orange-default);
64
+ border-radius: 14px;
65
+ background: var(--q-background-1);
66
+ box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
67
+ }
68
+
69
+ .lares-approval__strip {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 8px;
73
+ padding: 10px 16px;
74
+ background: var(--q-orange-alpha);
75
+ color: var(--q-orange-default);
76
+ font-size: 13px;
77
+ line-height: 18px;
78
+ }
79
+
80
+ .lares-approval__strip span {
81
+ width: 6px;
82
+ height: 6px;
83
+ flex: none;
84
+ border-radius: 50%;
85
+ background: var(--q-orange-default);
86
+ }
87
+
88
+ .lares-approval__body {
89
+ display: flex;
90
+ max-height: 160px;
91
+ flex-direction: column;
92
+ gap: 6px;
93
+ overflow-y: auto;
94
+ padding: 12px 16px 0;
95
+ }
96
+
97
+ .lares-approval__headline {
98
+ margin: 0;
99
+ color: var(--q-ink-1);
100
+ font-size: 15px;
101
+ font-weight: 500;
102
+ line-height: 24px;
103
+ }
104
+
105
+ .lares-approval__command {
106
+ margin: 0;
107
+ color: var(--q-ink-3);
108
+ font: 400 13px/20px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
109
+ word-break: break-all;
110
+ }
111
+
112
+ .lares-approval__actions {
113
+ display: flex;
114
+ justify-content: flex-end;
115
+ gap: 8px;
116
+ padding: 14px 16px;
117
+ }
118
+
119
+ .lares-approval__actions button {
120
+ height: 32px;
121
+ border: 1px solid var(--q-separator);
122
+ border-radius: 7px;
123
+ padding: 0 13px;
124
+ background: transparent;
125
+ color: var(--q-ink-2);
126
+ font: inherit;
127
+ }
128
+
129
+ .lares-approval__actions button:last-child {
130
+ border-color: transparent;
131
+ background: var(--q-blue-default);
132
+ color: var(--q-ink-on-brand);
133
+ }
134
+
135
+ .lares-approval__actions button:disabled {
136
+ opacity: 0.45;
137
+ }
138
+ </style>
@@ -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,16 +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
- <div v-else-if="ownsPlayback(row)" role="status" class="lares-message-readout">
85
+ <div v-else-if="!inProcess && ownsPlayback(row)" role="status" class="lares-message-readout">
85
86
  <span>{{ t(playbackChrome.status) }}</span>
86
87
  <button type="button" class="lares-message-stop" @click="$emit('stop-speaking')">{{ t(playbackChrome.stopLabel) }}</button>
87
88
  </div>
88
89
  <LaresMessageActions
89
- v-if="!row.pending"
90
+ v-if="!inProcess && !row.pending"
90
91
  :text="row.text"
91
92
  :reaction="reactions[actionKey(index)] || ''"
92
93
  :metrics="row.metrics || null"
@@ -221,8 +222,9 @@ export default {
221
222
  renderMarkdown,
222
223
  thinkSummary,
223
224
  shown(block) {
224
- if (block.kind === "fold") return this.processOpen(block) ? block.members : [];
225
- return [block];
225
+ if (block.kind !== "fold") return [block];
226
+ if (!this.processOpen(block)) return [];
227
+ return block.members.map((member) => ({ ...member, inProcess: true }));
226
228
  },
227
229
  processOpen(block) {
228
230
  return !block.foldable || Boolean(this.openFolds[block.key]);
@@ -487,6 +489,11 @@ export default {
487
489
  margin-top: 8px;
488
490
  }
489
491
 
492
+ .lares-log__item[data-process="true"] {
493
+ margin-top: 0;
494
+ gap: 0;
495
+ }
496
+
490
497
  .lares-msg[data-role="user"] {
491
498
  align-self: flex-end;
492
499
  max-width: 86%;
@@ -139,23 +139,14 @@
139
139
  </div>
140
140
 
141
141
  <Transition name="lares-panel" mode="out-in">
142
- <div v-if="state.approval" key="approval" class="desktop-composer__approval">
143
- <div class="desktop-composer__approval-strip">
144
- <span aria-hidden="true" />
145
- {{ t("approval.waiting") }}
146
- </div>
147
- <div class="desktop-composer__approval-body">
148
- {{ state.approval.reason || t("approval.escalation", { toolName: state.approval.toolName }) }}
149
- </div>
150
- <div class="desktop-composer__approval-actions">
151
- <button type="button" :disabled="state.approvalBusy" @click="$emit('answer-approval', 'rejected')">
152
- {{ t("approval.reject") }}
153
- </button>
154
- <button type="button" :disabled="state.approvalBusy" @click="$emit('answer-approval', 'allowed-once')">
155
- {{ t("approval.allowOnce") }}
156
- </button>
157
- </div>
158
- </div>
142
+ <LaresApprovalCard
143
+ v-if="state.approval"
144
+ key="approval"
145
+ :approval="state.approval"
146
+ :busy="state.approvalBusy"
147
+ :t="t"
148
+ @answer="$emit('answer-approval', $event)"
149
+ />
159
150
  <LaresDesktopQuestions
160
151
  v-else-if="state.question?.questions?.length"
161
152
  key="questions"
@@ -480,6 +471,7 @@ import { DEFAULT_WORKSPACE_TITLE } from "@olares/lares-core/workspace/constants"
480
471
  import { activeReferenceToken, insertReference } from "@olares/lares-core/larepass/references";
481
472
  import { draftImageStatus } from "@olares/lares-core/files/draft-images";
482
473
  import LaresVoiceInput from "../chat/VoiceInput.vue";
474
+ import LaresApprovalCard from "../chat/ApprovalCard.vue";
483
475
  import LaresDesktopQueue from "./DesktopQueue.vue";
484
476
  import LaresDesktopQuestions from "./DesktopQuestions.vue";
485
477
  import LaresDesktopContextMeter from "./DesktopContextMeter.vue";
@@ -542,7 +534,7 @@ const PermissionIcon = {
542
534
 
543
535
  export default {
544
536
  name: "LaresDesktopComposer",
545
- components: { LaresVoiceInput, LaresDesktopContextMeter, LaresDesktopGoal, LaresDesktopQuestions, LaresDesktopQueue, LaresDesktopTodos, LaresPcDialog, LaresPcPopover, LaresPcTooltip, ChevronIcon, CheckIcon, PlusIcon, PaperclipIcon, ArrowIcon, StopIcon, CloseIcon, MicIcon, PermissionIcon },
537
+ components: { LaresApprovalCard, LaresVoiceInput, LaresDesktopContextMeter, LaresDesktopGoal, LaresDesktopQuestions, LaresDesktopQueue, LaresDesktopTodos, LaresPcDialog, LaresPcPopover, LaresPcTooltip, ChevronIcon, CheckIcon, PlusIcon, PaperclipIcon, ArrowIcon, StopIcon, CloseIcon, MicIcon, PermissionIcon },
546
538
  props: {
547
539
  state: { type: Object, required: true },
548
540
  t: { type: Function, required: true },
@@ -959,14 +951,6 @@ export default {
959
951
  .desktop-composer__reference-item[data-selected="true"] { background:var(--q-background-hover); }
960
952
  .desktop-composer__reference-name { max-width:40%; flex:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
961
953
  .desktop-composer__reference-path { min-width:0; flex:1; overflow:hidden; color:var(--q-ink-3); text-overflow:ellipsis; white-space:nowrap; }
962
- .desktop-composer__approval { overflow:hidden; border:1px solid var(--q-separator); border-radius:14px; background:var(--q-background-2); box-shadow:0 1px 3px rgb(0 0 0 / 8%); }
963
- .desktop-composer__approval-strip { display:flex; height:30px; align-items:center; gap:7px; padding:0 12px; background:var(--q-background-3); color:var(--q-ink-3); font-size:12px; }
964
- .desktop-composer__approval-strip span { width:6px; height:6px; border-radius:50%; background:var(--q-orange-default); }
965
- .desktop-composer__approval-body { max-height:96px; overflow:auto; padding:14px 16px; color:var(--q-ink-1); font-size:14px; line-height:20px; }
966
- .desktop-composer__approval-actions { display:flex; justify-content:flex-end; gap:8px; padding:8px 12px 12px; }
967
- .desktop-composer__approval-actions button { height:32px; border:1px solid var(--q-separator); border-radius:7px; padding:0 13px; background:transparent; color:var(--q-ink-2); font:inherit; cursor:pointer; }
968
- .desktop-composer__approval-actions button:last-child { border-color:transparent; background:var(--q-blue-default); color:var(--q-ink-on-brand); }
969
- .desktop-composer__approval-actions button:disabled { opacity:.45; cursor:default; }
970
954
  .desktop-composer__attachments { display:flex; gap:8px; overflow-x:auto; padding:0 0 8px 2px; }
971
955
  .desktop-composer__attachments figure { position:relative; width:64px; height:64px; flex:0 0 64px; overflow:hidden; margin:0; border:1px solid var(--q-separator); border-radius:9px; background:var(--q-background-3); }
972
956
  .desktop-composer__attachments img { width:100%; height:100%; object-fit:cover; transition:opacity .22s ease; }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div v-if="rows.length" class="desktop-queue lares-queue-in">
2
+ <div v-if="rows.length" class="desktop-queue lares-queue-in" :data-tone="tone">
3
3
  <div class="desktop-queue__panel">
4
4
  <button
5
5
  v-if="rows.length > 1"
@@ -128,6 +128,7 @@ export default {
128
128
  items: { type: Array, default: () => [] },
129
129
  running: { type: Boolean, default: false },
130
130
  busy: { type: String, default: "" },
131
+ tone: { type: String, default: "" },
131
132
  t: { type: Function, required: true },
132
133
  },
133
134
  emits: ["action"],
@@ -198,4 +199,7 @@ export default {
198
199
  :deep(.desktop-queue__action:disabled) { opacity:.45; cursor:default; }
199
200
  .desktop-queue svg,
200
201
  :deep(.desktop-queue__action svg) { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:1.4; stroke-linecap:round; stroke-linejoin:round; }
202
+ .desktop-queue[data-tone="mobile"] { width:auto; margin:0 16px 8px; padding:0; }
203
+ .desktop-queue[data-tone="mobile"] .desktop-queue__panel { border-bottom:1px solid var(--q-separator); border-radius:14px; }
204
+ .desktop-queue[data-tone="mobile"] :deep(.desktop-queue__action) { width:36px; height:36px; }
201
205
  </style>
@@ -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) {
@@ -130,13 +130,23 @@
130
130
  @answer="forwardAnswer"
131
131
  />
132
132
  <LaresDesktopQueue
133
+ tone="mobile"
133
134
  :items="state.queue"
134
135
  :running="state.running"
135
136
  :busy="state.queueBusy"
136
137
  :t="t"
137
138
  @action="(itemId, action) => $emit('queue-action', itemId, action)"
138
139
  />
140
+ <div v-if="state.approval" class="lares-mobile-shell__approval">
141
+ <LaresApprovalCard
142
+ :approval="state.approval"
143
+ :busy="state.approvalBusy"
144
+ :t="t"
145
+ @answer="$emit('answer-approval', $event)"
146
+ />
147
+ </div>
139
148
  <LaresComposer
149
+ v-else
140
150
  ref="composer"
141
151
  :draft="state.draft"
142
152
  :sending="state.sending"
@@ -231,6 +241,7 @@
231
241
  import LaresChatBar from "../chat/ChatBar.vue";
232
242
  import LaresHistoryPanel from "../chat/HistoryPanel.vue";
233
243
  import LaresTranscript from "../chat/Transcript.vue";
244
+ import LaresApprovalCard from "../chat/ApprovalCard.vue";
234
245
  import LaresComposer from "../chat/Composer.vue";
235
246
  import LaresDesktopQueue from "../desktop/DesktopQueue.vue";
236
247
  import LaresPreview from "../preview/Preview.vue";
@@ -245,6 +256,7 @@ export default {
245
256
  LaresChatBar,
246
257
  LaresHistoryPanel,
247
258
  LaresTranscript,
259
+ LaresApprovalCard,
248
260
  LaresComposer,
249
261
  LaresDesktopQueue,
250
262
  LaresPreview,
@@ -266,7 +278,7 @@ export default {
266
278
  },
267
279
  emits: [
268
280
  "history", "create", "toggle-speak", "close-history", "pick-session", "retry", "open-file",
269
- "answer", "update-draft", "send", "queue-action", "open-model", "open-effort", "files",
281
+ "answer", "answer-approval", "update-draft", "send", "queue-action", "open-model", "open-effort", "files",
270
282
  "voice", "voice-action", "hold-start", "hold-end", "close-model", "choose-model",
271
283
  "close-effort", "choose-effort", "close-preview",
272
284
  "open-commands", "open-references", "pick-command", "remove-image",
@@ -335,6 +347,11 @@ export default {
335
347
  flex-direction: column;
336
348
  }
337
349
 
350
+ .lares-mobile-shell__approval {
351
+ flex: none;
352
+ margin: 0 16px calc(12px + env(safe-area-inset-bottom, 0px));
353
+ }
354
+
338
355
  .lares-shell__status,
339
356
  .lares-shell__hint {
340
357
  margin: 0 20px;