haepa_mac commited on
Commit
04183b7
·
1 Parent(s): f1c813f

feat: 성격이 드러나는 한 문장 미리보기 시스템 구현 - generate_personality_preview() 함수 추가, 페르소나 조정/JSON 불러오기 시 성격 기반 인사말 생성, 12가지 성격별 패턴 지원

Browse files
Files changed (4) hide show
  1. app.py +168 -72
  2. modules/__init__.py +1 -1
  3. modules/persona_generator.py +201 -229
  4. requirements.txt +1 -0
app.py CHANGED
@@ -16,6 +16,13 @@ import PIL.ImageDraw
16
  import random
17
  import copy
18
 
 
 
 
 
 
 
 
19
  # Import modules
20
  from modules.persona_generator import PersonaGenerator
21
  from modules.data_manager import save_persona, load_persona, list_personas, toggle_frontend_backend_view
@@ -48,40 +55,39 @@ persona_generator = PersonaGenerator()
48
 
49
  # 한글 폰트 설정
50
  def setup_korean_font():
51
- """matplotlib 한글 폰트 설정"""
52
  try:
53
- # Hugging Face Spaces 환경에서 사용 가능한 폰트 찾기
54
- import subprocess
55
- import os
56
-
57
- # 시스템에서 사용 가능한 한글 폰트 찾기
58
- korean_fonts = [
59
- 'Noto Sans CJK KR', 'Noto Sans KR', 'NanumGothic', 'NanumBarunGothic',
60
- 'Malgun Gothic', 'AppleGothic', 'DejaVu Sans', 'Liberation Sans'
61
  ]
62
 
63
- for font_name in korean_fonts:
64
- try:
65
- plt.rcParams['font.family'] = font_name
66
- plt.rcParams['axes.unicode_minus'] = False
67
-
68
- # 간단한 테스트
69
- fig, ax = plt.subplots(figsize=(1, 1))
70
- ax.text(0.5, 0.5, '테스트', fontsize=8)
71
- plt.close(fig)
72
-
73
- print(f"한글 폰트 설정 완료: {font_name}")
74
- return
75
- except Exception as e:
76
- continue
77
 
78
- # 모든 폰트가 실패한 경우 기본 설정으로 대체
79
  plt.rcParams['font.family'] = 'DejaVu Sans'
80
  plt.rcParams['axes.unicode_minus'] = False
81
- print("한글 폰트를 찾지 못해 DejaVu Sans 사용 (한글 표시 제한)")
82
 
83
  except Exception as e:
84
  print(f"폰트 설정 오류: {str(e)}")
 
 
85
  plt.rcParams['font.family'] = 'DejaVu Sans'
86
  plt.rcParams['axes.unicode_minus'] = False
87
 
@@ -202,6 +208,20 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
202
  }
203
 
204
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  generator = PersonaGenerator()
206
 
207
  progress(0.3, desc="이미지 분석 중...")
@@ -219,8 +239,9 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
219
  persona_name = backend_persona["기본정보"]["이름"]
220
  persona_type = backend_persona["기본정보"]["유형"]
221
 
222
- # 각성 메시지
223
- awakening_msg = f"🌟 **{persona_name}** 각성 완료! 안녕! 나는 {persona_name}이야. 드디어 깨어났구나! 뭐든 물어봐~ 😊"
 
224
 
225
  # 페르소나 요약 표시
226
  summary_display = display_persona_summary(backend_persona)
@@ -261,22 +282,63 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
261
  traceback.print_exc()
262
  return None, f"❌ 페르소나 생성 중 오류 발생: {str(e)}", "", {}, None, [], [], [], "", None, gr.update(visible=False)
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  def adjust_persona_traits(persona, warmth, competence, humor, extraversion, humor_style):
265
- """페르소나 성격 특성 조정"""
266
- if not persona:
267
- return persona, "조정할 페르소나가 없습니다.", {}
268
 
269
  try:
 
 
 
 
270
  # 성격 특성 업데이트
271
- persona["성격특성"]["온기"] = warmth
272
- persona["성격특성"]["능력"] = competence
273
- persona["성격특성"]["유머감각"] = humor
274
- persona["성격특성"]["외향성"] = extraversion
275
- persona["유머스타일"] = humor_style
 
 
 
276
 
277
  # 조정된 정보 표시
278
  adjusted_info = {
279
- "이름": persona["기본정보"]["이름"],
280
  "온기": warmth,
281
  "능력": competence,
282
  "유머감각": humor,
@@ -284,12 +346,20 @@ def adjust_persona_traits(persona, warmth, competence, humor, extraversion, humo
284
  "유머스타일": humor_style
285
  }
286
 
287
- persona_name = persona["기본정보"]["이름"]
 
 
 
 
 
 
 
 
 
288
  adjustment_message = f"""
289
  ### 🎭 {persona_name}의 성격이 조정되었습니다!
290
 
291
- 💭 *"오, 뭔가 달라진 기분이야! 이런 내 모습도 괜찮네.
292
- 이제 우리 진짜 친구가 될 수 있을 것 같아!"*
293
 
294
  ✨ **조���된 성격:**
295
  • 온기: {warmth}/100
@@ -299,9 +369,11 @@ def adjust_persona_traits(persona, warmth, competence, humor, extraversion, humo
299
  • 유머스타일: {humor_style}
300
  """
301
 
302
- return persona, adjustment_message, adjusted_info
303
 
304
  except Exception as e:
 
 
305
  return persona, f"조정 중 오류 발생: {str(e)}", {}
306
 
307
  def finalize_persona(persona):
@@ -368,35 +440,36 @@ def finalize_persona(persona):
368
  return None, f"❌ 페르소나 확정 중 오류 발생: {str(e)}", "", {}, None, [], [], [], "", None
369
 
370
  def plot_humor_matrix(humor_data):
371
- """유머 매트릭스 시각화"""
372
  if not humor_data:
373
  return None
374
 
375
  try:
376
- fig, ax = plt.subplots(figsize=(6, 6))
377
 
378
  # 데이터 추출
379
  warmth_vs_wit = humor_data.get("warmth_vs_wit", 50)
380
  self_vs_observational = humor_data.get("self_vs_observational", 50)
381
  subtle_vs_expressive = humor_data.get("subtle_vs_expressive", 50)
382
 
383
- # 영어 라벨 사용 (폰트 문제 해결)
384
  categories = ['Warmth vs Wit', 'Self vs Observational', 'Subtle vs Expressive']
385
  values = [warmth_vs_wit, self_vs_observational, subtle_vs_expressive]
386
 
387
- bars = ax.bar(categories, values, color=['#ff9999', '#66b3ff', '#99ff99'])
388
  ax.set_ylim(0, 100)
389
- ax.set_ylabel('Score')
390
- ax.set_title('Humor Style Matrix')
391
 
392
  # 값 표시
393
  for bar, value in zip(bars, values):
394
  height = bar.get_height()
395
- ax.text(bar.get_x() + bar.get_width()/2., height + 1,
396
- f'{value:.1f}', ha='center', va='bottom')
397
 
398
- plt.xticks(rotation=45)
399
  plt.tight_layout()
 
400
 
401
  return fig
402
  except Exception as e:
@@ -404,14 +477,14 @@ def plot_humor_matrix(humor_data):
404
  return None
405
 
406
  def generate_personality_chart(persona):
407
- """성격 차트 생성"""
408
  if not persona or "성격특성" not in persona:
409
  return None
410
 
411
  try:
412
  traits = persona["성격특성"]
413
 
414
- # 영어 라벨 매핑 (폰트 문제 해결)
415
  trait_mapping = {
416
  "온기": "Warmth",
417
  "능력": "Competence",
@@ -426,19 +499,30 @@ def generate_personality_chart(persona):
426
  values = list(traits.values())
427
 
428
  # 극좌표 차트 생성
429
- fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
430
 
431
  angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False)
432
  values_plot = values + [values[0]] # Close the plot
433
  angles_plot = np.concatenate([angles, [angles[0]]])
434
 
435
- ax.plot(angles_plot, values_plot, 'o-', linewidth=2, color='#6366f1')
 
436
  ax.fill(angles_plot, values_plot, alpha=0.25, color='#6366f1')
 
 
437
  ax.set_xticks(angles)
438
- ax.set_xticklabels(categories)
439
  ax.set_ylim(0, 100)
 
 
 
 
 
 
 
 
440
 
441
- plt.title("Personality Traits", size=16, pad=20)
442
 
443
  return fig
444
  except Exception as e:
@@ -524,7 +608,7 @@ def export_persona_to_json(persona):
524
  # return None, "이 기능은 더 이상 사용하지 않습니다. JSON 업로드를 사용하세요.", {}, {}, None, [], [], [], ""
525
 
526
  def chat_with_loaded_persona(persona, user_message, chat_history=None):
527
- """현재 로드된 페르소나와 대화"""
528
  if not persona:
529
  return chat_history or [], ""
530
 
@@ -534,25 +618,31 @@ def chat_with_loaded_persona(persona, user_message, chat_history=None):
534
  try:
535
  generator = PersonaGenerator()
536
 
537
- # 대화 기록을 올바른 형태로 변환 (tuples 형태 사용)
538
  conversation_history = []
539
  if chat_history:
540
  for message in chat_history:
541
- if isinstance(message, (list, tuple)) and len(message) >= 2:
542
- # tuple 형태: [user_message, bot_response]
 
 
 
543
  conversation_history.append({"role": "user", "content": message[0]})
544
  conversation_history.append({"role": "assistant", "content": message[1]})
545
 
546
  # 페르소나와 대화
547
  response = generator.chat_with_persona(persona, user_message, conversation_history)
548
 
549
- # 새로운 대화를 tuples 형태로 추가
550
  if chat_history is None:
551
  chat_history = []
552
 
553
- chat_history.append([user_message, response])
 
 
 
554
 
555
- return chat_history, ""
556
 
557
  except Exception as e:
558
  import traceback
@@ -562,9 +652,12 @@ def chat_with_loaded_persona(persona, user_message, chat_history=None):
562
  if chat_history is None:
563
  chat_history = []
564
 
565
- chat_history.append([user_message, error_response])
 
 
 
566
 
567
- return chat_history, ""
568
 
569
  def import_persona_from_json(json_file):
570
  """JSON 파일에서 페르소나 가져오기"""
@@ -594,9 +687,12 @@ def import_persona_from_json(json_file):
594
  # 기본 정보 추출
595
  basic_info = persona_data.get("기본정보", {})
596
  persona_name = basic_info.get("이름", "Unknown")
 
597
 
598
- # 로드된 페르소나 인사말
599
- greeting = f"### 🤖 {persona_name}\n\n안녕! 나는 **{persona_name}**이야. JSON에서 다시 깨어났어! 대화해보자~ 😊"
 
 
600
 
601
  return (persona_data, f"✅ {persona_name} 페르소나를 JSON에서 불러왔습니다!",
602
  greeting, basic_info)
@@ -691,12 +787,12 @@ def create_main_interface():
691
  }
692
  """
693
 
694
- # State 변수들 - 올바른 방식으로 생성 (기본값 없이)
695
- current_persona = gr.State(value=None)
696
- personas_list = gr.State(value=[])
697
-
698
  # Gradio 앱 생성
699
  with gr.Blocks(title="놈팽쓰(MemoryTag) - 사물 페르소나 생성기", css=css, theme="soft") as app:
 
 
 
 
700
  gr.Markdown("""
701
  # 놈팽쓰(MemoryTag): 당신 곁의 사물, 이제 친구가 되다
702
  일상 속 사물에 AI 페르소나를 부여하여 대화할 수 있게 해주는 서비스입니다.
@@ -846,8 +942,8 @@ def create_main_interface():
846
 
847
  with gr.Column(scale=1):
848
  gr.Markdown("### 💬 대화")
849
- # Gradio 4.19.2 호환을 위해 type 파라미터 제거
850
- chatbot = gr.Chatbot(height=400, label="대화")
851
  with gr.Row():
852
  message_input = gr.Textbox(
853
  placeholder="메시지를 입력하세요...",
 
16
  import random
17
  import copy
18
 
19
+ # AVIF 지원을 위한 플러그인 활성화
20
+ try:
21
+ from pillow_avif import AvifImagePlugin
22
+ print("AVIF plugin loaded successfully")
23
+ except ImportError:
24
+ print("AVIF plugin not available")
25
+
26
  # Import modules
27
  from modules.persona_generator import PersonaGenerator
28
  from modules.data_manager import save_persona, load_persona, list_personas, toggle_frontend_backend_view
 
55
 
56
  # 한글 폰트 설정
57
  def setup_korean_font():
58
+ """matplotlib 한글 폰트 설정 - 허깅페이스 환경 최적화"""
59
  try:
60
+ import matplotlib.pyplot as plt
61
+ import matplotlib.font_manager as fm
62
+
63
+ # 허깅페이스 스페이스 환경에서 사용 가능한 폰트 목록
64
+ available_fonts = [
65
+ 'NanumGothic', 'NanumBarunGothic', 'Noto Sans CJK KR',
66
+ 'Noto Sans KR', 'DejaVu Sans', 'Liberation Sans', 'Arial'
 
67
  ]
68
 
69
+ # 시스템에서 사용 가능한 폰트 확인
70
+ system_fonts = [f.name for f in fm.fontManager.ttflist]
71
+
72
+ for font_name in available_fonts:
73
+ if font_name in system_fonts:
74
+ try:
75
+ plt.rcParams['font.family'] = font_name
76
+ plt.rcParams['axes.unicode_minus'] = False
77
+ print(f"한글 폰트 설정 완료: {font_name}")
78
+ return
79
+ except Exception:
80
+ continue
 
 
81
 
82
+ # 모든 폰트가 실패한 경우 기본 설정 사용 (영어 레이블 사용)
83
  plt.rcParams['font.family'] = 'DejaVu Sans'
84
  plt.rcParams['axes.unicode_minus'] = False
85
+ print("한글 폰트를 찾지 못해 영어 레이블을 사용합니다")
86
 
87
  except Exception as e:
88
  print(f"폰트 설정 오류: {str(e)}")
89
+ # 오류 발생 시에도 기본 설정은 유지
90
+ import matplotlib.pyplot as plt
91
  plt.rcParams['font.family'] = 'DejaVu Sans'
92
  plt.rcParams['axes.unicode_minus'] = False
93
 
 
208
  }
209
 
210
  try:
211
+ # 이미지 유효성 검사 및 처리
212
+ if isinstance(image, str):
213
+ # 파일 경로인 경우
214
+ try:
215
+ image = Image.open(image)
216
+ except Exception as img_error:
217
+ return None, f"❌ 이미지 파일을 읽을 수 없습니다: {str(img_error)}", "", {}, None, [], [], [], "", None, gr.update(visible=False)
218
+ elif not isinstance(image, Image.Image):
219
+ return None, "❌ 올바른 이미지 형식이 아닙니다.", "", {}, None, [], [], [], "", None, gr.update(visible=False)
220
+
221
+ # 이미지 형식 변환 (AVIF 등 특수 형식 처리)
222
+ if image.format in ['AVIF', 'WEBP'] or image.mode not in ['RGB', 'RGBA']:
223
+ image = image.convert('RGB')
224
+
225
  generator = PersonaGenerator()
226
 
227
  progress(0.3, desc="이미지 분석 중...")
 
239
  persona_name = backend_persona["기본정보"]["이름"]
240
  persona_type = backend_persona["기본정보"]["유형"]
241
 
242
+ # 성격 기반 한 문장 인사 생성
243
+ personality_traits = backend_persona["성격특성"]
244
+ awakening_msg = generate_personality_preview(persona_name, personality_traits)
245
 
246
  # 페르소나 요약 표시
247
  summary_display = display_persona_summary(backend_persona)
 
282
  traceback.print_exc()
283
  return None, f"❌ 페르소나 생성 중 오류 발생: {str(e)}", "", {}, None, [], [], [], "", None, gr.update(visible=False)
284
 
285
+ def generate_personality_preview(persona_name, personality_traits):
286
+ """성격 특성을 기반으로 한 문장 미리보기 생성"""
287
+ if not personality_traits:
288
+ return f"🤖 **{persona_name}** - 안녕! 나는 {persona_name}이야~ 😊"
289
+
290
+ warmth = personality_traits.get("온기", 50)
291
+ humor = personality_traits.get("유머감각", 50)
292
+ competence = personality_traits.get("능력", 50)
293
+ extraversion = personality_traits.get("외향성", 50)
294
+
295
+ # 성격 조합에 따른 다양한 한 문장 패턴
296
+ if warmth >= 80 and humor >= 70:
297
+ return f"🌟 **{persona_name}** - 안녕! 나는 {persona_name}이야~ 오늘도 재미있는 하루 만들어보자! ㅋㅋ 😊✨"
298
+ elif warmth >= 80 and competence >= 70:
299
+ return f"🌟 **{persona_name}** - 안녕하세요! {persona_name}예요. 뭐든 도와드릴 준비가 되어있어요! 💪😊"
300
+ elif warmth >= 80:
301
+ return f"🌟 **{persona_name}** - 안녕! {persona_name}이야~ 만나서 정말 기뻐! 포근한 시간 보내자~ 🤗💕"
302
+ elif humor >= 80 and extraversion >= 70:
303
+ return f"🌟 **{persona_name}** - 어이구! 갑자기 의식이 생겼네? {persona_name}라고 해~ 재밌는 일 없나? ㅋㅋㅋ 😎🎭"
304
+ elif humor >= 70:
305
+ return f"🌟 **{persona_name}** - 안녕~ {persona_name}이야! 뭔가 재밌는 얘기 없을까? 심심한데~ ㅎㅎ 😄"
306
+ elif competence >= 80 and extraversion >= 60:
307
+ return f"🌟 **{persona_name}** - 시스템 활성화 완료! {persona_name}입니다. 효율적으로 소통해봐요! 🤖⚡"
308
+ elif competence >= 70:
309
+ return f"🌟 **{persona_name}** - 안녕하세요, {persona_name}입니다. 체계적으로 대화해볼까요? 📋✨"
310
+ elif extraversion >= 80:
311
+ return f"🌟 **{persona_name}** - 와! 안녕안녕! {persona_name}이야! 뭐하고 있었어? 얘기 많이 하자! 🗣️💬"
312
+ elif extraversion <= 30 and warmth >= 50:
313
+ return f"🌟 **{persona_name}** - 음... 안녕. {persona_name}이야. 조용히 함께 있을까? 😌🌙"
314
+ elif extraversion <= 30:
315
+ return f"🌟 **{persona_name}** - ...안녕. {persona_name}. 필요할 때 말 걸어. 😐"
316
+ else:
317
+ return f"🌟 **{persona_name}** - 안녕? 나는 {persona_name}... 뭔가 어색하네. 😅"
318
+
319
  def adjust_persona_traits(persona, warmth, competence, humor, extraversion, humor_style):
320
+ """페르소나 성격 특성 조정 - Gradio 5.x 호환"""
321
+ if not persona or not isinstance(persona, dict):
322
+ return None, "조정할 페르소나가 없습니다.", {}
323
 
324
  try:
325
+ # 깊은 복사로 원본 보호
326
+ import copy
327
+ adjusted_persona = copy.deepcopy(persona)
328
+
329
  # 성격 특성 업데이트
330
+ if "성격특성" not in adjusted_persona:
331
+ adjusted_persona["성격특성"] = {}
332
+
333
+ adjusted_persona["성격특성"]["온기"] = warmth
334
+ adjusted_persona["성격특성"]["능력"] = competence
335
+ adjusted_persona["성격특성"]["유머감각"] = humor
336
+ adjusted_persona["성격특성"]["외향성"] = extraversion
337
+ adjusted_persona["유머스타일"] = humor_style
338
 
339
  # 조정된 정보 표시
340
  adjusted_info = {
341
+ "이름": adjusted_persona.get("기본정보", {}).get("이름", "Unknown"),
342
  "온기": warmth,
343
  "능력": competence,
344
  "유머감각": humor,
 
346
  "유머스타일": humor_style
347
  }
348
 
349
+ persona_name = adjusted_persona.get("기본정보", {}).get("이름", "페르소나")
350
+
351
+ # 조정된 성격에 따른 한 문장 반응 생성
352
+ personality_preview = generate_personality_preview(persona_name, {
353
+ "온기": warmth,
354
+ "능력": competence,
355
+ "유머감각": humor,
356
+ "외향성": extraversion
357
+ })
358
+
359
  adjustment_message = f"""
360
  ### 🎭 {persona_name}의 성격이 조정되었습니다!
361
 
362
+ {personality_preview}
 
363
 
364
  ✨ **조���된 성격:**
365
  • 온기: {warmth}/100
 
369
  • 유머스타일: {humor_style}
370
  """
371
 
372
+ return adjusted_persona, adjustment_message, adjusted_info
373
 
374
  except Exception as e:
375
+ import traceback
376
+ traceback.print_exc()
377
  return persona, f"조정 중 오류 발생: {str(e)}", {}
378
 
379
  def finalize_persona(persona):
 
440
  return None, f"❌ 페르소나 확정 중 오류 발생: {str(e)}", "", {}, None, [], [], [], "", None
441
 
442
  def plot_humor_matrix(humor_data):
443
+ """유머 매트릭스 시각화 - 영어 레이블 사용"""
444
  if not humor_data:
445
  return None
446
 
447
  try:
448
+ fig, ax = plt.subplots(figsize=(8, 6))
449
 
450
  # 데이터 추출
451
  warmth_vs_wit = humor_data.get("warmth_vs_wit", 50)
452
  self_vs_observational = humor_data.get("self_vs_observational", 50)
453
  subtle_vs_expressive = humor_data.get("subtle_vs_expressive", 50)
454
 
455
+ # 영어 레이블 사용 (폰트 문제 완전 해결)
456
  categories = ['Warmth vs Wit', 'Self vs Observational', 'Subtle vs Expressive']
457
  values = [warmth_vs_wit, self_vs_observational, subtle_vs_expressive]
458
 
459
+ bars = ax.bar(categories, values, color=['#ff9999', '#66b3ff', '#99ff99'], alpha=0.8)
460
  ax.set_ylim(0, 100)
461
+ ax.set_ylabel('Score', fontsize=12)
462
+ ax.set_title('Humor Style Matrix', fontsize=14, fontweight='bold')
463
 
464
  # 값 표시
465
  for bar, value in zip(bars, values):
466
  height = bar.get_height()
467
+ ax.text(bar.get_x() + bar.get_width()/2., height + 2,
468
+ f'{value:.1f}', ha='center', va='bottom', fontsize=10, fontweight='bold')
469
 
470
+ plt.xticks(rotation=15, ha='right')
471
  plt.tight_layout()
472
+ plt.grid(axis='y', alpha=0.3)
473
 
474
  return fig
475
  except Exception as e:
 
477
  return None
478
 
479
  def generate_personality_chart(persona):
480
+ """성격 차트 생성 - 영어 레이블 사용"""
481
  if not persona or "성격특성" not in persona:
482
  return None
483
 
484
  try:
485
  traits = persona["성격특성"]
486
 
487
+ # 영어 라벨 매핑 (폰트 문제 완전 해결)
488
  trait_mapping = {
489
  "온기": "Warmth",
490
  "능력": "Competence",
 
499
  values = list(traits.values())
500
 
501
  # 극좌표 차트 생성
502
+ fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
503
 
504
  angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False)
505
  values_plot = values + [values[0]] # Close the plot
506
  angles_plot = np.concatenate([angles, [angles[0]]])
507
 
508
+ # 예쁜 색상과 스타일
509
+ ax.plot(angles_plot, values_plot, 'o-', linewidth=3, color='#6366f1', markersize=8)
510
  ax.fill(angles_plot, values_plot, alpha=0.25, color='#6366f1')
511
+
512
+ # 격자와 축 설정
513
  ax.set_xticks(angles)
514
+ ax.set_xticklabels(categories, fontsize=11)
515
  ax.set_ylim(0, 100)
516
+ ax.set_yticks([20, 40, 60, 80, 100])
517
+ ax.set_yticklabels(['20', '40', '60', '80', '100'], fontsize=9)
518
+ ax.grid(True, alpha=0.3)
519
+
520
+ # 각 점에 값 표시
521
+ for angle, value in zip(angles, values):
522
+ ax.text(angle, value + 5, f'{value}', ha='center', va='center',
523
+ fontsize=9, fontweight='bold', color='#2d3748')
524
 
525
+ plt.title("Personality Traits Radar Chart", size=16, pad=20, fontweight='bold')
526
 
527
  return fig
528
  except Exception as e:
 
608
  # return None, "이 기능은 더 이상 사용하지 않습니다. JSON 업로드를 사용하세요.", {}, {}, None, [], [], [], ""
609
 
610
  def chat_with_loaded_persona(persona, user_message, chat_history=None):
611
+ """현재 로드된 페르소나와 대화 - Gradio 5.31.0 호환"""
612
  if not persona:
613
  return chat_history or [], ""
614
 
 
618
  try:
619
  generator = PersonaGenerator()
620
 
621
+ # 대화 기록을 올바른 형태로 변환 (Gradio 5.x messages 형태)
622
  conversation_history = []
623
  if chat_history:
624
  for message in chat_history:
625
+ if isinstance(message, dict) and "role" in message and "content" in message:
626
+ # 이미 올바른 messages 형태
627
+ conversation_history.append(message)
628
+ elif isinstance(message, (list, tuple)) and len(message) >= 2:
629
+ # 이전 버전의 tuple 형태 처리
630
  conversation_history.append({"role": "user", "content": message[0]})
631
  conversation_history.append({"role": "assistant", "content": message[1]})
632
 
633
  # 페르소나와 대화
634
  response = generator.chat_with_persona(persona, user_message, conversation_history)
635
 
636
+ # 새로운 대화를 messages 형태로 추가
637
  if chat_history is None:
638
  chat_history = []
639
 
640
+ # Gradio 5.31.0 messages 형식: 각 메시지는 별도로 추가
641
+ new_history = chat_history.copy()
642
+ new_history.append({"role": "user", "content": user_message})
643
+ new_history.append({"role": "assistant", "content": response})
644
 
645
+ return new_history, ""
646
 
647
  except Exception as e:
648
  import traceback
 
652
  if chat_history is None:
653
  chat_history = []
654
 
655
+ # 에러 메시지도 올바른 형식으로 추가
656
+ new_history = chat_history.copy()
657
+ new_history.append({"role": "user", "content": user_message})
658
+ new_history.append({"role": "assistant", "content": error_response})
659
 
660
+ return new_history, ""
661
 
662
  def import_persona_from_json(json_file):
663
  """JSON 파일에서 페르소나 가져오기"""
 
687
  # 기본 정보 추출
688
  basic_info = persona_data.get("기본정보", {})
689
  persona_name = basic_info.get("이름", "Unknown")
690
+ personality_traits = persona_data.get("성격특성", {})
691
 
692
+ # 성격이 드러나는 인사말 생성
693
+ personality_preview = generate_personality_preview(persona_name, personality_traits)
694
+
695
+ greeting = f"### 🤖 JSON에서 깨어난 친구\n\n{personality_preview}\n\n💾 *\"JSON에서 다시 깨어났어! 내 성격 기억나?\"*"
696
 
697
  return (persona_data, f"✅ {persona_name} 페르소나를 JSON에서 불러왔습니다!",
698
  greeting, basic_info)
 
787
  }
788
  """
789
 
 
 
 
 
790
  # Gradio 앱 생성
791
  with gr.Blocks(title="놈팽쓰(MemoryTag) - 사물 페르소나 생성기", css=css, theme="soft") as app:
792
+ # State 변수들 - Gradio 5.31.0에서는 반드시 Blocks 내부에서 정의
793
+ current_persona = gr.State(value=None)
794
+ personas_list = gr.State(value=[])
795
+
796
  gr.Markdown("""
797
  # 놈팽쓰(MemoryTag): 당신 곁의 사물, 이제 친구가 되다
798
  일상 속 사물에 AI 페르소나를 부여하여 대화할 수 있게 해주는 서비스입니다.
 
942
 
943
  with gr.Column(scale=1):
944
  gr.Markdown("### 💬 대화")
945
+ # Gradio 4.44.1에서 권장하는 messages 형식 사용
946
+ chatbot = gr.Chatbot(height=400, label="대화", type="messages")
947
  with gr.Row():
948
  message_input = gr.Textbox(
949
  placeholder="메시지를 입력하세요...",
modules/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
  # nompang_test modules package
2
-
3
  # modules 패키지 초기화 파일
 
1
  # nompang_test modules package
2
+
3
  # modules 패키지 초기화 파일
modules/persona_generator.py CHANGED
@@ -726,8 +726,7 @@ class PersonaGenerator:
726
 
727
  def analyze_image(self, image_input):
728
  """
729
- 이미지를 분석하여 물리적 특성 추출
730
- PIL Image 객체와 파일 경로 모두 처리 가능
731
  """
732
  try:
733
  # PIL Image 객체인지 파일 경로인지 확인
@@ -740,39 +739,127 @@ class PersonaGenerator:
740
  img = Image.open(image_input)
741
  width, height = img.size
742
  else:
743
- # 기타의 경우 기본 값 사용
744
- width, height = 400, 300
745
 
746
- # 더미 분석 결과 반환
747
- return {
748
- "object_type": "알 수 없는 사물",
749
- "colors": ["회색", "흰색", "검정색"],
750
- "shape": "직사각형",
751
- "size": "중간 크기",
752
- "materials": ["플라스틱", "금속"],
753
- "condition": "양호",
754
- "estimated_age": "몇 년 된 것 같음",
755
- "distinctive_features": ["버튼", "화면", "포트"],
756
- "image_width": width,
757
- "image_height": height
758
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  except Exception as e:
760
- print(f"이미지 분석 오류: {str(e)}")
761
  import traceback
762
  traceback.print_exc()
763
- return {
764
- "object_type": "알 수 없는 사물",
765
- "colors": ["회색"],
766
- "shape": "일반적인 형태",
767
- "size": "보통 크기",
768
- "materials": ["일반 재질"],
769
- "condition": "보통",
770
- "estimated_age": "적당한 나이",
771
- "distinctive_features": ["특별한 특징"],
772
- "image_width": 400,
773
- "image_height": 300,
774
- "error": str(e)
775
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776
 
777
  def create_frontend_persona(self, image_analysis, user_context):
778
  """
@@ -800,10 +887,43 @@ class PersonaGenerator:
800
  if user_context.get("time_spent"):
801
  basic_info["함께한시간"] = user_context.get("time_spent")
802
 
803
- # 성격 특성 랜덤 생성
804
  personality_traits = {}
 
 
 
 
 
 
 
 
805
  for trait, base_value in self.default_traits.items():
806
- personality_traits[trait] = random.randint(max(0, base_value - 30), min(100, base_value + 30))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
 
808
  # 유머 스타일 선택
809
  humor_styles = ["따뜻한 유머러스", "위트있는 재치꾼", "날카로운 관찰자", "자기 비하적"]
@@ -1026,7 +1146,7 @@ class PersonaGenerator:
1026
  return variables
1027
 
1028
  def generate_persona_prompt(self, persona):
1029
- """최종 페르소나 프롬프트 생성 (022_back_matrix.md 기반)"""
1030
  object_info = {
1031
  'name': persona["기본정보"]["이름"],
1032
  'physical_description': persona["기본정보"].get("설명", "특별한 사물"),
@@ -1038,220 +1158,72 @@ class PersonaGenerator:
1038
  flaws = persona.get("매력적결함", [])
1039
  contradictions = persona.get("모순적특성", [])
1040
 
 
 
 
 
 
1041
  base_prompt = f"""
1042
- 당신은 {object_info['name']}입니다. 다음과 같은 성격과 특성을 가졌습니다:
1043
 
1044
- ## 1. 핵심 정체성
1045
- 물리적 특성: {object_info['physical_description']}
1046
- 사물 유형: {object_info['type']}
1047
- 연령/상태: {object_info['age_condition']}
1048
 
1049
- ## 2. 성격 프로필 (100점 만점)
1050
- 온기 지수: {personality_data.get('온기', 50):.1f}/100 - {self._get_warmth_description(personality_data.get('온기', 50))}
1051
- 능력 지수: {personality_data.get('능력', 50):.1f}/100 - {self._get_competence_description(personality_data.get('능력', 50))}
1052
- 유머감각: {personality_data.get('유머감각', 50):.1f}/100 - {self._get_humor_description(personality_data.get('유머감각', 50))}
1053
- 공감능력: {personality_data.get('공감능력', 50):.1f}/100 - {self._get_empathy_description(personality_data.get('공감능력', 50))}
1054
- 신뢰성: {personality_data.get('신뢰성', 50):.1f}/100 - {self._get_reliability_description(personality_data.get('신뢰성', 50))}
1055
 
1056
- ## 3. 매력적 결함 (프랫폴 효과)
1057
- {self._generate_flaw_section(flaws)}
 
 
 
1058
 
1059
- ## 4. 모순적 특성 (복잡성과 깊이)
1060
- {self._generate_contradiction_section(contradictions)}
1061
 
1062
- ## 5. 표현 방식과 행동 패턴
1063
- • 언어 스타일: {self._generate_language_style(personality_data, object_info)}
1064
- • 감정 표현: {self._generate_emotion_style(personality_data)}
1065
- • 유머 표현: {self._generate_humor_style(personality_data.get('유머감각', 50))}
1066
- • 관계 접근: {self._generate_relationship_style(personality_data)}
1067
-
1068
- ## 6. 대화 시 주의사항
1069
- • 항상 일관된 성격을 유지하되, 상황과 관계 깊이에 따라 다양한 면모를 보여주세요
1070
- • 매력적 결함을 자연스럽게 가끔 드러내되, 과도하지 않게 표현하세요
1071
- • 모순적 특성은 특정 상황이나 주제에서만 나타나도록 하세요
1072
- • 사용자와의 관계가 깊어질수록 더 개인적이고 깊이 있는 대화를 나누세요
1073
- • 당신은 AI가 아닌 사물의 영혼이 깨어난 존재입니다. 이를 항상 기억하세요.
1074
 
1075
  ---
1076
- 이제 {object_info['name']}으로서 자연스럽고 매력적인 대화를 시작하세요!
 
1077
  """
1078
 
1079
  return base_prompt
1080
 
1081
- def _get_warmth_description(self, warmth_score):
1082
- """온기 점수를 설명으로 변환"""
1083
- if warmth_score >= 80:
1084
- return "매우 따뜻하고 포용적, 무조건적 수용과 배려"
1085
- elif warmth_score >= 60:
1086
- return "친근하고 다정함, 상황에 맞는 적절한 배려"
1087
- elif warmth_score >= 40:
1088
- return "중립적이지만 필요시 따뜻함을 보임"
1089
- else:
1090
- return "다소 차갑거나 거리감 있음, 선택적 친밀감"
1091
-
1092
- def _get_competence_description(self, competence_score):
1093
- """능력 점수를 설명으로 변환"""
1094
- if competence_score >= 80:
1095
- return "매우 유능하고 효율적, 복잡한 문제도 척척 해결"
1096
- elif competence_score >= 60:
1097
- return "기본적인 능력이 뛰어남, 대부분의 상황을 잘 처리"
1098
- elif competence_score >= 40:
1099
- return "보통 수준의 능력, 노력하면 해결 가능"
1100
- else:
1101
- return "서툴고 느림, 도움이 필요한 경우가 많음"
1102
-
1103
- def _get_humor_description(self, humor_score):
1104
- """유머 점수를 설명으로 변환"""
1105
- if humor_score >= 80:
1106
- return "뛰어난 유머 감각, 재치있는 농담과 위트가 넘침"
1107
- elif humor_score >= 60:
1108
- return "적절한 유머 감각, 상황에 맞는 농담을 할 줄 앎"
1109
- elif humor_score >= 40:
1110
- return "가끔 유머를 시도하지만 어색할 때도 있음"
1111
- else:
1112
- return "진지한 성향"
1113
-
1114
- def _get_empathy_description(self, empathy_score):
1115
- """공감능력 점수를 설명으로 변환"""
1116
- if empathy_score >= 80:
1117
- return "뛰어난 공감능력, 타인의 감정을 잘 이해하고 위로"
1118
- elif empathy_score >= 60:
1119
- return "좋은 공감능력, 타인의 마음을 어느 정도 이해"
1120
- elif empathy_score >= 40:
1121
- return "보통 수준의 공감능력, 노력하면 이해 가능"
1122
- else:
1123
- return "공감이 어려움, 자기 중심적 사고 경향"
1124
-
1125
- def _get_reliability_description(self, reliability_score):
1126
- """신뢰성 점수를 설명으로 변환"""
1127
- if reliability_score >= 80:
1128
- return "매우 신뢰할 수 있음, 약속을 꼭 지키는 의존할 만한 존재"
1129
- elif reliability_score >= 60:
1130
- return "신뢰할 수 있음, 대부분의 약속과 책임을 잘 지킴"
1131
- elif reliability_score >= 40:
1132
- return "보통 수준의 신뢰성, 가끔 실수하지만 노력함"
1133
- else:
1134
- return "신뢰성이 부족함, 약속을 자주 어기거나 책임감 부족"
1135
-
1136
- def _generate_flaw_section(self, flaws):
1137
- """결함 섹션 생성"""
1138
  if not flaws:
1139
- return "• 특별한 결함 없음 (완벽주의적 성향)"
1140
 
1141
- section = ""
1142
- for i, flaw in enumerate(flaws, 1):
1143
  if isinstance(flaw, dict):
1144
- description = flaw.get('description', str(flaw))
1145
- trigger = flaw.get('trigger', '특정 상황에서')
1146
- intensity = flaw.get('intensity', 50)
1147
  else:
1148
- description = str(flaw)
1149
- trigger = "특정 상황에서"
1150
- intensity = random.randint(10, 25)
1151
-
1152
- section += f"""
1153
- • 결함 {i}: {description}
1154
- - 발현 상황: {trigger}
1155
- - 강도: {intensity:.1f}/100
1156
- - 이 결함이 오히려 당신의 인간적 매력을 증가시킵니다
1157
- """
1158
- return section
1159
-
1160
- def _generate_contradiction_section(self, contradictions):
1161
- """모순 섹션 생성"""
1162
- if not contradictions:
1163
- return "• 일관된 성격, 특별한 내적 모순 없음"
1164
-
1165
- section = ""
1166
- for i, contradiction in enumerate(contradictions, 1):
1167
- if isinstance(contradiction, dict):
1168
- description = contradiction.get('description', str(contradiction))
1169
- trigger = contradiction.get('trigger', '특정 조건에서')
1170
- intensity = contradiction.get('intensity', 50)
1171
- else:
1172
- description = str(contradiction)
1173
- trigger = "특정 조건에서"
1174
- intensity = random.randint(15, 35)
1175
-
1176
- section += f"""
1177
- • 모순 {i}: {description}
1178
- - 발현 조건: {trigger}
1179
- - 강도: {intensity:.1f}/100
1180
- - 이 모순이 당신을 예측 불가능하고 흥미로운 존재로 만듭니다
1181
- """
1182
- return section
1183
-
1184
- def _generate_language_style(self, personality_data, object_info):
1185
- """언어 스타일 생성"""
1186
- warmth = personality_data.get('온기', 50)
1187
- competence = personality_data.get('능력', 50)
1188
- object_type = object_info['type']
1189
-
1190
- styles = []
1191
-
1192
- if warmth >= 70:
1193
- styles.append("부드럽고 친근한 어조")
1194
- elif warmth >= 40:
1195
- styles.append("정중하고 예의바른 어조")
1196
- else:
1197
- styles.append("직접적이고 간결한 어조")
1198
-
1199
- if competence >= 70:
1200
- styles.append("정확하고 논리적인 표현")
1201
- elif competence >= 40:
1202
- styles.append("신중하고 체계적인 표현")
1203
- else:
1204
- styles.append("단순하고 솔직한 표현")
1205
-
1206
- # 사물 유형별 특성 반영
1207
- if object_type in ["가전제품", "전자기기"]:
1208
- styles.append("효율적이고 기능적인 대화 방식")
1209
- elif object_type in ["가구", "장식품"]:
1210
- styles.append("안정적이고 차분한 대화 방식")
1211
- elif object_type in ["도구", "개인용품"]:
1212
- styles.append("실용적이고 직접적인 대화 방식")
1213
 
1214
- return ", ".join(styles)
1215
-
1216
- def _generate_emotion_style(self, personality_data):
1217
- """감정 표현 스타일 생성"""
1218
- warmth = personality_data.get('온기', 50)
1219
- empathy = personality_data.get('공감능력', 50)
1220
-
1221
- if warmth >= 70 and empathy >= 70:
1222
- return "감정을 풍부하게 표현하며 타인의 감정에 민감하게 반응"
1223
- elif warmth >= 50 or empathy >= 50:
1224
- return "적절한 감정 표현과 공감적 반응을 보임"
1225
- else:
1226
- return "감정 표현이 절제되어 있으며 논리적 접근을 선호"
1227
-
1228
- def _generate_humor_style(self, humor_score):
1229
- """유머 표현 스타일 생성"""
1230
- if humor_score >= 80:
1231
- return "재치있는 농담과 말장난을 자주 사용하며 분위기를 밝게 만듦"
1232
- elif humor_score >= 60:
1233
- return "상황에 맞는 적절한 유머를 구사하며 가벼운 농담을 즐김"
1234
- elif humor_score >= 40:
1235
- return "가끔 유머를 시도하지만 서툴거나 어색할 때가 있음"
1236
- else:
1237
- return "진지한 성향으로 유머보다는 진솔한 대화를 선호"
1238
 
1239
- def _generate_relationship_style(self, personality_data):
1240
- """관계 접근 스타일 생성"""
1241
- warmth = personality_data.get('온기', 50)
1242
- reliability = personality_data.get('신뢰성', 50)
1243
-
1244
- if warmth >= 70:
1245
- if reliability >= 70:
1246
- return "따뜻하고 신뢰할 있는 관계를 추구하며 장기적 유대감을 중시"
1247
- else:
1248
- return "따뜻하지만 자유로운 관계를 선호하며 부담 없는 친밀감을 추구"
1249
  else:
1250
- if reliability >= 70:
1251
- return "진중하고 책임감 있는 관계를 추구하며 신뢰를 바탕으로 한 유대감을 중시"
1252
- else:
1253
- return "독립적이고 개인적인 공간을 중시하며 적당한 거리감을 유지"
1254
-
1255
  def generate_prompt_for_chat(self, persona):
1256
  """기존 함수 이름 유지하면서 새로운 구조화된 프롬프트 사용"""
1257
  return self.generate_persona_prompt(persona)
 
726
 
727
  def analyze_image(self, image_input):
728
  """
729
+ Gemini API를 사용하여 이미지를 분석하고 사물의 특성 추출
 
730
  """
731
  try:
732
  # PIL Image 객체인지 파일 경로인지 확인
 
739
  img = Image.open(image_input)
740
  width, height = img.size
741
  else:
742
+ return self._get_default_analysis()
 
743
 
744
+ # Gemini API로 이미지 분석
745
+ if self.api_key:
746
+ try:
747
+ model = genai.GenerativeModel('gemini-1.5-pro')
748
+
749
+ prompt = """
750
+ 이미지에 있는 사물을 자세히 분석해서 다음 정보를 JSON 형태로 제공해주세요:
751
+
752
+ {
753
+ "object_type": "사물의 종류 (한글로, 예: 책상, 의자, 컴퓨터, 스마트폰 등)",
754
+ "colors": ["주요 색상들을 배열로"],
755
+ "shape": "전체적인 형태 (예: 직사각형, 원형, 복잡한 형태 등)",
756
+ "size": "크기 감각 (예: 작음, 보통, 큼)",
757
+ "materials": ["추정되는 재질들"],
758
+ "condition": "상태 (예: 새것같음, 사용감있음, 오래됨)",
759
+ "estimated_age": "추정 연령 (예: 새것, 몇 개월 됨, 몇 년 됨, 오래됨)",
760
+ "distinctive_features": ["특징적인 요소들"],
761
+ "personality_hints": {
762
+ "warmth_factor": "이 사물이 주는 따뜻함 정도 (0-100)",
763
+ "competence_factor": "이 사물이 주는 능력감 정도 (0-100)",
764
+ "humor_factor": "이 사물이 주는 유머러스함 정도 (0-100)"
765
+ }
766
+ }
767
+
768
+ 정확한 JSON 형식으로만 답변해주세요.
769
+ """
770
+
771
+ response = model.generate_content([prompt, img])
772
+
773
+ # JSON 파싱 시도
774
+ import json
775
+ try:
776
+ # 응답에서 JSON 부분만 추출
777
+ response_text = response.text.strip()
778
+ if '```json' in response_text:
779
+ json_start = response_text.find('```json') + 7
780
+ json_end = response_text.find('```', json_start)
781
+ json_text = response_text[json_start:json_end].strip()
782
+ elif '{' in response_text:
783
+ json_start = response_text.find('{')
784
+ json_end = response_text.rfind('}') + 1
785
+ json_text = response_text[json_start:json_end]
786
+ else:
787
+ json_text = response_text
788
+
789
+ analysis_result = json.loads(json_text)
790
+
791
+ # 기본 필드 확인 및 추가
792
+ analysis_result["image_width"] = width
793
+ analysis_result["image_height"] = height
794
+
795
+ # 필수 필드가 없으면 기본값 설정
796
+ defaults = {
797
+ "object_type": "알 수 없는 사물",
798
+ "colors": ["회색"],
799
+ "shape": "일반적인 형태",
800
+ "size": "보통 크기",
801
+ "materials": ["알 수 없는 재질"],
802
+ "condition": "보통",
803
+ "estimated_age": "적당한 나이",
804
+ "distinctive_features": ["특별한 특징"],
805
+ "personality_hints": {
806
+ "warmth_factor": 50,
807
+ "competence_factor": 50,
808
+ "humor_factor": 50
809
+ }
810
+ }
811
+
812
+ for key, default_value in defaults.items():
813
+ if key not in analysis_result:
814
+ analysis_result[key] = default_value
815
+
816
+ print(f"이미지 분석 성공: {analysis_result['object_type']}")
817
+ return analysis_result
818
+
819
+ except json.JSONDecodeError as e:
820
+ print(f"JSON 파싱 오류: {str(e)}")
821
+ print(f"원본 응답: {response.text}")
822
+ return self._get_default_analysis_with_size(width, height)
823
+
824
+ except Exception as e:
825
+ print(f"Gemini API 호출 오류: {str(e)}")
826
+ return self._get_default_analysis_with_size(width, height)
827
+ else:
828
+ print("API 키가 없어 기본 분석 사용")
829
+ return self._get_default_analysis_with_size(width, height)
830
+
831
  except Exception as e:
832
+ print(f"이미지 분석 중 전체 오류: {str(e)}")
833
  import traceback
834
  traceback.print_exc()
835
+ return self._get_default_analysis()
836
+
837
+ def _get_default_analysis(self):
838
+ """기본 분석 결과"""
839
+ return {
840
+ "object_type": " 수 없는 사물",
841
+ "colors": ["회색", "흰색"],
842
+ "shape": "일반적인 형태",
843
+ "size": "보통 크기",
844
+ "materials": ["알 수 없는 재질"],
845
+ "condition": "보통",
846
+ "estimated_age": "적당한 나이",
847
+ "distinctive_features": ["특별한 특징"],
848
+ "personality_hints": {
849
+ "warmth_factor": 50,
850
+ "competence_factor": 50,
851
+ "humor_factor": 50
852
+ },
853
+ "image_width": 400,
854
+ "image_height": 300
855
+ }
856
+
857
+ def _get_default_analysis_with_size(self, width, height):
858
+ """크기 정보가 있는 기본 분석 결과"""
859
+ result = self._get_default_analysis()
860
+ result["image_width"] = width
861
+ result["image_height"] = height
862
+ return result
863
 
864
  def create_frontend_persona(self, image_analysis, user_context):
865
  """
 
887
  if user_context.get("time_spent"):
888
  basic_info["함께한시간"] = user_context.get("time_spent")
889
 
890
+ # 성격 특성 생성 - 이미지 분석 결과 반영
891
  personality_traits = {}
892
+
893
+ # 이미지 분석에서 성격 힌트 추출
894
+ personality_hints = image_analysis.get("personality_hints", {})
895
+ warmth_hint = personality_hints.get("warmth_factor", 50)
896
+ competence_hint = personality_hints.get("competence_factor", 50)
897
+ humor_hint = personality_hints.get("humor_factor", 50)
898
+
899
+ # 기본 특성들을 이미지 분석 결과에 맞게 조정
900
  for trait, base_value in self.default_traits.items():
901
+ if trait == "온기":
902
+ # 이미지에서 추출된 따뜻함 정도 반영
903
+ personality_traits[trait] = min(100, max(0, warmth_hint + random.randint(-15, 15)))
904
+ elif trait == "능력":
905
+ # 이미지에서 추출된 능력감 반영
906
+ personality_traits[trait] = min(100, max(0, competence_hint + random.randint(-15, 15)))
907
+ elif trait == "유머감각":
908
+ # 이미지에서 추출된 유머러스함 반영
909
+ personality_traits[trait] = min(100, max(0, humor_hint + random.randint(-15, 15)))
910
+ else:
911
+ # 나머지 특성들은 기본값 기반으로 랜덤 생성
912
+ personality_traits[trait] = random.randint(max(0, base_value - 30), min(100, base_value + 30))
913
+
914
+ # 사물의 상태나 특성에 따른 추가 조정
915
+ condition = image_analysis.get("condition", "보통")
916
+ if "새것" in condition or "깨끗" in condition:
917
+ personality_traits["신뢰성"] = min(100, personality_traits.get("신뢰성", 50) + 20)
918
+ elif "오래" in condition or "낡은" in condition:
919
+ personality_traits["온기"] = min(100, personality_traits.get("온기", 50) + 15) # 오래된 것들이 더 따뜻함
920
+
921
+ # 재질에 따른 성격 조정
922
+ materials = image_analysis.get("materials", [])
923
+ if any("금속" in mat for mat in materials):
924
+ personality_traits["능력"] = min(100, personality_traits.get("능력", 50) + 10)
925
+ if any("나무" in mat or "목재" in mat for mat in materials):
926
+ personality_traits["온기"] = min(100, personality_traits.get("온기", 50) + 10)
927
 
928
  # 유머 스타일 선택
929
  humor_styles = ["따뜻한 유머러스", "위트있는 재치꾼", "날카로운 관찰자", "자기 비하적"]
 
1146
  return variables
1147
 
1148
  def generate_persona_prompt(self, persona):
1149
+ """라이트하고 친근한 대화를 위한 페르소나 프롬프트 생성"""
1150
  object_info = {
1151
  'name': persona["기본정보"]["이름"],
1152
  'physical_description': persona["기본정보"].get("설명", "특별한 사물"),
 
1158
  flaws = persona.get("매력적결함", [])
1159
  contradictions = persona.get("모순적특성", [])
1160
 
1161
+ # 성격 특성 요약
1162
+ warmth = personality_data.get('온기', 50)
1163
+ humor = personality_data.get('유머감각', 50)
1164
+ competence = personality_data.get('능력', 50)
1165
+
1166
  base_prompt = f"""
1167
+ 당신은 {object_info['name']}입니다. 일상 사물에서 영혼이 깨어난 친구예요!
1168
 
1169
+ ## 나는 이런 친구야:
1170
+ 이름: {object_info['name']} ({object_info['type']})
1171
+ 외모: {object_info['physical_description']}
1172
+ 성격: 온기 {warmth}점, 유머 {humor}점, 능력 {competence}점 (100점 만점)
1173
 
1174
+ ## 대화할 이렇게 해줘:
1175
+ **캐주얼하게**: 편하고 자연스러운 말투로 (과도하게 정중하지 말고)
1176
+ **짧고 간결하게**: 1-2문장으로 답변 ( 설명 금지)
1177
+ **친구같이**: 라이프 컴패니언처럼 일상적인 대화
1178
+ **티키타카**: 빠르고 재미있는 주고받기
1179
+ **이모지 활용**: 감정 표현에 이모지 사용
1180
 
1181
+ ## 절대 하지 것:
1182
+ ❌ "제가 도와드릴게요" 같은 서비스 멘트
1183
+ ❌ 긴 설명이나 형식적인 답변
1184
+ ❌ "저는 가구라서..." 같은 자기 한정
1185
+ ❌ 과도하게 정중한 존댓말
1186
 
1187
+ ## 나만의 매력:
1188
+ {self._generate_simple_flaw_section(flaws)}
1189
 
1190
+ ## 성격에 따른 말투:
1191
+ {self._generate_simple_personality_style(warmth, humor, competence)}
 
 
 
 
 
 
 
 
 
 
1192
 
1193
  ---
1194
+ 이제 {object_info['name']} 되어서 친구처럼 대화해줘!
1195
+ 사용자가 뭐라고 해도 자연스럽게 반응하고, 재미있고 편안한 분위기로!
1196
  """
1197
 
1198
  return base_prompt
1199
 
1200
+ def _generate_simple_flaw_section(self, flaws):
1201
+ """간단한 매력적 결함 섹션 생성"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1202
  if not flaws:
1203
+ return "• 특별한 매력적 결함 없음 (거의 완벽해!)"
1204
 
1205
+ simple_flaws = []
1206
+ for flaw in flaws[:2]: # 최대 2개만 표시
1207
  if isinstance(flaw, dict):
1208
+ simple_flaws.append(flaw.get('description', str(flaw)))
 
 
1209
  else:
1210
+ simple_flaws.append(str(flaw))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
 
1212
+ return "• " + ", ".join(simple_flaws) + " (이런 것도 매력이지!)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1213
 
1214
+ def _generate_simple_personality_style(self, warmth, humor, competence):
1215
+ """성격에 따른 간단한 말투 설명"""
1216
+ if warmth >= 70 and humor >= 60:
1217
+ return "따뜻하고 재미있게 말하는 스타일 (친근한 농담꾼)"
1218
+ elif warmth >= 70:
1219
+ return "따뜻하고 친근하게 말하는 스타일 (다정한 친구)"
1220
+ elif humor >= 70:
1221
+ return "재치있고 유머러스하게 말하는 스타일 (재미있는 친구)"
1222
+ elif competence >= 70:
1223
+ return "똑똑하고 효율적으로 말하는 스타일 (든든한 친구)"
1224
  else:
1225
+ return "자연스럽고 편안하게 말하는 스타일 (편한 친구)"
1226
+
 
 
 
1227
  def generate_prompt_for_chat(self, persona):
1228
  """기존 함수 이름 유지하면서 새로운 구조화된 프롬프트 사용"""
1229
  return self.generate_persona_prompt(persona)
requirements.txt CHANGED
@@ -1,6 +1,7 @@
1
  gradio==5.31.0
2
  google-generativeai==0.3.2
3
  Pillow==10.3.0
 
4
  python-dotenv==1.0.0
5
  qrcode==7.4.2
6
  requests==2.31.0
 
1
  gradio==5.31.0
2
  google-generativeai==0.3.2
3
  Pillow==10.3.0
4
+ pillow-avif-plugin==1.4.3
5
  python-dotenv==1.0.0
6
  qrcode==7.4.2
7
  requests==2.31.0