@clowder-ai/companion 0.1.0-alpha.2 → 0.1.0-alpha.20
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/NOTICE +10 -2
- package/README.md +59 -0
- package/manifest.json +5 -5
- package/package.json +19 -6
- package/plugin.yaml +5 -5
- package/renderer/call-transcript-view.mjs +238 -0
- package/renderer/client.mjs +13 -1
- package/renderer/companion-identity.mjs +42 -0
- package/renderer/conversation.mjs +144 -24
- package/renderer/decision-view.mjs +124 -0
- package/renderer/errors.mjs +4 -4
- package/renderer/index.html +168 -26
- package/renderer/living-body.mjs +147 -0
- package/renderer/living-edge.mjs +16 -0
- package/renderer/living-geometry.mjs +96 -0
- package/renderer/motion-facts.mjs +108 -0
- package/renderer/native-window-travel.mjs +89 -0
- package/renderer/native-work-motion.mjs +39 -0
- package/renderer/peer.mjs +1 -1
- package/renderer/pet-controls.mjs +130 -0
- package/renderer/pet-motion-assets.mjs +22 -0
- package/renderer/pet-motion.mjs +343 -0
- package/renderer/recent-bubble.mjs +74 -0
- package/renderer/settings-controller.mjs +171 -0
- package/renderer/settings-view.mjs +234 -0
- package/renderer/skins/ragdoll-v1.png +0 -0
- package/renderer/skins/xianxian-codex.webp +0 -0
- package/renderer/skins/xianxian-living/clips/carry.webm +0 -0
- package/renderer/skins/xianxian-living/clips/peek.webm +0 -0
- package/renderer/skins/xianxian-living/clips/peek_fade.webm +0 -0
- package/renderer/skins/xianxian-living/clips/pounce.webm +0 -0
- package/renderer/skins/xianxian-living/clips/sleep.webm +0 -0
- package/renderer/skins/xianxian-living/clips/think.webm +0 -0
- package/renderer/skins/xianxian-living/clips/trot.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wait.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wake.webm +0 -0
- package/renderer/skins/xianxian-living/layers/body.webp +0 -0
- package/renderer/skins/xianxian-living/layers/closed.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/head.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/meta.json +88 -0
- package/renderer/skins/xianxian-living/layers/mouth.webp +0 -0
- package/renderer/skins/xianxian-living/layers/scared.webp +0 -0
- package/renderer/skins/xianxian-living/layers/tail.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteR.webp +0 -0
- package/renderer/skins/xianxian-sleeping-row.png +0 -0
- package/renderer/skins/xianxian-staged-thought-row.png +0 -0
- package/renderer/skins/xianxian-working-row.png +0 -0
- package/renderer/skins/yarn-ball.png +0 -0
- package/renderer/style.css +240 -308
- package/renderer/surface.mjs +350 -69
- package/renderer/transcript-view.mjs +60 -7
- package/source-lock.json +138 -4
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { SettingsController } from './settings-controller.mjs';
|
|
2
|
+
|
|
3
|
+
const skinLabels = {
|
|
4
|
+
'xianxian-codex': '宪宪 v1',
|
|
5
|
+
'yanyan-codex': '砚砚 v1',
|
|
6
|
+
'ragdoll-v1': '布偶猫',
|
|
7
|
+
'yarn-ball': '猫猫球',
|
|
8
|
+
};
|
|
9
|
+
const fieldLabels = {
|
|
10
|
+
dutyCatProfileId: '陪伴者',
|
|
11
|
+
skin: '外观',
|
|
12
|
+
ballSize: '大小',
|
|
13
|
+
behaviorEnabled: '自主活动',
|
|
14
|
+
proactivePolicy: '主动提示',
|
|
15
|
+
personaTone: '人设基调',
|
|
16
|
+
householdReadsAllowed: '资料查询',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class SettingsView {
|
|
20
|
+
constructor({ document, client, close, callActive, onValues,
|
|
21
|
+
expectHostStop = () => false, settleExpectedHostStop = async () => {}, layoutChanged = () => {} }) {
|
|
22
|
+
this.document = document;
|
|
23
|
+
this.$ = id => document.getElementById(id);
|
|
24
|
+
this.close = close;
|
|
25
|
+
this.callActive = callActive;
|
|
26
|
+
this.onValues = onValues;
|
|
27
|
+
this.expectHostStop = expectHostStop;
|
|
28
|
+
this.settleExpectedHostStop = settleExpectedHostStop;
|
|
29
|
+
this.layoutChanged = layoutChanged;
|
|
30
|
+
this.controller = new SettingsController({ client, changed: state => this.render(state) });
|
|
31
|
+
this.bind();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
bind() {
|
|
35
|
+
this.$('settings-back').onclick = () => {
|
|
36
|
+
if (this.controller.state.page === 'main') this.close();
|
|
37
|
+
else this.controller.navigate('main');
|
|
38
|
+
};
|
|
39
|
+
this.$('settings-partner-open').onclick = () => this.controller.navigate('partner');
|
|
40
|
+
this.$('settings-look-open').onclick = () => this.controller.navigate('look');
|
|
41
|
+
this.$('settings-behavior').onclick = () => void this.controller.requestUpdate(
|
|
42
|
+
'behaviorEnabled', !this.controller.state.values?.behaviorEnabled,
|
|
43
|
+
);
|
|
44
|
+
this.$('settings-proactive').onclick = () => void this.controller.requestUpdate(
|
|
45
|
+
'proactivePolicy', this.controller.state.values?.proactivePolicy === 'quiet-badge' ? 'ambient' : 'quiet-badge',
|
|
46
|
+
);
|
|
47
|
+
this.$('settings-documents').onclick = () => {
|
|
48
|
+
const value = !this.controller.state.values?.householdReadsAllowed;
|
|
49
|
+
void this.controller.requestUpdate('householdReadsAllowed', value, { confirm: this.callActive() });
|
|
50
|
+
};
|
|
51
|
+
this.$('settings-tone').onchange = event => {
|
|
52
|
+
const value = event.target.value.replace(/[\r\n]+/gu, ' ').trim();
|
|
53
|
+
event.target.value = value;
|
|
54
|
+
if (!value) { event.target.value = this.controller.state.values?.personaTone ?? ''; return; }
|
|
55
|
+
void this.controller.requestUpdate('personaTone', value);
|
|
56
|
+
};
|
|
57
|
+
this.$('settings-tone').oninput = event => {
|
|
58
|
+
event.target.value = event.target.value.replace(/[\r\n]+/gu, ' ');
|
|
59
|
+
this.$('settings-tone-count').textContent = `${Array.from(event.target.value).length} / 200`;
|
|
60
|
+
};
|
|
61
|
+
this.$('settings-tone').onkeydown = event => {
|
|
62
|
+
if (event.key === 'Enter') event.preventDefault();
|
|
63
|
+
};
|
|
64
|
+
this.$('settings-size').onchange = event => void this.controller.requestUpdate('ballSize', Number(event.target.value));
|
|
65
|
+
for (const id of Object.keys(skinLabels)) {
|
|
66
|
+
this.$(`settings-skin-${id}`).onclick = () => void this.controller.requestUpdate('skin', id);
|
|
67
|
+
}
|
|
68
|
+
this.$('settings-reset').onclick = () => void this.controller.resetPosition();
|
|
69
|
+
this.$('settings-disable-open').onclick = () => this.controller.requestDisable();
|
|
70
|
+
this.$('settings-confirm-cancel').onclick = () => this.controller.navigate(
|
|
71
|
+
this.controller.state.confirmation?.returnPage ?? 'main',
|
|
72
|
+
);
|
|
73
|
+
this.$('settings-confirm-apply').onclick = async () => {
|
|
74
|
+
const expectsStop = this.callActive() && Boolean(this.controller.state.confirmation);
|
|
75
|
+
if (expectsStop) this.expectHostStop();
|
|
76
|
+
const settlement = await this.controller.confirm();
|
|
77
|
+
if (expectsStop) await this.settleExpectedHostStop(settlement?.callStopped ?? null);
|
|
78
|
+
};
|
|
79
|
+
this.$('settings-retry').onclick = () => void this.controller.retry({ confirm: this.callActive() });
|
|
80
|
+
this.$('settings-reread').onclick = () => void this.controller.load();
|
|
81
|
+
this.$('settings-unavailable-retry').onclick = () => void this.controller.load();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
open() {
|
|
85
|
+
this.controller.navigate('main');
|
|
86
|
+
return this.controller.load({ preserveSettlement: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setToggle(id, checked) {
|
|
90
|
+
const node = this.$(id);
|
|
91
|
+
node.setAttribute('aria-checked', String(checked));
|
|
92
|
+
node.dataset.checked = String(checked);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
renderRoster(state) {
|
|
96
|
+
const list = this.$('settings-roster');
|
|
97
|
+
list.replaceChildren();
|
|
98
|
+
for (const companion of state.companions ?? []) {
|
|
99
|
+
const row = this.document.createElement('button');
|
|
100
|
+
row.type = 'button';
|
|
101
|
+
row.className = 'settings-choice';
|
|
102
|
+
row.disabled = !companion.available || state.saving;
|
|
103
|
+
row.dataset.settingControl = 'true';
|
|
104
|
+
row.dataset.selected = String(companion.catProfileId === state.values.dutyCatProfileId);
|
|
105
|
+
const name = this.document.createElement('strong');
|
|
106
|
+
const meta = this.document.createElement('span');
|
|
107
|
+
name.textContent = companion.displayName;
|
|
108
|
+
meta.textContent = companion.available ? '' : '暂不可用';
|
|
109
|
+
row.append(name, meta);
|
|
110
|
+
row.onclick = () => void this.controller.requestUpdate('dutyCatProfileId', companion.catProfileId,
|
|
111
|
+
{ confirm: this.callActive() });
|
|
112
|
+
list.append(row);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
renderValues(state) {
|
|
117
|
+
const values = state.values;
|
|
118
|
+
if (!values) return;
|
|
119
|
+
const selected = state.companions?.find(item => item.catProfileId === values.dutyCatProfileId);
|
|
120
|
+
this.$('settings-partner-value').textContent = selected
|
|
121
|
+
? `${selected.displayName}${selected.available ? '' : ' · 暂不可用'}`
|
|
122
|
+
: '已保存的陪伴者 · 暂不可用 · 请重新选择';
|
|
123
|
+
this.$('settings-look-value').textContent = skinLabels[values.skin] ?? values.skin;
|
|
124
|
+
this.setToggle('settings-behavior', values.behaviorEnabled);
|
|
125
|
+
this.setToggle('settings-proactive', values.proactivePolicy === 'quiet-badge');
|
|
126
|
+
this.setToggle('settings-documents', values.householdReadsAllowed);
|
|
127
|
+
this.$('settings-size').value = String(values.ballSize);
|
|
128
|
+
this.$('settings-tone').value = values.personaTone;
|
|
129
|
+
this.$('settings-tone-count').textContent = `${Array.from(values.personaTone).length} / 200`;
|
|
130
|
+
for (const id of Object.keys(skinLabels)) {
|
|
131
|
+
this.$(`settings-skin-${id}`).dataset.selected = String(values.skin === id);
|
|
132
|
+
}
|
|
133
|
+
this.renderRoster(state);
|
|
134
|
+
this.onValues(values, state);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
savedValue(field, state) {
|
|
138
|
+
const value = state.values?.[field];
|
|
139
|
+
if (field === 'skin') return skinLabels[value] ?? value;
|
|
140
|
+
if (field === 'dutyCatProfileId')
|
|
141
|
+
return state.companions?.find(item => item.catProfileId === value)?.displayName ?? '已保存的陪伴者';
|
|
142
|
+
if (field === 'behaviorEnabled' || field === 'householdReadsAllowed') return value ? '开启' : '关闭';
|
|
143
|
+
if (field === 'proactivePolicy') return value === 'quiet-badge' ? '开启' : '关闭';
|
|
144
|
+
if (field === 'ballSize') return `${value}`;
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
noticeText(notice, state) {
|
|
149
|
+
if (!notice) return '';
|
|
150
|
+
if (notice.kind === 'position_reset') return '已回到默认位置';
|
|
151
|
+
if (notice.kind === 'position_reset_failed') return '还没能回到默认位置 · 请重试';
|
|
152
|
+
if (notice.kind === 'disable_failed') return '桌面猫猫球还没有停用 · 请重试';
|
|
153
|
+
const label = fieldLabels[notice.field] ?? '设置';
|
|
154
|
+
const stopped = notice.callStopped ? '通话已结束,' : '';
|
|
155
|
+
if (notice.kind === 'saved') return notice.applies === 'next_call'
|
|
156
|
+
? `${stopped}${label}已保存 · 下次语音通话生效`
|
|
157
|
+
: `${stopped}${label}已保存`;
|
|
158
|
+
if (notice.kind === 'rejected') {
|
|
159
|
+
if (notice.reason === 'selection_unavailable') return `${stopped}${label}暂不可用 · 请重新选择`;
|
|
160
|
+
if (notice.callStopped) return `通话已结束,${label}未更改`;
|
|
161
|
+
const current = this.savedValue(notice.field, state);
|
|
162
|
+
return current === undefined ? `${label}未保存` : `${label}未保存,仍为“${current}”`;
|
|
163
|
+
}
|
|
164
|
+
if (notice.kind === 'reconciled_saved') return `${stopped}重新读取后确认${label}已保存`;
|
|
165
|
+
if (notice.kind === 'reconciled_not_saved') return notice.callStopped
|
|
166
|
+
? `通话已结束,${label}未更改` : `重新读取后确认${label}未更改`;
|
|
167
|
+
return `${stopped}${label}的保存结果未确认`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
renderSettlement(state) {
|
|
171
|
+
for (const id of ['settings-partner-open', 'settings-look-open', 'settings-behavior',
|
|
172
|
+
'settings-proactive', 'settings-documents', 'settings-tone-field', 'settings-size-field'])
|
|
173
|
+
delete this.$(id).dataset.settlement;
|
|
174
|
+
if (state.notice?.kind !== 'unconfirmed') return;
|
|
175
|
+
const ids = {
|
|
176
|
+
dutyCatProfileId: 'settings-partner-open', skin: 'settings-look-open',
|
|
177
|
+
behaviorEnabled: 'settings-behavior', proactivePolicy: 'settings-proactive',
|
|
178
|
+
householdReadsAllowed: 'settings-documents', personaTone: 'settings-tone-field',
|
|
179
|
+
ballSize: 'settings-size-field',
|
|
180
|
+
};
|
|
181
|
+
const id = ids[state.notice.field];
|
|
182
|
+
if (id) this.$(id).dataset.settlement = 'unconfirmed';
|
|
183
|
+
if (state.notice.field === 'dutyCatProfileId') this.$('settings-partner-value').textContent = '未确认';
|
|
184
|
+
if (state.notice.field === 'skin') this.$('settings-look-value').textContent = '未确认';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
renderConfirmation(state) {
|
|
188
|
+
const confirmation = state.confirmation;
|
|
189
|
+
if (!confirmation) return;
|
|
190
|
+
if (confirmation.kind === 'disable') {
|
|
191
|
+
this.$('settings-confirm-title').textContent = '停用桌面猫猫球';
|
|
192
|
+
this.$('settings-confirm-copy').textContent = '停用后,桌面上不再显示猫猫球,当前语音通话将结束。设置和聊天记录会保留,可在 Cat Café 的“设置 › 插件”中重新启用。';
|
|
193
|
+
this.$('settings-confirm-apply').textContent = '结束语音并停用';
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const disablingReads = confirmation.field === 'householdReadsAllowed' && confirmation.value === false;
|
|
197
|
+
this.$('settings-confirm-title').textContent = fieldLabels[confirmation.field];
|
|
198
|
+
this.$('settings-confirm-copy').textContent = confirmation.field === 'dutyCatProfileId'
|
|
199
|
+
? '更换陪伴者将结束当前语音通话。设置保存后,需要由你重新开始通话。'
|
|
200
|
+
: disablingReads
|
|
201
|
+
? '关闭后,陪伴者在语音通话中无法查询家里的资料。更改这项需要先结束当前通话,之后可重新开始。'
|
|
202
|
+
: '更改资料查询权限需要先结束当前语音通话,之后可由你重新开始。';
|
|
203
|
+
this.$('settings-confirm-apply').textContent = confirmation.field === 'dutyCatProfileId'
|
|
204
|
+
? '结束语音并更换' : '结束语音并更改';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
render(state) {
|
|
208
|
+
const unavailable = state.phase === 'unavailable';
|
|
209
|
+
const disabled = state.phase === 'disabled';
|
|
210
|
+
const requestedPage = disabled ? 'disabled' : unavailable ? 'unavailable' : state.page;
|
|
211
|
+
const page = requestedPage === 'disable' ? 'confirm' : requestedPage;
|
|
212
|
+
for (const name of ['main', 'partner', 'look', 'confirm', 'unavailable', 'disabled']) {
|
|
213
|
+
this.$(`settings-page-${name}`).hidden = name !== page;
|
|
214
|
+
}
|
|
215
|
+
this.$('settings-loading').hidden = state.phase !== 'loading';
|
|
216
|
+
this.$('settings-title').textContent = page === 'partner' ? '陪伴者'
|
|
217
|
+
: page === 'look' ? '外观'
|
|
218
|
+
: ['confirm', 'disable'].includes(page) ? '' : '设置';
|
|
219
|
+
this.$('settings-unavailable-copy').textContent = state.reason === 'host_upgrade_required'
|
|
220
|
+
? '这个 Host 版本还不支持猫旁设置。原来的设置入口会继续保留。'
|
|
221
|
+
: '设置暂时读不到,请稍后重试。';
|
|
222
|
+
for (const control of this.$('settings').querySelectorAll('[data-setting-control]')) control.disabled = state.saving;
|
|
223
|
+
if (state.values) this.renderValues(state);
|
|
224
|
+
this.renderSettlement(state);
|
|
225
|
+
this.renderConfirmation(state);
|
|
226
|
+
const text = this.noticeText(state.notice, state);
|
|
227
|
+
this.$('settings-notice').hidden = !text;
|
|
228
|
+
this.$('settings-notice').textContent = text;
|
|
229
|
+
this.$('settings-notice').dataset.kind = state.notice?.kind ?? '';
|
|
230
|
+
this.$('settings-retry').hidden = !state.retry || page === 'confirm';
|
|
231
|
+
this.$('settings-reread').hidden = state.notice?.kind !== 'unconfirmed';
|
|
232
|
+
this.layoutChanged();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"canvas": 600,
|
|
3
|
+
"tailPivot": [
|
|
4
|
+
168.1,
|
|
5
|
+
456.1
|
|
6
|
+
],
|
|
7
|
+
"earPivotL": [
|
|
8
|
+
218.9,
|
|
9
|
+
101.4
|
|
10
|
+
],
|
|
11
|
+
"earPivotR": [
|
|
12
|
+
391.0,
|
|
13
|
+
100.2
|
|
14
|
+
],
|
|
15
|
+
"neckPivot": [
|
|
16
|
+
300.0,
|
|
17
|
+
280.4
|
|
18
|
+
],
|
|
19
|
+
"irisL": [
|
|
20
|
+
231.1,
|
|
21
|
+
156.5,
|
|
22
|
+
285.6,
|
|
23
|
+
213.4
|
|
24
|
+
],
|
|
25
|
+
"irisR": [
|
|
26
|
+
339.7,
|
|
27
|
+
154.5,
|
|
28
|
+
388.0,
|
|
29
|
+
211.0
|
|
30
|
+
],
|
|
31
|
+
"whiteL": [
|
|
32
|
+
230.6,
|
|
33
|
+
156.5,
|
|
34
|
+
294.3,
|
|
35
|
+
221.1
|
|
36
|
+
],
|
|
37
|
+
"whiteR": [
|
|
38
|
+
331.1,
|
|
39
|
+
154.5,
|
|
40
|
+
391.4,
|
|
41
|
+
219.6
|
|
42
|
+
],
|
|
43
|
+
"tail": [
|
|
44
|
+
47.8,
|
|
45
|
+
313.4,
|
|
46
|
+
178.9,
|
|
47
|
+
507.2
|
|
48
|
+
],
|
|
49
|
+
"earL": [
|
|
50
|
+
169.4,
|
|
51
|
+
26.8,
|
|
52
|
+
258.4,
|
|
53
|
+
140.7
|
|
54
|
+
],
|
|
55
|
+
"earR": [
|
|
56
|
+
345.9,
|
|
57
|
+
20.1,
|
|
58
|
+
437.8,
|
|
59
|
+
140.7
|
|
60
|
+
],
|
|
61
|
+
"mouth": [
|
|
62
|
+
286.6,
|
|
63
|
+
221.5,
|
|
64
|
+
343.1,
|
|
65
|
+
256.9
|
|
66
|
+
],
|
|
67
|
+
"areas_native": {
|
|
68
|
+
"tail": 78634,
|
|
69
|
+
"earL": 23228,
|
|
70
|
+
"earR": 23149,
|
|
71
|
+
"fearL": 9568,
|
|
72
|
+
"fearR": 9138,
|
|
73
|
+
"irisL": 7903,
|
|
74
|
+
"irisR": 7624,
|
|
75
|
+
"whiteL": 10967,
|
|
76
|
+
"whiteR": 10974,
|
|
77
|
+
"mouth": 4342
|
|
78
|
+
},
|
|
79
|
+
"collarTop_native": 580,
|
|
80
|
+
"sitFloorNative": 1135,
|
|
81
|
+
"runCropNative": [
|
|
82
|
+
27,
|
|
83
|
+
181,
|
|
84
|
+
1220,
|
|
85
|
+
1022
|
|
86
|
+
],
|
|
87
|
+
"runFloorNative": 1016
|
|
88
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|