@clowder-ai/companion 0.1.0-alpha.27 → 0.1.0-alpha.29
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/manifest.json +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/renderer/call-controls-presentation.mjs +179 -0
- package/renderer/index.html +16 -6
- package/renderer/pet-controls.mjs +9 -2
- package/renderer/reply-letter-controller.mjs +18 -0
- package/renderer/reply-letter-view.mjs +23 -10
- package/renderer/style.css +34 -13
- package/renderer/surface.mjs +23 -22
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.29",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.5.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-NnvxPxATjH/A07F1gPzojizh05+046z4melKWHh0K7c="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.29",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.5.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-NnvxPxATjH/A07F1gPzojizh05+046z4melKWHh0K7c="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/** Presentation-only state for the active call surface. Media remains owned by explicit actions. */
|
|
2
|
+
export class CallControlsPresentation {
|
|
3
|
+
constructor({
|
|
4
|
+
document,
|
|
5
|
+
root,
|
|
6
|
+
pet,
|
|
7
|
+
input,
|
|
8
|
+
composers = [],
|
|
9
|
+
send = async () => false,
|
|
10
|
+
accepted = () => {},
|
|
11
|
+
changed = () => {},
|
|
12
|
+
collapseDelay = 1800,
|
|
13
|
+
schedule = (callback, delay) => setTimeout(callback, delay),
|
|
14
|
+
cancel = handle => clearTimeout(handle),
|
|
15
|
+
}) {
|
|
16
|
+
this.document = document;
|
|
17
|
+
this.root = root;
|
|
18
|
+
this.pet = pet;
|
|
19
|
+
this.input = input;
|
|
20
|
+
this.composers = composers;
|
|
21
|
+
this.send = send;
|
|
22
|
+
this.accepted = accepted;
|
|
23
|
+
this.changed = changed;
|
|
24
|
+
this.collapseDelay = collapseDelay;
|
|
25
|
+
this.schedule = schedule;
|
|
26
|
+
this.cancel = cancel;
|
|
27
|
+
this.presentation = 'lite';
|
|
28
|
+
this.panel = 'none';
|
|
29
|
+
this.active = false;
|
|
30
|
+
this.mediaLocked = false;
|
|
31
|
+
this.petHovered = false;
|
|
32
|
+
this.controlsHovered = false;
|
|
33
|
+
this.timer = undefined;
|
|
34
|
+
|
|
35
|
+
this.onPetEnter = () => {
|
|
36
|
+
this.petHovered = true;
|
|
37
|
+
if (this.active) this.reveal();
|
|
38
|
+
};
|
|
39
|
+
this.onPetLeave = () => {
|
|
40
|
+
this.petHovered = false;
|
|
41
|
+
this.armCollapse();
|
|
42
|
+
};
|
|
43
|
+
this.onControlsEnter = () => {
|
|
44
|
+
this.controlsHovered = true;
|
|
45
|
+
this.clearTimer();
|
|
46
|
+
};
|
|
47
|
+
this.onControlsLeave = () => {
|
|
48
|
+
this.controlsHovered = false;
|
|
49
|
+
this.armCollapse();
|
|
50
|
+
};
|
|
51
|
+
this.onFocusIn = () => this.clearTimer();
|
|
52
|
+
this.onFocusOut = () => queueMicrotask(() => this.armCollapse());
|
|
53
|
+
this.onInput = () => this.reevaluate();
|
|
54
|
+
|
|
55
|
+
root.dataset.presentation = this.presentation;
|
|
56
|
+
pet.addEventListener('pointerenter', this.onPetEnter);
|
|
57
|
+
pet.addEventListener('pointerleave', this.onPetLeave);
|
|
58
|
+
root.addEventListener('pointerenter', this.onControlsEnter);
|
|
59
|
+
root.addEventListener('pointerleave', this.onControlsLeave);
|
|
60
|
+
root.addEventListener('focusin', this.onFocusIn);
|
|
61
|
+
root.addEventListener('focusout', this.onFocusOut);
|
|
62
|
+
input.addEventListener('input', this.onInput);
|
|
63
|
+
for (const composer of composers) {
|
|
64
|
+
composer.submit = event => this.submit(event, composer.input);
|
|
65
|
+
composer.form.onsubmit = composer.submit;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setComposerState(identity, connecting) {
|
|
70
|
+
for (const { form, input } of this.composers) {
|
|
71
|
+
input.placeholder = identity ? `发消息给${identity.displayName}…` : '发消息给猫猫…';
|
|
72
|
+
form.querySelector('button').disabled = !identity || connecting;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async submit(event, input) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
const text = input.value;
|
|
79
|
+
if (!await this.send(text)) return;
|
|
80
|
+
if (input.value === text) input.value = '';
|
|
81
|
+
this.reevaluate();
|
|
82
|
+
this.accepted();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setActive(active) {
|
|
86
|
+
if (this.active === active) return;
|
|
87
|
+
this.active = active;
|
|
88
|
+
this.clearTimer();
|
|
89
|
+
if (!active) {
|
|
90
|
+
this.mediaLocked = false;
|
|
91
|
+
this.setPresentation('lite');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.setPresentation(this.mediaLocked ? 'controls' : 'lite');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setPanel(panel) {
|
|
98
|
+
this.panel = panel;
|
|
99
|
+
if (panel === 'actions') this.armCollapse();
|
|
100
|
+
else this.clearTimer();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setMediaLocked(locked) {
|
|
104
|
+
this.mediaLocked = locked;
|
|
105
|
+
if (!this.active) return;
|
|
106
|
+
if (locked) this.reveal();
|
|
107
|
+
else this.armCollapse();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
activate() {
|
|
111
|
+
if (!this.active) return false;
|
|
112
|
+
this.reveal();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
escape(panel) {
|
|
117
|
+
if (!this.active || panel !== 'actions') return false;
|
|
118
|
+
this.clearTimer();
|
|
119
|
+
if (!this.mediaLocked) this.setPresentation('lite');
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
reveal() {
|
|
124
|
+
if (!this.active) return;
|
|
125
|
+
this.clearTimer();
|
|
126
|
+
this.setPresentation('controls');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
reevaluate() {
|
|
130
|
+
if (this.canAutoCollapse()) this.armCollapse();
|
|
131
|
+
else this.clearTimer();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
armCollapse() {
|
|
135
|
+
this.clearTimer();
|
|
136
|
+
if (!this.canAutoCollapse()) return;
|
|
137
|
+
this.timer = this.schedule(() => {
|
|
138
|
+
this.timer = undefined;
|
|
139
|
+
if (this.canAutoCollapse()) this.setPresentation('lite');
|
|
140
|
+
}, this.collapseDelay);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
canAutoCollapse() {
|
|
144
|
+
return this.active
|
|
145
|
+
&& this.presentation === 'controls'
|
|
146
|
+
&& !this.mediaLocked
|
|
147
|
+
&& !this.petHovered
|
|
148
|
+
&& !this.controlsHovered
|
|
149
|
+
&& !this.root.contains(this.document.activeElement)
|
|
150
|
+
&& this.input.value.trim() === '';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setPresentation(next) {
|
|
154
|
+
if (this.presentation === next) return;
|
|
155
|
+
this.presentation = next;
|
|
156
|
+
this.root.dataset.presentation = next;
|
|
157
|
+
this.changed(next);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
clearTimer() {
|
|
161
|
+
if (this.timer === undefined) return;
|
|
162
|
+
this.cancel(this.timer);
|
|
163
|
+
this.timer = undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
close() {
|
|
167
|
+
this.clearTimer();
|
|
168
|
+
this.pet.removeEventListener('pointerenter', this.onPetEnter);
|
|
169
|
+
this.pet.removeEventListener('pointerleave', this.onPetLeave);
|
|
170
|
+
this.root.removeEventListener('pointerenter', this.onControlsEnter);
|
|
171
|
+
this.root.removeEventListener('pointerleave', this.onControlsLeave);
|
|
172
|
+
this.root.removeEventListener('focusin', this.onFocusIn);
|
|
173
|
+
this.root.removeEventListener('focusout', this.onFocusOut);
|
|
174
|
+
this.input.removeEventListener('input', this.onInput);
|
|
175
|
+
for (const composer of this.composers) {
|
|
176
|
+
if (composer.form.onsubmit === composer.submit) composer.form.onsubmit = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
package/renderer/index.html
CHANGED
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
<button id="call-chat" data-action="write"><svg class="icon" aria-hidden="true"><use href="#i-keyboard"/></svg><span>聊天</span><span id="call-chat-unread" class="action-unread" hidden></span></button>
|
|
64
64
|
<button id="end" class="destructive" data-action="stop"><span>挂断</span></button>
|
|
65
65
|
</div>
|
|
66
|
+
<form id="lite-call-compose"><input id="lite-call-message" aria-label="通话中给猫猫发消息" placeholder="发消息给猫猫…" maxlength="8000"><button type="submit" class="primary">发送</button></form>
|
|
66
67
|
<p id="status" role="status" hidden></p>
|
|
67
68
|
</section>
|
|
68
69
|
<section id="menu" class="floating name-card" aria-label="猫猫名片" hidden>
|
|
@@ -118,26 +119,35 @@
|
|
|
118
119
|
<div class="call-context-actions call-context-inline-actions"><button id="chat-mic" data-action="mute">静音</button><button id="chat-share" data-action="share"><span class="label">共享画面</span></button><button data-action="stop" class="destructive compact">挂断</button></div>
|
|
119
120
|
<div id="chat-share-context" class="call-share-line" hidden><svg class="icon" aria-hidden="true"><use href="#i-screen"/></svg><span><strong id="chat-share-target"></strong><small>仅共享此窗口</small></span></div>
|
|
120
121
|
</div>
|
|
121
|
-
<header id="chat-history-header"><strong id="chat-name">和猫猫写几句</strong
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
<header id="chat-history-header"><strong id="chat-name">和猫猫写几句</strong>
|
|
123
|
+
<button id="chat-history-dismiss" data-action="dismiss" aria-label="收起文字">收起</button>
|
|
124
|
+
</header>
|
|
125
|
+
<div id="chat-transcript-shell" class="chat-transcript-shell" hidden>
|
|
126
|
+
<div id="chat-transcript" role="log" aria-live="polite"><p class="empty">想说什么,就写在这里。</p></div>
|
|
127
|
+
<button id="reply-history-close" class="reply-history-close" data-action="dismiss" aria-label="关闭历史" title="关闭历史" hidden>
|
|
128
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 4 8 8m0-8-8 8"/></svg>
|
|
129
|
+
</button>
|
|
130
|
+
<button id="reply-return-latest" class="reply-return-latest" type="button" hidden>回到最新 ↓</button>
|
|
131
|
+
</div>
|
|
124
132
|
<div id="reply-sent" class="reply-sent" tabindex="-1" hidden>
|
|
125
133
|
<svg viewBox="0 0 36 27" aria-hidden="true"><rect x="1" y="1" width="34" height="25" rx="4"/><path d="M2.5 3.5 18 15 33.5 3.5"/></svg>
|
|
126
|
-
<span><strong id="reply-sent-status">已发送</strong><small id="reply-letter-text"></small></span>
|
|
134
|
+
<span><strong id="reply-sent-status">已发送</strong><small id="reply-letter-text" hidden></small></span>
|
|
127
135
|
</div>
|
|
128
136
|
<article id="reply-card" class="reply-card" hidden tabindex="-1">
|
|
129
137
|
<div class="reply-head"><span id="reply-input-text" class="reply-input-text"></span>
|
|
130
138
|
<button id="reply-retain" class="reply-ring" type="button" aria-label="留住回复" title="留住回复">
|
|
131
139
|
<svg viewBox="0 0 16 16" aria-hidden="true"><circle class="reply-ring-track" cx="8" cy="8" r="6.5"/><circle class="reply-ring-progress" cx="8" cy="8" r="6.5"/></svg>
|
|
132
140
|
</button>
|
|
133
|
-
<button id="reply-close" class="reply-close" type="button" aria-label="关闭回复" title="关闭回复" hidden
|
|
141
|
+
<button id="reply-close" class="reply-close" type="button" aria-label="关闭回复" title="关闭回复" hidden>
|
|
142
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 4 8 8m0-8-8 8"/></svg>
|
|
143
|
+
</button>
|
|
134
144
|
</div>
|
|
135
145
|
<div id="reply-body" class="reply-body"></div>
|
|
136
146
|
<small id="reply-body-status" class="reply-body-status" role="status"></small>
|
|
137
147
|
<button id="reply-history-open" class="reply-history-open" type="button">往上翻聊天记录</button>
|
|
138
148
|
</article>
|
|
139
149
|
<form id="compose"><textarea id="message" aria-label="给猫猫输入消息" placeholder="输入消息…" rows="1" maxlength="8000"></textarea><button type="submit" class="primary">发送</button></form>
|
|
140
|
-
<div class="chat-feedback"><small id="chat-status" role="status"></small><button id="reply-retry" type="button" hidden>↻ 重试</button></div>
|
|
150
|
+
<div id="chat-feedback" class="chat-feedback" hidden><small id="chat-status" role="status"></small><button id="reply-retry" type="button" hidden>↻ 重试</button></div>
|
|
141
151
|
<small id="chat-connection-status" role="status"></small>
|
|
142
152
|
<small id="chat-scope"><span id="connection-scope">语音与文字共用同一段聊天</span> <button id="history">在 Cat Café 中查看完整聊天 ↗</button></small>
|
|
143
153
|
</section>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Transient controls. The Host owns native placement, movement and hiding. */
|
|
2
|
-
export function bindPetControls(client, {
|
|
2
|
+
export function bindPetControls(client, {
|
|
3
|
+
action, error, changed, moved = () => {}, activate = () => false, escape = () => false,
|
|
4
|
+
}) {
|
|
3
5
|
const $ = id => document.getElementById(id);
|
|
4
6
|
const body = $('pet');
|
|
5
7
|
const panelIds = ['actions', 'menu', 'chat', 'bubble', 'decisions', 'transcript', 'settings'];
|
|
@@ -72,6 +74,7 @@ export function bindPetControls(client, { action, error, changed, moved = () =>
|
|
|
72
74
|
body.onclick = event => {
|
|
73
75
|
if (suppressClick) { suppressClick = false; return; }
|
|
74
76
|
if (event.detail > 1) return;
|
|
77
|
+
if (activate('click')) return;
|
|
75
78
|
void show(panel === 'menu' ? 'none' : 'menu');
|
|
76
79
|
};
|
|
77
80
|
body.oncontextmenu = event => { event.preventDefault(); void show('menu'); };
|
|
@@ -100,7 +103,11 @@ export function bindPetControls(client, { action, error, changed, moved = () =>
|
|
|
100
103
|
if (!event.target.closest('#anchor, .floating')) void show('none');
|
|
101
104
|
});
|
|
102
105
|
document.addEventListener('keydown', event => {
|
|
103
|
-
if (event.key === 'Escape' && panel !== 'none') {
|
|
106
|
+
if (event.key === 'Escape' && panel !== 'none') {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
if (!escape(panel)) void show('none');
|
|
109
|
+
body.focus();
|
|
110
|
+
}
|
|
104
111
|
});
|
|
105
112
|
document.querySelectorAll('[data-action]').forEach(button => button.onclick = () => {
|
|
106
113
|
const kind = button.dataset.action;
|
|
@@ -25,6 +25,7 @@ export class ReplyLetterController {
|
|
|
25
25
|
this.reply = null;
|
|
26
26
|
this.retained = false;
|
|
27
27
|
this.hasNewer = false;
|
|
28
|
+
this.historyPresentation = null;
|
|
28
29
|
this.selectedMessageId = null;
|
|
29
30
|
this.pauseReasons = new Set();
|
|
30
31
|
this.requestedContinuations = new Set();
|
|
@@ -95,6 +96,7 @@ export class ReplyLetterController {
|
|
|
95
96
|
this.mode = 'compose';
|
|
96
97
|
this.retained = false;
|
|
97
98
|
this.hasNewer = false;
|
|
99
|
+
this.historyPresentation = null;
|
|
98
100
|
this.selectedMessageId = null;
|
|
99
101
|
this.pauseReasons.clear();
|
|
100
102
|
this.emit();
|
|
@@ -164,6 +166,7 @@ export class ReplyLetterController {
|
|
|
164
166
|
this.reply = presentationPair.reply;
|
|
165
167
|
this.selectedMessageId = presentationPair.replyId;
|
|
166
168
|
this.mode = 'reply';
|
|
169
|
+
this.historyPresentation = null;
|
|
167
170
|
if (changedReply) {
|
|
168
171
|
this.retained = false;
|
|
169
172
|
this.pauseReasons.clear();
|
|
@@ -234,6 +237,7 @@ export class ReplyLetterController {
|
|
|
234
237
|
this.stopTimer();
|
|
235
238
|
this.reply = null;
|
|
236
239
|
this.retained = false;
|
|
240
|
+
this.historyPresentation = null;
|
|
237
241
|
this.selectedMessageId = null;
|
|
238
242
|
this.letter = [...this.pending.values()].at(-1) ?? null;
|
|
239
243
|
this.mode = this.letter ? 'sent' : 'idle';
|
|
@@ -258,6 +262,7 @@ export class ReplyLetterController {
|
|
|
258
262
|
this.stopTimer();
|
|
259
263
|
this.reply = null;
|
|
260
264
|
this.retained = false;
|
|
265
|
+
this.historyPresentation = null;
|
|
261
266
|
this.selectedMessageId = null;
|
|
262
267
|
this.letter = restored ?? [...this.pending.values()].at(-1) ?? null;
|
|
263
268
|
this.mode = this.letter ? 'sent' : 'idle';
|
|
@@ -275,6 +280,7 @@ export class ReplyLetterController {
|
|
|
275
280
|
this.reply = null;
|
|
276
281
|
this.retained = false;
|
|
277
282
|
this.hasNewer = false;
|
|
283
|
+
this.historyPresentation = null;
|
|
278
284
|
this.selectedMessageId = null;
|
|
279
285
|
this.requestedContinuations.clear();
|
|
280
286
|
this.dismissedReplies.clear();
|
|
@@ -287,6 +293,17 @@ export class ReplyLetterController {
|
|
|
287
293
|
this.stopTimer();
|
|
288
294
|
this.mode = 'history';
|
|
289
295
|
this.retained = true;
|
|
296
|
+
this.historyPresentation = 'conversation';
|
|
297
|
+
this.selectedMessageId = messageId;
|
|
298
|
+
this.hasNewer = false;
|
|
299
|
+
this.emit();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
openReplyHistory(messageId = newestHistoryAnchor(this.selectedMessageId, this.settled, this.history)) {
|
|
303
|
+
this.stopTimer();
|
|
304
|
+
this.mode = 'history';
|
|
305
|
+
this.retained = true;
|
|
306
|
+
this.historyPresentation = 'reply';
|
|
290
307
|
this.selectedMessageId = messageId;
|
|
291
308
|
this.hasNewer = false;
|
|
292
309
|
this.emit();
|
|
@@ -325,6 +342,7 @@ export class ReplyLetterController {
|
|
|
325
342
|
letter: this.letter,
|
|
326
343
|
reply: this.reply,
|
|
327
344
|
retained: this.retained,
|
|
345
|
+
historyPresentation: this.mode === 'history' ? this.historyPresentation : null,
|
|
328
346
|
selectedMessageId: this.selectedMessageId,
|
|
329
347
|
hasNewer: this.hasNewer,
|
|
330
348
|
blockedReason: this.blockedReason,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const phaseLabel = phase => ({
|
|
2
2
|
processing: '正在回复',
|
|
3
|
-
failed: '回复失败
|
|
4
|
-
interrupted: '回复中断
|
|
5
|
-
canceled: '回复已取消
|
|
6
|
-
withdrawn: '回复已撤回
|
|
3
|
+
failed: '回复失败',
|
|
4
|
+
interrupted: '回复中断',
|
|
5
|
+
canceled: '回复已取消',
|
|
6
|
+
withdrawn: '回复已撤回',
|
|
7
7
|
}[phase] ?? '已发送');
|
|
8
8
|
|
|
9
9
|
const draftStatus = settlement => ({
|
|
@@ -52,28 +52,38 @@ export class ReplyLetterView {
|
|
|
52
52
|
card.addEventListener('pointercancel', () => this.controller.resume('pointer'));
|
|
53
53
|
this.$('reply-retain').onclick = () => this.controller.pin();
|
|
54
54
|
this.$('reply-close').onclick = () => this.controller.close();
|
|
55
|
-
this.$('reply-history-open').onclick = () => this.controller.
|
|
55
|
+
this.$('reply-history-open').onclick = () => this.controller.openReplyHistory();
|
|
56
56
|
this.$('reply-return-latest').onclick = () => this.controller.returnLatest();
|
|
57
57
|
this.$('reply-retry').onclick = () => { if (this.value?.draft) this.retry(this.value.draft); };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
render(value) {
|
|
61
61
|
this.value = value;
|
|
62
|
-
const compose = value.mode === 'compose' || value.mode === 'history';
|
|
63
62
|
const history = value.mode === 'history';
|
|
63
|
+
const replyHistory = history && value.historyPresentation === 'reply';
|
|
64
|
+
const conversationHistory = history && !replyHistory;
|
|
65
|
+
const compose = value.mode === 'compose' || conversationHistory;
|
|
64
66
|
this.$('compose').hidden = !compose;
|
|
65
|
-
this.$('chat-history-header').hidden = !
|
|
67
|
+
this.$('chat-history-header').hidden = !conversationHistory;
|
|
68
|
+
this.$('chat-name').hidden = replyHistory;
|
|
69
|
+
this.$('chat-history-dismiss').hidden = replyHistory;
|
|
70
|
+
this.$('reply-history-close').hidden = !replyHistory;
|
|
71
|
+
this.$('chat-transcript-shell').hidden = !history;
|
|
72
|
+
this.$('chat-transcript-shell').dataset.hasNewer = String(history && value.hasNewer);
|
|
66
73
|
this.$('chat-transcript').hidden = !history;
|
|
67
|
-
this.$('chat-scope').hidden = !
|
|
74
|
+
this.$('chat-scope').hidden = !conversationHistory;
|
|
75
|
+
this.$('chat-connection-status').hidden = replyHistory;
|
|
68
76
|
this.$('reply-sent').hidden = value.mode !== 'sent';
|
|
69
77
|
this.$('reply-card').hidden = value.mode !== 'reply';
|
|
70
78
|
this.$('reply-return-latest').hidden = !history || !value.hasNewer;
|
|
71
79
|
|
|
72
80
|
const retrying = value.mode === 'compose' && value.draft?.settlement === 'unconfirmed';
|
|
73
81
|
this.$('reply-retry').hidden = !retrying;
|
|
74
|
-
|
|
82
|
+
const feedback = value.draft ? draftStatus(value.draft.settlement)
|
|
75
83
|
: value.draftNotice ? draftStatus(value.draftNotice)
|
|
76
84
|
: value.blockedReason === 'permission_required' ? '聊天权限已撤销' : '';
|
|
85
|
+
this.$('chat-status').textContent = feedback;
|
|
86
|
+
this.$('chat-feedback').hidden = !compose || (!feedback && !retrying);
|
|
77
87
|
if (value.draft) {
|
|
78
88
|
const input = this.$('message');
|
|
79
89
|
if (!input.value || input.value === this.lastRenderedDraft) input.value = value.draft.text;
|
|
@@ -81,7 +91,8 @@ export class ReplyLetterView {
|
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
if (value.letter) {
|
|
84
|
-
this.$('reply-letter-text').textContent =
|
|
94
|
+
this.$('reply-letter-text').textContent = '';
|
|
95
|
+
this.$('reply-letter-text').hidden = true;
|
|
85
96
|
this.$('reply-input-text').textContent = value.letter.text;
|
|
86
97
|
this.$('reply-sent-status').textContent = phaseLabel(value.letter.responsePhase);
|
|
87
98
|
}
|
|
@@ -99,5 +110,7 @@ export class ReplyLetterView {
|
|
|
99
110
|
this.$('reply-retain').dataset.running = String(canRetain && value.timer.running);
|
|
100
111
|
this.$('reply-retain').style.setProperty('--reply-visible-ms', `${value.timer.remainingMs}ms`);
|
|
101
112
|
this.$('chat').dataset.replyMode = value.mode;
|
|
113
|
+
this.$('chat').dataset.historyPresentation = history
|
|
114
|
+
? value.historyPresentation ?? 'conversation' : '';
|
|
102
115
|
}
|
|
103
116
|
}
|
package/renderer/style.css
CHANGED
|
@@ -86,10 +86,23 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
86
86
|
.floating .destructive:hover:not(:disabled) { background: #942c24; }
|
|
87
87
|
.floating .destructive.compact { min-width: 72px; padding: 6px 12px; }
|
|
88
88
|
#actions { padding: 12px; width: 412px; overflow: hidden; }
|
|
89
|
+
#actions[data-presentation="lite"] { width: 320px; display: flex; flex-direction: column; align-items: flex-end; border: 0; background: transparent; box-shadow: none; padding: 0; overflow: visible; }
|
|
90
|
+
#actions[data-presentation="lite"] > :not(.call-status-line, .call-recent) { display: none !important; }
|
|
91
|
+
#actions[data-presentation="lite"] .call-status-line { order: 2; min-height: 29px; width: fit-content; border: 1px solid #fff3; border-radius: 8px; background: var(--desktop-ink); box-shadow: 0 3px 8px #1817152e; color: var(--desktop-paper); padding: 5px 10px; }
|
|
92
|
+
#actions[data-presentation="lite"] .call-status-line strong { color: inherit; font-size: 12px; }
|
|
93
|
+
#actions[data-presentation="lite"] .call-status-line span:not(.status-dot) { color: #d8d1c6; font-size: 11px; }
|
|
94
|
+
#actions[data-presentation="lite"] .call-recent { order: 1; align-self: stretch; display: none; margin: 0 0 8px; border: 1px solid var(--desktop-line-strong); border-radius: 12px; background: var(--desktop-paper); box-shadow: 0 8px 18px #1817151f; padding: 9px 11px; }
|
|
95
|
+
#actions[data-presentation="lite"] .call-recent:has(p:not([hidden])) { display: block; }
|
|
96
|
+
#actions[data-presentation="lite"] .call-recent-label { display: none; }
|
|
97
|
+
#actions[data-presentation="lite"] .call-recent p { margin-top: 0; }
|
|
89
98
|
.action-row { display: flex; align-items: center; gap: 4px; }
|
|
90
99
|
.action-row button { display: flex; align-items: center; justify-content: center; gap: 7px; min-height: 35px; font-size: 12px; white-space: nowrap; }
|
|
91
100
|
.action-row button .icon { width: 16px; height: 16px; }
|
|
92
101
|
#active-actions { border-top: 1px solid var(--desktop-line); margin-top: 9px; padding-top: 9px; }
|
|
102
|
+
#lite-call-compose { display: none; align-items: center; gap: 8px; border-top: 1px solid var(--desktop-line); margin-top: 9px; padding-top: 9px; }
|
|
103
|
+
#actions[data-presentation="controls"] #lite-call-compose { display: flex; }
|
|
104
|
+
#lite-call-message { min-width: 0; height: 34px; flex: 1; border: 1px solid var(--desktop-line-strong); border-radius: 8px; background: var(--desktop-paper); color: var(--desktop-ink); padding: 6px 9px; }
|
|
105
|
+
#lite-call-compose button { height: 34px; padding: 6px 12px; }
|
|
93
106
|
#status { font-size: 12px; line-height: 1.45; margin: 8px 0 0; color: var(--desktop-muted); }
|
|
94
107
|
.call-status-line { min-height: 24px; display: flex; align-items: center; gap: 7px; white-space: nowrap; }
|
|
95
108
|
.call-status-line strong { font-size: 13px; font-weight: 600; }
|
|
@@ -188,11 +201,16 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
188
201
|
#work-notice { margin: 0; padding: 3px 11px 7px; color: var(--desktop-muted); font-size: 10px; }
|
|
189
202
|
#chat { width: 400px; max-height: 500px; display: flex; flex-direction: column; overflow: hidden; }
|
|
190
203
|
#chat[data-reply-mode="compose"] { width: 340px; }
|
|
191
|
-
#chat[data-reply-mode="sent"] { width:
|
|
204
|
+
#chat[data-reply-mode="sent"] { width: max-content; border: 0; background: transparent; box-shadow: none; overflow: visible; }
|
|
192
205
|
#chat[data-reply-mode="reply"] { width: 370px; }
|
|
206
|
+
#chat[data-reply-mode="history"][data-history-presentation="reply"] { width: 370px; }
|
|
193
207
|
#chat-history-header { padding: 7px 9px 7px 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--cafe-border-subtle); }
|
|
194
208
|
#chat-history-header button { font-size: 12px; color: var(--cafe-text-secondary); }
|
|
195
|
-
|
|
209
|
+
.chat-transcript-shell { position: relative; min-height: 0; flex: 1 1 230px; }
|
|
210
|
+
#chat[data-history-presentation="reply"] .chat-transcript-shell { flex-basis: 480px; }
|
|
211
|
+
#chat-transcript { height: 100%; overflow: auto; padding: 14px; line-height: 1.7; font-size: 13px; }
|
|
212
|
+
.chat-transcript-shell[data-has-newer="true"] #chat-transcript { padding-bottom: 56px; }
|
|
213
|
+
#chat[data-history-presentation="reply"] #chat-transcript { padding-top: 44px; }
|
|
196
214
|
#chat-transcript p { margin: 0 0 10px; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
197
215
|
#chat-transcript p:not(.empty) { width: fit-content; max-width: 92%; padding: 7px 10px; border-radius: 9px; }
|
|
198
216
|
#chat-transcript p.user { margin-left: auto; background: var(--desktop-rail); }
|
|
@@ -201,32 +219,35 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
201
219
|
#compose { display: flex; align-items: center; padding: 10px; gap: 8px; border-top: 1px solid var(--cafe-border-subtle); }
|
|
202
220
|
#chat[data-reply-mode="compose"] #compose { border: 0; }
|
|
203
221
|
#message { resize: none; border: 1px solid var(--desktop-line-strong); border-radius: 8px; min-width: 0; height: 34px; flex: 1; background: var(--desktop-paper); padding: 6px 9px; color: inherit; line-height: 1.5; }
|
|
222
|
+
#message:focus-visible { border-color: transparent; outline: 2px solid var(--cafe-accent); outline-offset: -2px; }
|
|
204
223
|
.chat-feedback { min-height: 0; display: flex; align-items: center; gap: 8px; padding: 0 12px 8px; color: #8f4730; }
|
|
205
224
|
.chat-feedback small { flex: 1; font-size: 11px; }
|
|
206
225
|
.chat-feedback button { color: #8f4730; font-size: 11px; }
|
|
207
226
|
#chat-connection-status, #chat-scope { color: var(--cafe-text-muted); font-size: 10px; padding: 0 14px 10px; }
|
|
208
|
-
|
|
209
|
-
.reply-sent
|
|
227
|
+
#chat-connection-status:empty { display: none; }
|
|
228
|
+
.reply-sent { align-items: center; gap: 5px; background: transparent; padding: 0; }
|
|
229
|
+
.reply-sent:not([hidden]) { display: flex; flex-direction: column; }
|
|
210
230
|
.reply-sent > svg { width: 36px; height: 27px; flex: none; overflow: visible; }
|
|
211
231
|
.reply-sent > svg rect { fill: var(--desktop-paper); stroke: var(--desktop-ink); stroke-width: 1.5; }
|
|
212
232
|
.reply-sent > svg path { fill: none; stroke: var(--desktop-ink); stroke-width: 1.5; stroke-linejoin: round; }
|
|
213
|
-
.reply-sent > span { min-width: 0;
|
|
214
|
-
.reply-sent strong { font-size:
|
|
215
|
-
.reply-sent small {
|
|
233
|
+
.reply-sent > span { min-width: 0; }
|
|
234
|
+
.reply-sent strong { display: block; border-radius: 999px; background: var(--desktop-ink); color: var(--desktop-paper); padding: 3px 8px; font-size: 10px; font-weight: 500; white-space: nowrap; }
|
|
235
|
+
.reply-sent small { display: none; }
|
|
216
236
|
.reply-card { position: relative; max-height: 500px; overflow-y: auto; padding: 12px 14px 10px; }
|
|
217
237
|
.reply-head { display: flex; align-items: flex-start; gap: 8px; }
|
|
218
238
|
.reply-input-text { min-width: 0; flex: 1; color: var(--desktop-muted); font-size: 12px; line-height: 1.45; overflow-wrap: anywhere; }
|
|
219
|
-
.reply-ring, .reply-close { width: 24px; height: 24px; flex: none; display: grid; place-items: center; padding: 0; color: var(--desktop-muted); }
|
|
239
|
+
#chat .reply-ring, #chat .reply-close, #chat .reply-history-close { width: 24px; height: 24px; flex: none; display: grid; place-items: center; padding: 0; color: var(--desktop-muted); }
|
|
220
240
|
.reply-ring svg { width: 18px; height: 18px; transform: rotate(-90deg); }
|
|
221
|
-
.reply-ring circle { fill: none; stroke-width: 1
|
|
222
|
-
.reply-ring-track { stroke: var(--desktop-line
|
|
223
|
-
.reply-ring-progress { stroke: var(--desktop-
|
|
241
|
+
.reply-ring circle { fill: none; stroke-width: 1; }
|
|
242
|
+
.reply-ring-track { stroke: var(--desktop-line); }
|
|
243
|
+
.reply-ring-progress { stroke: var(--desktop-muted); stroke-linecap: round; stroke-dasharray: 40.84; stroke-dashoffset: 0; }
|
|
224
244
|
.reply-ring[data-running="true"] .reply-ring-progress { animation: reply-countdown var(--reply-visible-ms) linear forwards; }
|
|
225
|
-
.reply-close {
|
|
245
|
+
.reply-close svg, .reply-history-close svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; }
|
|
246
|
+
.reply-history-close { position: absolute; top: 10px; right: 10px; z-index: 1; background: var(--desktop-rail); }
|
|
226
247
|
.reply-body { margin-top: 7px; color: var(--desktop-ink); font-size: 14px; line-height: 1.65; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
227
248
|
.reply-body-status { display: block; margin-top: 6px; color: var(--desktop-muted); font-size: 11px; }
|
|
228
249
|
.reply-history-open { margin-top: 8px; padding: 3px 0; color: var(--desktop-muted); font-size: 11px; }
|
|
229
|
-
.reply-return-latest { position: absolute;
|
|
250
|
+
.reply-return-latest { position: absolute; left: 50%; bottom: 10px; z-index: 1; transform: translateX(-50%); border: 1px solid var(--desktop-line) !important; background: var(--desktop-ink) !important; box-shadow: 0 2px 8px #1817151a; color: var(--desktop-paper); font-size: 11px; white-space: nowrap; }
|
|
230
251
|
@keyframes reply-countdown { to { stroke-dashoffset: 40.84; } }
|
|
231
252
|
.call-transcript { width: 420px; height: 210px; display: flex; flex-direction: column; overflow: hidden; }
|
|
232
253
|
.call-transcript[data-expanded="true"] { height: 500px; }
|
package/renderer/surface.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { WorkOverviewView } from './work-overview.mjs';
|
|
|
21
21
|
import { MeetingStatus, renderMeetingStatus } from './meeting-state.mjs';
|
|
22
22
|
import { VisibleTextPresentation } from './visible-text-receipt.mjs';
|
|
23
23
|
import { ConversationSurface } from './conversation-surface.mjs';
|
|
24
|
+
import { CallControlsPresentation } from './call-controls-presentation.mjs';
|
|
24
25
|
const $ = id => document.getElementById(id);
|
|
25
26
|
const label = (id, text) => { $(id).querySelector('.label').textContent = text; };
|
|
26
27
|
const status = text => {
|
|
@@ -50,7 +51,7 @@ const nativeTravel = new NativeWindowTravel({
|
|
|
50
51
|
changed: value => motion.syncTravel(value),
|
|
51
52
|
});
|
|
52
53
|
let sharing = false, pendingScreen = false, transcriptLoading = false, previousPhase = 'idle';
|
|
53
|
-
let didMove = false, settingsView, settingsValues, decisionPanel, workView;
|
|
54
|
+
let didMove = false, settingsView, settingsValues, decisionPanel, workView, callControlsPresentation;
|
|
54
55
|
let previousControlPanel = 'none';
|
|
55
56
|
// F229 stays quiet: a history ID change has no provenance for proactive text.
|
|
56
57
|
const ambientPanel = active => active ? 'actions' : 'none';
|
|
@@ -70,6 +71,8 @@ if (!window.clowderCompanion) {
|
|
|
70
71
|
transcript, callTranscript, bubble, callBubble,
|
|
71
72
|
});
|
|
72
73
|
const controls = bindPetControls(client, {
|
|
74
|
+
activate: () => { const handled = callControlsPresentation?.activate() ?? false; if (handled) void controls.show('actions'); return handled; },
|
|
75
|
+
escape: panel => callControlsPresentation?.escape(panel) ?? false,
|
|
73
76
|
error: (error, context) => status(context?.panel === 'settings' && error?.code === 'invalid_request' ? '这个 Host 版本还不支持猫旁设置。原来的设置入口会继续保留。' : explainError(error)),
|
|
74
77
|
moved: (dx, dy) => {
|
|
75
78
|
if (dx !== 'stop') { didMove = true; nativeTravel.setManual(true); motion.move(dx, dy); return; }
|
|
@@ -81,6 +84,7 @@ if (!window.clowderCompanion) {
|
|
|
81
84
|
setTimeout(() => motion.setDockedEdge(detectDockedEdge(window, $('pet').getBoundingClientRect())), 100);
|
|
82
85
|
},
|
|
83
86
|
changed: panel => {
|
|
87
|
+
callControlsPresentation?.setPanel(panel);
|
|
84
88
|
visibleText.retry();
|
|
85
89
|
if (previousControlPanel === 'chat' && panel !== 'chat') conversationSurface.closeHistory();
|
|
86
90
|
if (panel === 'chat' && conversationSurface.controller.mode === 'idle') conversationSurface.openHistory();
|
|
@@ -123,15 +127,23 @@ if (!window.clowderCompanion) {
|
|
|
123
127
|
}
|
|
124
128
|
},
|
|
125
129
|
});
|
|
130
|
+
callControlsPresentation = new CallControlsPresentation({
|
|
131
|
+
document, root: $('actions'), pet: $('pet'), input: $('lite-call-message'),
|
|
132
|
+
composers: [{ form: $('call-compose'), input: $('call-message') },
|
|
133
|
+
{ form: $('lite-call-compose'), input: $('lite-call-message') }],
|
|
134
|
+
send: text => conversation.send(text),
|
|
135
|
+
accepted: () => { motion.signal('received'); void conversationSurface.readHistory(); },
|
|
136
|
+
changed: () => controls.remeasure(),
|
|
137
|
+
});
|
|
126
138
|
const screen = new ScreenShare({
|
|
127
|
-
screenRequest: () => client.screenPick(),
|
|
128
|
-
screenStart: (selectionId, name) => client.screenOpen(selectionId, name),
|
|
139
|
+
screenRequest: () => client.screenPick(), screenStart: (selectionId, name) => client.screenOpen(selectionId, name),
|
|
129
140
|
screenFrame: (selectionId, frame) => client.screenFrame(selectionId, {
|
|
130
141
|
...frame, frameId: crypto.randomUUID(), sourceLabel: '所选画面', observedAt: Date.now(),
|
|
131
142
|
}), screenStop: () => client.screenClose(),
|
|
132
143
|
}, value => {
|
|
133
144
|
const wasActive = sharing || pendingScreen;
|
|
134
145
|
sharing = value.sharing; pendingScreen = value.pending === true;
|
|
146
|
+
callControlsPresentation.setMediaLocked(sharing || pendingScreen);
|
|
135
147
|
for (const id of ['share', 'menu-share', 'menu-call-share', 'chat-share', 'settings-share']) label(id, sharing ? '停止共享' : pendingScreen ? '取消选屏' : '共享画面');
|
|
136
148
|
for (const prefix of ['call', 'menu', 'chat', 'transcript', 'settings']) {
|
|
137
149
|
$(`${prefix}-share-context`).hidden = !sharing;
|
|
@@ -181,6 +193,7 @@ if (!window.clowderCompanion) {
|
|
|
181
193
|
const canReceiveOnly = value.identity?.audio?.supportedModes?.includes('receive_only') === true;
|
|
182
194
|
const retryReceiveOnly = value.failed && value.retryMode === 'receive_only' && canReceiveOnly;
|
|
183
195
|
visibleText.setCall(value.callId);
|
|
196
|
+
callControlsPresentation.setActive(active);
|
|
184
197
|
void controls.setAmbient(ambientPanel(active));
|
|
185
198
|
$('begin').hidden = active; $('begin').disabled = !value.identity;
|
|
186
199
|
$('listen').hidden = active || !canReceiveOnly; $('listen').disabled = !value.identity;
|
|
@@ -197,6 +210,7 @@ if (!window.clowderCompanion) {
|
|
|
197
210
|
$('share').disabled = value.phase !== 'talking';
|
|
198
211
|
$('menu-call-share').disabled = value.phase !== 'talking';
|
|
199
212
|
$('compose').querySelector('button').disabled = !value.identity || value.phase === 'connecting';
|
|
213
|
+
callControlsPresentation.setComposerState(value.identity, value.phase === 'connecting');
|
|
200
214
|
status(value.message ?? '');
|
|
201
215
|
$('status').hidden = !value.message || /^(点击|正在听|只听模式|只听已结束|语音通话已结束|已发送)/.test(value.message);
|
|
202
216
|
$('menu-status').hidden = !value.message;
|
|
@@ -232,8 +246,7 @@ if (!window.clowderCompanion) {
|
|
|
232
246
|
const companionIdentity = currentCompanionIdentity(identity);
|
|
233
247
|
renderMeetingStatus(document, meetingStatus.update(active ? identity?.meeting : null));
|
|
234
248
|
const nativeSnapshot = normalizeNativeWork(identity?.nativeWork);
|
|
235
|
-
const workLabel = nativeWorkLabel(nativeSnapshot, identity?.nativeActivity);
|
|
236
|
-
const workState = workLabel ? `${identity.displayName}${workLabel}` : '';
|
|
249
|
+
const workLabel = nativeWorkLabel(nativeSnapshot, identity?.nativeActivity), workState = workLabel ? `${identity.displayName}${workLabel}` : '';
|
|
237
250
|
setTexts(['call-work', 'chat-call-work', 'transcript-call-work', 'menu-call-work', 'settings-call-work'], workState);
|
|
238
251
|
setHidden(['call-work', 'chat-call-work', 'transcript-call-work', 'menu-call-work', 'settings-call-work'], !workState);
|
|
239
252
|
motion.setContext({ skin: identity?.skin, phase: value.phase,
|
|
@@ -248,8 +261,8 @@ if (!window.clowderCompanion) {
|
|
|
248
261
|
? `${companionIdentity.title} · ${companionIdentity.partnerLabel}` : identity.displayName;
|
|
249
262
|
$('chat-name').textContent = conversationSurface?.threadTitle ?? companionLabel; $('menu-name').textContent = companionLabel;
|
|
250
263
|
$('message').placeholder = `发消息给${identity.displayName}…`;
|
|
251
|
-
$('
|
|
252
|
-
|
|
264
|
+
$('pet').setAttribute('aria-label', active ? `${companionLabel}:指向或点击显示通话控制,右键打开名片,拖动移动`
|
|
265
|
+
: `${companionLabel}:点击或右键打开名片,拖动移动`);
|
|
253
266
|
$('documents').querySelector('.menu-status').textContent = identity.documentsAllowed ? '开启' : '暂停';
|
|
254
267
|
$('documents').setAttribute('aria-pressed', String(identity.documentsAllowed));
|
|
255
268
|
if (companionIdentity) {
|
|
@@ -322,28 +335,16 @@ if (!window.clowderCompanion) {
|
|
|
322
335
|
} finally { transcriptLoading = false; }
|
|
323
336
|
}
|
|
324
337
|
$('share-badge').onclick = toggleScreen;
|
|
325
|
-
async function submitCallMessage(event) {
|
|
326
|
-
event.preventDefault(); const input = $('call-message'); const text = input.value;
|
|
327
|
-
if (await conversation.send(text)) {
|
|
328
|
-
motion.signal('received');
|
|
329
|
-
if (input.value === text) input.value = '';
|
|
330
|
-
void conversationSurface.readHistory();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
$('call-compose').onsubmit = submitCallMessage;
|
|
334
338
|
$('call-unread').onclick = () => callTranscript.scrollToBottom();
|
|
335
339
|
const unsubscribe = client.subscribe(event => {
|
|
336
340
|
if (event.kind === 'media-stopped') void conversation.hostStopped(event.reason);
|
|
337
341
|
if (event.kind === 'view-dismiss') controls.dismiss();
|
|
338
342
|
});
|
|
339
|
-
const statusMonitor = setInterval(() => void conversation.refresh(), 1000);
|
|
340
|
-
const
|
|
341
|
-
const decisionMonitor = setInterval(() => { if (controls.panel !== 'decisions') void decisionPanel.read(); }, 15000);
|
|
342
|
-
const workMonitor = setInterval(() => { if (controls.panel === 'menu') void workView.load(); }, 15000);
|
|
343
|
+
const statusMonitor = setInterval(() => void conversation.refresh(), 1000), transcriptMonitor = setInterval(() => void readCallTranscript(), 1000);
|
|
344
|
+
const decisionMonitor = setInterval(() => { if (controls.panel !== 'decisions') void decisionPanel.read(); }, 15000), workMonitor = setInterval(() => { if (controls.panel === 'menu') void workView.load(); }, 15000);
|
|
343
345
|
void conversation.refresh(); conversationSurface.start(); void decisionPanel.read(); void settingsView.controller.load(); void controls.show('none');
|
|
344
346
|
window.addEventListener('beforeunload', () => {
|
|
345
|
-
clearInterval(statusMonitor); clearInterval(transcriptMonitor); clearInterval(decisionMonitor); clearInterval(workMonitor);
|
|
346
|
-
conversationSurface.close();
|
|
347
|
+
clearInterval(statusMonitor); clearInterval(transcriptMonitor); clearInterval(decisionMonitor); clearInterval(workMonitor); conversationSurface.close(); callControlsPresentation.close();
|
|
347
348
|
unsubscribe(); visibleText.close(); nativeTravel.close(); motion.close(); void conversation.end();
|
|
348
349
|
});
|
|
349
350
|
}
|