humanda5 commited on
Commit
2cd8c90
·
1 Parent(s): 033af6f

[테스트중]사용자 개입 구현

Browse files
npc_social_network/npc/npc_relationship.py CHANGED
@@ -2,6 +2,8 @@
2
  # portfolio/npc_social_network/npc/npc_relationship.py
3
 
4
  from typing import List, Optional, TYPE_CHECKING
 
 
5
  if TYPE_CHECKING:
6
  from .npc_memory import Memory
7
  from .npc_base import NPC
@@ -82,6 +84,36 @@ class RelationshipManager:
82
  profile = self._get_or_create_profile(target_name)
83
  return profile.summary
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def summarize_relationship(self, target_name: str, npc_manager: "NPCManager"):
86
  """ LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
87
  from ..models.llm_helper import query_llm_with_prompt
 
2
  # portfolio/npc_social_network/npc/npc_relationship.py
3
 
4
  from typing import List, Optional, TYPE_CHECKING
5
+ from .npc_manager import get_korean_postposition
6
+
7
  if TYPE_CHECKING:
8
  from .npc_memory import Memory
9
  from .npc_base import NPC
 
84
  profile = self._get_or_create_profile(target_name)
85
  return profile.summary
86
 
87
+ def set_relationship(self, target_name: str, relationship_type: str):
88
+ """플레이어 개입 등으로 관계 유형과 점수를 직접 설정"""
89
+ from .. import simulation_core
90
+
91
+ profile = self._get_or_create_profile(target_name)
92
+
93
+ # 설정된 타입에 따라 점수를 부여 (값 조절 가능)
94
+ score_map = {
95
+ "best friend": 80.0,
96
+ "friend": 50.0,
97
+ "acquaintance": 10.0,
98
+ "stranger": 0.0,
99
+ "nuisance": -10.0,
100
+ "rival": -50.0,
101
+ "enemy": -80.0
102
+ }
103
+
104
+ profile.type = relationship_type
105
+ profile.score = score_map.get(relationship_type, 0.0)
106
+
107
+ target_npc = self.owner_npc.manager.get_npc_by_korean_name(target_name)
108
+ target_korean_name = target_npc.korean_name if target_npc else target_name
109
+
110
+ self_postposition = get_korean_postposition(self.owner_npc.korean_name, "은", "는")
111
+ target_postposition = get_korean_postposition(target_korean_name, "과", "와")
112
+ # 관계 요약도 간단하게 업데이트
113
+ profile.summary = f"{target_korean_name}{target_postposition} {self.owner_npc.korean_name}{self_postposition} {relationship_type} 관계이다."
114
+
115
+ simulation_core.add_log(f"[관계 설정] {self.owner_npc.korean_name} -> {target_korean_name} 관계가 '{relationship_type}'(으)로 설정되었습니다.")
116
+
117
  def summarize_relationship(self, target_name: str, npc_manager: "NPCManager"):
118
  """ LLM을 사용하여 특정 대상과의 관계를 주기적으로 요약하고 업데이트"""
119
  from ..models.llm_helper import query_llm_with_prompt
npc_social_network/routes/npc_route.py CHANGED
@@ -113,4 +113,27 @@ def get_npc_details(npc_name):
113
  "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
114
  "memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
115
  }
116
- return jsonify(details)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "특별한 목표 없음",
114
  "memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
115
  }
116
+ return jsonify(details)
117
+
118
+ @npc_bp.route("/api/force_relationship", methods=['POST'])
119
+ def force_relationship():
120
+ """두 NPC의 관계를 강제로 설정하는 API"""
121
+ data = request.json
122
+ npc1_name = data.get("npc1_name")
123
+ npc2_name = data.get("npc2_name")
124
+ relationship_type = data.get("relationship_type")
125
+
126
+ if not all([npc1_name, npc2_name, relationship_type]) or npc1_name == npc2_name:
127
+ return jsonify({"success": False, "error": "Invalid data"}), 400
128
+
129
+ with simulation_core.simulation_lock:
130
+ npc1 = simulation_core.npc_manager.get_npc_by_korean_name(npc1_name)
131
+ npc2 = simulation_core.npc_manager.get_npc_by_korean_name(npc2_name)
132
+
133
+ if npc1 and npc2:
134
+ # 관계는 상호작용이므로, 양쪽 모두에게 관계를 설정
135
+ npc1.relationships.set_relationship(npc2.name, relationship_type)
136
+ npc2.relationships.set_relationship(npc1.name, relationship_type)
137
+ return jsonify({"success": True})
138
+
139
+ return jsonify({"success": False, "error": "NPC not found"}), 404
npc_social_network/templates/dashboard.html CHANGED
@@ -26,6 +26,53 @@
26
  #log-panel { flex-basis: 55%; display: flex; flex-direction: column; }
27
  #log-container { flex: 1; overflow-y: auto; font-size: 13px; line-height: 1.6; color: #495057; white-space: pre-wrap; }
28
  h2 { margin-top: 0; margin-bottom: 15px; font-size: 18px; color: #343a40; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </style>
30
  </head>
31
  <body>
@@ -33,12 +80,35 @@
33
  <div id="network-panel">
34
  <div id="network"></div>
35
  <div id="controls">
 
 
 
 
 
36
  <button id="play-pause-btn">Play / Pause</button>
37
  <button id="tick-btn">Next Tick</button>
38
  <button id="refresh-btn">⟳ 새로고침</button>
39
- <select id="event-npc-select"></select>
40
- <input type="text" id="event-text-input" placeholder="이벤트 내용">
41
- <button id="inject-event-btn">이벤트 주입</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </div>
43
  </div>
44
  <div id="side-panel">
@@ -65,6 +135,10 @@
65
  const injectEventBtn = document.getElementById('inject-event-btn');
66
  const eventNpcSelect = document.getElementById('event-npc-select');
67
  const eventTextInput = document.getElementById('event-text-input');
 
 
 
 
68
 
69
  let network = null;
70
  let nodesDataSet = new vis.DataSet([]);
@@ -135,16 +209,18 @@
135
  }
136
 
137
  function updateNpcSelect(npcNodes) {
138
- const currentSelection = eventNpcSelect.value;
139
- eventNpcSelect.innerHTML = '';
140
- npcNodes.forEach(node => {
141
- const option = document.createElement('option');
142
- // FINAL FIX: select 박스의 value와 text에 모두 한글 이름(label)을 사용합니다.
143
- option.value = node.label;
144
- option.textContent = node.label;
145
- eventNpcSelect.appendChild(option);
146
- });
147
- if (currentSelection) eventNpcSelect.value = currentSelection;
 
 
148
  }
149
 
150
  function updateLog(logMessages) {
@@ -163,6 +239,21 @@
163
  autoUpdateInterval = null;
164
  }
165
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
  // 모든 이벤트 리스너 복구
168
  playPauseBtn.addEventListener('click', async () => {
 
26
  #log-panel { flex-basis: 55%; display: flex; flex-direction: column; }
27
  #log-container { flex: 1; overflow-y: auto; font-size: 13px; line-height: 1.6; color: #495057; white-space: pre-wrap; }
28
  h2 { margin-top: 0; margin-bottom: 15px; font-size: 18px; color: #343a40; }
29
+
30
+ /* 하단 관계도 패널 배경 */
31
+ #controls {
32
+ background-color: #2c3e50;
33
+ }
34
+
35
+ /* 2. 탭 버튼 스타일 */
36
+ .control-tabs {
37
+ display: flex;
38
+ gap: 5px;
39
+ margin-bottom: 10px;
40
+ }
41
+ .tab-btn {
42
+ flex: 1;
43
+ padding: 10px;
44
+ cursor: pointer;
45
+ background-color: #e9ecef;
46
+ border: 1px solid #dee2e6;
47
+ border-bottom: none;
48
+ border-radius: 8px 8px 0 0;
49
+ font-weight: 500;
50
+ color: #495057;
51
+ }
52
+ .tab-btn.active {
53
+ background-color: #ffffff;
54
+ border-bottom: 1px solid #ffffff;
55
+ }
56
+
57
+ /* 탭 패널 스타일 */
58
+ .control-panel {
59
+ display: none; /* 기본적으로 모든 패널 숨김 */
60
+ flex-direction: column;
61
+ gap: 10px;
62
+ padding: 15px;
63
+ border: 1px solid #dee2e6;
64
+ border-radius: 0 0 8px 8px;
65
+ background-color: #ffffff;
66
+ }
67
+
68
+ .control-panel.active {
69
+ display: flex; /* 활성화된 패널만 보이도록 */
70
+ }
71
+ .control-panel select, .control-panel input {
72
+ width: 100%;
73
+ box-sizing: border-box;
74
+ }
75
+
76
  </style>
77
  </head>
78
  <body>
 
80
  <div id="network-panel">
81
  <div id="network"></div>
82
  <div id="controls">
83
+ <div class="control-tabs">
84
+ <button id="tab-event" class="tab-btn active">이벤트 주입</button>
85
+ <button id="tab-relation" class="tab-btn">관계 설정</button>
86
+ </div>
87
+
88
  <button id="play-pause-btn">Play / Pause</button>
89
  <button id="tick-btn">Next Tick</button>
90
  <button id="refresh-btn">⟳ 새로고침</button>
91
+
92
+ <div id="event-inject-panel" class="control-panel active">
93
+ <select id="event-npc-select"></select>
94
+ <input type="text" id="event-text-input" placeholder="이벤트 내용">
95
+ <button id="inject-event-btn">이벤트 주입</button>
96
+ </div>
97
+
98
+ <div id="relationship-control-panel" class="control-panel">
99
+ <select id="relation-npc1-select"></select>
100
+ <select id="relation-npc2-select"></select>
101
+ <select id="relationship-type-select">
102
+ <option value="best friend">매우 친한 친구</option>
103
+ <option value="friend">친구</option>
104
+ <option value="acquaintance">지인</option>
105
+ <option value="stranger">낯선 사람</option>
106
+ <option value="nuisance">불편한 사람</option>
107
+ <option value="rival">싫은 사람</option>
108
+ <option value="enemy">적</option>
109
+ </select>
110
+ <button id="force-relationship-btn">관계 설정</button>
111
+ </div>
112
  </div>
113
  </div>
114
  <div id="side-panel">
 
135
  const injectEventBtn = document.getElementById('inject-event-btn');
136
  const eventNpcSelect = document.getElementById('event-npc-select');
137
  const eventTextInput = document.getElementById('event-text-input');
138
+ const relationNpc1Select = document.getElementById('relation-npc1-select');
139
+ const relationNpc2Select = document.getElementById('relation-npc2-select');
140
+ const relationshipTypeSelect = document.getElementById('relationship-type-select');
141
+ const forceRelationshipBtn = document.getElementById('force-relationship-btn');
142
 
143
  let network = null;
144
  let nodesDataSet = new vis.DataSet([]);
 
209
  }
210
 
211
  function updateNpcSelect(npcNodes) {
212
+ const selections = [eventNpcSelect, relationNpc1Select, relationNpc2Select];
213
+ selections.forEach(select => {
214
+ const currentVal = select.value;
215
+ select.innerHTML = '';
216
+ npcNodes.forEach(node => {
217
+ const option = document.createElement('option');
218
+ option.value = node.label;
219
+ option.textContent = node.label;
220
+ select.appendChild(option);
221
+ });
222
+ if (currentVal) select.value = currentVal;
223
+ })
224
  }
225
 
226
  function updateLog(logMessages) {
 
239
  autoUpdateInterval = null;
240
  }
241
  }
242
+
243
+ forceRelationshipBtn.addEventListener('click', async () => {
244
+ const npc1_name = relationNpc1Select.value;
245
+ const npc2_name = relationNpc2Select.value;
246
+ const relationship_type = relationshipTypeSelect.value;
247
+ if (!npc1_name || !npc2_name || npc1_name === npc2_name) {
248
+ return alert('서로 다른 두 명의 NPC를 선택해주세요.');
249
+ }
250
+ await fetch('/npc_social_network/api/force_relationship', {
251
+ method: 'POST',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: JSON.stringify({ npc1_name, npc2_name, relationship_type })
254
+ });
255
+ await updateWorld();
256
+ });
257
 
258
  // 모든 이벤트 리스너 복구
259
  playPauseBtn.addEventListener('click', async () => {