humanda5
api key ๋ณ€๊ฒฝ ui ์ถ”๊ฐ€
00b7ce9
# portfolio/npc_social_network/routes/npc_route.py
from flask import Blueprint, render_template, request, jsonify, url_for
from .. import simulation_core
from ..models.gemini_setup import initialize_model
import os
npc_bp = Blueprint(
"npc_social",
__name__,
static_folder="../static",
static_url_path="/npc_social_network"
)
@npc_bp.route("/")
def dashboard():
return render_template("dashboard.html")
# ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹œ์ž‘ํ•˜๋Š” API
@npc_bp.route("/api/initialize_simulation", methods=['POST'])
def api_initialize_simulation():
"""API ํ‚ค๋ฅผ ๋ฐ›์•„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹œ์ž‘ํ•˜๋Š” API"""
from ..models.gemini_setup import initialize_model
data = request.json
model_name = data.get("model_name", "gemini-2.0-flash")
api_key = data.get("api_key")
if not api_key:
return jsonify({"success": False, "error": "API ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."}), 400
# 1. ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹œ๋„
new_model = initialize_model(model_name, api_key)
if new_model:
# 2. ์„ฑ๊ณต์‹œ, ์ „์—ญ ๋ชจ๋ธ์„ ์„ค์ •ํ•˜๊ณ  ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹œ์ž‘
simulation_core.active_llm_model = new_model
if not simulation_core.simulation_initialized:
simulation_core.initialize_simulation()
simulation_core.start_simulation_loop()
return jsonify({"success": True, "message": f"'{model_name}' ๋ชจ๋ธ๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค."})
else:
return jsonify({"success": False, "error": "API ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ชจ๋ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."}), 400
@npc_bp.route("/api/world_state", methods=['GET'])
def get_world_state():
if not simulation_core.simulation_initialized:
return jsonify({"status": "needs_initialization", "log": simulation_core.event_log})
with simulation_core.simulation_lock:
if not simulation_core.npc_manager:
return jsonify({"error": "Simulation not started"}), 500
all_npcs = list(simulation_core.npc_manager.all())
current_log = list(simulation_core.event_log)
is_paused = simulation_core.simulation_paused
nodes = []
for npc in all_npcs:
# ๊ฐ NPC์˜ ์˜์–ด ID๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: elin -> elin.png)
image_filename = f"images/npc/{npc.name}.png"
# if not os.path.exists(image_filename):
# image_filename = f"images/npc/npc.png"
nodes.append({
# id๋Š” ์˜์–ด๋กœ, label์€ ํ•œ๊ธ€๋กœ ๋ถ„๋ฆฌ
"id": npc.name,
"label": npc.korean_name,
"shape": "image", # ๋…ธ๋“œ ๋ชจ์–‘์„ 'image'๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
"image": url_for('npc_social.static', filename=image_filename), # ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
"size": 40 # ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ ์ ˆํžˆ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
})
edges = []
drawn_relations = set()
for npc in all_npcs:
for target_name, profile in npc.relationships.relationships.items():
from_id = npc.name
to_id = target_name
relation_pair = tuple(sorted((from_id, to_id)))
if relation_pair in drawn_relations: continue
color = {"best friend": "#00E676", "friend": "#4CAF50", "acquaintance": "#81D4FA",
"nuisance": "#FFC107", "rival": "#FF9800", "enemy": "#F44336",
"stranger":"#BDBDBD"}.get(profile.type, "gray")
edges.append({
"from": from_id, "to": to_id,
"label": f"{profile.type} ({profile.score:.1f})",
"color": color
})
drawn_relations.add(relation_pair)
# ํ”Œ๋ ˆ์ด์–ด ์ž…๋ ฅ ๋Œ€๊ธฐ ์ƒํƒœ ์ •๋ณด
waiting_for_player = False
player_conversation_info = None
if simulation_core.conversation_manager and simulation_core.conversation_manager.is_conversation_active():
conv = simulation_core.conversation_manager.active_conversation
if conv.waiting_for_player:
waiting_for_player = True
# ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๋ง์„ ๊ฑด NPC์˜ ์ •๋ณด ์ฐพ๊ธฐ
other_npc = conv.participants[1 - conv.turn_index]
player_conversation_info = {
"npc_name": other_npc.korean_name,
"last_utterance": conv.conversation_history[-1] if conv.conversation_history else ""
}
return jsonify({ "nodes": nodes, "edges": edges, "log": current_log, "paused": is_paused, "waiting_for_player": waiting_for_player, "player_conversation": player_conversation_info })
@npc_bp.route("/api/toggle_simulation", methods=['POST'])
def toggle_simulation():
with simulation_core.simulation_lock:
simulation_core.simulation_paused = not simulation_core.simulation_paused
status = "์ •์ง€๋จ" if simulation_core.simulation_paused else "์‹คํ–‰ ์ค‘"
simulation_core.add_log(f"์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด {status} ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
is_paused = simulation_core.simulation_paused
return jsonify({"paused": is_paused})
@npc_bp.route("/api/manual_tick", methods=['POST'])
def manual_tick():
# ์ˆ˜๋™ ํ‹ฑ์€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ๋ฉˆ์ถฐ์žˆ์„ ๋•Œ๋งŒ ์ž‘๋™ํ•˜๋„๋ก ํ•จ
if simulation_core.simulation_paused:
simulation_core.tick_simulation()
else:
simulation_core.add_log("๊ฒฝ๊ณ : ์‹œ๋ฎฌ๋ ˆ์ด์…˜์ด ์‹คํ–‰ ์ค‘์ผ ๋•Œ๋Š” ์ˆ˜๋™ ํ‹ฑ์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
return get_world_state()
@npc_bp.route("/api/inject_event", methods=['POST'])
def inject_event():
"""ํ”Œ๋ ˆ์ด์–ด๊ฐ€ NPC์—๊ฒŒ '๊ณผ๊ฑฐ ๊ธฐ์–ต'์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ฃผ์ž…ํ•˜๋Š” ํ•จ์ˆ˜"""
data = request.json
npc_name, event_text = data.get("npc_name"), data.get("event_text")
with simulation_core.simulation_lock:
npc = simulation_core.npc_manager.get_npc_by_korean_name(npc_name)
if npc and event_text:
npc.remember(content=event_text, importance=8, emotion="nostalgia", memory_type="Recalled_Past") # ์ด๋ฒคํŠธ ํƒ€์ž…: ์ƒ๊ธฐ๋œ ๊ณผ๊ฑฐ
simulation_core.add_log(f"ํ”Œ๋ ˆ์ด์–ด ๊ฐœ์ž… -> {npc_name}์—๊ฒŒ '์ƒ๊ธฐ๋œ ๊ณผ๊ฑฐ' ์ฃผ์ž…: '{event_text}'")
return jsonify({"success": True})
return jsonify({"success": False, "error": "Invalid data"}), 400
# get_npc_details ์™€ ๊ฐ™์€ ๋‚˜๋จธ์ง€ ์ฝ๊ธฐ ์ „์šฉ API๋„ Lock์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
@npc_bp.route("/api/npc_details/<npc_name>", methods=['GET'])
def get_npc_details(npc_name):
with simulation_core.simulation_lock:
npc = simulation_core.npc_manager.get_npc_by_korean_name(npc_name)
if not npc:
return jsonify({"error": "NPC not found"}), 404
details = {
"name": npc.korean_name, "age": npc.age, "job": npc.job,
"personality_summary": npc.personality.get_narrative_summary(),
"emotions": npc.get_composite_emotion_state(top_n=5),
"goals": npc.planner.current_goal.description if npc.planner.has_active_plan() else "ํŠน๋ณ„ํ•œ ๋ชฉํ‘œ ์—†์Œ",
"memories": [mem.content for mem in npc.memory_store.get_recent_memories(limit=10)]
}
return jsonify(details)
@npc_bp.route("/api/force_relationship", methods=['POST'])
def force_relationship():
"""๋‘ NPC์˜ ๊ด€๊ณ„๋ฅผ ๊ฐ•์ œ๋กœ ์„ค์ •ํ•˜๋Š” API"""
data = request.json
npc1_name = data.get("npc1_name")
npc2_name = data.get("npc2_name")
relationship_type = data.get("relationship_type")
if not all([npc1_name, npc2_name, relationship_type]) or npc1_name == npc2_name:
return jsonify({"success": False, "error": "Invalid data"}), 400
with simulation_core.simulation_lock:
npc1 = simulation_core.npc_manager.get_npc_by_korean_name(npc1_name)
npc2 = simulation_core.npc_manager.get_npc_by_korean_name(npc2_name)
if npc1 and npc2:
# ๊ด€๊ณ„๋Š” ์ƒํ˜ธ์ž‘์šฉ์ด๋ฏ€๋กœ, ์–‘์ชฝ ๋ชจ๋‘์—๊ฒŒ ๊ด€๊ณ„๋ฅผ ์„ค์ •
npc1.relationships.set_relationship(npc2.name, relationship_type)
npc2.relationships.set_relationship(npc1.name, relationship_type)
return jsonify({"success": True})
return jsonify({"success": False, "error": "NPC not found"}), 404
@npc_bp.route("/api/orchestrate_conversation", methods=['Post'])
def orchestrate_conversation():
"""ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ง€์ •ํ•œ ๋‘ NPC์™€ ์ƒํ™ฉ์œผ๋กœ ๋Œ€ํ™”๋ฅผ ์‹œ์ž‘์‹œํ‚ค๋Š” API"""
data = request.json
npc1_name = data.get("npc1_name")
npc2_name = data.get("npc2_name")
situation = data.get("situation") # ๋Œ€ํ™” ํ•ต์‹ฌ 'topic'
if not all([npc1_name, npc2_name, situation]) or npc1_name == npc2_name:
return jsonify({"success": False, "error": "Invalid data"}), 400
# ๋ฝ์„ ๊ฑธ์ง€ ์•Š๊ณ  ๋งค๋‹ˆ์ €์— ๋ฐ”๋กœ ์š”์ฒญ
npc1 = simulation_core.npc_manager.get_npc_by_korean_name(npc1_name)
npc2 = simulation_core.npc_manager.get_npc_by_korean_name(npc2_name)
if npc1 and npc2:
# ๊ธฐ์กด์˜ ๋Œ€ํ™” ์‹œ์ž‘ ํ•จ์ˆ˜ ์žฌํ™œ์šฉ
simulation_core.conversation_manager.start_conversation(npc1, npc2, topic=situation)
return jsonify({"success": True})
return jsonify({"success": False, "error": "NPC not found"}), 404
@npc_bp.route("/api/player_response", methods=['POST'])
def player_response():
"""ํ”Œ๋ ˆ์ด์–ด์˜ ๋Œ€ํ™” ์‘๋‹ต์„ ๋ฐ›์•„ '๋Œ€๊ธฐ์—ด'์— ์ถ”๊ฐ€, ์ฒ˜๋ฆฌ๋Š” simulation_loop์—์„œ ์ง„ํ–‰"""
data = request.json
utterance = data.get("utterance")
if not utterance:
return jsonify({"success": False, "error": "Invalid data"}), 400
simulation_core.set_player_utterance(utterance)
return jsonify({"success": True})
@npc_bp.route("/api/toggle_player", methods = ['POST'])
def toggle_player():
"""ํ”Œ๋ ˆ์ด์–ด์˜ ํ™œ์„ฑํ™” ์ƒํƒœ๋ฅผ ํ† ๊ธ€ํ•˜๋Š” API"""
with simulation_core.simulation_lock:
# npc_manager์˜ ์ƒํƒœ๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝ
simulation_core.npc_manager.set_player_active(not simulation_core.npc_manager.player_is_active)
is_active = simulation_core.npc_manager.player_is_active
status_text = "ํ™œ์„ฑํ™”" if is_active else "๋น„ํ™œ์„ฑํ™”"
simulation_core.add_log(f"ํ”Œ๋ ˆ์ด์–ด ์ƒํƒœ๊ฐ€ '{status_text}'๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
return jsonify({"success": True, "player_is_active": is_active})
@npc_bp.route("/api/set_llm_config", methods=['POST'])
def set_llm_config():
"""UI๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๋ชจ๋ธ๊ณผ API ํ‚ค๋กœ LLM์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์žฌ์„ค์ •ํ•˜๋Š” API"""
from ..models.gemini_setup import initialize_model
data = request.json
model_name = data.get("model_name")
api_key = data.get("api_key")
if not all([model_name, api_key]):
return jsonify({"success": False, "error": "๋ชจ๋ธ๊ณผ API ํ‚ค๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."}), 400
# ์ƒˆ๋กœ์šด ์„ค์ •์œผ๋กœ ๋ชจ๋ธ์„ ์ดˆ๊ธฐํ™” ์‹œ๋„
new_model = initialize_model(model_name, api_key)
if new_model:
# ์„ฑ๊ณต ์‹œ, simulation_core์˜ ์ „์—ญ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ์šด ๋ชจ๋ธ๋กœ ๊ต์ฒด
simulation_core.active_llm_model = new_model
return jsonify({"success": True})
else:
return jsonify({"success": False, "error": "API ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ชจ๋ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."}), 400