rorshi commited on
Commit
09e5b85
·
1 Parent(s): f0fa1a7

이야기 주제 제공 기능 완성

Browse files
npc_social_network/routes/npc_route.py CHANGED
@@ -136,4 +136,26 @@ def force_relationship():
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
140
+
141
+ @npc_bp.route("/api/orchestrate_conversation", methods=['Post'])
142
+ def orchestrate_conversation():
143
+ """플레이어가 지정한 두 NPC와 상황으로 대화를 시작시키는 API"""
144
+ data = request.json
145
+ npc1_name = data.get("npc1_name")
146
+ npc2_name = data.get("npc2_name")
147
+ situation = data.get("situation") # 대화 핵심 'topic'
148
+
149
+ if not all([npc1_name, npc2_name, situation]) or npc1_name == npc2_name:
150
+ return jsonify({"success": False, "error": "Invalid data"}), 400
151
+
152
+ # 락을 걸지 않고 매니저에 바로 요청
153
+ npc1 = simulation_core.npc_manager.get_npc_by_korean_name(npc1_name)
154
+ npc2 = simulation_core.npc_manager.get_npc_by_korean_name(npc2_name)
155
+
156
+ if npc1 and npc2:
157
+ # 기존의 대화 시작 함수 재활용
158
+ simulation_core.conversation_manager.start_conversation(npc1, npc2, topic=situation)
159
+ return jsonify({"success": True})
160
+
161
+ return jsonify({"success": False, "error": "NPC not found"}), 404
npc_social_network/templates/dashboard.html CHANGED
@@ -14,10 +14,10 @@
14
  #network { width: 100%; flex: 1; border-bottom: 1px solid var(--border-color); }
15
 
16
  /* 전체 컨트롤 패널 (가장 바깥쪽) */
17
- #controls { padding: 12px; display: flex; gap: 15px; align-items: stretch; background-color: #3e3f41; border-radius: 0 0 12px 12px;}
18
 
19
  /* 메인 컨트롤 버튼 */
20
- #main-controls { display: flex; flex-direction: column; gap: 10px; }
21
  #main-controls button { padding: 8px 15px; border-radius: 6px; border: none; cursor: pointer; color: rgb(0, 0, 0); font-size: 14px; font-weight: 500; transition: background-color 0.2s;}
22
  #play-pause-btn.playing { background-color: var(--primary-color); }
23
  #play-pause-btn.paused { background-color: var(--success-color); }
@@ -25,6 +25,7 @@
25
  #refresh-btn { background-color: #ffc107; color: black; }
26
 
27
  /* 개입 기능 선택 탭*/
 
28
  #intervention-tabs { display: flex; flex-direction: column; gap: 10px;}
29
  .tab-btn { padding: 8px 15px; border-radius: 6px; border: 1px solid var(--border-color); cursor: pointer; font-weight: 500; color: #495057; text-align: center;}
30
  .tab-btn.active { background-color: var(--primary-color); color: white; border-color: var(--primary-color);}
@@ -62,6 +63,7 @@
62
  <div id="intervention-tabs">
63
  <button id="tab-event" class="tab-btn active">이벤트 주입</button>
64
  <button id="tab-relation" class="tab-btn">관계 설정</button>
 
65
  </div>
66
  <!-- 개입 기능 패널 -->
67
  <div id="intervention-panels">
@@ -84,6 +86,12 @@
84
  </select>
85
  <button id="force-relationship-btn">관계 설정</button>
86
  </div>
 
 
 
 
 
 
87
  </div>
88
  </div>
89
  </div>
@@ -124,6 +132,13 @@
124
  const relationNpc2Select = document.getElementById('relation-npc2-select');
125
  const relationshipTypeSelect = document.getElementById('relationship-type-select');
126
  const forceRelationshipBtn = document.getElementById('force-relationship-btn');
 
 
 
 
 
 
 
127
 
128
  let network = null;
129
  let nodesDataSet = new vis.DataSet([]);
@@ -143,12 +158,40 @@
143
  panelEvent.classList.add('active');
144
  tabRelation.classList.remove('active');
145
  panelRelation.classList.remove('active');
 
 
146
  });
147
  tabRelation.addEventListener('click', () => {
 
 
148
  tabRelation.classList.add('active');
149
  panelRelation.classList.add('active');
 
 
 
 
 
150
  tabEvent.classList.remove('active');
151
  panelEvent.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  });
153
 
154
  async function updateWorld() {
@@ -207,7 +250,7 @@
207
  }
208
 
209
  function updateNpcSelect(npcNodes) {
210
- const selections = [eventNpcSelect, relationNpc1Select, relationNpc2Select];
211
  selections.forEach(select => {
212
  const currentVal = select.value;
213
  select.innerHTML = '';
 
14
  #network { width: 100%; flex: 1; border-bottom: 1px solid var(--border-color); }
15
 
16
  /* 전체 컨트롤 패널 (가장 바깥쪽) */
17
+ #controls { padding: 15px; display: flex; flex-direction: column; gap: 15px; background-color: #3e3f41; border-radius: 0 0 12px 12px;}
18
 
19
  /* 메인 컨트롤 버튼 */
20
+ #main-controls { display: flex; gap: 10px; justify-content: flex-start;}
21
  #main-controls button { padding: 8px 15px; border-radius: 6px; border: none; cursor: pointer; color: rgb(0, 0, 0); font-size: 14px; font-weight: 500; transition: background-color 0.2s;}
22
  #play-pause-btn.playing { background-color: var(--primary-color); }
23
  #play-pause-btn.paused { background-color: var(--success-color); }
 
25
  #refresh-btn { background-color: #ffc107; color: black; }
26
 
27
  /* 개입 기능 선택 탭*/
28
+ #intervention-area { display: flex; gap: 15px; align-items: flex-start;}
29
  #intervention-tabs { display: flex; flex-direction: column; gap: 10px;}
30
  .tab-btn { padding: 8px 15px; border-radius: 6px; border: 1px solid var(--border-color); cursor: pointer; font-weight: 500; color: #495057; text-align: center;}
31
  .tab-btn.active { background-color: var(--primary-color); color: white; border-color: var(--primary-color);}
 
63
  <div id="intervention-tabs">
64
  <button id="tab-event" class="tab-btn active">이벤트 주입</button>
65
  <button id="tab-relation" class="tab-btn">관계 설정</button>
66
+ <button id="tab-orchestrate" class="tab-btn">상황 연출</button>
67
  </div>
68
  <!-- 개입 기능 패널 -->
69
  <div id="intervention-panels">
 
86
  </select>
87
  <button id="force-relationship-btn">관계 설정</button>
88
  </div>
89
+ <div id="panel-orchestrate" class="control-panel">
90
+ <select id="orch-npc1-select" title="대화를 시작할 NPC"></select>
91
+ <select id="orch-npc2-select" title="대화 상대 NPC"></select>
92
+ <input type="text" id="situation-text-input" placeholder="대화 주제 또는 상황">
93
+ <button id="orchestrate-btn">대화 시작</button>
94
+ </div>
95
  </div>
96
  </div>
97
  </div>
 
132
  const relationNpc2Select = document.getElementById('relation-npc2-select');
133
  const relationshipTypeSelect = document.getElementById('relationship-type-select');
134
  const forceRelationshipBtn = document.getElementById('force-relationship-btn');
135
+ // 대화 시작하기
136
+ const tabOrchestrate = document.getElementById('tab-orchestrate');
137
+ const panelOrchestrate = document.getElementById('panel-orchestrate');
138
+ const orchNpc1Select = document.getElementById('orch-npc1-select');
139
+ const orchNpc2Select = document.getElementById('orch-npc2-select');
140
+ const situationTextInput = document.getElementById('situation-text-input');
141
+ const orchestrateBtn = document.getElementById('orchestrate-btn');
142
 
143
  let network = null;
144
  let nodesDataSet = new vis.DataSet([]);
 
158
  panelEvent.classList.add('active');
159
  tabRelation.classList.remove('active');
160
  panelRelation.classList.remove('active');
161
+ tabOrchestrate.classList.remove('active');
162
+ panelOrchestrate.classList.remove('active');
163
  });
164
  tabRelation.addEventListener('click', () => {
165
+ tabEvent.classList.remove('active');
166
+ panelEvent.classList.remove('active');
167
  tabRelation.classList.add('active');
168
  panelRelation.classList.add('active');
169
+ tabOrchestrate.classList.remove('active');
170
+ panelOrchestrate.classList.remove('active');
171
+ });
172
+ // 상황 연출 탭 클릭 이벤트
173
+ tabOrchestrate.addEventListener('click', () => {
174
  tabEvent.classList.remove('active');
175
  panelEvent.classList.remove('active');
176
+ tabRelation.classList.remove('active');
177
+ panelRelation.classList.remove('active');
178
+ tabOrchestrate.classList.add('active');
179
+ panelOrchestrate.classList.add('active');
180
+ });
181
+
182
+ // 대화 시작 버튼 이벤트 리스너 추가
183
+ orchestrateBtn.addEventListener('click', async () => {
184
+ const npc1_name = orchNpc1Select.value;
185
+ const npc2_name = orchNpc2Select.value;
186
+ const situation = situationTextInput.value;
187
+ if (!npc1_name || !npc2_name || !situation || npc1_name === npc2_name) {
188
+ return alert ('서로 다른 두 NPC와 상황을 모두 입력해주세요.');
189
+ }
190
+ await fetch('/npc_social_network/api/orchestrate_conversation', {
191
+ method: 'POST',
192
+ headers: { 'Content-Type': 'application/json' },
193
+ body: JSON.stringify({ npc1_name, npc2_name, situation })
194
+ });
195
  });
196
 
197
  async function updateWorld() {
 
250
  }
251
 
252
  function updateNpcSelect(npcNodes) {
253
+ const selections = [eventNpcSelect, relationNpc1Select, relationNpc2Select, orchNpc1Select, orchNpc2Select];
254
  selections.forEach(select => {
255
  const currentVal = select.value;
256
  select.innerHTML = '';