@clowder-ai/companion 0.1.0-alpha.28 → 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 +1 -0
- package/renderer/pet-controls.mjs +9 -2
- package/renderer/style.css +13 -0
- 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>
|
|
@@ -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;
|
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; }
|
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
|
}
|