nps_test / temp /view_functions.py
haepa_mac
Update nompang_test app with 127 personality variables
1c7b7bd
import json
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import PIL.ImageDraw
import os
import time
import random
import pandas as pd
import gradio as gr
import tempfile
import base64
from datetime import datetime
# 성격 데이터 시각화 함수
def plot_humor_matrix(humor_data):
if not humor_data:
return None
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import RegularPolygon
# 데이터 준비
warmth_vs_wit = humor_data.get("warmth_vs_wit", 50)
self_vs_observational = humor_data.get("self_vs_observational", 50)
subtle_vs_expressive = humor_data.get("subtle_vs_expressive", 50)
# 3차원 데이터 정규화 (0~1 범위)
warmth = warmth_vs_wit / 100
self_ref = self_vs_observational / 100
expressive = subtle_vs_expressive / 100
# 그래프 생성
fig, ax = plt.subplots(figsize=(7, 6))
ax.set_aspect('equal')
# 축 설정
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
# 삼각형 그리기
triangle = RegularPolygon((0, 0), 3, radius=1, orientation=0, edgecolor='gray', facecolor='none')
ax.add_patch(triangle)
# 축 라벨 위치 계산
angle = np.linspace(0, 2*np.pi, 3, endpoint=False)
x = 1.1 * np.cos(angle)
y = 1.1 * np.sin(angle)
# 축 라벨 추가
labels = ['따뜻함', '자기참조', '표현적']
opposite_labels = ['재치', '관찰형', '은은함']
for i in range(3):
ax.text(x[i], y[i], labels[i], ha='center', va='center', fontsize=12)
ax.text(-x[i]/2, -y[i]/2, opposite_labels[i], ha='center', va='center', fontsize=10, color='gray')
# 내부 가이드라인 그리기
for j in [0.33, 0.66]:
inner_triangle = RegularPolygon((0, 0), 3, radius=j, orientation=0, edgecolor='lightgray', facecolor='none', linestyle='--')
ax.add_patch(inner_triangle)
# 포인트 계산
# 삼각좌표계 변환 (barycentric coordinates)
# 각 차원의 값을 삼각형 내부의 점으로 변환
tx = x[0] * warmth + x[1] * self_ref + x[2] * expressive
ty = y[0] * warmth + y[1] * self_ref + y[2] * expressive
# 포인트 그리기
ax.scatter(tx, ty, s=150, color='red', zorder=5)
# 축 제거
ax.axis('off')
# 제목 추가
plt.title('유머 스타일 매트릭스', fontsize=14)
return fig
# 성격 차트 생성 함수
def generate_personality_chart(persona):
"""Generate a radar chart for personality traits"""
if not persona or "성격특성" not in persona:
# Return empty image with default PIL
img = Image.new('RGB', (400, 400), color='white')
draw = PIL.ImageDraw.Draw(img)
draw.text((150, 180), "데이터 없음", fill='black')
img_path = os.path.join("data", "temp_chart.png")
img.save(img_path)
return img_path
# Get traits
traits = persona["성격특성"]
# Create radar chart
categories = list(traits.keys())
values = list(traits.values())
# Add the first value again to close the loop
categories.append(categories[0])
values.append(values[0])
# Convert to radians
angles = np.linspace(0, 2*np.pi, len(categories), endpoint=True)
# 한글 폰트 설정 - 기본적으로 사용 가능한 폰트를 먼저 시도
# Matplotlib에서 지원하는 한글 폰트 목록
korean_fonts = ['NanumGothic', 'NanumGothicCoding', 'NanumMyeongjo', 'Malgun Gothic', 'Gulim', 'Batang', 'Arial Unicode MS', 'DejaVu Sans']
# 폰트 설정
plt.rcParams['font.family'] = 'sans-serif' # 기본 폰트 패밀리
# 여러 폰트를 시도
font_found = False
for font in korean_fonts:
try:
plt.rcParams['font.sans-serif'] = [font] + plt.rcParams.get('font.sans-serif', [])
plt.text(0, 0, '테스트', fontfamily=font)
font_found = True
print(f"성공적으로 한글 폰트를 설정했습니다: {font}")
break
except:
continue
if not font_found:
print("한글 지원 폰트를 찾을 수 없습니다. 영문으로 표시합니다.")
# 영어 라벨 매핑
english_labels = {
"온기": "Warmth",
"능력": "Ability",
"신뢰성": "Trust",
"친화성": "Friendly",
"창의성": "Creative",
"유머감각": "Humor",
"외향성": "Extraversion"
}
categories = [english_labels.get(cat, cat) for cat in categories]
# Create plot with improved aesthetics
fig, ax = plt.subplots(figsize=(7, 7), subplot_kw=dict(polar=True))
# 배경 스타일 개선
ax.set_facecolor('#f8f9fa')
fig.patch.set_facecolor('#f8f9fa')
# Grid 스타일 개선
ax.grid(True, color='#e0e0e0', linestyle='-', linewidth=0.5, alpha=0.7)
# 각도 라벨 위치 및 색상 조정
ax.set_rlabel_position(90)
ax.tick_params(colors='#6b7280')
# Y축 라벨 제거 및 눈금 표시
ax.set_yticklabels([])
ax.set_yticks([20, 40, 60, 80, 100])
# 범위 설정
ax.set_ylim(0, 100)
# 차트 그리기
# 1. 채워진 영역
ax.fill(angles, values, alpha=0.25, color='#6366f1')
# 2. 테두리 선
ax.plot(angles, values, 'o-', linewidth=2, color='#6366f1')
# 3. 데이터 포인트 강조
ax.scatter(angles[:-1], values[:-1], s=100, color='#6366f1', edgecolor='white', zorder=10)
# 4. 각 축 설정
ax.set_thetagrids(angles[:-1] * 180/np.pi, categories[:-1], fontsize=12)
# 제목 추가
name = persona.get("기본정보", {}).get("이름", "Unknown")
plt.title(f"{name} 성격 특성", size=16, color='#374151', pad=20, fontweight='bold')
# 저장
timestamp = int(time.time())
img_path = os.path.join("data", f"chart_{timestamp}.png")
os.makedirs(os.path.dirname(img_path), exist_ok=True)
plt.savefig(img_path, format='png', bbox_inches='tight', dpi=150, facecolor=fig.get_facecolor())
plt.close(fig)
return img_path
# 페르소나 저장 함수
def save_current_persona(current_persona):
"""Save current persona to a JSON file"""
if not current_persona:
return "저장할 페르소나가 없습니다."
try:
# 깊은 복사를 통해 원본 데이터를 유지
import copy
persona_copy = copy.deepcopy(current_persona)
# 저장 불가능한 객체 제거
keys_to_remove = []
for key in persona_copy:
if key in ["personality_profile", "humor_matrix", "_state"] or callable(persona_copy[key]):
keys_to_remove.append(key)
for key in keys_to_remove:
persona_copy.pop(key, None)
# 중첩된 딕셔너리와 리스트 내의 비직렬화 가능 객체 제거
def clean_data(data):
if isinstance(data, dict):
for k in list(data.keys()):
if callable(data[k]):
del data[k]
elif isinstance(data[k], (dict, list)):
data[k] = clean_data(data[k])
return data
elif isinstance(data, list):
return [clean_data(item) if isinstance(item, (dict, list)) else item for item in data if not callable(item)]
else:
return data
# 데이터 정리
cleaned_persona = clean_data(persona_copy)
# 최종 검증: JSON 직렬화 가능 여부 확인
import json
try:
json.dumps(cleaned_persona)
except TypeError as e:
print(f"JSON 직렬화 오류: {str(e)}")
# 기본 정보만 유지하고 나머지는 안전한 데이터만 포함
basic_info = cleaned_persona.get("기본정보", {})
성격특성 = cleaned_persona.get("성격특성", {})
매력적결함 = cleaned_persona.get("매력적결함", [])
모순적특성 = cleaned_persona.get("모순적특성", [])
cleaned_persona = {
"기본정보": basic_info,
"성격특성": 성격특성,
"매력적결함": 매력적결함,
"모순적특성": 모순적특성
}
# 외부 함수 호출이 필요한 부분
from modules.data_manager import save_persona
filepath = save_persona(cleaned_persona)
if filepath:
name = current_persona.get("기본정보", {}).get("이름", "Unknown")
return f"{name} 페르소나가 저장되었습니다: {filepath}"
else:
return "페르소나 저장에 실패했습니다."
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"저장 오류 상세: {error_details}")
return f"저장 중 오류 발생: {str(e)}"
# 성격 미세조정 함수
def refine_persona(persona, warmth, competence, creativity, extraversion, humor, trust, humor_style):
"""페르소나의 성격을 미세조정하는 함수"""
if not persona:
return persona, "페르소나가 없습니다."
try:
# 복사본 생성
refined_persona = persona.copy()
# 성격 특성 업데이트
if "성격특성" in refined_persona:
refined_persona["성격특성"]["온기"] = int(warmth)
refined_persona["성격특성"]["능력"] = int(competence)
refined_persona["성격특성"]["창의성"] = int(creativity)
refined_persona["성격특성"]["외향성"] = int(extraversion)
refined_persona["성격특성"]["유머감각"] = int(humor)
refined_persona["성격특성"]["신뢰성"] = int(trust)
# 유머 스타일 업데이트
refined_persona["유머스타일"] = humor_style
# 127개 성격 변수가 있으면 업데이트
if "성격변수127" in refined_persona:
# 온기 관련 변수 업데이트
for var in ["W01_친절함", "W02_친근함", "W06_공감능력", "W07_포용력"]:
if var in refined_persona["성격변수127"]:
refined_persona["성격변수127"][var] = int(warmth * 0.9 + random.randint(0, 20))
# 능력 관련 변수 업데이트
for var in ["C01_효율성", "C02_지능", "C05_정확성", "C09_실행력"]:
if var in refined_persona["성격변수127"]:
refined_persona["성격변수127"][var] = int(competence * 0.9 + random.randint(0, 20))
# 창의성 관련 변수 업데이트
for var in ["C04_창의성", "C08_통찰력"]:
if var in refined_persona["성격변수127"]:
refined_persona["성격변수127"][var] = int(creativity * 0.9 + random.randint(0, 20))
# 외향성 관련 변수 업데이트
for var in ["E01_사교성", "E02_활동성", "E03_자기주장", "E06_열정성"]:
if var in refined_persona["성격변수127"]:
refined_persona["성격변수127"][var] = int(extraversion * 0.9 + random.randint(0, 20))
# 유머 관련 변수 업데이트
if "H01_유머감각" in refined_persona["성격변수127"]:
refined_persona["성격변수127"]["H01_유머감각"] = int(humor * 0.9 + random.randint(0, 20))
# 신뢰성 관련 변수 업데이트
if "W04_신뢰성" in refined_persona["성격변수127"]:
refined_persona["성격변수127"]["W04_신뢰성"] = int(trust * 0.9 + random.randint(0, 20))
# 유머 매트릭스 업데이트
if "유머매트릭스" in refined_persona:
if humor_style == "위트있는 재치꾼":
refined_persona["유머매트릭스"]["warmth_vs_wit"] = 30
refined_persona["유머매트릭스"]["self_vs_observational"] = 50
refined_persona["유머매트릭스"]["subtle_vs_expressive"] = 70
elif humor_style == "따뜻한 유머러스":
refined_persona["유머매트릭스"]["warmth_vs_wit"] = 80
refined_persona["유머매트릭스"]["self_vs_observational"] = 60
refined_persona["유머매트릭스"]["subtle_vs_expressive"] = 60
elif humor_style == "날카로운 관찰자":
refined_persona["유머매트릭스"]["warmth_vs_wit"] = 40
refined_persona["유머매트릭스"]["self_vs_observational"] = 20
refined_persona["유머매트릭스"]["subtle_vs_expressive"] = 50
elif humor_style == "자기 비하적":
refined_persona["유머매트릭스"]["warmth_vs_wit"] = 60
refined_persona["유머매트릭스"]["self_vs_observational"] = 85
refined_persona["유머매트릭스"]["subtle_vs_expressive"] = 40
return refined_persona, "성격이 성공적으로 미세조정되었습니다."
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"성격 미세조정 오류: {error_details}")
return persona, f"성격 미세조정 중 오류가 발생했습니다: {str(e)}"
# 페르소나 리스트 가져오기 함수
def get_personas_list():
"""Get list of personas for the dataframe"""
from modules.data_manager import list_personas
personas = list_personas()
# Convert to dataframe format
df_data = []
for i, persona in enumerate(personas):
df_data.append([
persona["name"],
persona["type"],
persona["created_at"],
persona["filename"]
])
return df_data, personas
# 선택한 페르소나 불러오기 함수
def load_selected_persona(selected_row, personas_list):
"""Load persona from the selected row in the dataframe"""
if selected_row is None or len(selected_row) == 0:
return None, "선택된 페르소나가 없습니다.", None, None, None
try:
# Get filepath from selected row
selected_index = selected_row.index[0] if hasattr(selected_row, 'index') else 0
filepath = personas_list[selected_index]["filepath"]
# Load persona
from modules.data_manager import load_persona, toggle_frontend_backend_view
persona = load_persona(filepath)
if not persona:
return None, "페르소나 로딩에 실패했습니다.", None, None, None
# Generate HTML views
from temp.frontend_view import create_frontend_view_html
from temp.backend_view import create_backend_view_html
frontend_view, backend_view = toggle_frontend_backend_view(persona)
frontend_html = create_frontend_view_html(frontend_view)
backend_html = create_backend_view_html(backend_view)
# Generate personality chart
chart_image_path = generate_personality_chart(frontend_view)
return persona, f"{persona['기본정보']['이름']}을(를) 로드했습니다.", frontend_html, backend_html, chart_image_path
except Exception as e:
return None, f"페르소나 로딩 중 오류 발생: {str(e)}", None, None, None
# 현재 페르소나 정보 표시 함수
def update_current_persona_info(current_persona):
if not current_persona:
return {}, {}, None, [], [], []
# 기본 정보
basic_info = {
"이름": current_persona.get("기본정보", {}).get("이름", "Unknown"),
"유형": current_persona.get("기본정보", {}).get("유형", "Unknown"),
"생성일": current_persona.get("기본정보", {}).get("생성일시", "Unknown"),
"설명": current_persona.get("기본정보", {}).get("설명", "")
}
# 성격 특성
personality_traits = {}
if "성격특성" in current_persona:
personality_traits = current_persona["성격특성"]
# 성격 요약 정보
personality_summary = {}
if "성격요약" in current_persona:
personality_summary = current_persona["성격요약"]
elif "성격변수127" in current_persona:
# 직접 성격 요약 계산
try:
variables = current_persona["성격변수127"]
# 카테고리별 평균 계산
summary = {}
category_counts = {}
for var_name, value in variables.items():
category = var_name[0] if var_name and len(var_name) > 0 else "기타"
if category == "W": # 온기
summary["온기"] = summary.get("온기", 0) + value
category_counts["온기"] = category_counts.get("온기", 0) + 1
elif category == "C": # 능력
summary["능력"] = summary.get("능력", 0) + value
category_counts["능력"] = category_counts.get("능력", 0) + 1
elif category == "E": # 외향성
summary["외향성"] = summary.get("외향성", 0) + value
category_counts["외향성"] = category_counts.get("외향성", 0) + 1
elif category == "O": # 개방성
summary["창의성"] = summary.get("창의성", 0) + value
category_counts["창의성"] = category_counts.get("창의성", 0) + 1
elif category == "H": # 유머
summary["유머감각"] = summary.get("유머감각", 0) + value
category_counts["유머감각"] = category_counts.get("유머감각", 0) + 1
# 평균 계산
for category in summary:
if category_counts[category] > 0:
summary[category] = summary[category] / category_counts[category]
# 기본값 설정 (데이터가 없는 경우)
if "온기" not in summary:
summary["온기"] = 50
if "능력" not in summary:
summary["능력"] = 50
if "외향성" not in summary:
summary["외향성"] = 50
if "창의성" not in summary:
summary["창의성"] = 50
if "유머감각" not in summary:
summary["유머감각"] = 50
personality_summary = summary
except Exception as e:
print(f"성격 요약 계산 오류: {str(e)}")
personality_summary = {
"온기": 50,
"능력": 50,
"외향성": 50,
"창의성": 50,
"유머감각": 50
}
# 유머 매트릭스 차트
humor_chart = None
if "유머매트릭스" in current_persona:
humor_chart = plot_humor_matrix(current_persona["유머매트릭스"])
# 매력적 결함 데이터프레임
attractive_flaws_df = get_attractive_flaws_df(current_persona)
# 모순적 특성 데이터프레임
contradictions_df = get_contradictions_df(current_persona)
# 127개 성격 변수 데이터프레임
personality_variables_df = get_personality_variables_df(current_persona)
return basic_info, personality_traits, humor_chart, attractive_flaws_df, contradictions_df, personality_variables_df
# 성격 변수 데이터프레임 생성 함수
def get_personality_variables_df(persona):
if not persona or "성격변수127" not in persona:
return []
variables = persona["성격변수127"]
if isinstance(variables, dict):
rows = []
for var_name, score in variables.items():
# 변수 설명은 앱의 메인 파일에서 정의되어 있을 것이므로 일단 빈 문자열로 처리
description = ""
rows.append([var_name, score, description])
return rows
return []
# 매력적 결함 데이터프레임 생성 함수
def get_attractive_flaws_df(persona):
if not persona or "매력적결함" not in persona:
return []
flaws = persona["매력적결함"]
effects = [
"인간적 매력 +25%",
"관계 깊이 +30%",
"공감 유발 +20%"
]
return [[flaw, effects[i] if i < len(effects) else "매력 증가"] for i, flaw in enumerate(flaws)]
# 모순적 특성 데이터프레임 생성 함수
def get_contradictions_df(persona):
if not persona or "모순적특성" not in persona:
return []
contradictions = persona["모순적특성"]
effects = [
"복잡성 +35%",
"흥미도 +28%"
]
return [[contradiction, effects[i] if i < len(effects) else "깊이감 증가"] for i, contradiction in enumerate(contradictions)]
def export_persona_json(persona):
"""
페르소나를 JSON 파일로 내보내는 기능
"""
if not persona:
return None, "페르소나가 없습니다."
try:
# persona 객체를 JSON으로 직렬화
persona_dict = persona.copy()
# 복잡한 객체를 딕셔너리로 변환
if "humor_matrix" in persona_dict and hasattr(persona_dict["humor_matrix"], "to_dict"):
persona_dict["humor_matrix"] = persona_dict["humor_matrix"].to_dict()
if "personality" in persona_dict and hasattr(persona_dict["personality"], "to_dict"):
persona_dict["personality"] = persona_dict["personality"].to_dict()
# 현재 시간을 파일명에 추가
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
object_name = persona.get("name", "unknown_persona").replace(" ", "_").lower()
filename = f"{object_name}_{timestamp}.json"
# JSON 문자열로 변환
json_str = json.dumps(persona_dict, ensure_ascii=False, indent=2)
return filename, json_str
except Exception as e:
print(f"페르소나 내보내기 실패: {e}")
return None, f"페르소나 내보내기 실패: {e}"
def import_persona_json(file_obj):
"""
JSON 파일로부터 페르소나를 불러오는 기능
"""
if not file_obj:
return None, "업로드된 파일이 없습니다."
try:
# JSON 파일을 로드하여 딕셔너리로 변환
content = file_obj.read().decode('utf-8')
persona_dict = json.loads(content)
# 필수 필드 확인
required_fields = ["name", "object_type"]
for field in required_fields:
if field not in persona_dict:
return None, f"유효하지 않은 페르소나 파일: {field} 필드가 없습니다."
# 복잡한 객체 재구성
if "humor_matrix" in persona_dict and isinstance(persona_dict["humor_matrix"], dict):
persona_dict["humor_matrix"] = HumorMatrix.from_dict(persona_dict["humor_matrix"])
if "personality" in persona_dict and isinstance(persona_dict["personality"], dict):
persona_dict["personality"] = PersonalityProfile.from_dict(persona_dict["personality"])
return persona_dict, f"{persona_dict['name']} 페르소나를 로드했습니다."
except Exception as e:
print(f"페르소나 불러오기 실패: {e}")
return None, f"페르소나 불러오기 실패: {e}"