haepa_mac commited on
Commit
698b201
·
1 Parent(s): 3c25171

Fix all major issues: Chatbot type, image processing, Korean fonts, State handling, save/download features

Browse files
Files changed (1) hide show
  1. app.py +242 -60
app.py CHANGED
@@ -6,6 +6,7 @@ import google.generativeai as genai
6
  from PIL import Image
7
  from dotenv import load_dotenv
8
  import matplotlib.pyplot as plt
 
9
  import numpy as np
10
  import base64
11
  import io
@@ -45,6 +46,37 @@ os.makedirs("data/conversations", exist_ok=True)
45
  # Initialize the persona generator
46
  persona_generator = PersonaGenerator()
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  # Gradio theme
49
  theme = gr.themes.Soft(
50
  primary_hue="indigo",
@@ -92,6 +124,22 @@ body, h1, h2, h3, p, div, span, button, input, textarea, label, select, option {
92
  border-radius: 4px;
93
  transition: width 0.5s ease-in-out;
94
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  """
96
 
97
  # Variable descriptions
@@ -115,8 +163,8 @@ HUMOR_STYLE_MAPPING = {
115
  def create_persona_from_image(image, name, location, time_spent, object_type, progress=gr.Progress()):
116
  """페르소나 생성 함수"""
117
  if image is None:
118
- return None, "이미지를 업로드해주세요.", {}, {}, None, [], [], []
119
-
120
  progress(0.1, desc="이미지 분석 중...")
121
 
122
  user_context = {
@@ -130,7 +178,13 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
130
  generator = PersonaGenerator()
131
 
132
  progress(0.3, desc="이미지 분석 중...")
133
- image_analysis = generator.analyze_image(image)
 
 
 
 
 
 
134
 
135
  if object_type:
136
  image_analysis["object_type"] = object_type
@@ -151,7 +205,11 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
151
  }
152
 
153
  personality_traits = backend_persona.get("성격특성", {})
154
- humor_chart = plot_humor_matrix(backend_persona.get("유머매트릭스", {}))
 
 
 
 
155
 
156
  attractive_flaws_df = []
157
  contradictions_df = []
@@ -171,48 +229,85 @@ def create_persona_from_image(image, name, location, time_spent, object_type, pr
171
  personality_variables_df = [[var_name, score, VARIABLE_DESCRIPTIONS.get(var_name, "")]
172
  for var_name, score in variables.items()]
173
 
174
- return backend_persona, "페르소나 생성 완료!", basic_info, personality_traits, humor_chart, attractive_flaws_df, contradictions_df, personality_variables_df
 
 
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  except Exception as e:
177
- print(f"페르소나 생성 오류: {str(e)}")
178
- return None, f"오류 발생: {str(e)}", {}, {}, None, [], [], []
179
 
180
  def generate_personality_chart(persona):
181
  """성격 차트 생성"""
182
  if not persona or "성격특성" not in persona:
183
- img = Image.new('RGB', (400, 400), color='white')
184
- draw = PIL.ImageDraw.Draw(img)
185
- draw.text((150, 180), "No data", fill='black')
186
- img_path = os.path.join("data", "temp_chart.png")
187
- os.makedirs("data", exist_ok=True)
188
- img.save(img_path)
189
- return img_path
190
-
191
- traits = persona["성격특성"]
192
- categories = list(traits.keys())
193
- values = list(traits.values())
194
-
195
- # 차트 생성
196
- fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
197
-
198
- angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False)
199
- values_plot = values + [values[0]] # Close the plot
200
- angles_plot = np.concatenate([angles, [angles[0]]])
201
-
202
- ax.plot(angles_plot, values_plot, 'o-', linewidth=2, color='#6366f1')
203
- ax.fill(angles_plot, values_plot, alpha=0.25, color='#6366f1')
204
- ax.set_xticks(angles)
205
- ax.set_xticklabels(categories)
206
- ax.set_ylim(0, 100)
207
-
208
- plt.title("성격 특성", size=16, pad=20)
209
 
210
- timestamp = int(time.time())
211
- img_path = os.path.join("data", f"chart_{timestamp}.png")
212
- plt.savefig(img_path, format='png', bbox_inches='tight', dpi=150)
213
- plt.close(fig)
214
-
215
- return img_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
  def save_persona_to_file(persona):
218
  """페르소나 저장"""
@@ -220,21 +315,69 @@ def save_persona_to_file(persona):
220
  return "저장할 페르소나가 없습니다."
221
 
222
  try:
 
223
  persona_copy = copy.deepcopy(persona)
224
 
225
- # 저장 불가능한 객체 제거
226
- for key in list(persona_copy.keys()):
227
- if callable(persona_copy[key]):
228
- persona_copy.pop(key, None)
 
 
 
 
229
 
 
230
  filepath = save_persona(persona_copy)
231
  if filepath:
232
  name = persona.get("기본정보", {}).get("이름", "Unknown")
233
- return f"{name} 페르소나가 저장되었습니다."
234
  else:
235
- return "페르소나 저장에 실패했습니다."
236
  except Exception as e:
237
- return f"저장 중 오류 발생: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
  def get_saved_personas():
240
  """저장된 페르소나 목록 가져오기"""
@@ -249,24 +392,30 @@ def get_saved_personas():
249
  persona["created_at"]
250
  ])
251
  return df_data, personas
252
- except Exception:
 
253
  return [], []
254
 
255
  def load_persona_from_selection(selected_row, personas_list):
256
  """선택된 페르소나 로드"""
257
  if selected_row is None or len(selected_row) == 0:
258
- return None, "선택된 페르소나가 없습니다.", {}, {}, None, [], [], []
259
 
260
  try:
261
- selected_index = selected_row.index[0] if hasattr(selected_row, 'index') else 0
 
 
 
 
 
262
  if selected_index >= len(personas_list):
263
- return None, "잘못된 선택입니다.", {}, {}, None, [], [], []
264
 
265
  filepath = personas_list[selected_index]["filepath"]
266
  persona = load_persona(filepath)
267
 
268
  if not persona:
269
- return None, "페르소나 로딩에 실패했습니다.", {}, {}, None, [], [], []
270
 
271
  basic_info = {
272
  "이름": persona.get("기본정보", {}).get("이름", "Unknown"),
@@ -295,10 +444,19 @@ def load_persona_from_selection(selected_row, personas_list):
295
  personality_variables_df = [[var_name, score, VARIABLE_DESCRIPTIONS.get(var_name, "")]
296
  for var_name, score in variables.items()]
297
 
298
- return persona, f"{persona['기본정보']['이름']}을(를) 로드했습니다.", basic_info, personality_traits, humor_chart, attractive_flaws_df, contradictions_df, personality_variables_df
 
 
 
 
 
 
299
 
300
  except Exception as e:
301
- return None, f"로딩 중 오류 발생: {str(e)}", {}, {}, None, [], [], []
 
 
 
302
 
303
  def chat_with_loaded_persona(persona, user_message, chat_history=None):
304
  """페르소나와 대화"""
@@ -309,7 +467,7 @@ def chat_with_loaded_persona(persona, user_message, chat_history=None):
309
  return chat_history, ""
310
 
311
  if not persona:
312
- chat_history.append([user_message, "페르소나가 로드되지 않았습니다."])
313
  return chat_history, ""
314
 
315
  try:
@@ -322,8 +480,9 @@ def chat_with_loaded_persona(persona, user_message, chat_history=None):
322
 
323
  # 메인 인터페이스 생성
324
  def create_main_interface():
325
- current_persona = gr.State(value=None)
326
- personas_list = gr.State(value=[])
 
327
 
328
  with gr.Blocks(theme=theme, css=css, title="놈팽쓰(MemoryTag)") as app:
329
  gr.Markdown("""
@@ -361,12 +520,22 @@ def create_main_interface():
361
  status_output = gr.Markdown("")
362
 
363
  with gr.Column(scale=1):
 
 
 
364
  basic_info_output = gr.JSON(label="기본 정보")
365
  personality_traits_output = gr.JSON(label="성격 특성")
366
 
367
  with gr.Row():
368
  save_btn = gr.Button("페르소나 저장", variant="secondary")
369
  chart_btn = gr.Button("성격 차트 생성", variant="secondary")
 
 
 
 
 
 
 
370
 
371
  # 상세 정보 탭
372
  with gr.Tab("상세 정보", id="details"):
@@ -384,7 +553,7 @@ def create_main_interface():
384
  )
385
 
386
  with gr.Column():
387
- personality_chart_output = gr.Image(label="성격 차트")
388
  humor_chart_output = gr.Plot(label="유머 매트릭스")
389
 
390
  with gr.Accordion("127개 성격 변수", open=False):
@@ -410,7 +579,8 @@ def create_main_interface():
410
 
411
  with gr.Column(scale=1):
412
  gr.Markdown("### 대화")
413
- chatbot = gr.Chatbot(height=400, label="대화")
 
414
  with gr.Row():
415
  message_input = gr.Textbox(
416
  placeholder="메시지를 입력하세요...",
@@ -425,7 +595,8 @@ def create_main_interface():
425
  inputs=[image_input, name_input, location_input, time_spent_input, object_type_input],
426
  outputs=[
427
  current_persona, status_output, basic_info_output, personality_traits_output,
428
- humor_chart_output, attractive_flaws_output, contradictions_output, personality_variables_output
 
429
  ]
430
  )
431
 
@@ -441,6 +612,16 @@ def create_main_interface():
441
  outputs=[personality_chart_output]
442
  )
443
 
 
 
 
 
 
 
 
 
 
 
444
  refresh_btn.click(
445
  fn=get_saved_personas,
446
  outputs=[persona_table, personas_list]
@@ -451,7 +632,8 @@ def create_main_interface():
451
  inputs=[persona_table, personas_list],
452
  outputs=[
453
  current_persona, load_status, basic_info_output, personality_traits_output,
454
- humor_chart_output, attractive_flaws_output, contradictions_output, personality_variables_output
 
455
  ]
456
  ).then(
457
  fn=generate_personality_chart,
 
6
  from PIL import Image
7
  from dotenv import load_dotenv
8
  import matplotlib.pyplot as plt
9
+ import matplotlib.font_manager as fm
10
  import numpy as np
11
  import base64
12
  import io
 
46
  # Initialize the persona generator
47
  persona_generator = PersonaGenerator()
48
 
49
+ # 한글 폰트 설정
50
+ def setup_korean_font():
51
+ """matplotlib 한글 폰트 설정"""
52
+ try:
53
+ # 사용 가능한 한글 폰트 찾기
54
+ available_fonts = fm.findSystemFonts()
55
+ korean_fonts = ['NanumGothic', 'NanumBarunGothic', 'Malgun Gothic', 'AppleGothic', 'Noto Sans CJK KR']
56
+
57
+ for font_name in korean_fonts:
58
+ try:
59
+ plt.rcParams['font.family'] = font_name
60
+ # 테스트 텍스트로 확인
61
+ fig, ax = plt.subplots(figsize=(1, 1))
62
+ ax.text(0.5, 0.5, '한글', fontsize=10)
63
+ plt.close(fig)
64
+ print(f"한글 폰트 설정 완료: {font_name}")
65
+ break
66
+ except:
67
+ continue
68
+ else:
69
+ # 폰트를 찾지 못한 경우 기본 설정
70
+ plt.rcParams['font.family'] = 'DejaVu Sans'
71
+ plt.rcParams['axes.unicode_minus'] = False
72
+ print("한글 폰트를 찾지 못해 기본 폰트 사용")
73
+ except Exception as e:
74
+ print(f"폰트 설정 오류: {str(e)}")
75
+ plt.rcParams['font.family'] = 'DejaVu Sans'
76
+
77
+ # 폰트 초기 설정
78
+ setup_korean_font()
79
+
80
  # Gradio theme
81
  theme = gr.themes.Soft(
82
  primary_hue="indigo",
 
124
  border-radius: 4px;
125
  transition: width 0.5s ease-in-out;
126
  }
127
+
128
+ .persona-greeting {
129
+ background: #f0f4ff;
130
+ border-left: 4px solid #6366f1;
131
+ padding: 15px;
132
+ margin: 15px 0;
133
+ border-radius: 8px;
134
+ font-style: italic;
135
+ }
136
+
137
+ .download-section {
138
+ background: #f8f9fa;
139
+ padding: 15px;
140
+ border-radius: 8px;
141
+ margin-top: 15px;
142
+ }
143
  """
144
 
145
  # Variable descriptions
 
163
  def create_persona_from_image(image, name, location, time_spent, object_type, progress=gr.Progress()):
164
  """페르소나 생성 함수"""
165
  if image is None:
166
+ return None, "이미지를 업로드해주세요.", {}, {}, None, [], [], [], "", None
167
+
168
  progress(0.1, desc="이미지 분석 중...")
169
 
170
  user_context = {
 
178
  generator = PersonaGenerator()
179
 
180
  progress(0.3, desc="이미지 분석 중...")
181
+ # 이미지 처리 방식 수정 - PIL Image 객체를 직접 전달
182
+ if isinstance(image, str):
183
+ # 파일 경로인 경우
184
+ image_analysis = generator.analyze_image(image)
185
+ else:
186
+ # PIL Image 객체인 경우 (Gradio 4.x 기본 방식)
187
+ image_analysis = generator.analyze_image(image)
188
 
189
  if object_type:
190
  image_analysis["object_type"] = object_type
 
205
  }
206
 
207
  personality_traits = backend_persona.get("성격특성", {})
208
+
209
+ # 유머 매트릭스 차트 생성
210
+ humor_chart = None
211
+ if "유머매트릭스" in backend_persona:
212
+ humor_chart = plot_humor_matrix(backend_persona["유머매트릭스"])
213
 
214
  attractive_flaws_df = []
215
  contradictions_df = []
 
229
  personality_variables_df = [[var_name, score, VARIABLE_DESCRIPTIONS.get(var_name, "")]
230
  for var_name, score in variables.items()]
231
 
232
+ # 페르소나 인사말 생성
233
+ persona_name = basic_info.get("이름", "친구")
234
+ greeting = f"안녕! 나는 {persona_name}이야. 드디어 깨어났구나! 뭐든 물어봐~ 😊"
235
 
236
+ return (backend_persona, "페르소나 생성 완료!", basic_info, personality_traits,
237
+ humor_chart, attractive_flaws_df, contradictions_df, personality_variables_df,
238
+ greeting, None)
239
+
240
+ except Exception as e:
241
+ import traceback
242
+ error_msg = traceback.format_exc()
243
+ print(f"페르소나 생성 오류: {error_msg}")
244
+ return (None, f"오류 발생: {str(e)}", {}, {}, None, [], [], [], "", None)
245
+
246
+ def plot_humor_matrix(humor_data):
247
+ """유머 매트릭스 시각화"""
248
+ if not humor_data:
249
+ return None
250
+
251
+ try:
252
+ fig, ax = plt.subplots(figsize=(6, 6))
253
+
254
+ # 데이터 추출
255
+ warmth_vs_wit = humor_data.get("warmth_vs_wit", 50)
256
+ self_vs_observational = humor_data.get("self_vs_observational", 50)
257
+ subtle_vs_expressive = humor_data.get("subtle_vs_expressive", 50)
258
+
259
+ # 간단한 막대 차트로 표시
260
+ categories = ['따뜻함vs위트', '자기참조vs관찰', '미묘함vs표현']
261
+ values = [warmth_vs_wit, self_vs_observational, subtle_vs_expressive]
262
+
263
+ bars = ax.bar(categories, values, color=['#ff9999', '#66b3ff', '#99ff99'])
264
+ ax.set_ylim(0, 100)
265
+ ax.set_ylabel('점수')
266
+ ax.set_title('유머 스타일 매트릭스')
267
+
268
+ # 값 표시
269
+ for bar, value in zip(bars, values):
270
+ height = bar.get_height()
271
+ ax.text(bar.get_x() + bar.get_width()/2., height + 1,
272
+ f'{value:.1f}', ha='center', va='bottom')
273
+
274
+ plt.xticks(rotation=45)
275
+ plt.tight_layout()
276
+
277
+ return fig
278
  except Exception as e:
279
+ print(f"유머 차트 생성 오류: {str(e)}")
280
+ return None
281
 
282
  def generate_personality_chart(persona):
283
  """성격 차트 생성"""
284
  if not persona or "성격특성" not in persona:
285
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
+ try:
288
+ traits = persona["성격특성"]
289
+ categories = list(traits.keys())
290
+ values = list(traits.values())
291
+
292
+ # 극좌표 차트 생성
293
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
294
+
295
+ angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False)
296
+ values_plot = values + [values[0]] # Close the plot
297
+ angles_plot = np.concatenate([angles, [angles[0]]])
298
+
299
+ ax.plot(angles_plot, values_plot, 'o-', linewidth=2, color='#6366f1')
300
+ ax.fill(angles_plot, values_plot, alpha=0.25, color='#6366f1')
301
+ ax.set_xticks(angles)
302
+ ax.set_xticklabels(categories)
303
+ ax.set_ylim(0, 100)
304
+
305
+ plt.title("성격 특성", size=16, pad=20)
306
+
307
+ return fig
308
+ except Exception as e:
309
+ print(f"성격 차트 생성 오류: {str(e)}")
310
+ return None
311
 
312
  def save_persona_to_file(persona):
313
  """페르소나 저장"""
 
315
  return "저장할 페르소나가 없습니다."
316
 
317
  try:
318
+ # 깊은 복사로 원본 보호
319
  persona_copy = copy.deepcopy(persona)
320
 
321
+ # JSON 직렬화 불가능한 객체들 제거
322
+ keys_to_remove = []
323
+ for key, value in persona_copy.items():
324
+ if callable(value) or hasattr(value, '__call__'):
325
+ keys_to_remove.append(key)
326
+
327
+ for key in keys_to_remove:
328
+ persona_copy.pop(key, None)
329
 
330
+ # 저장 실행
331
  filepath = save_persona(persona_copy)
332
  if filepath:
333
  name = persona.get("기본정보", {}).get("이름", "Unknown")
334
+ return f"{name} 페르소나가 저장되었습니다: {filepath}"
335
  else:
336
+ return "페르소나 저장에 실패했습니다."
337
  except Exception as e:
338
+ import traceback
339
+ error_msg = traceback.format_exc()
340
+ print(f"저장 오류: {error_msg}")
341
+ return f"❌ 저장 중 오류 발생: {str(e)}"
342
+
343
+ def export_persona_to_json(persona):
344
+ """페르소나를 JSON 파일로 내보내기"""
345
+ if not persona:
346
+ return None, "내보낼 페르소나가 없습니다."
347
+
348
+ try:
349
+ # 깊은 복사로 원본 보호
350
+ persona_copy = copy.deepcopy(persona)
351
+
352
+ # JSON 직렬화 불가능한 객체들 제거
353
+ keys_to_remove = []
354
+ for key, value in persona_copy.items():
355
+ if callable(value) or hasattr(value, '__call__'):
356
+ keys_to_remove.append(key)
357
+
358
+ for key in keys_to_remove:
359
+ persona_copy.pop(key, None)
360
+
361
+ # JSON 파일 생성
362
+ persona_name = persona_copy.get("기본정보", {}).get("이름", "persona")
363
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
364
+ filename = f"{persona_name}_{timestamp}.json"
365
+
366
+ # 임시 파일 생성
367
+ temp_dir = "data/temp"
368
+ os.makedirs(temp_dir, exist_ok=True)
369
+ filepath = os.path.join(temp_dir, filename)
370
+
371
+ with open(filepath, 'w', encoding='utf-8') as f:
372
+ json.dump(persona_copy, f, ensure_ascii=False, indent=2)
373
+
374
+ return filepath, f"✅ JSON 파일이 생성되었습니다: {filename}"
375
+
376
+ except Exception as e:
377
+ import traceback
378
+ error_msg = traceback.format_exc()
379
+ print(f"JSON 내보내기 오류: {error_msg}")
380
+ return None, f"❌ JSON 내보내기 중 오류 발생: {str(e)}"
381
 
382
  def get_saved_personas():
383
  """저장된 페르소나 목록 가져오기"""
 
392
  persona["created_at"]
393
  ])
394
  return df_data, personas
395
+ except Exception as e:
396
+ print(f"페르소나 목록 로딩 오류: {str(e)}")
397
  return [], []
398
 
399
  def load_persona_from_selection(selected_row, personas_list):
400
  """선택된 페르소나 로드"""
401
  if selected_row is None or len(selected_row) == 0:
402
+ return None, "선택된 페르소나가 없습니다.", {}, {}, None, [], [], [], ""
403
 
404
  try:
405
+ # DataFrame에서 선택된 행의 인덱스 추출
406
+ if hasattr(selected_row, 'index'):
407
+ selected_index = selected_row.index[0]
408
+ else:
409
+ selected_index = 0
410
+
411
  if selected_index >= len(personas_list):
412
+ return None, "잘못된 선택입니다.", {}, {}, None, [], [], [], ""
413
 
414
  filepath = personas_list[selected_index]["filepath"]
415
  persona = load_persona(filepath)
416
 
417
  if not persona:
418
+ return None, "페르소나 로딩에 실패했습니다.", {}, {}, None, [], [], [], ""
419
 
420
  basic_info = {
421
  "이름": persona.get("기본정보", {}).get("이름", "Unknown"),
 
444
  personality_variables_df = [[var_name, score, VARIABLE_DESCRIPTIONS.get(var_name, "")]
445
  for var_name, score in variables.items()]
446
 
447
+ # 로드된 페르소나 인사말
448
+ persona_name = basic_info.get("이름", "친구")
449
+ greeting = f"반가워! 나는 {persona_name}이야. 다시 만나서 기뻐! 😊"
450
+
451
+ return (persona, f"✅ {persona['기본정보']['이름']}을(를) 로드했습니다.",
452
+ basic_info, personality_traits, humor_chart, attractive_flaws_df,
453
+ contradictions_df, personality_variables_df, greeting)
454
 
455
  except Exception as e:
456
+ import traceback
457
+ error_msg = traceback.format_exc()
458
+ print(f"로딩 오류: {error_msg}")
459
+ return None, f"❌ 로딩 중 오류 발생: {str(e)}", {}, {}, None, [], [], [], ""
460
 
461
  def chat_with_loaded_persona(persona, user_message, chat_history=None):
462
  """페르소나와 대화"""
 
467
  return chat_history, ""
468
 
469
  if not persona:
470
+ chat_history.append([user_message, "페르소나가 로드되지 않았습니다. 먼저 페르소나를 생성하거나 불러오세요."])
471
  return chat_history, ""
472
 
473
  try:
 
480
 
481
  # 메인 인터페이스 생성
482
  def create_main_interface():
483
+ # State 변수들 - 올바른 방식으로 생성
484
+ current_persona = gr.State()
485
+ personas_list = gr.State()
486
 
487
  with gr.Blocks(theme=theme, css=css, title="놈팽쓰(MemoryTag)") as app:
488
  gr.Markdown("""
 
520
  status_output = gr.Markdown("")
521
 
522
  with gr.Column(scale=1):
523
+ # 페르소나 인사말 표시
524
+ persona_greeting = gr.Markdown("", elem_classes=["persona-greeting"])
525
+
526
  basic_info_output = gr.JSON(label="기본 정보")
527
  personality_traits_output = gr.JSON(label="성격 특성")
528
 
529
  with gr.Row():
530
  save_btn = gr.Button("페르소나 저장", variant="secondary")
531
  chart_btn = gr.Button("성격 차트 생성", variant="secondary")
532
+
533
+ # 다운로드 섹션
534
+ with gr.Group():
535
+ gr.Markdown("### 📁 페르소나 내보내기")
536
+ export_btn = gr.Button("JSON 파일로 내보내기", variant="outline")
537
+ download_file = gr.File(label="다운로드", visible=False)
538
+ export_status = gr.Markdown("")
539
 
540
  # 상세 정보 탭
541
  with gr.Tab("상세 정보", id="details"):
 
553
  )
554
 
555
  with gr.Column():
556
+ personality_chart_output = gr.Plot(label="성격 차트")
557
  humor_chart_output = gr.Plot(label="유머 매트릭스")
558
 
559
  with gr.Accordion("127개 성격 변수", open=False):
 
579
 
580
  with gr.Column(scale=1):
581
  gr.Markdown("### 대화")
582
+ # Gradio 4.x 호환을 위해 명시적으로 type 지정
583
+ chatbot = gr.Chatbot(height=400, label="대화", type="tuples")
584
  with gr.Row():
585
  message_input = gr.Textbox(
586
  placeholder="메시지를 입력하세요...",
 
595
  inputs=[image_input, name_input, location_input, time_spent_input, object_type_input],
596
  outputs=[
597
  current_persona, status_output, basic_info_output, personality_traits_output,
598
+ humor_chart_output, attractive_flaws_output, contradictions_output,
599
+ personality_variables_output, persona_greeting, download_file
600
  ]
601
  )
602
 
 
612
  outputs=[personality_chart_output]
613
  )
614
 
615
+ export_btn.click(
616
+ fn=export_persona_to_json,
617
+ inputs=[current_persona],
618
+ outputs=[download_file, export_status]
619
+ ).then(
620
+ fn=lambda x: gr.update(visible=True) if x else gr.update(visible=False),
621
+ inputs=[download_file],
622
+ outputs=[download_file]
623
+ )
624
+
625
  refresh_btn.click(
626
  fn=get_saved_personas,
627
  outputs=[persona_table, personas_list]
 
632
  inputs=[persona_table, personas_list],
633
  outputs=[
634
  current_persona, load_status, basic_info_output, personality_traits_output,
635
+ humor_chart_output, attractive_flaws_output, contradictions_output,
636
+ personality_variables_output, persona_greeting
637
  ]
638
  ).then(
639
  fn=generate_personality_chart,