clawmate 1.2.0 → 1.4.0
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/electron-builder.yml +1 -1
- package/index.js +945 -17
- package/main/ai-bridge.js +76 -16
- package/main/ai-connector.js +94 -11
- package/main/autostart.js +3 -3
- package/main/file-command-parser.js +360 -0
- package/main/index.js +19 -5
- package/main/ipc-handlers.js +107 -2
- package/main/platform.js +48 -1
- package/main/smart-file-ops.js +373 -0
- package/main/telegram.js +593 -0
- package/main/tray.js +307 -39
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
- package/preload/preload.js +19 -3
- package/renderer/first-run.html +2 -2
- package/renderer/index.html +2 -0
- package/renderer/js/ai-controller.js +312 -7
- package/renderer/js/app.js +19 -6
- package/renderer/js/browser-watcher.js +172 -0
- package/renderer/js/character.js +119 -22
- package/renderer/js/interactions.js +45 -2
- package/renderer/js/memory.js +108 -1
- package/renderer/js/metrics.js +607 -0
- package/renderer/js/mode-manager.js +53 -9
- package/renderer/js/pet-engine.js +372 -30
- package/renderer/js/state-machine.js +7 -0
- package/renderer/launcher.html +3 -3
- package/shared/messages.js +110 -0
- package/shared/personalities.js +37 -2
- package/skills/launch-pet/index.js +1 -1
- package/skills/launch-pet/skill.json +1 -1
package/renderer/js/character.js
CHANGED
|
@@ -55,7 +55,7 @@ const Character = (() => {
|
|
|
55
55
|
],
|
|
56
56
|
|
|
57
57
|
walk: [
|
|
58
|
-
// Frame 0:
|
|
58
|
+
// Frame 0: 왼쪽 다리 세트 앞으로 크게 벌림, 집게 열림
|
|
59
59
|
[
|
|
60
60
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
61
61
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -70,17 +70,17 @@ const Character = (() => {
|
|
|
70
70
|
[0,0,1,2,1,1,1,1,1,1,1,1,2,1,0,0],
|
|
71
71
|
[0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
|
|
72
72
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
73
|
-
[0,
|
|
74
|
-
[
|
|
73
|
+
[0,3,3,0,0,0,3,3,3,3,0,0,0,3,3,0],
|
|
74
|
+
[3,3,0,0,0,0,0,3,3,0,0,0,0,0,3,3],
|
|
75
75
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
76
76
|
],
|
|
77
|
-
// Frame 1:
|
|
77
|
+
// Frame 1: 양쪽 다리 모임 (접촉 순간), 집게 반 닫힘
|
|
78
78
|
[
|
|
79
79
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
80
80
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
81
|
-
[0,6,0,
|
|
82
|
-
[0,6,0,
|
|
83
|
-
[0,0,6,6,0,0,0,0,0,0,0,
|
|
81
|
+
[0,0,6,0,6,0,0,0,0,0,0,6,0,6,0,0],
|
|
82
|
+
[0,0,6,0,6,0,0,0,0,0,0,6,0,6,0,0],
|
|
83
|
+
[0,0,0,6,6,0,0,0,0,0,0,0,6,6,0,0],
|
|
84
84
|
[0,0,0,6,1,1,0,0,0,0,1,1,6,0,0,0],
|
|
85
85
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
86
86
|
[0,0,0,1,1,4,5,1,1,4,5,1,1,0,0,0],
|
|
@@ -89,11 +89,11 @@ const Character = (() => {
|
|
|
89
89
|
[0,0,1,2,1,1,1,1,1,1,1,1,2,1,0,0],
|
|
90
90
|
[0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
|
|
91
91
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
92
|
-
[0,0,0,3,3,3,
|
|
93
|
-
[0,0,0,
|
|
92
|
+
[0,0,0,3,3,0,3,3,3,3,0,3,3,0,0,0],
|
|
93
|
+
[0,0,0,3,3,0,0,3,3,0,0,3,3,0,0,0],
|
|
94
94
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
95
95
|
],
|
|
96
|
-
// Frame 2:
|
|
96
|
+
// Frame 2: 오른쪽 다리 세트 앞으로 크게 벌림, 집게 닫힘
|
|
97
97
|
[
|
|
98
98
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
99
99
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
@@ -108,17 +108,17 @@ const Character = (() => {
|
|
|
108
108
|
[0,0,1,2,1,1,1,1,1,1,1,1,2,1,0,0],
|
|
109
109
|
[0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
|
|
110
110
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
111
|
-
[0,0,3,3,
|
|
112
|
-
[0,3,3,
|
|
111
|
+
[0,0,3,3,0,0,3,3,3,3,0,0,3,3,0,0],
|
|
112
|
+
[0,3,3,0,0,0,0,3,3,0,0,0,0,3,3,0],
|
|
113
113
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
114
114
|
],
|
|
115
|
-
// Frame 3:
|
|
115
|
+
// Frame 3: 양쪽 다리 모임 (접촉 순간), 집게 반 열림
|
|
116
116
|
[
|
|
117
117
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
118
118
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
119
|
+
[0,6,0,0,6,0,0,0,0,0,0,6,0,0,6,0],
|
|
120
|
+
[0,6,0,0,6,0,0,0,0,0,0,6,0,0,6,0],
|
|
119
121
|
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
120
|
-
[0,0,6,6,0,0,0,0,0,0,0,0,6,6,0,0],
|
|
121
|
-
[0,0,0,6,6,0,0,0,0,0,0,6,6,0,0,0],
|
|
122
122
|
[0,0,0,6,1,1,0,0,0,0,1,1,6,0,0,0],
|
|
123
123
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
124
124
|
[0,0,0,1,1,4,5,1,1,4,5,1,1,0,0,0],
|
|
@@ -127,14 +127,14 @@ const Character = (() => {
|
|
|
127
127
|
[0,0,1,2,1,1,1,1,1,1,1,1,2,1,0,0],
|
|
128
128
|
[0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
|
|
129
129
|
[0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
|
|
130
|
-
[0,0,0,3,3,3,
|
|
131
|
-
[0,0,0,
|
|
130
|
+
[0,0,0,3,3,0,3,3,3,3,0,3,3,0,0,0],
|
|
131
|
+
[0,0,0,3,3,0,0,3,3,0,0,3,3,0,0,0],
|
|
132
132
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
133
133
|
],
|
|
134
134
|
],
|
|
135
135
|
|
|
136
136
|
climb: [
|
|
137
|
-
// Frame 0: 기어오르기
|
|
137
|
+
// Frame 0: 기어오르기 — 윗다리 세트 뻗음, 집게 열림
|
|
138
138
|
[
|
|
139
139
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
140
140
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -147,13 +147,13 @@ const Character = (() => {
|
|
|
147
147
|
[0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0],
|
|
148
148
|
[0,0,0,1,2,1,1,1,1,0,0,0,0,0,0,0],
|
|
149
149
|
[0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0],
|
|
150
|
-
[0,0,0,3,3,0,3,3,0,0,0,0,0,0,0,0],
|
|
151
150
|
[0,0,3,3,0,0,0,3,3,0,0,0,0,0,0,0],
|
|
151
|
+
[0,3,3,0,0,0,0,0,3,3,0,0,0,0,0,0],
|
|
152
152
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
153
153
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
154
154
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
155
155
|
],
|
|
156
|
-
// Frame 1: 기어오르기
|
|
156
|
+
// Frame 1: 기어오르기 — 다리 모임 (교차 순간)
|
|
157
157
|
[
|
|
158
158
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
159
159
|
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
@@ -166,12 +166,50 @@ const Character = (() => {
|
|
|
166
166
|
[0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0],
|
|
167
167
|
[0,0,0,1,2,1,1,1,1,0,0,0,0,0,0,0],
|
|
168
168
|
[0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0],
|
|
169
|
-
[0,0,3,3,0,
|
|
169
|
+
[0,0,0,3,3,0,3,3,0,0,0,0,0,0,0,0],
|
|
170
170
|
[0,0,0,3,3,0,3,3,0,0,0,0,0,0,0,0],
|
|
171
171
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
172
172
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
173
173
|
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
174
174
|
],
|
|
175
|
+
// Frame 2: 기어오르기 — 아랫다리 세트 뻗음, 집게 닫힘
|
|
176
|
+
[
|
|
177
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
178
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
179
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
180
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
181
|
+
[0,0,0,0,6,6,1,1,0,0,0,0,0,0,0,0],
|
|
182
|
+
[0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0],
|
|
183
|
+
[0,0,0,0,1,4,5,1,1,0,0,0,0,0,0,0],
|
|
184
|
+
[0,0,0,0,1,4,5,1,1,1,0,0,0,0,0,0],
|
|
185
|
+
[0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0],
|
|
186
|
+
[0,0,0,1,2,1,1,1,1,0,0,0,0,0,0,0],
|
|
187
|
+
[0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0],
|
|
188
|
+
[0,0,0,3,3,0,3,3,0,0,0,0,0,0,0,0],
|
|
189
|
+
[0,0,3,3,0,0,0,3,3,0,0,0,0,0,0,0],
|
|
190
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
191
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
192
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
193
|
+
],
|
|
194
|
+
// Frame 3: 기어오르기 — 다리 모임 (교차 순간, 반대쪽)
|
|
195
|
+
[
|
|
196
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
197
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
198
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
199
|
+
[0,0,0,0,6,6,0,0,0,0,0,0,0,0,0,0],
|
|
200
|
+
[0,0,0,0,6,6,1,1,0,0,0,0,0,0,0,0],
|
|
201
|
+
[0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0],
|
|
202
|
+
[0,0,0,0,1,4,5,1,1,0,0,0,0,0,0,0],
|
|
203
|
+
[0,0,0,0,1,4,5,1,1,1,0,0,0,0,0,0],
|
|
204
|
+
[0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0],
|
|
205
|
+
[0,0,0,1,2,1,1,1,1,0,0,0,0,0,0,0],
|
|
206
|
+
[0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0],
|
|
207
|
+
[0,0,3,3,0,0,0,3,3,0,0,0,0,0,0,0],
|
|
208
|
+
[0,0,3,3,0,0,0,3,3,0,0,0,0,0,0,0],
|
|
209
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
210
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
211
|
+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
|
212
|
+
],
|
|
175
213
|
],
|
|
176
214
|
|
|
177
215
|
sleep: [
|
|
@@ -335,10 +373,12 @@ const Character = (() => {
|
|
|
335
373
|
jumping: 'excited', // 점프: excited 프레임셋 재활용
|
|
336
374
|
rappelling: 'climb', // 레펠: climb 프레임셋 재활용
|
|
337
375
|
falling: 'scared', // 낙하: scared 프레임셋 재활용
|
|
376
|
+
custom: 'walk', // 커스텀 이동: 기본은 walk, 패턴별로 동적 변경 가능
|
|
338
377
|
};
|
|
339
378
|
|
|
340
379
|
let currentCanvas = null;
|
|
341
380
|
let currentColorMap = null;
|
|
381
|
+
let originalFrames = null; // 원본 프레임 백업 (리셋용)
|
|
342
382
|
|
|
343
383
|
function createCanvas(container) {
|
|
344
384
|
const canvas = document.createElement('canvas');
|
|
@@ -395,5 +435,62 @@ const Character = (() => {
|
|
|
395
435
|
return (FRAMES[frameSet] || FRAMES.idle).length;
|
|
396
436
|
}
|
|
397
437
|
|
|
398
|
-
|
|
438
|
+
/**
|
|
439
|
+
* 커스텀 프레임 데이터 설정 (AI 생성 캐릭터용)
|
|
440
|
+
* 기존 프레임을 백업하고 새 프레임으로 교체
|
|
441
|
+
*/
|
|
442
|
+
function setCustomFrames(newFrames) {
|
|
443
|
+
if (!originalFrames) {
|
|
444
|
+
originalFrames = {};
|
|
445
|
+
for (const [key, value] of Object.entries(FRAMES)) {
|
|
446
|
+
originalFrames[key] = JSON.parse(JSON.stringify(value));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
for (const [key, value] of Object.entries(newFrames)) {
|
|
450
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
451
|
+
FRAMES[key] = value;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* 캐릭터 데이터 일괄 설정 (색상 + 프레임)
|
|
458
|
+
* @param {object} data - { colorMap?, frames? }
|
|
459
|
+
*/
|
|
460
|
+
function setCharacterData(data) {
|
|
461
|
+
if (data.colorMap) {
|
|
462
|
+
setColorMap(data.colorMap);
|
|
463
|
+
}
|
|
464
|
+
if (data.frames) {
|
|
465
|
+
setCustomFrames(data.frames);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 원래 캐릭터로 리셋
|
|
471
|
+
*/
|
|
472
|
+
function resetCharacter() {
|
|
473
|
+
if (originalFrames) {
|
|
474
|
+
for (const key of Object.keys(FRAMES)) {
|
|
475
|
+
delete FRAMES[key];
|
|
476
|
+
}
|
|
477
|
+
Object.assign(FRAMES, originalFrames);
|
|
478
|
+
originalFrames = null;
|
|
479
|
+
}
|
|
480
|
+
// 기본 색상으로 리셋
|
|
481
|
+
setColorMap({
|
|
482
|
+
primary: '#ff4f40',
|
|
483
|
+
secondary: '#ff775f',
|
|
484
|
+
dark: '#8B4513',
|
|
485
|
+
eye: '#ffffff',
|
|
486
|
+
pupil: '#111111',
|
|
487
|
+
claw: '#ff4f40',
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
createCanvas, setColorMap, renderFrame, getFrameCount,
|
|
493
|
+
setCustomFrames, setCharacterData, resetCharacter,
|
|
494
|
+
SIZE, FRAMES, STATE_FRAMES,
|
|
495
|
+
};
|
|
399
496
|
})();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 마우스/클릭/드래그 상호작용 시스템
|
|
3
3
|
*
|
|
4
|
-
* AI 연결 시: 이벤트를
|
|
4
|
+
* AI 연결 시: 이벤트를 AI에 전달 → AI가 반응 결정
|
|
5
5
|
* AI 미연결 시: 자율 반응 (랜덤 FSM)
|
|
6
6
|
*/
|
|
7
7
|
const Interactions = (() => {
|
|
@@ -53,6 +53,8 @@ const Interactions = (() => {
|
|
|
53
53
|
clickTimer = setTimeout(() => {
|
|
54
54
|
if (clickCount >= 3) {
|
|
55
55
|
onTripleClick();
|
|
56
|
+
} else if (clickCount === 2) {
|
|
57
|
+
onDoubleClick();
|
|
56
58
|
} else if (clickCount === 1) {
|
|
57
59
|
onSingleClick();
|
|
58
60
|
}
|
|
@@ -100,18 +102,24 @@ const Interactions = (() => {
|
|
|
100
102
|
PetEngine.start();
|
|
101
103
|
window.clawmate.setClickThrough(true);
|
|
102
104
|
|
|
103
|
-
// AI에 드래그 이벤트 리포트
|
|
105
|
+
// AI에 드래그 이벤트 리포트 + 반응 기록
|
|
104
106
|
if (dragStartPos) {
|
|
107
|
+
const draggedAction = StateMachine.getState();
|
|
108
|
+
Memory.recordReaction(draggedAction, 'drag');
|
|
105
109
|
AIController.reportDrag(dragStartPos, endPos);
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function onSingleClick() {
|
|
110
114
|
const pos = PetEngine.getPosition();
|
|
115
|
+
const currentAction = StateMachine.getState();
|
|
111
116
|
|
|
112
117
|
// AI에 클릭 이벤트 리포트
|
|
113
118
|
AIController.reportClick(pos);
|
|
114
119
|
|
|
120
|
+
// 유저 반응 기록 — 현재 행동 중 클릭 = 긍정 반응
|
|
121
|
+
Memory.recordReaction(currentAction, 'click');
|
|
122
|
+
|
|
115
123
|
// AI 연결 시: AI가 반응 결정 (아무것도 안 함, AI 응답 대기)
|
|
116
124
|
// AI 미연결 시: 자율 반응
|
|
117
125
|
if (AIController.isAutonomous()) {
|
|
@@ -123,7 +131,38 @@ const Interactions = (() => {
|
|
|
123
131
|
spawnHeartEffect();
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
function onDoubleClick() {
|
|
135
|
+
const pos = PetEngine.getPosition();
|
|
136
|
+
const currentAction = StateMachine.getState();
|
|
137
|
+
|
|
138
|
+
// 유저 반응 기록
|
|
139
|
+
Memory.recordReaction(currentAction, 'double_click');
|
|
140
|
+
|
|
141
|
+
// AI에 더블클릭 리포트
|
|
142
|
+
if (window.clawmate.reportToAI) {
|
|
143
|
+
window.clawmate.reportToAI('double_click', { position: pos });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 자율 모드: 더블클릭 = 특별 반응 (점프 + 기분좋음)
|
|
147
|
+
if (AIController.isAutonomous()) {
|
|
148
|
+
StateMachine.forceState('excited');
|
|
149
|
+
PetEngine.jumpTo(
|
|
150
|
+
pos.x + (Math.random() - 0.5) * 200,
|
|
151
|
+
Math.max(100, pos.y - 150)
|
|
152
|
+
);
|
|
153
|
+
Speech.show('우와! 더블클릭이다!');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Memory.recordClick();
|
|
157
|
+
Memory.recordClick(); // 더블클릭 = 2회 클릭
|
|
158
|
+
spawnHeartEffect();
|
|
159
|
+
spawnStarEffect();
|
|
160
|
+
}
|
|
161
|
+
|
|
126
162
|
function onTripleClick() {
|
|
163
|
+
const currentAction = StateMachine.getState();
|
|
164
|
+
Memory.recordReaction(currentAction, 'triple_click');
|
|
165
|
+
|
|
127
166
|
if (typeof ModeManager !== 'undefined') {
|
|
128
167
|
ModeManager.toggle();
|
|
129
168
|
}
|
|
@@ -139,6 +178,10 @@ const Interactions = (() => {
|
|
|
139
178
|
const dist = Math.hypot(e.clientX - (pos.x + 32), e.clientY - (pos.y + 32));
|
|
140
179
|
|
|
141
180
|
if (dist < 100) {
|
|
181
|
+
// 유저 반응 기록 — 커서 접근 = 관심 표현
|
|
182
|
+
const curAction = StateMachine.getState();
|
|
183
|
+
Memory.recordReaction(curAction, 'cursor_near');
|
|
184
|
+
|
|
142
185
|
// AI에 커서 접근 리포트
|
|
143
186
|
AIController.reportCursorNear(dist);
|
|
144
187
|
|
package/renderer/js/memory.js
CHANGED
|
@@ -14,8 +14,22 @@ const Memory = (() => {
|
|
|
14
14
|
milestones: [],
|
|
15
15
|
evolutionStage: 0,
|
|
16
16
|
interactionStreak: 0, // 연속 방문 일수
|
|
17
|
+
|
|
18
|
+
// --- 모션 히스토리 ---
|
|
19
|
+
motionHistory: [], // 최근 100개 상태 전환 기록 [{state, timestamp, duration}]
|
|
20
|
+
motionStats: {}, // 상태별 누적 시간 {idle: 12345, walking: 6789, ...}
|
|
21
|
+
|
|
22
|
+
// --- 유저 반응 저장 ---
|
|
23
|
+
reactionLog: [], // 최근 50개 유저 반응 [{action, reaction, timestamp}]
|
|
24
|
+
favoriteActions: {}, // 행동별 긍정 반응 횟수 {excited: 5, walking: 2, ...}
|
|
25
|
+
dislikedActions: {}, // 행동별 부정 반응 횟수 (무시/이탈)
|
|
17
26
|
};
|
|
18
27
|
|
|
28
|
+
let lastMotionState = null;
|
|
29
|
+
let lastMotionTime = 0;
|
|
30
|
+
const MAX_MOTION_HISTORY = 100;
|
|
31
|
+
const MAX_REACTION_LOG = 50;
|
|
32
|
+
|
|
19
33
|
let evolutionStages = null;
|
|
20
34
|
|
|
21
35
|
async function init() {
|
|
@@ -304,6 +318,95 @@ const Memory = (() => {
|
|
|
304
318
|
container.appendChild(acc);
|
|
305
319
|
}
|
|
306
320
|
|
|
321
|
+
// --- 모션 히스토리 기록 ---
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 상태 전환 기록
|
|
325
|
+
* StateMachine에서 상태 변경 시 호출
|
|
326
|
+
*/
|
|
327
|
+
function recordMotion(newState) {
|
|
328
|
+
const now = Date.now();
|
|
329
|
+
|
|
330
|
+
// 이전 상태의 지속 시간 계산 → 통계 누적
|
|
331
|
+
if (lastMotionState && lastMotionTime > 0) {
|
|
332
|
+
const duration = now - lastMotionTime;
|
|
333
|
+
if (!data.motionStats[lastMotionState]) data.motionStats[lastMotionState] = 0;
|
|
334
|
+
data.motionStats[lastMotionState] += duration;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 히스토리에 추가
|
|
338
|
+
data.motionHistory.push({
|
|
339
|
+
state: newState,
|
|
340
|
+
timestamp: now,
|
|
341
|
+
from: lastMotionState || 'init',
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// 최대 크기 초과 시 오래된 것 제거
|
|
345
|
+
if (data.motionHistory.length > MAX_MOTION_HISTORY) {
|
|
346
|
+
data.motionHistory = data.motionHistory.slice(-MAX_MOTION_HISTORY);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
lastMotionState = newState;
|
|
350
|
+
lastMotionTime = now;
|
|
351
|
+
|
|
352
|
+
// 10회 전환마다 자동 저장
|
|
353
|
+
if (data.motionHistory.length % 10 === 0) save();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 유저 반응 기록
|
|
358
|
+
* 특정 행동 중 사용자가 클릭/드래그 등의 반응을 보인 경우
|
|
359
|
+
*
|
|
360
|
+
* @param {string} action - 펫이 하고 있던 행동
|
|
361
|
+
* @param {string} reaction - 'click' | 'drag' | 'cursor_near' | 'triple_click' | 'double_click'
|
|
362
|
+
*/
|
|
363
|
+
function recordReaction(action, reaction) {
|
|
364
|
+
const now = Date.now();
|
|
365
|
+
|
|
366
|
+
// 반응 로그 추가
|
|
367
|
+
data.reactionLog.push({ action, reaction, timestamp: now });
|
|
368
|
+
if (data.reactionLog.length > MAX_REACTION_LOG) {
|
|
369
|
+
data.reactionLog = data.reactionLog.slice(-MAX_REACTION_LOG);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 클릭/더블클릭은 긍정 반응으로 분류
|
|
373
|
+
if (reaction === 'click' || reaction === 'double_click' || reaction === 'cursor_near') {
|
|
374
|
+
if (!data.favoriteActions[action]) data.favoriteActions[action] = 0;
|
|
375
|
+
data.favoriteActions[action]++;
|
|
376
|
+
}
|
|
377
|
+
// 드래그(잡아서 옮김)는 약간 부정 반응
|
|
378
|
+
if (reaction === 'drag') {
|
|
379
|
+
if (!data.dislikedActions[action]) data.dislikedActions[action] = 0;
|
|
380
|
+
data.dislikedActions[action]++;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
save();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 사용자 선호 행동 Top N 반환
|
|
388
|
+
* AI가 행동 결정 시 참고
|
|
389
|
+
*/
|
|
390
|
+
function getFavoriteActions(topN = 5) {
|
|
391
|
+
const entries = Object.entries(data.favoriteActions || {});
|
|
392
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
393
|
+
return entries.slice(0, topN).map(([action, count]) => ({ action, count }));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* 최근 모션 히스토리 반환
|
|
398
|
+
*/
|
|
399
|
+
function getMotionHistory(limit = 20) {
|
|
400
|
+
return (data.motionHistory || []).slice(-limit);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 상태별 누적 시간 반환
|
|
405
|
+
*/
|
|
406
|
+
function getMotionStats() {
|
|
407
|
+
return { ...(data.motionStats || {}) };
|
|
408
|
+
}
|
|
409
|
+
|
|
307
410
|
async function save() {
|
|
308
411
|
try {
|
|
309
412
|
await window.clawmate.saveMemory(data);
|
|
@@ -318,5 +421,9 @@ const Memory = (() => {
|
|
|
318
421
|
return data.evolutionStage;
|
|
319
422
|
}
|
|
320
423
|
|
|
321
|
-
return {
|
|
424
|
+
return {
|
|
425
|
+
init, recordClick, getData, getEvolutionStage, save,
|
|
426
|
+
recordMotion, recordReaction, getFavoriteActions,
|
|
427
|
+
getMotionHistory, getMotionStats,
|
|
428
|
+
};
|
|
322
429
|
})();
|