rorshi commited on
Commit
dde6bd9
·
1 Parent(s): cf1ebab

flask를 통해 백엔드 구축

Browse files
app.py CHANGED
@@ -1,12 +1,11 @@
1
  # portfolio/app.py
2
  from flask import Flask, render_template, url_for, redirect
3
  from npc_social_network.routes.npc_route import npc_bp
 
4
  # from stock.routes.stock_route import stock_bp
5
  import os
6
  import subprocess
7
 
8
- # 시뮬레이션 프로세스를 관리할 전역 변수
9
- simulation_process = None
10
 
11
  def create_app():
12
  app = Flask(__name__) # static/template 경로를 기본값
@@ -15,29 +14,20 @@ def create_app():
15
  app.register_blueprint(npc_bp)
16
  # app.register_blueprint(stock_bp)
17
 
 
18
  @app.route("/")
19
  def index():
20
  return render_template("main.html")
21
 
22
- @app.route("/run_npc_simulation")
23
- def run_npc_simulation():
24
- global simulation_process
25
- # 이미 실행 중인 프로세스가 있다면 종료
26
- if simulation_process and simulation_process.poll() is None:
27
- simulation_process.terminate()
28
- simulation_process.wait()
29
-
30
- # 파이썬 가상환경의 실행 파일 경로를 명시적으로 지정할 수 있음
31
- python_exec = "python"
32
- # 비동기로 Pygame 시뮬레이션 실행
33
- subprocess.Popen([python_exec, "-m", "npc_social_network.maps.villages.town_hall"]) # 비동기로 실행 (창 띄움)
34
-
35
- # 실행 후 사용자 안내 페이지 표시
36
- return redirect(url_for('npc_social.home'))
37
-
38
  return app
39
 
40
  if __name__ == '__main__':
 
41
  app = create_app()
42
- print("[MAIN] Starting Flask app only (Background NPC interactions handled by separate runner).", flush=True)
43
- app.run(debug=True, use_reloader=False)
 
 
 
 
 
 
1
  # portfolio/app.py
2
  from flask import Flask, render_template, url_for, redirect
3
  from npc_social_network.routes.npc_route import npc_bp
4
+ from npc_social_network import simulation_core
5
  # from stock.routes.stock_route import stock_bp
6
  import os
7
  import subprocess
8
 
 
 
9
 
10
  def create_app():
11
  app = Flask(__name__) # static/template 경로를 기본값
 
14
  app.register_blueprint(npc_bp)
15
  # app.register_blueprint(stock_bp)
16
 
17
+ # 포트폴리오의 메인 랜딩 페이지
18
  @app.route("/")
19
  def index():
20
  return render_template("main.html")
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  return app
23
 
24
  if __name__ == '__main__':
25
+ # 1. flask 앱 실행
26
  app = create_app()
27
+ # 2. NPC 시뮬레이션 초기화 백그라운드 스레드 시작
28
+ simulation_core.initialize_simulation()
29
+
30
+ # 3. Flask 웹 서버 실행
31
+ # use_reloader=False는 디버그 모드에서 앱이 두 번 실행되는 것을 방지하여,
32
+ # 시뮬레이션 스레드가 두 번 시작되지 않도록 합니다.
33
+ app.run(debug=True, use_reloader=False, port=5000)
npc_social_network/npc/npc_base.py CHANGED
@@ -231,11 +231,11 @@ class NPC:
231
  self.emotion.update_emotion(emotion, strength)
232
  self._emotion_buffer = self.emotion.get_buffer()
233
 
234
- def decay_emotion(self):
235
  """
236
  시간이 지남에 따라 모든 감정이 서서히 감소
237
  """
238
- self.emotion.decay_emotion()
239
  self._emotion_buffer = self.emotion.get_buffer()
240
 
241
  def recall_all_memories(self) -> List[str]:
@@ -334,7 +334,7 @@ class NPC:
334
  print(f"[Personality Update] {self.name} → sensitive: {self.personality['sensitive']:.3f}, "
335
  f"stoic: {self.personality['stoic']:.3f}, cognitive_bias: {self.personality['cognitive_bias']:.3f}", flush=True)
336
 
337
- def decay_memory(self):
338
  """
339
  시간이 지남에 따라 기억 importance 감소
340
  - MemoryStore의 decay_memories에 self(NPC 객체)를 전달
@@ -388,7 +388,7 @@ class NPC:
388
  summary = summarize_memories(memories)
389
  if "[LLM Error]" in summary: return
390
  self.remember(
391
- Memory(content=f"[요약된 기억] summary",
392
  importance=9,
393
  emotion=self.emotion.get_dominant_emotion(),
394
  memory_type="Summary")
 
231
  self.emotion.update_emotion(emotion, strength)
232
  self._emotion_buffer = self.emotion.get_buffer()
233
 
234
+ def decay_emotions(self):
235
  """
236
  시간이 지남에 따라 모든 감정이 서서히 감소
237
  """
238
+ self.emotion.decay_emotions()
239
  self._emotion_buffer = self.emotion.get_buffer()
240
 
241
  def recall_all_memories(self) -> List[str]:
 
334
  print(f"[Personality Update] {self.name} → sensitive: {self.personality['sensitive']:.3f}, "
335
  f"stoic: {self.personality['stoic']:.3f}, cognitive_bias: {self.personality['cognitive_bias']:.3f}", flush=True)
336
 
337
+ def decay_memories(self):
338
  """
339
  시간이 지남에 따라 기억 importance 감소
340
  - MemoryStore의 decay_memories에 self(NPC 객체)를 전달
 
388
  summary = summarize_memories(memories)
389
  if "[LLM Error]" in summary: return
390
  self.remember(
391
+ Memory(content=f"[요약된 기억] {summary}",
392
  importance=9,
393
  emotion=self.emotion.get_dominant_emotion(),
394
  memory_type="Summary")
npc_social_network/routes/npc_route.py CHANGED
@@ -1,11 +1,9 @@
1
  # portfolio/npc_social_network/routes/npc_route.py
2
- from flask import Blueprint, render_template, request, jsonify
3
- from npc_social_network.models.gemini_setup import load_gemini
4
  from npc_social_network.maps.manager.simulation_manager import load_simulation, save_simulation
5
  from ..npc.npc_manager import NPCManager
6
- from npc_social_network.npc.npc_memory_embedder import embed_npc_memories, search_similar_memories
7
- import threading
8
- import time
9
 
10
  npc_bp = Blueprint(
11
  "npc_social",
@@ -14,58 +12,99 @@ npc_bp = Blueprint(
14
  template_folder="../templates",
15
  static_folder="../static")
16
 
17
- # LLM Model
18
- llm_model = load_gemini()
 
 
 
 
 
19
 
20
- def get_npc_manager() -> NPCManager:
21
- """
22
- 저장된 시뮬레이션 상태 파일을 불러와 NPC 매니저를 가져옵니다.
23
- 웹은 함수를 통해서만 NPC 데이터에 접근합니다.
24
- """
25
- npc_manager = load_simulation()
26
- if npc_manager is None:
27
- # 만약 저장 파일이 없다면, 웹 UI가 비어있지 않도록 임시 매니저를 생성.
28
- npc_manager = NPCManager()
29
- return npc_manager
 
 
 
 
 
 
30
 
31
- # --------------------
32
- # 라우트 정의
33
- # --------------------
 
 
 
34
 
35
- @npc_bp.route("/", methods=['GET'])
36
- def home():
37
- # 페이지가 처음 로드될 때, 저장된 NPC 목록을 전달
38
- npc_manager = get_npc_manager()
39
- npc_names = list(npc_manager.npc_dict.keys())
40
- # chat.html에 시뮬레이션 실행 상태와 NPC 목록을 전달
41
- return render_template("chat.html", npc_names=npc_names or ["(시뮬레이션 미실행)"])
 
 
 
 
 
42
 
43
- # 채팅 처리 API
44
- @npc_bp.route("/chat", methods=['POST'])
45
- def chat():
46
- """웹 채팅 상호작용 후, 변경된 상태 파일에 저장"""
47
- npc_manager = get_npc_manager()
48
- if not npc_manager.all():
49
- return jsonify({"error": "시뮬레이션이 실행되지 않았습니다. 먼저 시뮬레이션을 시작해주세요."}), 400
50
-
51
- user_input = request.json.get("message")
52
- npc_name = request.json.get("npc")
53
- npc = npc_manager.get_npc_by_name(npc_name)
54
-
55
- if npc is None:
56
- return jsonify({"error": "NPC를 찾을 수 없습니다."}), 404
57
 
58
- # generate_dialogue가 time_context를 요구하므로 임시 값을 전달
59
- # generate_dialogue를 통해 응답 생성 및 내부 상태 업데이트
60
- npc_reply = npc.generate_dialogue(user_input, time_context="Web Interaction")
 
 
61
 
62
- # 상호작용 후 변경된 상태를 저장
63
- save_simulation(npc_manager)
 
 
 
 
 
 
64
 
65
- return jsonify({
66
- "npc_reply": npc_reply,
67
- "dominant_emotion": npc.emotion.get_dominant_emotion(),
68
- "memory_summary": npc.summarize_emotional_state(),
69
- "emotion_state": npc.get_composite_emotion_state(),
70
- "relationship_with_player": npc.get_relationship_description("플레이어")
71
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # portfolio/npc_social_network/routes/npc_route.py
2
+ from flask import Blueprint, render_template, request, jsonify, url_for
3
+ from .. import simulation_core
4
  from npc_social_network.maps.manager.simulation_manager import load_simulation, save_simulation
5
  from ..npc.npc_manager import NPCManager
6
+
 
 
7
 
8
  npc_bp = Blueprint(
9
  "npc_social",
 
12
  template_folder="../templates",
13
  static_folder="../static")
14
 
15
+ # --------------------------------------------------
16
+ # API 엔드포인트 정의
17
+ # --------------------------------------------------
18
+ @npc_bp.route("/")
19
+ def dashboard():
20
+ """월드 관찰자 대시보드 페이지를 렌더링합니다."""
21
+ return render_template("dashboard.html")
22
 
23
+ @npc_bp.route("api/world_state", method=['GET'])
24
+ def get_world_state():
25
+ """프론트엔드에 전달할 월드 전체 데이터를 JSON으로 반환합니다."""
26
+ if not simulation_core.npc_manager:
27
+ return jsonify({"error":"Simulation not started"}), 500
28
+
29
+ nodes = []
30
+ for npc in simulation_core.npc_manager.all():
31
+ nodes.append({
32
+ "id": npc.name,
33
+ "label": npc.name,
34
+ "shape": "image",
35
+ "image": url_for('npc_social.static', filename=f'image/npc/npc{npc.name.lower()}.png'),
36
+ "job": npc.job,
37
+ "age": npc.age
38
+ })
39
 
40
+ edges = []
41
+ drawn_relations = set()
42
+ for npc in simulation_core.npc_manager.all():
43
+ for target_name, profile in npc.relationships.relationships.items():
44
+ relation_pair = tuple(sorted(npc.name, target_name))
45
+ if relation_pair in drawn_relations: continue
46
 
47
+ color = {"best friend": "#0400FF", "friend": "#00FF00", "acquaintance": "#68D4FF",
48
+ "nuisance": "#FCF814", "rival": "#FF9E0D", "enemy": "#FF0000",
49
+ "stranger":"#5E5C5C" }.get(profile.type, "gray")
50
+
51
+ edges.append({
52
+ "from": npc.name,
53
+ "to": target_name,
54
+ "label": f"{profile.type} ({profile.score: .1f})",
55
+ "color": color,
56
+ "width": 2
57
+ })
58
+ drawn_relations.add(relation_pair)
59
 
60
+ return jsonify({
61
+ "nodes": nodes,
62
+ "edges": edges,
63
+ "log": simulation_core.event_log,
64
+ "paused": simulation_core.simulation_paused
65
+ })
 
 
 
 
 
 
 
 
66
 
67
+ @npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
68
+ def get_npc_details(npc_name):
69
+ npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
70
+ if not npc:
71
+ return jsonify({"error": "NPC not found"}), 404
72
 
73
+ details = {
74
+ "name": npc.name, "age": npc.age, "job": npc.job,
75
+ "personality_summary": npc.personality.get_personality_summary(),
76
+ "emotions": npc.get_composite_emotion_state(top_n=5),
77
+ "goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "���별한 목표 없음",
78
+ "memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
79
+ }
80
+ return jsonify(details)
81
 
82
+ @npc_bp.route("/api/toggle_simulation", methods=['POST'])
83
+ def toggle_simulation():
84
+ simulation_core.simulation_paused = not simulation_core.simulation_paused
85
+ status = "정지됨" if simulation_core.simulation_paused else "실행 중"
86
+ simulation_core.add_log(f"시뮬레이션이 {status} 상태로 변경되었습니다.")
87
+ return jsonify({"paused": simulation_core.simulation_paused})
88
+
89
+ @npc_bp.route("/api/manual_tick", methods=['POST'])
90
+ def manual_tick():
91
+ # 수동 틱은 시뮬레이션이 멈춰있을 때만 작동하도록 함
92
+ if simulation_core.simulation_paused:
93
+ simulation_core.tick_simulation()
94
+ else:
95
+ simulation_core.add_log("경고: 시뮬레이션이 실행 중일 때는 수동 틱을 할 수 없습니다.")
96
+ return get_world_state()
97
+
98
+
99
+ @npc_bp.route("/api/inject_event", methods=['POST'])
100
+ def inject_event():
101
+ """플레이어가 NPC에게 이벤트를 직접 주입하는 함수"""
102
+ data = request.json
103
+ npc_name, event_text = data.get("npc_name"), data.get("event_text")
104
+ npc = simulation_core.npc_manager.get_npc_by_name(npc_name)
105
+
106
+ if npc and event_text:
107
+ npc.remember(content=f"[요약된 기억] {event_text}", importance=10, emotion="surprise", memory_type="Summary") # 수정 필요: importance, emotion 값을 요약된 기억에 맞게 넣어줄 함수
108
+ simulation_core.add_log(f"{npc_name}: '{event_text}'")
109
+ return jsonify({"success": True})
110
+ return jsonify({"success": False, "error": "Invalid data"}), 400
npc_social_network/simulation_core.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # npc_social_network/simulation_core.py
2
+ # 시뮬레이션의 '상태 저장소' & '엔진'
3
+
4
+ import threading
5
+ import time
6
+ from .npc.npc_manager import NPCManager
7
+ from .scenarios.scenario_setup import setup_initial_scenario
8
+ from .manager.simulation_manager import save_simulation, load_simulation
9
+
10
+ #------------------------------------------
11
+ # 1. 시뮬레이션 상태 (전역 변수)
12
+ # - 이 변수들이 시뮬레이션 세계의 모든 정보를 담습니다.
13
+ #------------------------------------------
14
+ npc_manager: NPCManager = None
15
+ simulation_paused = True
16
+ event_log = []
17
+
18
+ #------------------------------------------
19
+ # 2. 시뮬레이션 로진 함수
20
+ #------------------------------------------
21
+ def add_log(message):
22
+ """이벤트 로그에 메시지를 추가합니다."""
23
+ timestamp = time.strftime("%H:%M:%S")
24
+ print(f"[{timestamp}] {message}")
25
+ event_log.insert(0, f"[{timestamp}] {message}")
26
+ if len(event_log) > 100:
27
+ event_log.pop()
28
+
29
+ def tick_simulation():
30
+ """시뮬레이션의 시간을 한 단계 진행"""
31
+ if not npc_manager or simulation_paused:
32
+ return
33
+
34
+ add_log("시뮬레이션 틱 시작")
35
+
36
+ for npc in npc_manager.all():
37
+ npc.decay_emotions()
38
+ npc.decay_memories()
39
+ npc.update_autonomous_behavior("자율 행동 시간")
40
+
41
+ if len(npc_manager.all()) >= 2:
42
+ try:
43
+ add_log("NPC 간 자율 상호작용을 시도합니다.")
44
+ npc_manager.initiate_npc_to_npc_interaction("자율 행동 시간")
45
+ except Exception as e:
46
+ add_log(f"상호작용 중 오류 발생: {e}")
47
+
48
+ def simulation_loop():
49
+ """백그라운드에서 주기적으로 시뮬레이션을 실행하는 루프"""
50
+ add_log("백그라운드 시뮬레이션 루프 시작됨.")
51
+ while True:
52
+ if not simulation_paused:
53
+ tick_simulation()
54
+ time.sleep(5) # 5초에 한 번씩 틱 발생
55
+
56
+ def initialize_simulation():
57
+ """서버 시작 시 시뮬레이션을 초기화합니다."""
58
+ global npc_manager
59
+ npc_manager = load_simulation()
60
+ if npc_manager is None:
61
+ npc_manager = setup_initial_scenario()
62
+ save_simulation(npc_manager)
63
+
64
+ add_log("시뮬레이션이 성공적으로 초기화되었습니다.")
65
+
66
+ # 백그라운드 스레드에서 시뮬레이션 루프 시작
67
+ simulation_thread = threading.Thread(target=simulation_loop, daemon=True)
68
+ simulation_thread.start()