@olares/lares-mobile 0.15.7 → 0.15.9
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 +254 -144
- package/src/chat/ChatBar.vue +134 -33
- package/src/chat/Composer.vue +245 -213
- package/src/chat/Deliverables.vue +83 -11
- package/src/chat/HistoryPanel.vue +40 -1
- package/src/chat/QuestionCard.vue +3 -3
- package/src/chat/Transcript.vue +162 -4
- package/src/chat/format.js +11 -0
- package/src/chat/playback.js +41 -0
- package/src/chat/voice.js +169 -39
- package/src/desktop/DesktopPreview.vue +10 -2
- package/src/desktop/DesktopShell.vue +2 -0
- package/src/host.js +131 -21
- package/src/index.d.ts +5 -0
- package/src/mobile/MobileNewSession.vue +6 -117
- package/src/mobile/MobileShell.vue +57 -69
- package/src/preview/Preview.vue +30 -16
- package/src/preview/PreviewBody.vue +4 -1
- package/src/settings/AgentSettings.vue +16 -8
- package/src/settings/Sheet.vue +4 -0
|
@@ -5,22 +5,47 @@
|
|
|
5
5
|
<figure v-for="item in group.media" :key="item.path" class="lares-turn-media">
|
|
6
6
|
<figcaption class="lares-turn-media-caption">
|
|
7
7
|
<span :title="item.path">{{ item.name }}</span>
|
|
8
|
-
<button type="button" class="lares-turn-open" @click="$emit('open', item.path)">{{ t("openInTab") }}</button>
|
|
8
|
+
<button type="button" class="lares-turn-open" @click="$emit('open', item.path)">{{ t(popup ? "viewMedia" : "openInTab") }}</button>
|
|
9
9
|
</figcaption>
|
|
10
|
-
<
|
|
10
|
+
<button
|
|
11
11
|
v-if="item.kind === 'image'"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
:
|
|
15
|
-
@load="$emit('media')"
|
|
12
|
+
type="button"
|
|
13
|
+
class="lares-turn-media-frame"
|
|
14
|
+
:aria-label="t('viewMedia')"
|
|
16
15
|
@click="$emit('open', item.path)"
|
|
17
|
-
|
|
16
|
+
>
|
|
17
|
+
<img
|
|
18
|
+
class="lares-turn-media-image"
|
|
19
|
+
:src="mediaUrl(item)"
|
|
20
|
+
:alt="item.name"
|
|
21
|
+
@load="$emit('media')"
|
|
22
|
+
/>
|
|
23
|
+
</button>
|
|
24
|
+
<button
|
|
25
|
+
v-else-if="item.kind === 'video' && popup"
|
|
26
|
+
type="button"
|
|
27
|
+
class="lares-turn-media-frame"
|
|
28
|
+
:aria-label="t('viewMedia')"
|
|
29
|
+
@click="$emit('open', item.path)"
|
|
30
|
+
>
|
|
31
|
+
<video
|
|
32
|
+
class="lares-turn-media-video"
|
|
33
|
+
:src="mediaUrl(item)"
|
|
34
|
+
playsinline
|
|
35
|
+
muted
|
|
36
|
+
preload="metadata"
|
|
37
|
+
@loadedmetadata="$emit('media')"
|
|
38
|
+
/>
|
|
39
|
+
<span class="lares-turn-media-play" aria-hidden="true" />
|
|
40
|
+
</button>
|
|
18
41
|
<video
|
|
19
42
|
v-else-if="item.kind === 'video'"
|
|
20
43
|
class="lares-turn-media-video"
|
|
21
44
|
:src="mediaUrl(item)"
|
|
22
45
|
controls
|
|
23
46
|
playsinline
|
|
47
|
+
preload="metadata"
|
|
48
|
+
@loadedmetadata="$emit('media')"
|
|
24
49
|
/>
|
|
25
50
|
<audio
|
|
26
51
|
v-else-if="item.kind === 'audio'"
|
|
@@ -60,6 +85,7 @@ export default {
|
|
|
60
85
|
props: {
|
|
61
86
|
group: { type: Object, required: true },
|
|
62
87
|
mediaUrl: { type: Function, required: true },
|
|
88
|
+
popup: { type: Boolean, default: false },
|
|
63
89
|
t: { type: Function, required: true },
|
|
64
90
|
},
|
|
65
91
|
emits: ["open", "media"],
|
|
@@ -90,8 +116,9 @@ export default {
|
|
|
90
116
|
|
|
91
117
|
.lares-turn-media {
|
|
92
118
|
display: flex;
|
|
119
|
+
box-sizing: border-box;
|
|
93
120
|
width: fit-content;
|
|
94
|
-
max-width: 100
|
|
121
|
+
max-width: min(100%, 660px);
|
|
95
122
|
flex-direction: column;
|
|
96
123
|
gap: 8px;
|
|
97
124
|
margin: 0;
|
|
@@ -136,10 +163,52 @@ export default {
|
|
|
136
163
|
background: var(--q-background-hover);
|
|
137
164
|
}
|
|
138
165
|
|
|
166
|
+
.lares-turn-media-frame {
|
|
167
|
+
position: relative;
|
|
168
|
+
display: block;
|
|
169
|
+
width: fit-content;
|
|
170
|
+
max-width: 100%;
|
|
171
|
+
margin: 0;
|
|
172
|
+
padding: 0;
|
|
173
|
+
border: 0;
|
|
174
|
+
background: transparent;
|
|
175
|
+
cursor: pointer;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.lares-turn-media-frame:active {
|
|
179
|
+
opacity: 0.86;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.lares-turn-media-frame img,
|
|
183
|
+
.lares-turn-media-frame video {
|
|
184
|
+
pointer-events: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.lares-turn-media-play {
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: 50%;
|
|
190
|
+
left: 50%;
|
|
191
|
+
width: 52px;
|
|
192
|
+
height: 52px;
|
|
193
|
+
border-radius: 999px;
|
|
194
|
+
background: rgb(0 0 0 / 55%);
|
|
195
|
+
transform: translate(-50%, -50%);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.lares-turn-media-play::before {
|
|
199
|
+
content: "";
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 16px;
|
|
202
|
+
left: 20px;
|
|
203
|
+
border-width: 10px 0 10px 16px;
|
|
204
|
+
border-style: solid;
|
|
205
|
+
border-color: transparent transparent transparent #fff;
|
|
206
|
+
}
|
|
207
|
+
|
|
139
208
|
.lares-turn-media-image,
|
|
140
209
|
.lares-turn-media-video {
|
|
141
210
|
display: block;
|
|
142
|
-
max-width:
|
|
211
|
+
max-width: 100%;
|
|
143
212
|
max-height: 420px;
|
|
144
213
|
border-radius: 10px;
|
|
145
214
|
object-fit: contain;
|
|
@@ -147,11 +216,14 @@ export default {
|
|
|
147
216
|
|
|
148
217
|
.lares-turn-media-audio {
|
|
149
218
|
display: block;
|
|
150
|
-
width:
|
|
219
|
+
width: 520px;
|
|
220
|
+
max-width: 100%;
|
|
151
221
|
}
|
|
152
222
|
|
|
153
223
|
.lares-turn-media-model3d {
|
|
154
|
-
|
|
224
|
+
box-sizing: border-box;
|
|
225
|
+
width: 520px;
|
|
226
|
+
max-width: 100%;
|
|
155
227
|
padding: 16px;
|
|
156
228
|
color: var(--q-ink-3);
|
|
157
229
|
font-size: 15px;
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
<aside class="lares-history__sheet" role="dialog" :aria-label="t('bar.history')">
|
|
10
10
|
<header class="lares-history__head">
|
|
11
11
|
<h2 class="lares-history__title">{{ t("bar.history") }}</h2>
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
class="lares-history__new"
|
|
15
|
+
:aria-label="t('bar.newAria')"
|
|
16
|
+
@click="$emit('create', null)"
|
|
17
|
+
>
|
|
18
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
19
|
+
<path d="M5 4.2h14a2.2 2.2 0 0 1 2.2 2.2v8.2a2.2 2.2 0 0 1-2.2 2.2h-5.2L12 19.8l-1.8-3H5a2.2 2.2 0 0 1-2.2-2.2V6.4A2.2 2.2 0 0 1 5 4.2z" />
|
|
20
|
+
<path d="M12 7.6v5.2M9.4 10.2h5.2" />
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
12
23
|
</header>
|
|
13
24
|
<header class="lares-history__toolbar">
|
|
14
25
|
<span v-if="!searchOpen" class="lares-history__section-label">{{ sectionTitle }}</span>
|
|
@@ -517,7 +528,7 @@ export default {
|
|
|
517
528
|
align-items: center;
|
|
518
529
|
flex-shrink: 0;
|
|
519
530
|
height: 56px;
|
|
520
|
-
padding: 0 20px;
|
|
531
|
+
padding: 0 12px 0 20px;
|
|
521
532
|
}
|
|
522
533
|
|
|
523
534
|
.lares-history__title {
|
|
@@ -527,6 +538,34 @@ export default {
|
|
|
527
538
|
line-height: 26px;
|
|
528
539
|
}
|
|
529
540
|
|
|
541
|
+
.lares-history__new {
|
|
542
|
+
display: inline-flex;
|
|
543
|
+
align-items: center;
|
|
544
|
+
justify-content: center;
|
|
545
|
+
width: 36px;
|
|
546
|
+
height: 36px;
|
|
547
|
+
margin-left: auto;
|
|
548
|
+
border: 0;
|
|
549
|
+
border-radius: 999px;
|
|
550
|
+
padding: 0;
|
|
551
|
+
background: var(--q-background-3);
|
|
552
|
+
color: var(--q-ink-2);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.lares-history__new svg {
|
|
556
|
+
width: 22px;
|
|
557
|
+
height: 22px;
|
|
558
|
+
fill: none;
|
|
559
|
+
stroke: currentColor;
|
|
560
|
+
stroke-width: 1.75;
|
|
561
|
+
stroke-linecap: round;
|
|
562
|
+
stroke-linejoin: round;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.lares-history__new:active {
|
|
566
|
+
background: var(--q-btn-bg-pressed);
|
|
567
|
+
}
|
|
568
|
+
|
|
530
569
|
.lares-history__toolbar {
|
|
531
570
|
display: flex;
|
|
532
571
|
flex-shrink: 0;
|
|
@@ -25,7 +25,7 @@ export default {
|
|
|
25
25
|
name: "LaresQuestionCard",
|
|
26
26
|
props: {
|
|
27
27
|
questions: { type: Array, default: () => [] },
|
|
28
|
-
|
|
28
|
+
answerable: { type: Boolean, default: false },
|
|
29
29
|
busy: { type: Boolean, default: false },
|
|
30
30
|
t: { type: Function, required: true },
|
|
31
31
|
},
|
|
@@ -47,11 +47,11 @@ export default {
|
|
|
47
47
|
return Array.isArray(this.current?.options) ? this.current.options : [];
|
|
48
48
|
},
|
|
49
49
|
disabled() {
|
|
50
|
-
return this.busy || !this.
|
|
50
|
+
return this.busy || !this.answerable || !this.current;
|
|
51
51
|
},
|
|
52
52
|
hint() {
|
|
53
53
|
if (this.busy) return this.t("question.busy");
|
|
54
|
-
if (!this.
|
|
54
|
+
if (!this.answerable) return this.t("question.waiting");
|
|
55
55
|
return "";
|
|
56
56
|
},
|
|
57
57
|
},
|
package/src/chat/Transcript.vue
CHANGED
|
@@ -10,8 +10,25 @@
|
|
|
10
10
|
<span class="lares-sk lares-sk--line lares-sk--w56" />
|
|
11
11
|
</li>
|
|
12
12
|
<li v-else-if="items.length === 0 && !running" class="lares-log__empty">{{ t("chat.empty") }}</li>
|
|
13
|
+
<template v-for="block in blocks" :key="block.key">
|
|
14
|
+
<li v-if="block.kind === 'fold'" class="lares-log__item" data-type="process">
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
class="lares-process"
|
|
18
|
+
:data-phase="block.phase"
|
|
19
|
+
:data-open="processOpen(block) ? 'true' : 'false'"
|
|
20
|
+
:disabled="!block.foldable"
|
|
21
|
+
:aria-expanded="processOpen(block) ? 'true' : 'false'"
|
|
22
|
+
@click="toggleProcess(block.key)"
|
|
23
|
+
>
|
|
24
|
+
<span class="lares-process__label">{{ processLabel(block) }}</span>
|
|
25
|
+
<svg v-if="block.foldable" class="lares-process__chevron" viewBox="0 0 24 24" aria-hidden="true">
|
|
26
|
+
<path d="M6.5 9 12 14.5 17.5 9" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
</button>
|
|
29
|
+
</li>
|
|
13
30
|
<li
|
|
14
|
-
v-for="
|
|
31
|
+
v-for="{ row, index } in shown(block)"
|
|
15
32
|
:key="index"
|
|
16
33
|
class="lares-log__item"
|
|
17
34
|
:data-type="row.type"
|
|
@@ -35,7 +52,7 @@
|
|
|
35
52
|
<LaresQuestionCard
|
|
36
53
|
v-else-if="!questionTakeover && row.type === 'tool' && row.name === 'ask_user_question' && row.status === 'running'"
|
|
37
54
|
:questions="questionItems(row)"
|
|
38
|
-
:
|
|
55
|
+
:answerable="Boolean(question?.eventId)"
|
|
39
56
|
:busy="questionBusy"
|
|
40
57
|
:t="t"
|
|
41
58
|
@pick="$emit('answer', $event, row)"
|
|
@@ -69,23 +86,30 @@
|
|
|
69
86
|
@react="setReaction(index, $event)"
|
|
70
87
|
/>
|
|
71
88
|
</template>
|
|
72
|
-
|
|
89
|
+
<LaresDeliverables
|
|
73
90
|
v-else-if="row.type === 'files'"
|
|
74
91
|
:group="fileGroup(row.paths)"
|
|
75
92
|
:media-url="mediaUrl"
|
|
93
|
+
:popup="popup"
|
|
76
94
|
:t="t"
|
|
77
95
|
@open="$emit('open', $event)"
|
|
78
96
|
@media="$emit('media')"
|
|
79
97
|
/>
|
|
80
98
|
</li>
|
|
99
|
+
</template>
|
|
100
|
+
<li v-if="running && !hideRunning" class="lares-log__running" data-chat-running="true">
|
|
101
|
+
<span class="lares-log__sr" role="status" aria-live="polite" aria-atomic="true">{{ t("chat.deepDiving") }}</span>
|
|
102
|
+
<span class="lares-log__running-text" aria-hidden="true">{{ runningText }}</span>
|
|
103
|
+
</li>
|
|
81
104
|
</ol>
|
|
82
105
|
</template>
|
|
83
106
|
|
|
84
107
|
<script>
|
|
85
108
|
import { partitionPreviews } from "@olares/lares-core/files/preview-groups";
|
|
86
109
|
import { parseAskUserQuestions } from "@olares/lares-core/larepass/questions";
|
|
110
|
+
import { hidesRunningClock, processFoldLabel, transcriptBlocks } from "@olares/lares-core/larepass/process-fold";
|
|
87
111
|
import { STAGE_COPY } from "@olares/lares-core/larepass/stage-copy";
|
|
88
|
-
import { retryStatus as formatRetry, thinkSummary } from "./format.js";
|
|
112
|
+
import { retryStatus as formatRetry, runningLabel, thinkSummary } from "./format.js";
|
|
89
113
|
import { renderMarkdown } from "./markdown.js";
|
|
90
114
|
import LaresDeliverables from "./Deliverables.vue";
|
|
91
115
|
import LaresQuestionCard from "./QuestionCard.vue";
|
|
@@ -98,6 +122,7 @@ export default {
|
|
|
98
122
|
props: {
|
|
99
123
|
items: { type: Array, default: () => [] },
|
|
100
124
|
running: { type: Boolean, default: false },
|
|
125
|
+
runningSince: { type: Number, default: 0 },
|
|
101
126
|
loading: { type: Boolean, default: false },
|
|
102
127
|
previews: { type: Object, default: () => ({}) },
|
|
103
128
|
mediaUrl: { type: Function, required: true },
|
|
@@ -105,6 +130,7 @@ export default {
|
|
|
105
130
|
rememberScroll: { type: Function, required: true },
|
|
106
131
|
sticking: { type: Function, required: true },
|
|
107
132
|
sessionId: { type: String, default: "" },
|
|
133
|
+
popup: { type: Boolean, default: false },
|
|
108
134
|
question: { type: Object, default: null },
|
|
109
135
|
questionBusy: { type: Boolean, default: false },
|
|
110
136
|
questionTakeover: { type: Boolean, default: false },
|
|
@@ -118,25 +144,66 @@ export default {
|
|
|
118
144
|
logPin: 0,
|
|
119
145
|
stageCopy: STAGE_COPY,
|
|
120
146
|
reactions: {},
|
|
147
|
+
openFolds: {},
|
|
148
|
+
now: Date.now(),
|
|
149
|
+
clock: 0,
|
|
121
150
|
};
|
|
122
151
|
},
|
|
152
|
+
computed: {
|
|
153
|
+
runningText() {
|
|
154
|
+
return runningLabel(this.runningSince, this.t, this.now);
|
|
155
|
+
},
|
|
156
|
+
blocks() {
|
|
157
|
+
return transcriptBlocks(this.items, { running: this.running });
|
|
158
|
+
},
|
|
159
|
+
hideRunning() {
|
|
160
|
+
return hidesRunningClock(this.blocks);
|
|
161
|
+
},
|
|
162
|
+
},
|
|
123
163
|
watch: {
|
|
124
164
|
sessionId() {
|
|
125
165
|
this.reactions = {};
|
|
166
|
+
this.openFolds = {};
|
|
126
167
|
},
|
|
168
|
+
running: "syncClock",
|
|
169
|
+
runningSince: "syncClock",
|
|
127
170
|
},
|
|
128
171
|
mounted() {
|
|
129
172
|
this.bindLog();
|
|
130
173
|
this.restoreLog();
|
|
174
|
+
this.syncClock();
|
|
131
175
|
},
|
|
132
176
|
beforeUnmount() {
|
|
133
177
|
this.captureLog();
|
|
134
178
|
this.unbindLog();
|
|
135
179
|
if (this.logPin) cancelAnimationFrame(this.logPin);
|
|
180
|
+
clearInterval(this.clock);
|
|
136
181
|
},
|
|
137
182
|
methods: {
|
|
183
|
+
syncClock() {
|
|
184
|
+
clearInterval(this.clock);
|
|
185
|
+
this.clock = 0;
|
|
186
|
+
this.now = Date.now();
|
|
187
|
+
if (!this.running || !this.runningSince) return;
|
|
188
|
+
this.clock = setInterval(() => {
|
|
189
|
+
this.now = Date.now();
|
|
190
|
+
}, 1000);
|
|
191
|
+
},
|
|
138
192
|
renderMarkdown,
|
|
139
193
|
thinkSummary,
|
|
194
|
+
shown(block) {
|
|
195
|
+
if (block.kind === "fold") return this.processOpen(block) ? block.members : [];
|
|
196
|
+
return [block];
|
|
197
|
+
},
|
|
198
|
+
processOpen(block) {
|
|
199
|
+
return !block.foldable || Boolean(this.openFolds[block.key]);
|
|
200
|
+
},
|
|
201
|
+
processLabel(block) {
|
|
202
|
+
return processFoldLabel(this.t, block);
|
|
203
|
+
},
|
|
204
|
+
toggleProcess(key) {
|
|
205
|
+
this.openFolds = { ...this.openFolds, [key]: !this.openFolds[key] };
|
|
206
|
+
},
|
|
140
207
|
questionItems(row) {
|
|
141
208
|
if (this.question?.questions?.length) return this.question.questions;
|
|
142
209
|
return parseAskUserQuestions(row.argsRaw);
|
|
@@ -270,11 +337,37 @@ export default {
|
|
|
270
337
|
|
|
271
338
|
@media (prefers-reduced-motion: reduce) {
|
|
272
339
|
.lares-sk,
|
|
340
|
+
.lares-log__running-text,
|
|
341
|
+
.lares-process[data-phase="live"] .lares-process__label,
|
|
273
342
|
.lares-log__item[data-pending="true"] {
|
|
274
343
|
animation: none;
|
|
275
344
|
}
|
|
276
345
|
}
|
|
277
346
|
|
|
347
|
+
.lares-log__running {
|
|
348
|
+
margin-top: 4px;
|
|
349
|
+
font-size: 14px;
|
|
350
|
+
line-height: 20px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.lares-log__running-text {
|
|
354
|
+
color: var(--q-blue-default);
|
|
355
|
+
animation: lares-running-shimmer 1.6s ease-in-out infinite;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@keyframes lares-running-shimmer {
|
|
359
|
+
50% { opacity: 0.5; }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.lares-log__sr {
|
|
363
|
+
position: absolute;
|
|
364
|
+
width: 1px;
|
|
365
|
+
height: 1px;
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
clip: rect(0 0 0 0);
|
|
368
|
+
white-space: nowrap;
|
|
369
|
+
}
|
|
370
|
+
|
|
278
371
|
.lares-log__item {
|
|
279
372
|
display: flex;
|
|
280
373
|
flex-direction: column;
|
|
@@ -283,6 +376,71 @@ export default {
|
|
|
283
376
|
max-width: 100%;
|
|
284
377
|
}
|
|
285
378
|
|
|
379
|
+
.lares-process {
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
box-sizing: border-box;
|
|
383
|
+
width: 100%;
|
|
384
|
+
min-width: 0;
|
|
385
|
+
min-height: 33px;
|
|
386
|
+
border: 0;
|
|
387
|
+
border-bottom: 0.5px solid var(--q-separator);
|
|
388
|
+
padding: 0 0 8px;
|
|
389
|
+
background: transparent;
|
|
390
|
+
color: var(--q-ink-3);
|
|
391
|
+
font: inherit;
|
|
392
|
+
text-align: left;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.lares-process:disabled {
|
|
397
|
+
cursor: default;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.lares-process[data-phase="live"],
|
|
401
|
+
.lares-process[data-phase="summary"] {
|
|
402
|
+
min-height: 0;
|
|
403
|
+
border-bottom: 0;
|
|
404
|
+
padding: 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.lares-process[data-phase="live"] .lares-process__label {
|
|
408
|
+
animation: lares-running-shimmer 1.6s ease-in-out infinite;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@media (hover: hover) {
|
|
412
|
+
.lares-process:hover:not(:disabled) {
|
|
413
|
+
color: var(--q-ink-2);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.lares-process__label {
|
|
418
|
+
min-width: 0;
|
|
419
|
+
overflow: hidden;
|
|
420
|
+
font-size: 14px;
|
|
421
|
+
line-height: 24px;
|
|
422
|
+
text-overflow: ellipsis;
|
|
423
|
+
white-space: nowrap;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.lares-process__chevron {
|
|
427
|
+
flex: none;
|
|
428
|
+
width: 14px;
|
|
429
|
+
height: 14px;
|
|
430
|
+
margin-left: 4px;
|
|
431
|
+
transition: transform 0.1s;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.lares-process[data-open="true"] .lares-process__chevron {
|
|
435
|
+
transform: rotate(180deg);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@media (prefers-reduced-motion: reduce) {
|
|
439
|
+
.lares-process__chevron {
|
|
440
|
+
transition: none;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
286
444
|
.lares-log__item[data-pending="true"] {
|
|
287
445
|
animation: lares-msg-in var(--lares-duration-normal, 220ms) var(--lares-ease-out, cubic-bezier(0.32, 0.72, 0, 1));
|
|
288
446
|
}
|
package/src/chat/format.js
CHANGED
|
@@ -87,6 +87,17 @@ export function metricsLine(metrics, t, now = Date.now()) {
|
|
|
87
87
|
return parts.join(" · ");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** The open turn's live clock; dsh never shows less than one second. */
|
|
91
|
+
export function runningLabel(since, t, now = Date.now()) {
|
|
92
|
+
if (!since) return t("chat.deepDiving");
|
|
93
|
+
const { minutes, seconds } = durationParts(Math.max(1000, now - since));
|
|
94
|
+
return t("chat.deepDivingFor", {
|
|
95
|
+
duration: minutes > 0
|
|
96
|
+
? t("duration.minutes", { minutes, seconds })
|
|
97
|
+
: t("duration.seconds", { seconds }),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
export function retryStatus(row) {
|
|
91
102
|
const copy = STAGE_COPY.retry;
|
|
92
103
|
const label = row.retryState === "started"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const SILENT_WAV = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One element, unlocked inside the gesture that asks the question (a voice
|
|
5
|
+
* press or a send tap), then reused for the answer. iOS only lets an element
|
|
6
|
+
* play later if it first played inside a gesture.
|
|
7
|
+
*/
|
|
8
|
+
export function createAnswerPlayer() {
|
|
9
|
+
const audio = typeof Audio === "function" ? new Audio() : null;
|
|
10
|
+
let url = "";
|
|
11
|
+
const release = () => {
|
|
12
|
+
if (!url) return;
|
|
13
|
+
URL.revokeObjectURL(url);
|
|
14
|
+
url = "";
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
unlock() {
|
|
18
|
+
if (!audio || !audio.paused) return;
|
|
19
|
+
audio.src = SILENT_WAV;
|
|
20
|
+
void audio.play().catch(() => {});
|
|
21
|
+
},
|
|
22
|
+
async play(blob) {
|
|
23
|
+
if (!audio || !blob) return false;
|
|
24
|
+
release();
|
|
25
|
+
url = URL.createObjectURL(blob);
|
|
26
|
+
audio.src = url;
|
|
27
|
+
audio.onended = release;
|
|
28
|
+
try {
|
|
29
|
+
await audio.play();
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
release();
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
stop() {
|
|
37
|
+
audio?.pause();
|
|
38
|
+
release();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|