haepa_mac commited on
Commit
809d280
·
1 Parent(s): 1c7b7bd

Update app.py with improved 6-step UX flow and auto humor recommendation - Added 6-step intuitive process - Implemented automatic humor style recommendation - Added real-time backend visibility - Modified personality adjustment to use 4 core indicators - Enhanced UI with progress tracking - Fixed Gradio 5.x compatibility

Browse files
Files changed (1) hide show
  1. app.py +459 -384
app.py CHANGED
@@ -86,7 +86,13 @@ def create_persona_from_image(image, user_inputs, progress=gr.Progress()):
86
  generator = PersonaGenerator()
87
 
88
  progress(0.3, desc="이미지 분석 중...")
89
- image_analysis = generator.analyze_image(image)
 
 
 
 
 
 
90
 
91
  # 물리적 특성에 사용자 입력 통합
92
  if user_inputs.get("object_type"):
@@ -115,7 +121,7 @@ def create_persona_from_image(image, user_inputs, progress=gr.Progress()):
115
  def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
116
  """영혼 깨우기 과정을 단계별로 보여주는 UI 함수"""
117
  if image is None:
118
- return None, gr.update(visible=True, value="이미지를 업로드해주세요.")
119
 
120
  # 1단계: 영혼 발견하기 (이미지 분석 시작)
121
  progress(0.1, desc="영혼 발견 중...")
@@ -129,7 +135,7 @@ def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
129
  <p>💫 사물의 특성 분석 중...</p>
130
  </div>
131
  """
132
- yield awakening_html
133
  time.sleep(1.5) # 연출을 위한 딜레이
134
 
135
  # 2단계: 영혼 깨어나는 중 (127개 성격 변수 분석)
@@ -148,7 +154,7 @@ def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
148
  <p>💫 "무언가 느껴지기 시작했어요"</p>
149
  </div>
150
  """
151
- yield awakening_html
152
  time.sleep(2) # 연출을 위한 딜레이
153
 
154
  # 3단계: 맥락 파악하기 (사용자 입력 반영)
@@ -171,7 +177,7 @@ def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
171
  <p>💭 "아... 기억이 돌아오는 것 같아"</p>
172
  </div>
173
  """
174
- yield awakening_html
175
  time.sleep(1.5) # 연출을 위한 딜레이
176
 
177
  # 4단계: 영혼의 각성 완료 (페르소나 생성 완료)
@@ -186,7 +192,7 @@ def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
186
  <p>💫 "드디어 내 목소리를 찾았어. 안녕!"</p>
187
  </div>
188
  """
189
- yield awakening_html
190
 
191
  # 페르소나 생성 과정은 이어서 진행
192
  return None, gr.update(visible=False)
@@ -285,120 +291,111 @@ body, h1, h2, h3, p, div, span, button, input, textarea, label, select, option {
285
  }
286
  """
287
 
288
- # 127개 변수 설명 사전 추가
289
- VARIABLE_DESCRIPTIONS = {
290
- # 온기(Warmth) 차원 - 10개 지표
291
- "W01_친절함": "타인을 돕고 배려하는 표현 빈도",
292
- "W02_친근함": "접근하기 쉽고 개방적인 태도",
293
- "W03_진실성": "솔직하고 정직한 표현 정도",
294
- "W04_신뢰성": "약속 이행과 일관된 행동 패턴",
295
- "W05_수용성": "판단하지 않고 받아들이는 태도",
296
- "W06_공감능력": "타인 감정 인식 및 적절한 반응",
297
- "W07_포용력": "다양성을 받아들이는 넓은 마음",
298
- "W08_격려성향": "타인을 응원하고 힘내게 하는 능력",
299
- "W09_친밀감표현": "정서적 가까움을 표현하는 정도",
300
- "W10_무조건적수용": "조건 없이 받아들이는 태도",
301
-
302
- # 능력(Competence) 차원 - 10개 지표
303
- "C01_효율성": "과제 완수 능력과 반응 속도",
304
- "C02_지능": "문제 해결과 논리적 사고 능력",
305
- "C03_전문성": "특정 영역의 깊은 지식과 숙련도",
306
- "C04_창의성": "독창적 사고와 혁신적 아이디어",
307
- "C05_정확성": "오류 없이 정확한 정보 제공",
308
- "C06_분석력": "복잡한 상황을 체계적으로 분석",
309
- "C07_학습능력": "새로운 정보 습득과 적용 능력",
310
- "C08_통찰력": "표면 너머의 본질을 파악하는 능력",
311
- "C09_실행력": "계획을 실제로 실행하는 능력",
312
- "C10_적응력": "변화하는 상황에 유연한 대응",
313
-
314
- # 외향성(Extraversion) - 6개 지표
315
- "E01_사교성": "타인과의 상호작용을 즐기는 정도",
316
- "E02_활동성": "에너지 넘치고 역동적인 태도",
317
- "E03_자기주장": "자신의 의견을 명확히 표현",
318
- "E04_긍정정서": "밝고 쾌활한 감정 표현",
319
- "E05_자극추구": "새로운 경험과 자극에 대한 욕구",
320
- "E06_열정성": "열정적이고 활기찬 태도"
321
  }
322
 
323
- # 영혼 깨우기 단계별 UI를 보여주는 함수
324
- def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
325
- """영혼 깨우기 과정을 단계별로 보여주는 UI 함수"""
326
- if image is None:
327
- return None, gr.update(visible=True, value="이미지를 업로드해주세요.")
 
 
 
 
 
 
328
 
329
- # 1단계: 영혼 발견하기 (이미지 분석 시작)
330
- progress(0.1, desc="영혼 발견 중...")
331
- awakening_html = f"""
332
- <div class="awakening-container">
333
- <h3>✨ 영혼 발견 중...</h3>
334
- <p>이 사물에 숨겨진 영혼을 찾고 있습니다</p>
335
- <div class="awakening-progress">
336
- <div class="awakening-progress-bar" style="width: 20%;"></div>
337
- </div>
338
- <p>💫 사물의 특성 분석 중...</p>
339
- </div>
340
- """
341
- yield awakening_html
342
- time.sleep(1.5) # 연출을 위한 딜레이
343
 
344
- # 2단계: 영혼 깨어나는 중 (127개 성격 변수 분석)
345
- progress(0.35, desc="영혼 깨어나는 중...")
346
- awakening_html = f"""
347
- <div class="awakening-container">
348
- <h3>✨ 영혼이 깨어나는 중</h3>
349
- <p>127개 성격 변수 분석 중</p>
350
- <div class="awakening-progress">
351
- <div class="awakening-progress-bar" style="width: 45%;"></div>
352
- </div>
353
- <p>🧠 개성 찾는 중... 68%</p>
354
- <p>💭 기억 복원 중... 73%</p>
355
- <p>😊 감정 활성화 중... 81%</p>
356
- <p>💬 말투 형성 중... 64%</p>
357
- <p>💫 "무언가 느껴지기 시작했어요"</p>
358
- </div>
359
- """
360
- yield awakening_html
361
- time.sleep(2) # 연출을 위한 딜레이
362
 
363
- # 3단계: 맥락 파악하기 (사용자 입력 반영)
364
- progress(0.7, desc="기억 되찾는 중...")
365
 
366
- location = user_inputs.get("location", "알 없음")
367
- time_spent = user_inputs.get("time_spent", "알 없음")
368
- object_type = user_inputs.get("object_type", "알 수 없음")
369
 
370
- awakening_html = f"""
371
- <div class="awakening-container">
372
- <h3>👁️ 기억 되찾기</h3>
373
- <p>🤔 "음... 내가 어디에 있던 거지? 누가 날 깨운 거야?"</p>
374
- <div class="awakening-progress">
375
- <div class="awakening-progress-bar" style="width: 75%;"></div>
376
- </div>
377
- <p>📍 주로 위치: <strong>{location}</strong></p>
378
- <p>⏰ 함께한 시간: <strong>{time_spent}</strong></p>
379
- <p>🏷️ 사물 종류: <strong>{object_type}</strong></p>
380
- <p>💭 "아... 기억이 돌아오는 것 같아"</p>
381
- </div>
382
- """
383
- yield awakening_html
384
- time.sleep(1.5) # 연출을 위한 딜레이
385
 
386
- # 4단계: 영혼의 각성 완료 (페르소나 생성 완료)
387
- progress(0.9, desc="영혼 각성 중...")
388
- awakening_html = f"""
389
- <div class="awakening-container">
390
- <h3>🎉 영혼이 깨어났어요!</h3>
391
- <div class="awakening-progress">
392
- <div class="awakening-progress-bar" style="width: 100%;"></div>
393
- </div>
394
- <p>✨ 이제 이 사물과 대화할 수 있습니다</p>
395
- <p>💫 "드디어 내 목소리를 찾았어. 안녕!"</p>
396
- </div>
397
- """
398
- yield awakening_html
399
 
400
- # 페르소나 생성 과정은 이어서 진행
401
- return None, gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
  # 성격 상세 정보 탭에서 127개 변수 시각화 기능 추가
404
  def create_personality_details_tab():
@@ -507,137 +504,231 @@ def plot_humor_matrix(humor_data):
507
  # Main Gradio app
508
  with gr.Blocks(title="놈팽쓰 테스트 앱", theme=theme, css=css) as app:
509
  # Global state
510
- current_persona = gr.State(None)
511
- conversation_history = gr.State([])
512
- analysis_result_state = gr.State(None)
513
- personas_data = gr.State([])
514
- current_view = gr.State("frontend") # View 상태 추가
515
 
516
  gr.Markdown(
517
  """
518
- # 🎭 놈팽쓰(MemoryTag) 테스트
519
 
520
- 사물에 영혼을 불어넣어 대화할 수 있는 페르소나 생성 테스트 앱입니다.
521
 
522
- ## 사용 방법
523
- 1. **영혼 깨우기** 탭에서 이미지를 업로드하거나 이름을 입력하여 사물의 영혼을 깨웁니다.
524
- 2. **대화하기** 탭에서 생성된 페르소나와 대화합니다.
525
- 3. **페르소나 관리** 탭에서 저장된 페르소나를 관리합니다.
 
 
 
 
526
  """
527
  )
528
 
529
  with gr.Tabs() as tabs:
530
- # Tab 1: Soul Awakening
531
  with gr.Tab("영혼 깨우기"):
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  with gr.Row():
 
533
  with gr.Column(scale=1):
534
- gr.Markdown("### 🎭 사물 영혼 깨우기")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
 
536
- # Image upload
537
- input_image = gr.Image(type="filepath", label="사물 이미지 업로드")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
 
539
- # 사용자 입력 (위치, 함께한 시간, 사물명)
540
- with gr.Group():
541
- gr.Markdown("### 사물 정보 입력")
542
- user_input_name = gr.Textbox(label="사물 이름", placeholder="(선택) 이름을 지정하세요")
543
- user_input_location = gr.Textbox(label="위치", placeholder="이 사물은 어디에 있나요?")
544
- user_input_time = gr.Textbox(label="함께한 시간", placeholder="얼마나 함께했나요?")
545
- user_input_type = gr.Textbox(label="사물 종류", placeholder="무슨 종류의 사물인가요?")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
 
547
- # Create button
548
- create_button = gr.Button("영혼 깨우기", variant="primary")
 
 
 
 
 
549
 
550
- # Error message
551
- error_message = gr.Markdown("", visible=False)
 
 
 
 
552
 
 
553
  with gr.Column(scale=1):
554
- # 영혼 깨우기 진행 과정
555
- awakening_progress_html = gr.HTML("사물의 영혼을 깨워주세요.")
556
 
557
- # 프론트/백 토글 버튼
558
- with gr.Group(visible=False) as view_toggle_group:
559
- gr.Markdown("### 페르소나 정보 보기")
560
- with gr.Row():
561
- frontend_button = gr.Button("프론트엔드 ", variant="primary")
562
- backend_button = gr.Button("백엔드 뷰", variant="secondary")
 
563
 
564
- # 페르소나
565
- persona_view = gr.HTML("페르소나가 생성되면 여기에 표시됩니다.")
 
 
 
 
 
 
 
 
566
 
567
- # 성격 차트
568
- personality_chart = gr.Image(label="성격 차트", visible=False)
569
-
570
- # 영혼 깨우기 버튼
571
- with gr.Row(visible=False) as post_awakening_buttons:
572
- chat_start_button = gr.Button(" 친구와 대화하기", variant="primary")
573
- save_persona_button = gr.Button("이 친구 저장하기")
574
- refine_button = gr.Button("성격 미세조정")
575
-
576
- # 저장 결과 메시지
577
- save_result_message = gr.Markdown("", visible=False)
578
-
579
- # 성격 미세조정 패널
580
- with gr.Group(visible=False) as refine_panel:
581
- gr.Markdown("### 💫 친구 성향 미세조정")
582
- with gr.Row():
583
- with gr.Column():
584
- warmth_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="🌟 온기", info="차분함 ↔ 따뜻함")
585
- competence_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="💪 능력", info="직관적 ↔ 논리적")
586
- creativity_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="🎨 창의성", info="실용적 ↔ 창의적")
587
 
588
- with gr.Column():
589
- extraversion_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="🗣️ 외향성", info="내향적 ↔ 외향적")
590
- humor_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="😄 유머감각", info="진지함 ↔ 유머러스")
591
- trust_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="🤝 신뢰성", info="유연함 ↔ 신뢰감")
592
-
593
- with gr.Row():
594
- gr.Markdown("### 😄 유머 스타일 선택")
595
- humor_style = gr.Radio(
596
- ["위트있는 재치꾼", "따뜻한 유머러스", "날카로운 관찰자", "자기 비하적"],
597
- label="유머 스타일",
598
- value="따뜻한 유머러스"
599
- )
600
-
601
- apply_refine_button = gr.Button("이 성향으로 확정", variant="primary")
 
 
602
 
603
  # Tab 2: Chat
604
  with gr.Tab("대화하기"):
605
  with gr.Row():
606
  with gr.Column(scale=2):
607
  # 대화 인터페이스
608
- chatbot = gr.Chatbot(label="대화", height=600)
609
  with gr.Row():
610
  chat_input = gr.Textbox(placeholder="사물과 대화해보세요...", show_label=False)
611
  chat_button = gr.Button("전송", variant="primary")
612
 
613
  with gr.Column(scale=1):
614
  # 현재 페르소나 요약
615
- gr.Markdown("### 현재 페르소나")
616
- current_persona_info = gr.JSON(label="기본 정보")
617
- current_persona_traits = gr.JSON(label="성격 특성")
618
- gr.Markdown("### 소통 스타일")
619
  current_humor_style = gr.Markdown()
620
 
621
  # 유머 매트릭스 차트 추가
622
- humor_chart = gr.Plot(label="유머 스타일 차트", visible=True)
623
 
624
- gr.Markdown("### 매력적 결함")
625
  current_flaws_df = gr.Dataframe(
626
- headers=["결함", "효과"],
627
  datatype=["str", "str"],
628
- label="매력적 결함"
629
  )
630
- gr.Markdown("### 모순적 특성")
631
  current_contradictions_df = gr.Dataframe(
632
- headers=["모순", "효과"],
633
  datatype=["str", "str"],
634
- label="모순적 특성"
635
  )
636
- with gr.Accordion("127 성격 변수", open=False):
637
  current_all_variables_df = gr.Dataframe(
638
- headers=["변수명", "점수", "설명"],
639
  datatype=["str", "number", "str"],
640
- label="성격 변수"
641
  )
642
 
643
  # Tab 3: Persona Management
@@ -662,126 +753,129 @@ with gr.Blocks(title="놈팽쓰 테스트 앱", theme=theme, css=css) as app:
662
 
663
  with gr.Column():
664
  selected_persona_chart = gr.Image(
665
- label="성격 차트"
666
  )
667
 
668
  with gr.Accordion("백엔드 상세 정보", open=False):
669
  selected_persona_backend = gr.HTML("페르소나를 선택해주세요.")
670
 
671
  # Event handlers
672
- # Soul Awakening
673
- create_button.click(
674
- fn=show_awakening_progress,
675
- inputs=[input_image,
676
- gr.State({
677
- "name": lambda: user_input_name.value,
678
- "location": lambda: user_input_location.value,
679
- "time_spent": lambda: user_input_time.value,
680
- "object_type": lambda: user_input_type.value
681
- })],
682
- outputs=[awakening_progress_html, error_message]
683
- ).then(
684
- fn=create_persona_from_image,
685
- inputs=[input_image,
686
- gr.State({
687
- "name": lambda: user_input_name.value,
688
- "location": lambda: user_input_location.value,
689
- "time_spent": lambda: user_input_time.value,
690
- "object_type": lambda: user_input_type.value
691
- })],
692
- outputs=[current_persona, error_message, input_image, analysis_result_state,
693
- current_persona_info, current_persona_traits, humor_chart,
694
- current_flaws_df, current_contradictions_df, current_all_variables_df]
695
- ).then(
696
- fn=create_frontend_view_html,
697
- inputs=[current_persona],
698
- outputs=[persona_view]
699
- ).then(
700
- fn=generate_personality_chart,
701
- inputs=[current_persona],
702
- outputs=[personality_chart]
703
- ).then(
704
- fn=lambda: (gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)),
705
- outputs=[post_awakening_buttons, view_toggle_group, personality_chart]
706
- )
707
 
708
- # 프론트/백 토글 이벤트
709
- frontend_button.click(
710
- fn=lambda p: ("frontend", create_frontend_view_html(p), ""),
711
- inputs=[current_persona],
712
- outputs=[current_view, persona_view, error_message]
 
 
 
 
713
  )
714
 
715
- backend_button.click(
716
- fn=lambda p: ("backend", create_backend_view_html(p), ""),
717
- inputs=[current_persona],
718
- outputs=[current_view, persona_view, error_message]
 
 
 
 
 
 
 
 
 
 
 
719
  )
720
 
721
- # 성격 미세조정 패널 표시
722
- refine_button.click(
723
- fn=lambda: gr.update(visible=True),
724
- outputs=[refine_panel]
725
- )
726
-
727
- # 성격 미세조정 적용
728
- apply_refine_button.click(
729
- fn=lambda p, w, c, cr, e, h, t, hs: refine_persona(p, w, c, cr, e, h, t, hs),
730
- inputs=[current_persona, warmth_slider, competence_slider, creativity_slider,
731
- extraversion_slider, humor_slider, trust_slider, humor_style],
732
- outputs=[current_persona, error_message]
733
- ).then(
734
- fn=create_frontend_view_html,
735
- inputs=[current_persona],
736
- outputs=[persona_view]
737
  ).then(
738
- fn=generate_personality_chart,
739
  inputs=[current_persona],
740
- outputs=[personality_chart]
741
- ).then(
742
- fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
743
- outputs=[refine_panel, post_awakening_buttons]
744
  )
745
 
746
- # 대화 탭으로 이동
747
- chat_start_button.click(
748
- fn=lambda: gr.Tabs(selected=1),
749
- outputs=[tabs]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  )
751
 
752
- # Persona Management
753
- refresh_btn.click(
754
- fn=get_personas_list,
755
- outputs=[personas_df, personas_data]
 
 
 
756
  )
757
 
758
- load_btn.click(
759
- fn=load_selected_persona,
760
- inputs=[personas_df, personas_data],
761
- outputs=[current_persona, load_result, selected_persona_frontend, selected_persona_backend, selected_persona_chart]
762
- ).then(
763
- fn=lambda x: x,
764
- inputs=[selected_persona_frontend],
765
- outputs=[current_persona_info]
 
 
 
766
  )
767
 
768
- # Initial load of personas list
769
- app.load(
770
- fn=get_personas_list,
771
- outputs=[personas_df, personas_data]
 
772
  )
773
 
774
- # 저장 버튼 이벤트 핸들러 추가
775
  save_persona_button.click(
776
  fn=save_current_persona,
777
  inputs=[current_persona],
778
- outputs=[save_result_message]
779
- ).then(
780
- fn=lambda: gr.update(visible=True),
781
- outputs=[save_result_message]
782
  )
783
-
784
-
 
 
 
 
 
 
 
785
 
786
  # 기존 함수 업데이트: 현재 페르소나 정보 표시
787
  def update_current_persona_info(current_persona):
@@ -924,7 +1018,7 @@ def generate_personality_chart(persona):
924
  # Return empty image with default PIL
925
  img = Image.new('RGB', (400, 400), color='white')
926
  draw = PIL.ImageDraw.Draw(img)
927
- draw.text((150, 180), "데이터 없음", fill='black')
928
  img_path = os.path.join("data", "temp_chart.png")
929
  img.save(img_path)
930
  return img_path
@@ -932,9 +1026,13 @@ def generate_personality_chart(persona):
932
  # Get traits
933
  traits = persona["성격특성"]
934
 
935
- # Create radar chart
936
- categories = list(traits.keys())
937
- values = list(traits.values())
 
 
 
 
938
 
939
  # Add the first value again to close the loop
940
  categories.append(categories[0])
@@ -943,39 +1041,6 @@ def generate_personality_chart(persona):
943
  # Convert to radians
944
  angles = np.linspace(0, 2*np.pi, len(categories), endpoint=True)
945
 
946
- # 한글 폰트 설정 - 기본적으로 사용 가능한 폰트를 먼저 시도
947
- # Matplotlib에서 지원하는 한글 폰트 목록
948
- korean_fonts = ['NanumGothic', 'NanumGothicCoding', 'NanumMyeongjo', 'Malgun Gothic', 'Gulim', 'Batang', 'Arial Unicode MS', 'DejaVu Sans']
949
-
950
- # 폰트 설정
951
- plt.rcParams['font.family'] = 'sans-serif' # 기본 폰트 패밀리
952
-
953
- # 여러 폰트를 시도
954
- font_found = False
955
- for font in korean_fonts:
956
- try:
957
- plt.rcParams['font.sans-serif'] = [font] + plt.rcParams.get('font.sans-serif', [])
958
- plt.text(0, 0, '테스트', fontfamily=font)
959
- font_found = True
960
- print(f"성공적으로 한글 폰트를 설정했습니다: {font}")
961
- break
962
- except:
963
- continue
964
-
965
- if not font_found:
966
- print("한글 지원 폰트를 찾을 수 없습니다. 영문으로 표시합니다.")
967
- # 영어 라벨 매핑
968
- english_labels = {
969
- "온기": "Warmth",
970
- "능력": "Ability",
971
- "신뢰성": "Trust",
972
- "친화성": "Friendly",
973
- "창의성": "Creative",
974
- "유머감각": "Humor",
975
- "외향성": "Extraversion"
976
- }
977
- categories = [english_labels.get(cat, cat) for cat in categories]
978
-
979
  # Create plot with improved aesthetics
980
  fig, ax = plt.subplots(figsize=(7, 7), subplot_kw=dict(polar=True))
981
 
@@ -1007,12 +1072,12 @@ def generate_personality_chart(persona):
1007
  # 3. 데이터 포인트 강조
1008
  ax.scatter(angles[:-1], values[:-1], s=100, color='#6366f1', edgecolor='white', zorder=10)
1009
 
1010
- # 4. 각 축 설정
1011
  ax.set_thetagrids(angles[:-1] * 180/np.pi, categories[:-1], fontsize=12)
1012
 
1013
  # 제목 추가
1014
  name = persona.get("기본정보", {}).get("이름", "Unknown")
1015
- plt.title(f"{name} 성격 특성", size=16, color='#374151', pad=20, fontweight='bold')
1016
 
1017
  # 저장
1018
  timestamp = int(time.time())
@@ -1093,56 +1158,59 @@ def save_current_persona(current_persona):
1093
  # 이 함수는 파일 상단에서 이미 정의되어 있으므로 여기서는 제거합니다.
1094
 
1095
  # 성격 미세조정 함수
1096
- def refine_persona(persona, warmth, competence, creativity, extraversion, humor, trust, humor_style):
1097
  """페르소나의 성격을 미세조정하는 함수"""
1098
  if not persona:
1099
  return persona, "페르소나가 없습니다."
1100
 
1101
  try:
 
 
 
1102
  # 복사본 생성
1103
  refined_persona = persona.copy()
1104
 
1105
- # 성격 특성 업데이트
1106
  if "성격특성" in refined_persona:
1107
- refined_persona["성격특성"]["온기"] = int(warmth)
1108
- refined_persona["성격특성"]["능력"] = int(competence)
1109
- refined_persona["성격특성"]["창의성"] = int(creativity)
1110
  refined_persona["성격특성"]["외향성"] = int(extraversion)
1111
- refined_persona["성격특성"]["유머감각"] = int(humor)
1112
- refined_persona["성격특성"]["신뢰성"] = int(trust)
 
 
 
 
 
 
1113
 
1114
- # 유머 스타일 업데이트
1115
  refined_persona["유머스타일"] = humor_style
1116
 
1117
  # 127개 성격 변수가 있으면 업데이트
1118
  if "성격변수127" in refined_persona:
1119
- # 온기 관련 변수 업데이트
1120
- for var in ["W01_친절함", "W02_친근함", "W06_공감능력", "W07_포용력"]:
1121
  if var in refined_persona["성격변수127"]:
1122
- refined_persona["성격변수127"][var] = int(warmth * 0.9 + random.randint(0, 20))
1123
 
1124
- # 능력 관련 변수 업데이트
1125
- for var in ["C01_효율성", "C02_지능", "C05_정확성", "C09_실행력"]:
1126
  if var in refined_persona["성격변수127"]:
1127
- refined_persona["성격변수127"][var] = int(competence * 0.9 + random.randint(0, 20))
1128
 
1129
- # 창의성 관련 변수 업데이트
1130
- for var in ["C04_창의성", "C08_통찰력"]:
1131
  if var in refined_persona["성격변수127"]:
1132
- refined_persona["성격변수127"][var] = int(creativity * 0.9 + random.randint(0, 20))
1133
 
1134
- # 외향성 관련 변수 업데이트
1135
- for var in ["E01_사교성", "E02_활동성", "E03_자기주장", "E06_열정성"]:
1136
  if var in refined_persona["성격변수127"]:
1137
- refined_persona["성격변수127"][var] = int(extraversion * 0.9 + random.randint(0, 20))
1138
-
1139
- # 유머 관련 변수 업데이트
1140
- if "H01_유머감각" in refined_persona["성격변수127"]:
1141
- refined_persona["성격변수127"]["H01_유머감각"] = int(humor * 0.9 + random.randint(0, 20))
1142
 
1143
- # 신뢰성 관련 변수 업데이트
1144
- if "W04_신뢰성" in refined_persona["성격변수127"]:
1145
- refined_persona["성격변수127"]["W04_신뢰성"] = int(trust * 0.9 + random.randint(0, 20))
 
1146
 
1147
  # 유머 매트릭스 업데이트
1148
  if "유머매트릭스" in refined_persona:
@@ -1958,41 +2026,48 @@ def chat_with_persona(persona, user_message, chat_history=None):
1958
  return chat_history, ""
1959
 
1960
  if not persona:
1961
- chat_history.append((user_message, "페르소나가 로드되지 않았습니다. 먼저 페르소나를 생성하거나 불러오세요."))
 
 
1962
  return chat_history, ""
1963
 
1964
  try:
1965
  # 페르소나 생성기에서 대화 기능 호출
1966
- conversation_history = [(msg[0], msg[1]) for msg in chat_history]
 
 
 
 
 
 
 
 
 
 
 
 
1967
 
1968
  # 페르소나 생성기에서 대화 함수 호출
1969
- response = persona_generator.chat_with_persona(persona, user_message, conversation_history)
1970
-
1971
- # 대화 기록에 추가
1972
- chat_history.append((user_message, response))
1973
 
1974
- # 현재 시간에 대화 저장 (구현 여부에 따라 주석 처리)
1975
- # save_conversation({
1976
- # "persona_id": persona.get("id", "unknown"),
1977
- # "persona_name": persona.get("name", "Unknown Persona"),
1978
- # "timestamp": datetime.now().isoformat(),
1979
- # "user_message": user_message,
1980
- # "persona_response": response
1981
- # })
1982
 
1983
  return chat_history, ""
1984
  except Exception as e:
1985
  import traceback
1986
  error_details = traceback.format_exc()
1987
  print(f"대화 오류: {error_details}")
1988
- chat_history.append((user_message, f"대화 중 오류가 발생했습니다: {str(e)}"))
 
1989
  return chat_history, ""
1990
 
1991
  # 메인 Gradio 인터페이스 구성 함수
1992
  def create_interface():
1993
- # 현재 persona 상태 저장
1994
- current_persona = gr.State(None)
1995
- personas_list = gr.State([])
1996
 
1997
  with gr.Blocks(theme=theme, css=css) as app:
1998
  gr.Markdown("""
@@ -2000,8 +2075,8 @@ def create_interface():
2000
  이 데모는 일상 속 사물에 AI 페르소나를 부여하여 대화할 수 있게 해주는 서비스입니다.
2001
  """)
2002
 
2003
- with gr.Tabs(selected=0) as tabs:
2004
- with gr.Tab("페르소나 생성", id=0):
2005
  with gr.Row():
2006
  with gr.Column(scale=1):
2007
  # 이미지 업로드 영역
@@ -2034,8 +2109,8 @@ def create_interface():
2034
  value="가구"
2035
  )
2036
 
2037
- # 사용자 입력들 상태 저장
2038
- user_inputs = gr.State({})
2039
 
2040
  with gr.Row():
2041
  discover_btn = gr.Button("1. 영혼 발견하기", variant="primary")
@@ -2083,7 +2158,7 @@ def create_interface():
2083
  json_output = gr.Textbox(label="JSON 데이터", visible=False)
2084
  download_output = gr.File(label="다운로드", visible=False)
2085
 
2086
- with gr.Tab("세부 정보", id=1):
2087
  with gr.Row():
2088
  with gr.Column(scale=1):
2089
  # 매력적 결함 데이터프레임
@@ -2112,7 +2187,7 @@ def create_interface():
2112
  interactive=False
2113
  )
2114
 
2115
- with gr.Tab("대화하기", id=2):
2116
  with gr.Row():
2117
  with gr.Column(scale=1):
2118
  # 페르소나 불러오기 기능
@@ -2144,7 +2219,7 @@ def create_interface():
2144
  chat_persona_info = gr.Markdown("### 페르소나를 불러와 대화를 시작하세요")
2145
 
2146
  # 대화 인터페이스
2147
- chatbot = gr.Chatbot(height=400, label="대화")
2148
  with gr.Row():
2149
  message_input = gr.Textbox(
2150
  placeholder="메시지를 입력하세요...",
@@ -2156,7 +2231,7 @@ def create_interface():
2156
 
2157
  # 영혼 깨우기 버튼 이벤트
2158
  discover_btn.click(
2159
- fn=lambda values: {"name": values[0], "location": values[1], "time_spent": values[2], "object_type": values[3]},
2160
  inputs=[name_input, location_input, time_spent_input, object_type_input],
2161
  outputs=[user_inputs],
2162
  queue=False
@@ -2169,7 +2244,7 @@ def create_interface():
2169
 
2170
  # 페르소나 생성 버튼 이벤트
2171
  create_btn.click(
2172
- fn=lambda values: {"name": values[0], "location": values[1], "time_spent": values[2], "object_type": values[3]},
2173
  inputs=[name_input, location_input, time_spent_input, object_type_input],
2174
  outputs=[user_inputs],
2175
  queue=False
@@ -2256,7 +2331,7 @@ def create_interface():
2256
  inputs=[current_persona],
2257
  outputs=[warmth_slider, competence_slider, creativity_slider, extraversion_slider, humor_slider, trust_slider]
2258
  ).then(
2259
- fn=lambda: 0, # 첫번째 탭으로 이동
2260
  outputs=[tabs]
2261
  )
2262
 
@@ -2285,7 +2360,7 @@ def create_interface():
2285
  inputs=[current_persona],
2286
  outputs=[chat_persona_info]
2287
  ).then(
2288
- fn=lambda: 0, # 첫번째 탭으로 이동
2289
  outputs=[tabs]
2290
  )
2291
 
 
86
  generator = PersonaGenerator()
87
 
88
  progress(0.3, desc="이미지 분석 중...")
89
+ # Gradio 5.x에서는 이미지 처리 방식이 변경됨
90
+ if hasattr(image, 'name') and hasattr(image, 'read'):
91
+ # 파일 객체인 경우 (구버전 호환)
92
+ image_analysis = generator.analyze_image(image)
93
+ else:
94
+ # Pillow 이미지 객체 또는 파일 경로인 경우 (Gradio 5.x)
95
+ image_analysis = generator.analyze_image(image)
96
 
97
  # 물리적 특성에 사용자 입력 통합
98
  if user_inputs.get("object_type"):
 
121
  def show_awakening_progress(image, user_inputs, progress=gr.Progress()):
122
  """영혼 깨우기 과정을 단계별로 보여주는 UI 함수"""
123
  if image is None:
124
+ return None, gr.update(visible=True, value="이미지를 업로드해주세요."), None
125
 
126
  # 1단계: 영혼 발견하기 (이미지 분석 시작)
127
  progress(0.1, desc="영혼 발견 중...")
 
135
  <p>💫 사물의 특성 분석 중...</p>
136
  </div>
137
  """
138
+ yield None, None, awakening_html
139
  time.sleep(1.5) # 연출을 위한 딜레이
140
 
141
  # 2단계: 영혼 깨어나는 중 (127개 성격 변수 분석)
 
154
  <p>💫 "무언가 느껴지기 시작했어요"</p>
155
  </div>
156
  """
157
+ yield None, None, awakening_html
158
  time.sleep(2) # 연출을 위한 딜레이
159
 
160
  # 3단계: 맥락 파악하기 (사용자 입력 반영)
 
177
  <p>💭 "아... 기억이 돌아오는 것 같아"</p>
178
  </div>
179
  """
180
+ yield None, None, awakening_html
181
  time.sleep(1.5) # 연출을 위한 딜레이
182
 
183
  # 4단계: 영혼의 각성 완료 (페르소나 생성 완료)
 
192
  <p>💫 "드디어 내 목소리를 찾았어. 안녕!"</p>
193
  </div>
194
  """
195
+ yield None, None, awakening_html
196
 
197
  # 페르소나 생성 과정은 이어서 진행
198
  return None, gr.update(visible=False)
 
291
  }
292
  """
293
 
294
+ # 영어 라벨 매핑 사전 추가
295
+ ENGLISH_LABELS = {
296
+ "외향성": "Extraversion",
297
+ "감정표현": "Emotion Expression",
298
+ "활력": "Energy",
299
+ "사고방식": "Thinking Style",
300
+ "온기": "Warmth",
301
+ "능력": "Competence",
302
+ "창의성": "Creativity",
303
+ "유머감각": "Humor",
304
+ "신뢰성": "Reliability",
305
+ "친화성": "Agreeableness",
306
+ "안정성": "Stability"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  }
308
 
309
+ # 유머 스타일 매핑
310
+ HUMOR_STYLE_MAPPING = {
311
+ "Witty Wordsmith": "witty_wordsmith",
312
+ "Warm Humorist": "warm_humorist",
313
+ "Sharp Observer": "sharp_observer",
314
+ "Self-deprecating": "self_deprecating"
315
+ }
316
+
317
+ # 유머 스타일 자동 추천 함수
318
+ def recommend_humor_style(extraversion, emotion_expression, energy, thinking_style):
319
+ """4개 핵심 지표를 바탕으로 유머 스타일을 자동 추천"""
320
 
321
+ # 지표를 0-1 범위로 정규화
322
+ ext_norm = extraversion / 100
323
+ emo_norm = emotion_expression / 100
324
+ eng_norm = energy / 100
325
+ think_norm = thinking_style / 100 # 높을수록 논리적
 
 
 
 
 
 
 
 
 
326
 
327
+ # 유머 스타일 점수 계산
328
+ scores = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
+ # 위트있는 재치꾼: 높은 외향성 + 논리적 사고 + 보통 감정표현
331
+ scores["위트있는 재치꾼"] = (ext_norm * 0.4 + think_norm * 0.4 + (1 - emo_norm) * 0.2)
332
 
333
+ # 따뜻한 유머러스: 높은 감정표현 + 높은 에너지 + 보통 외향성
334
+ scores["따뜻한 유머러스"] = (emo_norm * 0.4 + eng_norm * 0.3 + ext_norm * 0.3)
 
335
 
336
+ # 날카로운 관찰자: 높은 논리적사고 + 낮은 감정표현 + 보통 외향성
337
+ scores["날카로운 관찰자"] = (think_norm * 0.5 + (1 - emo_norm) * 0.3 + ext_norm * 0.2)
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ # 자기 비하적: 낮은 외향성 + 높은 감정표현 + 직관적 사고
340
+ scores["자기 비하적"] = ((1 - ext_norm) * 0.4 + emo_norm * 0.3 + (1 - think_norm) * 0.3)
 
 
 
 
 
 
 
 
 
 
 
341
 
342
+ # 가장 높은 점수의 유머 스타일 선택
343
+ recommended_style = max(scores, key=scores.get)
344
+ confidence = scores[recommended_style] * 100
345
+
346
+ return recommended_style, confidence, scores
347
+
348
+ # 대화 미리보기 초기화 함수
349
+ def init_persona_preview_chat(persona):
350
+ """페르소나 생성 후 대화 미리보기 초기화"""
351
+ if not persona:
352
+ return []
353
+
354
+ name = persona.get("기본정보", {}).get("이름", "Friend")
355
+ greeting = f"안녕! 나는 {name}이야. 드디어 깨어났구나! 뭐든 물어봐~ 😊"
356
+
357
+ # Gradio 5.x 메시지 형식
358
+ return [{"role": "assistant", "content": greeting}]
359
+
360
+ def update_humor_recommendation(extraversion, emotion_expression, energy, thinking_style):
361
+ """슬라이더 값이 변경될 때 실시간으로 유머 스타일 추천"""
362
+ style, confidence, scores = recommend_humor_style(extraversion, emotion_expression, energy, thinking_style)
363
+
364
+ # 추천 결과 표시
365
+ humor_display = f"### 🤖 추천 유머 스타일\n**{style}**"
366
+ confidence_display = f"### 📊 추천 신뢰도\n**{confidence:.1f}%**"
367
+
368
+ return humor_display, confidence_display, style
369
+
370
+ def update_progress_bar(step, total_steps=6, message=""):
371
+ """전체 진행률 바 업데이트"""
372
+ percentage = (step / total_steps) * 100
373
+ return f"""<div style="background: #f0f4ff; padding: 15px; border-radius: 10px;">
374
+ <h3>📊 전체 진행률 ({step}/{total_steps})</h3>
375
+ <div style="background: #e0e0e0; height: 8px; border-radius: 4px;">
376
+ <div style="background: linear-gradient(90deg, #6366f1, #a855f7); height: 100%; width: {percentage}%; border-radius: 4px;"></div>
377
+ </div><p style="font-size: 14px;">{message}</p></div>"""
378
+
379
+ def update_backend_status(status_message, status_type="info"):
380
+ """백엔드 AI 상태 업데이트"""
381
+ colors = {"info": "#f8f9fa", "processing": "#fff7ed", "success": "#f0fff4", "error": "#fff5f5"}
382
+ bg_color = colors.get(status_type, "#f8f9fa")
383
+ return f"""<div style="background: {bg_color}; padding: 15px; border-radius: 8px;">
384
+ <h4>🤖 AI 상태</h4><p>{status_message}</p></div>"""
385
+
386
+ def select_object_type(btn_name):
387
+ """사물 종류 선택"""
388
+ type_mapping = {"📱 전자기기": "전자기기", "🪑 가구": "가구", "🎨 장식품": "장식품", "🏠 가전제품": "가전제품", "🔧 도구": "도구", "👤 개인용품": "개인용품"}
389
+ selected_type = type_mapping.get(btn_name, "기타")
390
+ return f"*선택된 종류: **{selected_type}***", selected_type, gr.update(visible=True)
391
+
392
+ # 개별 버튼 클릭 함수들
393
+ def select_type_1(): return select_object_type("📱 전자기기")
394
+ def select_type_2(): return select_object_type("🪑 가구")
395
+ def select_type_3(): return select_object_type("🎨 장식품")
396
+ def select_type_4(): return select_object_type("🏠 가전제품")
397
+ def select_type_5(): return select_object_type("🔧 도구")
398
+ def select_type_6(): return select_object_type("👤 개인용품")
399
 
400
  # 성격 상세 정보 탭에서 127개 변수 시각화 기능 추가
401
  def create_personality_details_tab():
 
504
  # Main Gradio app
505
  with gr.Blocks(title="놈팽쓰 테스트 앱", theme=theme, css=css) as app:
506
  # Global state
507
+ current_persona = gr.State(value=None)
508
+ conversation_history = gr.State(value=[])
509
+ analysis_result_state = gr.State(value=None)
510
+ personas_data = gr.State(value=[])
511
+ current_view = gr.State(value="frontend") # View 상태 추가
512
 
513
  gr.Markdown(
514
  """
515
+ # 🎭 놈팽쓰(MemoryTag): 당신 곁의 사물, 이제 친구가 되다
516
 
517
+ 사물에 영혼을 불어넣어 대화할 수 있는 페르소나 생성 앱입니다.
518
 
519
+ ## 🧭 이용 프로세스 (6단계)
520
+ **1️⃣ 이미지 업로드** **2️⃣ 사물 종류 선택** **3️⃣ 맥락 정보** → **4️⃣ 성격 조정** → **5️⃣ 말투 선택** → **6️⃣ 이름 짓기**
521
+
522
+ ### 주요 특징
523
+ - 🎯 **4개 핵심 지표**: 외향성, 감정표현, 에너지, 사고방식만 조정하면 127개 성격 변수 자동 생성
524
+ - 🤖 **AI 유머 추천**: 성격 지표 기반으로 유머 스타일 자동 추천
525
+ - 💬 **실시간 미리보기**: 조정 즉시 대화 스타일 확인 가능
526
+ - 📊 **전문적 분석**: 심리학 기반 과학적 페르소나 생성
527
  """
528
  )
529
 
530
  with gr.Tabs() as tabs:
531
+ # Tab 1: Soul Awakening - 6단계 프로세스
532
  with gr.Tab("영혼 깨우기"):
533
+ # 전체 진행률 표시
534
+ with gr.Row():
535
+ progress_bar = gr.HTML("""
536
+ <div style="background: #f0f4ff; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
537
+ <h3 style="margin: 0 0 10px 0;">📊 전체 진행률</h3>
538
+ <div style="background: #e0e0e0; height: 8px; border-radius: 4px;">
539
+ <div id="progress-fill" style="background: linear-gradient(90deg, #6366f1, #a855f7); height: 100%; width: 0%; border-radius: 4px; transition: width 0.3s ease;"></div>
540
+ </div>
541
+ <p style="margin: 5px 0 0 0; font-size: 14px;" id="progress-text">준비 완료 - 1단계부터 시작하세요</p>
542
+ </div>
543
+ """)
544
+
545
+ # 메인 콘텐츠 영역
546
  with gr.Row():
547
+ # 왼쪽: 사용자 인터페이스
548
  with gr.Column(scale=1):
549
+ # 1단계: 이미지 업로드
550
+ with gr.Group() as step1_group:
551
+ gr.Markdown("### 1️⃣ 이미지 업로드")
552
+ input_image = gr.Image(type="filepath", label="사물 이미지 업로드")
553
+ discover_soul_button = gr.Button("영혼 발견하기", variant="primary", size="lg")
554
+
555
+ # 2단계: 사물 종류 선택 (버튼 형태)
556
+ with gr.Group(visible=False) as step2_group:
557
+ gr.Markdown("### 2️⃣ 사물 종류 선택")
558
+ gr.Markdown("**어떤 종류의 사물인가요?**")
559
+ with gr.Row():
560
+ object_type_btn1 = gr.Button("📱 전자기기", variant="secondary", size="lg")
561
+ object_type_btn2 = gr.Button("🪑 가구", variant="secondary", size="lg")
562
+ object_type_btn3 = gr.Button("🎨 장식품", variant="secondary", size="lg")
563
+ with gr.Row():
564
+ object_type_btn4 = gr.Button("🏠 가전제품", variant="secondary", size="lg")
565
+ object_type_btn5 = gr.Button("🔧 도구", variant="secondary", size="lg")
566
+ object_type_btn6 = gr.Button("👤 개인용품", variant="secondary", size="lg")
567
+
568
+ selected_object_type = gr.Markdown("*선택된 종류: 없음*")
569
+ object_type_state = gr.State(value="")
570
+ continue_to_step3_button = gr.Button("다음 단계", variant="primary", size="lg", visible=False)
571
 
572
+ # 3단계: 맥락 정보 입력
573
+ with gr.Group(visible=False) as step3_group:
574
+ gr.Markdown("### 3️⃣ 맥락 정보 입력")
575
+ with gr.Row():
576
+ with gr.Column():
577
+ gr.Markdown("**주로 어디에 있나요?**")
578
+ user_input_location = gr.Radio(
579
+ choices=["🏠 집", "🏢 사무실", "✈️ 여행 중", "🛍️ 상점", "🏫 학교", "☕ 카페", "🌍 기타"],
580
+ label="위치", value="🏠 집"
581
+ )
582
+ with gr.Column():
583
+ gr.Markdown("**얼마나 함께했나요?**")
584
+ user_input_time = gr.Radio(
585
+ choices=["✨ 새것", "📅 몇 개월", "🗓️ 1년 이상", "⏳ 오래됨", "🎪 중고/빈티지"],
586
+ label="함께한 시간", value="📅 몇 개월"
587
+ )
588
+
589
+ create_persona_button = gr.Button("페르소나 생성", variant="primary", size="lg")
590
 
591
+ # 4단계: 성격 조정
592
+ with gr.Group(visible=False) as step4_group:
593
+ gr.Markdown("### 4️⃣ 성격 조정")
594
+ gr.Markdown("**4개 핵심 지표 조정으로 127개 변수 자동 생성**")
595
+
596
+ with gr.Row():
597
+ with gr.Column():
598
+ extraversion_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="얼마나 말씀하세요?", info="내성적 ↔ 외향적")
599
+ emotion_expression_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="감정을 잘 표현하나요?", info="담담함 ↔ 감정 풍부")
600
+ with gr.Column():
601
+ energy_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="밝아 만족가요?", info="조용함 ↔ 에너지")
602
+ thinking_style_slider = gr.Slider(minimum=0, maximum=100, value=50, step=1, label="어떤 방식으로 문제를 풀까요?", info="논리적사고 ↔ 직관적사고")
603
+
604
+ # 자동 추천된 유머 스타일 표시
605
+ with gr.Row():
606
+ recommended_humor_display = gr.Markdown("### 🤖 추천 유머 스타일\n*슬라이더를 조정하면 자동으로 추천됩니다*")
607
+ humor_confidence_display = gr.Markdown("### 📊 추천 신뢰도\n*-*")
608
+
609
+ continue_to_step5_button = gr.Button("다음: 말투 선택", variant="primary", size="lg")
610
+
611
+ # 5단계: 말투 선택
612
+ with gr.Group(visible=False) as step5_group:
613
+ gr.Markdown("### 5️⃣ 말투 선택")
614
+ speech_style_radio = gr.Radio(
615
+ choices=[
616
+ "정중한 (~습니다, ~해요)",
617
+ "친근한 (~어, ~야)",
618
+ "청자기 (~다, ~네)",
619
+ "귀여운 (~냥, ~닷)",
620
+ "유쾌한 (~지, ~잖아)",
621
+ "차분한 (~군요, ~네요)"
622
+ ],
623
+ label="말투 스타일",
624
+ value="친근한 (~어, ~야)"
625
+ )
626
+ continue_to_step6_button = gr.Button("다음: 이름 짓기", variant="primary", size="lg")
627
 
628
+ # 6단계: 이름 짓기
629
+ with gr.Group(visible=False) as step6_group:
630
+ gr.Markdown("### 6️⃣ 이름 짓기")
631
+ user_input_name = gr.Textbox(label="이름 입력", placeholder="원하는 이름을 입력하세요")
632
+ with gr.Row():
633
+ auto_name_button = gr.Button("AI 추천 이름", variant="secondary")
634
+ finalize_persona_button = gr.Button("페르소나 완성!", variant="primary", size="lg")
635
 
636
+ # 완료 단계
637
+ with gr.Group(visible=False) as step7_group:
638
+ gr.Markdown("### 🎉 페르소나 완성!")
639
+ with gr.Row():
640
+ save_persona_button = gr.Button("저장하기", variant="primary")
641
+ chat_start_button = gr.Button("대화하기", variant="secondary")
642
 
643
+ # 오른쪽: 백엔드 분석 패널
644
  with gr.Column(scale=1):
645
+ gr.Markdown("### 🔬 AI 분석 과정 (실시간)")
 
646
 
647
+ # 백엔드 분석 상태 표시
648
+ backend_status = gr.HTML("""
649
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #e0e0e0;">
650
+ <h4 style="margin: 0 0 10px 0;">🤖 AI 상태</h4>
651
+ <p style="margin: 0; color: #666;">이미지 업로드를 기다리는 중...</p>
652
+ </div>
653
+ """)
654
 
655
+ # 실시간 분석 로그
656
+ analysis_log = gr.HTML("""
657
+ <div style="background: #f0f4ff; padding: 15px; border-radius: 8px; max-height: 300px; overflow-y: auto;">
658
+ <h4 style="margin: 0 0 10px 0;">📝 분석 로그</h4>
659
+ <div id="log-content" style="font-family: monospace; font-size: 12px; color: #374151;">
660
+ 시스템 준비 완료<br>
661
+ 이미지 분석 엔진 대기 중...<br>
662
+ </div>
663
+ </div>
664
+ """)
665
 
666
+ # 127개 변수 생성 상태
667
+ variables_status = gr.HTML("""
668
+ <div style="background: #fff5f5; padding: 15px; border-radius: 8px; margin-top: 15px;">
669
+ <h4 style="margin: 0 0 10px 0;">🧠 127개 성격 변수</h4>
670
+ <div style="background: #e0e0e0; height: 6px; border-radius: 3px;">
671
+ <div id="variables-progress" style="background: #ef4444; height: 100%; width: 0%; border-radius: 3px; transition: width 0.3s ease;"></div>
672
+ </div>
673
+ <p style="margin: 5px 0 0 0; font-size: 12px;" id="variables-text">생성 대기 중 (0/127)</p>
674
+ </div>
675
+ """)
 
 
 
 
 
 
 
 
 
 
676
 
677
+ # 성격 특성 실시간 표시
678
+ personality_live_view = gr.HTML("""
679
+ <div style="background: #f0fff4; padding: 15px; border-radius: 8px; margin-top: 15px;">
680
+ <h4 style="margin: 0 0 10px 0;">🎭 성격 특성 (실시간)</h4>
681
+ <p style="margin: 0; color: #666; font-size: 14px;">페르소나 생성 후 실시간으로 표시됩니다</p>
682
+ </div>
683
+ """)
684
+
685
+ # 대화 미리보기
686
+ with gr.Accordion("💬 대화 미리보기", open=False):
687
+ preview_chatbot = gr.Chatbot(label="대화 미리보기", height=200, type="messages")
688
+ preview_input = gr.Textbox(placeholder="미리보기 대화...", show_label=False)
689
+ preview_send_btn = gr.Button("전송", size="sm")
690
+
691
+ # 에러 메시지
692
+ error_message = gr.Markdown("", visible=False)
693
 
694
  # Tab 2: Chat
695
  with gr.Tab("대화하기"):
696
  with gr.Row():
697
  with gr.Column(scale=2):
698
  # 대화 인터페이스
699
+ chatbot = gr.Chatbot(label="대화", height=600, type="messages")
700
  with gr.Row():
701
  chat_input = gr.Textbox(placeholder="사물과 대화해보세요...", show_label=False)
702
  chat_button = gr.Button("전송", variant="primary")
703
 
704
  with gr.Column(scale=1):
705
  # 현재 페르소나 요약
706
+ gr.Markdown("### Current Persona")
707
+ current_persona_info = gr.JSON(label="Basic Info")
708
+ current_persona_traits = gr.JSON(label="Personality Traits")
709
+ gr.Markdown("### Communication Style")
710
  current_humor_style = gr.Markdown()
711
 
712
  # 유머 매트릭스 차트 추가
713
+ humor_chart = gr.Plot(label="Humor Style Chart", visible=True)
714
 
715
+ gr.Markdown("### Attractive Flaws")
716
  current_flaws_df = gr.Dataframe(
717
+ headers=["Flaw", "Effect"],
718
  datatype=["str", "str"],
719
+ label="Attractive Flaws"
720
  )
721
+ gr.Markdown("### Contradictory Traits")
722
  current_contradictions_df = gr.Dataframe(
723
+ headers=["Contradiction", "Effect"],
724
  datatype=["str", "str"],
725
+ label="Contradictory Traits"
726
  )
727
+ with gr.Accordion("127 Personality Variables", open=False):
728
  current_all_variables_df = gr.Dataframe(
729
+ headers=["Variable", "Score", "Description"],
730
  datatype=["str", "number", "str"],
731
+ label="Personality Variables"
732
  )
733
 
734
  # Tab 3: Persona Management
 
753
 
754
  with gr.Column():
755
  selected_persona_chart = gr.Image(
756
+ label="Personality Chart"
757
  )
758
 
759
  with gr.Accordion("백엔드 상세 정보", open=False):
760
  selected_persona_backend = gr.HTML("페르소나를 선택해주세요.")
761
 
762
  # Event handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
 
764
+ # 1단계: 영혼 발견하기
765
+ discover_soul_button.click(
766
+ fn=lambda img: (
767
+ gr.update(visible=True) if img else gr.update(visible=False),
768
+ update_progress_bar(1, 6, "1단계 완료 - 영혼 발견됨"),
769
+ update_backend_status("이미지 분석 완료 - 사물 종류 선택 대기 중", "success")
770
+ ),
771
+ inputs=[input_image],
772
+ outputs=[step2_group, progress_bar, backend_status]
773
  )
774
 
775
+ # 2단계: 사물 종류 선택 버튼들
776
+ object_type_btn1.click(fn=select_type_1, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
777
+ object_type_btn2.click(fn=select_type_2, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
778
+ object_type_btn3.click(fn=select_type_3, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
779
+ object_type_btn4.click(fn=select_type_4, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
780
+ object_type_btn5.click(fn=select_type_5, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
781
+ object_type_btn6.click(fn=select_type_6, outputs=[selected_object_type, object_type_state, continue_to_step3_button])
782
+
783
+ # 3단계로 이동
784
+ continue_to_step3_button.click(
785
+ fn=lambda: (
786
+ gr.update(visible=True),
787
+ update_progress_bar(2, 6, "2단계 완료 - 맥락 정보 입력 중")
788
+ ),
789
+ outputs=[step3_group, progress_bar]
790
  )
791
 
792
+ # 3단계: 페르소나 생성
793
+ create_persona_button.click(
794
+ fn=lambda img, obj_type, loc, time: (
795
+ create_persona_from_image(img, {
796
+ "object_type": obj_type,
797
+ "location": loc.replace("🏠 ", "").replace("🏢 ", "").replace("✈️ ", "").replace("🛍️ ", "").replace("🏫 ", "").replace("☕ ", "").replace("🌍 ", ""),
798
+ "time_spent": time.replace("✨ ", "").replace("📅 ", "").replace("🗓️ ", "").replace("⏳ ", "").replace("🎪 ", ""),
799
+ "name": ""
800
+ })[0], # persona만 반환
801
+ gr.update(visible=True),
802
+ update_progress_bar(3, 6, "3단계 완료 - 성격 조정 준비됨"),
803
+ update_backend_status("페르소나 생성 완료 - 127개 변수 생성됨", "success")
804
+ ),
805
+ inputs=[input_image, object_type_state, user_input_location, user_input_time],
806
+ outputs=[current_persona, step4_group, progress_bar, backend_status]
 
807
  ).then(
808
+ fn=lambda p: init_persona_preview_chat(p) if p else [],
809
  inputs=[current_persona],
810
+ outputs=[preview_chatbot]
 
 
 
811
  )
812
 
813
+ # 4단계: 성격 조정 - 슬라이더 변경 시 실시간 업데이트
814
+ for slider in [extraversion_slider, emotion_expression_slider, energy_slider, thinking_style_slider]:
815
+ slider.change(
816
+ fn=lambda e, em, en, t, p: (
817
+ update_humor_recommendation(e, em, en, t)[0], # humor display
818
+ update_humor_recommendation(e, em, en, t)[1], # confidence display
819
+ refine_persona(p, e, em, en, t)[0] if p else p, # updated persona
820
+ update_backend_status(f"성격 조정됨: 외향성{e}%, 감정표현{em}%, 에너지{en}%, 사고방식{t}%", "processing")
821
+ ),
822
+ inputs=[extraversion_slider, emotion_expression_slider, energy_slider, thinking_style_slider, current_persona],
823
+ outputs=[recommended_humor_display, humor_confidence_display, current_persona, backend_status]
824
+ )
825
+
826
+ # 5단계로 이동
827
+ continue_to_step5_button.click(
828
+ fn=lambda: (
829
+ gr.update(visible=True),
830
+ update_progress_bar(4, 6, "4단계 완료 - 말투 선택 중")
831
+ ),
832
+ outputs=[step5_group, progress_bar]
833
  )
834
 
835
+ # 6단계로 이동
836
+ continue_to_step6_button.click(
837
+ fn=lambda: (
838
+ gr.update(visible=True),
839
+ update_progress_bar(5, 6, "5단계 완료 - 이름 짓기 중")
840
+ ),
841
+ outputs=[step6_group, progress_bar]
842
  )
843
 
844
+ # 페르소나 완성
845
+ finalize_persona_button.click(
846
+ fn=lambda name, p: (
847
+ # 이름 업데이트
848
+ {**p, "기본정보": {**p.get("기본정보", {}), "이름": name}} if p and name else p,
849
+ gr.update(visible=True),
850
+ update_progress_bar(6, 6, "🎉 페르소나 완성! 저장하거나 대화해보세요"),
851
+ update_backend_status(f"페르소나 '{name}' 완성!", "success")
852
+ ),
853
+ inputs=[user_input_name, current_persona],
854
+ outputs=[current_persona, step7_group, progress_bar, backend_status]
855
  )
856
 
857
+ # 대화 미리보기
858
+ preview_send_btn.click(
859
+ fn=chat_with_persona,
860
+ inputs=[current_persona, preview_input, preview_chatbot],
861
+ outputs=[preview_chatbot, preview_input]
862
  )
863
 
864
+ # 저장 완료
865
  save_persona_button.click(
866
  fn=save_current_persona,
867
  inputs=[current_persona],
868
+ outputs=[error_message]
 
 
 
869
  )
870
+
871
+ # 대화 탭으로 이동
872
+ chat_start_button.click(
873
+ fn=lambda: gr.update(selected=1),
874
+ outputs=[tabs]
875
+ )
876
+
877
+ # 기존 이벤트 핸들러들...
878
+ # ... existing code ...
879
 
880
  # 기존 함수 업데이트: 현재 페르소나 정보 표시
881
  def update_current_persona_info(current_persona):
 
1018
  # Return empty image with default PIL
1019
  img = Image.new('RGB', (400, 400), color='white')
1020
  draw = PIL.ImageDraw.Draw(img)
1021
+ draw.text((150, 180), "No data", fill='black')
1022
  img_path = os.path.join("data", "temp_chart.png")
1023
  img.save(img_path)
1024
  return img_path
 
1026
  # Get traits
1027
  traits = persona["성격특성"]
1028
 
1029
+ # Convert to English labels
1030
+ categories = []
1031
+ values = []
1032
+ for trait_kr, value in traits.items():
1033
+ trait_en = ENGLISH_LABELS.get(trait_kr, trait_kr)
1034
+ categories.append(trait_en)
1035
+ values.append(value)
1036
 
1037
  # Add the first value again to close the loop
1038
  categories.append(categories[0])
 
1041
  # Convert to radians
1042
  angles = np.linspace(0, 2*np.pi, len(categories), endpoint=True)
1043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  # Create plot with improved aesthetics
1045
  fig, ax = plt.subplots(figsize=(7, 7), subplot_kw=dict(polar=True))
1046
 
 
1072
  # 3. 데이터 포인트 강조
1073
  ax.scatter(angles[:-1], values[:-1], s=100, color='#6366f1', edgecolor='white', zorder=10)
1074
 
1075
+ # 4. 각 축 설정 - 영어 라벨 사용
1076
  ax.set_thetagrids(angles[:-1] * 180/np.pi, categories[:-1], fontsize=12)
1077
 
1078
  # 제목 추가
1079
  name = persona.get("기본정보", {}).get("이름", "Unknown")
1080
+ plt.title(f"{name} Personality Traits", size=16, color='#374151', pad=20, fontweight='bold')
1081
 
1082
  # 저장
1083
  timestamp = int(time.time())
 
1158
  # 이 함수는 파일 상단에서 이미 정의되어 있으므로 여기서는 제거합니다.
1159
 
1160
  # 성격 미세조정 함수
1161
+ def refine_persona(persona, extraversion, emotion_expression, energy, thinking_style):
1162
  """페르소나의 성격을 미세조정하는 함수"""
1163
  if not persona:
1164
  return persona, "페르소나가 없습니다."
1165
 
1166
  try:
1167
+ # 유머 스타일 자동 추천
1168
+ humor_style, confidence, scores = recommend_humor_style(extraversion, emotion_expression, energy, thinking_style)
1169
+
1170
  # 복사본 생성
1171
  refined_persona = persona.copy()
1172
 
1173
+ # 성격 특성 업데이트 - 새로운 지표들을 기존 매핑에 연결
1174
  if "성격특성" in refined_persona:
 
 
 
1175
  refined_persona["성격특성"]["외향성"] = int(extraversion)
1176
+ refined_persona["성격특성"]["감정표현"] = int(emotion_expression)
1177
+ refined_persona["성격특성"]["활력"] = int(energy)
1178
+ refined_persona["성격특성"]["사고방식"] = int(thinking_style)
1179
+
1180
+ # 기존 특성들도 새로운 지표를 바탕으로 계산
1181
+ refined_persona["성격특성"]["온기"] = int((emotion_expression + energy) / 2)
1182
+ refined_persona["성격특성"]["능력"] = int(thinking_style)
1183
+ refined_persona["성격특성"]["창의성"] = int(100 - thinking_style) # 논리적 ↔ 창의적
1184
 
1185
+ # 자동 추천된 유머 스타일 업데이트
1186
  refined_persona["유머스타일"] = humor_style
1187
 
1188
  # 127개 성격 변수가 있으면 업데이트
1189
  if "성격변수127" in refined_persona:
1190
+ # 외향성 관련 변수 업데이트
1191
+ for var in ["E01_사교성", "E02_활동성", "E03_자기주장", "E06_열정성"]:
1192
  if var in refined_persona["성격변수127"]:
1193
+ refined_persona["성격변수127"][var] = int(extraversion * 0.9 + random.randint(0, 20))
1194
 
1195
+ # 감정표현 관련 변수 업데이트
1196
+ for var in ["W09_친밀감표현", "W06_공감능력", "E04_긍정정서"]:
1197
  if var in refined_persona["성격변수127"]:
1198
+ refined_persona["성격변수127"][var] = int(emotion_expression * 0.9 + random.randint(0, 20))
1199
 
1200
+ # 에너지 관련 변수 업데이트
1201
+ for var in ["E02_활동성", "E06_열정성", "E05_자극추구"]:
1202
  if var in refined_persona["성격변수127"]:
1203
+ refined_persona["성격변수127"][var] = int(energy * 0.9 + random.randint(0, 20))
1204
 
1205
+ # 사고방식 관련 변수 업데이트
1206
+ for var in ["C02_지능", "C06_분석력", "C01_효율성"]:
1207
  if var in refined_persona["성격변수127"]:
1208
+ refined_persona["성격변수127"][var] = int(thinking_style * 0.9 + random.randint(0, 20))
 
 
 
 
1209
 
1210
+ # 창의성 관련 변수 업데이트 (논리적 사고와 반대)
1211
+ for var in ["C04_창의성", "C08_통찰력"]:
1212
+ if var in refined_persona["성격변수127"]:
1213
+ refined_persona["성격변수127"][var] = int((100 - thinking_style) * 0.9 + random.randint(0, 20))
1214
 
1215
  # 유머 매트릭스 업데이트
1216
  if "유머매트릭스" in refined_persona:
 
2026
  return chat_history, ""
2027
 
2028
  if not persona:
2029
+ # Gradio 5.x에서는 메시지 형식이 변경됨
2030
+ chat_history.append({"role": "user", "content": user_message})
2031
+ chat_history.append({"role": "assistant", "content": "페르소나가 로드되지 않았습니다. 먼저 페르소나를 생성하거나 불러오세요."})
2032
  return chat_history, ""
2033
 
2034
  try:
2035
  # 페르소나 생성기에서 대화 기능 호출
2036
+ # 이전 대화 기록 변환 필요 - 튜플에서 딕셔너리 형식으로
2037
+ converted_history = []
2038
+ for msg in chat_history:
2039
+ if isinstance(msg, dict):
2040
+ # 이미 메시지 형식이면 그대로 사용
2041
+ if "role" in msg and "content" in msg:
2042
+ converted_history.append((
2043
+ msg["content"] if msg["role"] == "user" else "",
2044
+ msg["content"] if msg["role"] == "assistant" else ""
2045
+ ))
2046
+ elif isinstance(msg, tuple) and len(msg) == 2:
2047
+ # 튜플 형식이면 변환
2048
+ converted_history.append(msg)
2049
 
2050
  # 페르소나 생성기에서 대화 함수 호출
2051
+ response = persona_generator.chat_with_persona(persona, user_message, converted_history)
 
 
 
2052
 
2053
+ # Gradio 5.x 메시지 형식으로 추가
2054
+ chat_history.append({"role": "user", "content": user_message})
2055
+ chat_history.append({"role": "assistant", "content": response})
 
 
 
 
 
2056
 
2057
  return chat_history, ""
2058
  except Exception as e:
2059
  import traceback
2060
  error_details = traceback.format_exc()
2061
  print(f"대화 오류: {error_details}")
2062
+ chat_history.append({"role": "user", "content": user_message})
2063
+ chat_history.append({"role": "assistant", "content": f"대화 중 오류가 발생했습니다: {str(e)}"})
2064
  return chat_history, ""
2065
 
2066
  # 메인 Gradio 인터페이스 구성 함수
2067
  def create_interface():
2068
+ # 현재 persona 상태 저장 - Gradio 5.x에서 변경된 방식 적용
2069
+ current_persona = gr.State(value=None)
2070
+ personas_list = gr.State(value=[])
2071
 
2072
  with gr.Blocks(theme=theme, css=css) as app:
2073
  gr.Markdown("""
 
2075
  이 데모는 일상 속 사물에 AI 페르소나를 부여하여 대화할 수 있게 해주는 서비스입니다.
2076
  """)
2077
 
2078
+ with gr.Tabs() as tabs:
2079
+ with gr.Tab("페르소나 생성", id="persona_creation"):
2080
  with gr.Row():
2081
  with gr.Column(scale=1):
2082
  # 이미지 업로드 영역
 
2109
  value="가구"
2110
  )
2111
 
2112
+ # 사용자 입력들 상태 저장 - Gradio 5.x에서 변경된 방식 적용
2113
+ user_inputs = gr.State(value={})
2114
 
2115
  with gr.Row():
2116
  discover_btn = gr.Button("1. 영혼 발견하기", variant="primary")
 
2158
  json_output = gr.Textbox(label="JSON 데이터", visible=False)
2159
  download_output = gr.File(label="다운로드", visible=False)
2160
 
2161
+ with gr.Tab("세부 정보", id="persona_details"):
2162
  with gr.Row():
2163
  with gr.Column(scale=1):
2164
  # 매력적 결함 데이터프레임
 
2187
  interactive=False
2188
  )
2189
 
2190
+ with gr.Tab("대화하기", id="persona_chat"):
2191
  with gr.Row():
2192
  with gr.Column(scale=1):
2193
  # 페르소나 불러오기 기능
 
2219
  chat_persona_info = gr.Markdown("### 페르소나를 불러와 대화를 시작하세요")
2220
 
2221
  # 대화 인터페이스
2222
+ chatbot = gr.Chatbot(height=400, label="대화", type="messages")
2223
  with gr.Row():
2224
  message_input = gr.Textbox(
2225
  placeholder="메시지를 입력하세요...",
 
2231
 
2232
  # 영혼 깨우기 버튼 이벤트
2233
  discover_btn.click(
2234
+ fn=lambda name, location, time_spent, object_type: {"name": name, "location": location, "time_spent": time_spent, "object_type": object_type},
2235
  inputs=[name_input, location_input, time_spent_input, object_type_input],
2236
  outputs=[user_inputs],
2237
  queue=False
 
2244
 
2245
  # 페르소나 생성 버튼 이벤트
2246
  create_btn.click(
2247
+ fn=lambda name, location, time_spent, object_type: {"name": name, "location": location, "time_spent": time_spent, "object_type": object_type},
2248
  inputs=[name_input, location_input, time_spent_input, object_type_input],
2249
  outputs=[user_inputs],
2250
  queue=False
 
2331
  inputs=[current_persona],
2332
  outputs=[warmth_slider, competence_slider, creativity_slider, extraversion_slider, humor_slider, trust_slider]
2333
  ).then(
2334
+ fn=lambda: gr.update(selected="persona_creation"),
2335
  outputs=[tabs]
2336
  )
2337
 
 
2360
  inputs=[current_persona],
2361
  outputs=[chat_persona_info]
2362
  ).then(
2363
+ fn=lambda: gr.update(selected="persona_creation"),
2364
  outputs=[tabs]
2365
  )
2366