clawmate 1.3.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 +198 -14
- package/main/ai-bridge.js +24 -18
- package/main/ai-connector.js +17 -12
- package/main/autostart.js +3 -3
- package/main/file-command-parser.js +40 -4
- package/main/index.js +7 -5
- package/main/ipc-handlers.js +6 -3
- package/main/telegram.js +36 -3
- package/main/tray.js +214 -59
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -4
- package/preload/preload.js +4 -4
- package/renderer/first-run.html +2 -2
- package/renderer/js/ai-controller.js +20 -9
- package/renderer/js/app.js +9 -6
- package/renderer/js/browser-watcher.js +1 -1
- package/renderer/js/interactions.js +45 -2
- package/renderer/js/memory.js +108 -1
- package/renderer/js/metrics.js +2 -2
- package/renderer/js/mode-manager.js +53 -9
- package/renderer/launcher.html +3 -3
- 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/app.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* ClawMate 렌더러 초기화
|
|
3
3
|
*
|
|
4
4
|
* 아키텍처:
|
|
5
|
-
*
|
|
5
|
+
* AI (뇌) ←→ AI Bridge (WebSocket) ←→ AI Controller (렌더러)
|
|
6
6
|
* ↓
|
|
7
7
|
* StateMachine / PetEngine / Speech
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* AI 연결 시: AI가 모든 행동/말/감정 결정
|
|
10
|
+
* AI 미연결 시: 자율 모드 (FSM 기반) 로 혼자 놀기
|
|
11
11
|
*/
|
|
12
12
|
(async function initClawMate() {
|
|
13
13
|
const petContainer = document.getElementById('pet-container');
|
|
@@ -45,7 +45,10 @@
|
|
|
45
45
|
Interactions.spawnStarEffect();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// 모션 히스토리 기록
|
|
49
|
+
Memory.recordMotion(newState);
|
|
50
|
+
|
|
51
|
+
// 상태 변화를 AI에 리포트
|
|
49
52
|
if (window.clawmate.reportToAI) {
|
|
50
53
|
window.clawmate.reportToAI('state_change', {
|
|
51
54
|
from: prevState, to: newState,
|
|
@@ -62,7 +65,7 @@
|
|
|
62
65
|
// 메모리 초기화 (진화 상태 포함)
|
|
63
66
|
await Memory.init();
|
|
64
67
|
|
|
65
|
-
// AI 컨트롤러 초기화 (
|
|
68
|
+
// AI 컨트롤러 초기화 (AI 연결 관리)
|
|
66
69
|
AIController.init();
|
|
67
70
|
|
|
68
71
|
// 상호작용 초기화
|
|
@@ -92,7 +95,7 @@
|
|
|
92
95
|
// AI 연결 상태 표시
|
|
93
96
|
const connected = await window.clawmate.isAIConnected();
|
|
94
97
|
if (connected) {
|
|
95
|
-
Speech.show('
|
|
98
|
+
Speech.show('AI와 연결됨. 지시를 기다리는 중...');
|
|
96
99
|
} else {
|
|
97
100
|
Speech.show('안녕! 나 혼자서도 잘 놀 수 있어!');
|
|
98
101
|
}
|
|
@@ -82,7 +82,7 @@ const BrowserWatcher = (() => {
|
|
|
82
82
|
/**
|
|
83
83
|
* AI에 브라우징 컨텍스트 전송
|
|
84
84
|
* 제목 + 커서 위치 + 화면 캡처를 한번에 전송
|
|
85
|
-
* AI
|
|
85
|
+
* AI가 분석하고 코멘트 생성
|
|
86
86
|
*/
|
|
87
87
|
async function reportBrowsingToAI(title, category, titleChanged) {
|
|
88
88
|
if (!window.clawmate.reportToAI) return;
|
|
@@ -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
|
})();
|
package/renderer/js/metrics.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 펫 동작 품질 실시간 계측기 (Self-Observation System)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* ClawMate가 자신의 동작 품질을 관찰하고 계량할 수 있도록
|
|
5
5
|
* 렌더러 측에서 다양한 메트릭을 수집하여 main process로 전송한다.
|
|
6
6
|
*
|
|
7
7
|
* 수집 메트릭:
|
|
@@ -290,7 +290,7 @@ const Metrics = (() => {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
//
|
|
293
|
+
// AI에 상태 변화 리포트
|
|
294
294
|
if (window.clawmate && window.clawmate.reportToAI) {
|
|
295
295
|
window.clawmate.reportToAI('state_change', {
|
|
296
296
|
from: prevState, to: newState,
|
|
@@ -22,14 +22,32 @@ const ModeManager = (() => {
|
|
|
22
22
|
const p = personalities[mode];
|
|
23
23
|
if (!p) return;
|
|
24
24
|
|
|
25
|
+
// Incarnation 모드: 활성 인격체가 있으면 반영
|
|
26
|
+
const persona = (mode === 'incarnation' && window._persona)
|
|
27
|
+
? window._persona.getActivePersona()
|
|
28
|
+
: null;
|
|
29
|
+
|
|
25
30
|
// 캐릭터 색상 업데이트
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
let colors;
|
|
32
|
+
if (mode === 'pet') {
|
|
33
|
+
colors = { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#ffffff', pupil: '#111111', claw: '#ff4f40' };
|
|
34
|
+
} else if (persona?.color) {
|
|
35
|
+
// 인격체 커스텀 색상
|
|
36
|
+
colors = {
|
|
37
|
+
primary: persona.color.primary || '#ff4f40',
|
|
38
|
+
secondary: persona.color.secondary || '#ff775f',
|
|
39
|
+
dark: persona.color.dark || '#8B4513',
|
|
40
|
+
eye: persona.color.eye || '#00BFA5',
|
|
41
|
+
pupil: persona.color.pupil || '#004D40',
|
|
42
|
+
claw: persona.color.claw || '#ff4f40',
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
colors = { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#00BFA5', pupil: '#004D40', claw: '#ff4f40' };
|
|
46
|
+
}
|
|
29
47
|
Character.setColorMap(colors);
|
|
30
48
|
|
|
31
|
-
// 속도 조정
|
|
32
|
-
PetEngine.setSpeedMultiplier(p.speedMultiplier);
|
|
49
|
+
// 속도 조정 (인격체 우선)
|
|
50
|
+
PetEngine.setSpeedMultiplier(persona?.speedMultiplier ?? p.speedMultiplier);
|
|
33
51
|
|
|
34
52
|
// CSS 클래스
|
|
35
53
|
pet.classList.remove('mode-pet', 'mode-incarnation');
|
|
@@ -38,8 +56,34 @@ const ModeManager = (() => {
|
|
|
38
56
|
// 말풍선 스타일
|
|
39
57
|
Speech.setMode(mode);
|
|
40
58
|
|
|
41
|
-
// 성격 적용
|
|
42
|
-
|
|
59
|
+
// 성격 적용 (인격체가 있으면 병합)
|
|
60
|
+
if (persona) {
|
|
61
|
+
StateMachine.setPersonality({
|
|
62
|
+
...p,
|
|
63
|
+
playfulness: persona.playfulness ?? p.playfulness,
|
|
64
|
+
shyness: persona.shyness ?? p.shyness,
|
|
65
|
+
boldness: persona.boldness ?? p.boldness,
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
StateMachine.setPersonality(p);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 인격체 변경 (Incarnation 모드에서 봇 전환 시)
|
|
74
|
+
*/
|
|
75
|
+
function setPersona(personaData) {
|
|
76
|
+
if (window._persona) {
|
|
77
|
+
window._persona.setActivePersona(personaData);
|
|
78
|
+
// 현재 Incarnation 모드면 즉시 반영
|
|
79
|
+
if (currentMode === 'incarnation') {
|
|
80
|
+
applyMode('incarnation');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getPersona() {
|
|
86
|
+
return window._persona ? window._persona.getActivePersona() : null;
|
|
43
87
|
}
|
|
44
88
|
|
|
45
89
|
async function toggle() {
|
|
@@ -50,7 +94,7 @@ const ModeManager = (() => {
|
|
|
50
94
|
spawnTransitionEffect(newMode);
|
|
51
95
|
Speech.show(newMode === 'pet'
|
|
52
96
|
? 'Clawby 모드로 변신!'
|
|
53
|
-
: '
|
|
97
|
+
: 'Claw... 각성했다.');
|
|
54
98
|
}
|
|
55
99
|
|
|
56
100
|
function spawnTransitionEffect(mode) {
|
|
@@ -78,5 +122,5 @@ const ModeManager = (() => {
|
|
|
78
122
|
return currentMode;
|
|
79
123
|
}
|
|
80
124
|
|
|
81
|
-
return { init, toggle, getMode, applyMode };
|
|
125
|
+
return { init, toggle, getMode, applyMode, setPersona, getPersona };
|
|
82
126
|
})();
|
package/renderer/launcher.html
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
|
|
26
26
|
<div class="mode-card" id="mode-incarnation" onclick="selectMode('incarnation')">
|
|
27
27
|
<div class="mode-icon">\u{1F980}</div>
|
|
28
|
-
<h2>
|
|
28
|
+
<h2>Claw</h2>
|
|
29
29
|
<p>육체를 얻은 존재</p>
|
|
30
|
-
<span class="mode-desc">침착하고 대담한
|
|
30
|
+
<span class="mode-desc">침착하고 대담한 Claw가 바탕화면에 강림합니다.</span>
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
33
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
document.getElementById('mode-' + mode).classList.add('selected');
|
|
46
46
|
const btn = document.getElementById('start-btn');
|
|
47
47
|
btn.disabled = false;
|
|
48
|
-
btn.textContent = mode === 'pet' ? 'Clawby 시작!' : '
|
|
48
|
+
btn.textContent = mode === 'pet' ? 'Clawby 시작!' : 'Claw 각성!';
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async function startPet() {
|
package/shared/personalities.js
CHANGED
|
@@ -16,7 +16,7 @@ const PERSONALITIES = {
|
|
|
16
16
|
sleepResistance: 0.2, // 수면 저항 (낮음=잘 잠)
|
|
17
17
|
},
|
|
18
18
|
incarnation: {
|
|
19
|
-
name: '
|
|
19
|
+
name: 'Claw',
|
|
20
20
|
title: '육체를 얻은 존재',
|
|
21
21
|
playfulness: 0.3,
|
|
22
22
|
shyness: 0.1,
|
|
@@ -28,6 +28,40 @@ const PERSONALITIES = {
|
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* 동적 인격체 (Incarnation 모드에서 봇의 인격을 반영)
|
|
33
|
+
*
|
|
34
|
+
* 사용자가 여러 봇을 쓸 때, 현재 포커싱된 채팅의 봇 인격체가 반영됨.
|
|
35
|
+
* set_persona 명령으로 동적 업데이트 가능.
|
|
36
|
+
*/
|
|
37
|
+
let activePersona = null;
|
|
38
|
+
|
|
39
|
+
function setActivePersona(persona) {
|
|
40
|
+
activePersona = {
|
|
41
|
+
name: persona.name || 'Claw',
|
|
42
|
+
title: persona.title || '육체를 얻은 존재',
|
|
43
|
+
personality: persona.personality || '', // "침착하고 논리적인", "활발하고 유머러스한"
|
|
44
|
+
speakingStyle: persona.speakingStyle || '', // "존댓말", "반말", "도도한"
|
|
45
|
+
color: persona.color || null, // { primary, secondary, eye } 커스텀 색상
|
|
46
|
+
playfulness: persona.playfulness ?? 0.3,
|
|
47
|
+
shyness: persona.shyness ?? 0.1,
|
|
48
|
+
boldness: persona.boldness ?? 0.9,
|
|
49
|
+
speedMultiplier: persona.speedMultiplier ?? 1.0,
|
|
50
|
+
idleChatterChance: persona.idleChatterChance ?? 0.08,
|
|
51
|
+
greetings: persona.greetings || [], // 커스텀 인사말 목록
|
|
52
|
+
catchphrases: persona.catchphrases || [], // 특징적 말버릇
|
|
53
|
+
};
|
|
54
|
+
return activePersona;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getActivePersona() {
|
|
58
|
+
return activePersona;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function clearActivePersona() {
|
|
62
|
+
activePersona = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
/**
|
|
32
66
|
* 진화 단계별 외형 변화 파라미터
|
|
33
67
|
* 모든 진화는 긍정적/귀여운 방향으로만 진행
|
|
@@ -101,6 +135,7 @@ const EVOLUTION_STAGES = {
|
|
|
101
135
|
if (typeof window !== 'undefined') {
|
|
102
136
|
window._personalities = PERSONALITIES;
|
|
103
137
|
window._evolutionStages = EVOLUTION_STAGES;
|
|
138
|
+
window._persona = { setActivePersona, getActivePersona, clearActivePersona };
|
|
104
139
|
} else if (typeof module !== 'undefined') {
|
|
105
|
-
module.exports = { PERSONALITIES, EVOLUTION_STAGES };
|
|
140
|
+
module.exports = { PERSONALITIES, EVOLUTION_STAGES, setActivePersona, getActivePersona, clearActivePersona };
|
|
106
141
|
}
|