@clowder-ai/companion 0.1.0-alpha.3 → 0.1.0-alpha.30

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