haepada commited on
Commit
bed0080
·
verified ·
1 Parent(s): c9c9cb7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -229
app.py CHANGED
@@ -53,18 +53,12 @@ def serve_wishes(path):
53
 
54
 
55
  class SimpleDB:
56
- """데이터 저장 및 관리 클래스"""
57
-
58
- def __init__(self, wishes_path="data/wishes.json"):
59
- """
60
- 영구 저장이 필요한 소원 데이터만 파일로 관리
61
- Args:
62
- wishes_path: 소원 데이터 저장 경로
63
- """
64
  self.wishes_path = wishes_path
65
- os.makedirs(os.path.dirname(wishes_path), exist_ok=True)
 
66
  self.wishes = self._load_json(wishes_path)
67
- self.session_reflections = [] # 임시 메모리에만 저장되는 감상
68
 
69
  def _load_json(self, file_path):
70
  if not os.path.exists(file_path):
@@ -77,28 +71,29 @@ class SimpleDB:
77
  print(f"Error loading {file_path}: {e}")
78
  return []
79
 
80
- def save_reflection(self, name, reflection, sentiment):
81
- """
82
- 감상은 세션 메모리에만 저장
83
- """
84
  reflection_data = {
85
- "timestamp": datetime.now().strftime("%H:%M:%S"),
86
  "name": name,
87
  "reflection": reflection,
88
  "sentiment": sentiment
89
  }
90
- self.session_reflections.append(reflection_data)
 
 
91
  return True
92
 
93
- def save_wish(self, name, wish, emotion_data=None):
94
- """
95
- 소원은 영구 저장
96
- """
97
  wish_data = {
98
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
99
  "name": name,
100
  "wish": wish,
101
- "emotion": emotion_data
 
102
  }
103
  self.wishes.append(wish_data)
104
  self._save_json(self.wishes_path, self.wishes)
@@ -113,17 +108,11 @@ class SimpleDB:
113
  print(f"Error saving to {file_path}: {e}")
114
  return False
115
 
116
- def get_session_reflections(self):
117
- """현재 세션의 감상만 반환"""
118
- return self.session_reflections
119
 
120
  def get_all_wishes(self):
121
- """저장된 모든 소원 반환"""
122
- return sorted(self.wishes, key=lambda x: x["timestamp"], reverse=True)
123
-
124
- def clear_session_data(self):
125
- """세션 데이터 초기화"""
126
- self.session_reflections = []
127
 
128
  # API 설정
129
  HF_API_TOKEN = os.getenv("roots", "")
@@ -261,63 +250,59 @@ def analyze_voice(audio_data, state):
261
  return state, "음성을 먼저 녹음해주세요.", "", "", ""
262
 
263
  try:
264
- sr, y = audio_data
265
- y = y.astype(np.float32)
266
-
267
- if len(y) == 0:
268
- return state, "음성이 감지되지 않았습니다.", "", "", ""
269
-
270
- acoustic_features = calculate_baseline_features((sr, y))
271
- if acoustic_features is None:
272
  return state, "음성 분석에 실패했습니다.", "", "", ""
273
 
274
- # 음성 인식
 
 
 
 
 
 
 
275
  if speech_recognizer:
276
  try:
 
277
  transcription = speech_recognizer({"sampling_rate": sr, "raw": y.astype(np.float32)})
278
  text = transcription["text"]
279
  except Exception as e:
280
  print(f"Speech recognition error: {e}")
281
- text = "음성 인식 실패"
282
- else:
283
- text = "음성 인식 모델을 불러올 수 없습니다."
284
-
285
- # 음성 감정 분석
286
- voice_emotion = map_acoustic_to_emotion(acoustic_features, state.get("baseline_features"))
287
-
288
- # 텍스트 감정 분석
289
- if text_analyzer and text:
290
- try:
291
- text_sentiment = text_analyzer(text)[0]
292
- text_result = f"텍스트 감정 분석: {text_sentiment['label']} (점수: {text_sentiment['score']:.2f})"
293
- except Exception as e:
294
- print(f"Text analysis error: {e}")
295
- text_sentiment = {"label": "unknown", "score": 0.0}
296
- text_result = "텍스트 감정 분석 실패"
297
- else:
298
- text_sentiment = {"label": "unknown", "score": 0.0}
299
- text_result = "텍스트 감정 분석을 수행할 수 없습니다."
300
-
301
- voice_result = (
302
- f"음성 감정: {voice_emotion['primary']} "
303
- f"(강도: {voice_emotion['intensity']:.1f}%, 신뢰도: {voice_emotion['confidence']:.2f})\n"
304
- f"특징: {', '.join(voice_emotion['characteristics'])}\n"
305
- f"상세 분석:\n"
306
- f"- 에너지 레벨: {voice_emotion['details']['energy_level']}\n"
307
- f"- 말하기 속도: {voice_emotion['details']['speech_rate']}\n"
308
- f"- 음높이 변화: {voice_emotion['details']['pitch_variation']}\n"
309
- f"- 음성 크기: {voice_emotion['details']['voice_volume']}"
310
- )
311
 
312
  # 프롬프트 생성
313
- prompt = generate_detailed_prompt(text, voice_emotion, text_sentiment)
314
  state = {**state, "final_prompt": prompt}
315
 
316
- return state, text, voice_result, text_result, prompt
317
 
318
  except Exception as e:
319
  print(f"Error in analyze_voice: {str(e)}")
320
- return state, f"오류 발생: {str(e)}", "", "", ""
321
 
322
 
323
  def generate_detailed_prompt(text, emotions, text_sentiment):
@@ -628,21 +613,6 @@ def create_interface():
628
  .gradio-button:active {
629
  transform: scale(0.98);
630
  }
631
- .loader {
632
- width: 48px;
633
- height: 48px;
634
- border: 5px solid #FFF;
635
- border-bottom-color: #FF3D00;
636
- border-radius: 50%;
637
- display: inline-block;
638
- box-sizing: border-box;
639
- animation: rotation 1s linear infinite;
640
- }
641
-
642
- @keyframes rotation {
643
- 0% { transform: rotate(0deg); }
644
- 100% { transform: rotate(360deg); }
645
- }
646
  """
647
 
648
  with gr.Blocks(theme=gr.themes.Soft(), css=css) as app:
@@ -667,13 +637,13 @@ def create_interface():
667
  interactive=True
668
  )
669
  name_submit_btn = gr.Button("굿판 시작하기", variant="primary")
670
-
671
  # 2단계: 세계관 설명
672
  story_section = gr.Column(visible=False)
673
  with story_section:
674
  gr.Markdown(ONCHEON_STORY)
675
  continue_btn = gr.Button("준비하기", variant="primary")
676
-
677
  # 3단계: 축원 의식
678
  blessing_section = gr.Column(visible=False)
679
  with blessing_section:
@@ -688,146 +658,88 @@ def create_interface():
688
  streaming=False
689
  )
690
  set_baseline_btn = gr.Button("축원 마치기", variant="primary")
691
-
692
- with gr.Column() as analysis_section:
693
- analysis_status = gr.Markdown("")
694
- loading_icon = gr.HTML("""
695
- <div style="display:flex;justify-content:center;align-items:center;height:50px;overflow:hidden;">
696
- <div class="loader" style="width:30px;height:30px;"></div>
697
- </div>
698
- """)
699
- voice_features = gr.Textbox(
700
- label="음성 특성 분석",
701
- interactive=False,
702
- visible=False
703
- )
704
- text_sentiment = gr.Textbox(
705
- label="기본 감정 상태",
706
- interactive=False,
707
- visible=False
708
- )
709
- analysis_result = gr.Markdown(visible=False)
710
- next_button = gr.Button(
711
- "다음 단계로",
712
- variant="primary",
713
- visible=False
714
- )
715
-
716
- def next_section():
717
- """다음 단계로 이동하는 핸들러"""
718
- return (
719
- gr.update(visible=False), # blessing_section
720
- gr.update(visible=True) # entry_guide_section
721
- )
722
 
723
  def handle_blessing_complete(audio, state):
724
- """축원 완료 처리 핸들러"""
725
  if audio is None:
726
  return (
727
- state,
728
  "음성을 먼저 녹음해주세요.",
729
- gr.update(visible=False), # loading_icon
730
- gr.update(visible=False), # voice_features
731
- gr.update(visible=False), # text_sentiment
732
- gr.update(visible=False), # analysis_result
733
- gr.update(visible=False), # next_button
734
- gr.update(visible=True), # blessing_section
735
- gr.update(visible=False) # entry_guide_section
736
  )
737
 
738
  try:
 
739
  features = calculate_baseline_features(audio)
740
  if features is None:
741
  return (
742
  state,
743
- "음성 분석에 실패했습니다. 다시 시도해주세요.",
744
- gr.update(visible=False),
745
- gr.update(visible=False),
746
- gr.update(visible=False),
747
- gr.update(visible=False),
748
- gr.update(visible=False),
749
  gr.update(visible=True),
750
  gr.update(visible=False)
751
  )
752
-
 
753
  baseline_emotion = map_acoustic_to_emotion(features)
754
 
755
- voice_features_text = f"""
756
- 🎭 음성 특성 분석:
757
- 에너지 레벨: {baseline_emotion['details']['energy_level']}
758
- 말하기 속도: {baseline_emotion['details']['speech_rate']}
759
- • 음높이 변화: {baseline_emotion['details']['pitch_variation']}
760
- • 음성 크기: {baseline_emotion['details']['voice_volume']}
761
- """
 
 
762
 
763
- sentiment_text = f"""
764
- 💫 기본 감정 상태:
765
  • 주요 감정: {baseline_emotion['primary']}
766
  • 감정 강도: {baseline_emotion['intensity']:.1f}%
767
  • 특징: {', '.join(baseline_emotion['characteristics'])}
768
- """
769
 
770
- analysis_text = """
771
- ✨ 기준 음성 분석이 완료되었습니다.
772
- 결과를 확인하신 후 아래의 '다음 단계로' 버튼을 눌러주세요.
773
  """
774
 
775
- new_state = {
776
- **state,
777
- "baseline_features": features,
778
- "baseline_emotion": baseline_emotion,
779
- "analysis_complete": True
780
- }
781
-
782
  return (
783
  new_state,
784
- "✅ 축원이 완료되었습니다.",
785
- gr.update(visible=False), # loading_icon
786
- gr.update(value=voice_features_text, visible=True), # voice_features
787
- gr.update(value=sentiment_text, visible=True), # text_sentiment
788
- gr.update(value=analysis_text, visible=True), # analysis_result
789
- gr.update(visible=True), # next_button
790
- gr.update(visible=True), # blessing_section
791
- gr.update(visible=False) # entry_guide_section
792
  )
793
-
794
  except Exception as e:
795
  print(f"Error in handle_blessing_complete: {str(e)}")
796
  return (
797
  state,
798
- f"오류가 발생했습니다. 다시 시도해주세요.",
799
- gr.update(visible=False),
800
- gr.update(visible=False),
801
- gr.update(visible=False),
802
- gr.update(visible=False),
803
- gr.update(visible=False),
804
  gr.update(visible=True),
805
  gr.update(visible=False)
806
  )
807
-
808
  # 4단계: 굿판 입장 안내
809
  entry_guide_section = gr.Column(visible=False)
810
  with entry_guide_section:
811
  gr.Markdown("## 굿판으로 입장하기")
812
- gr.Markdown(
813
- """
814
- * 청신 탭으로 이동해 주세요.
815
- * 부산광역시 동래구 온천장역에서 시작하면 더욱 깊은 경험을 시작할 수 있습니다.
816
- * (본격적인 경험을 시작하기에 앞서 이동을 권장드립니다)
817
- """
818
- )
819
  enter_btn = gr.Button("청신 의식 시작하기", variant="primary")
820
 
821
  with gr.TabItem("청신") as tab_listen:
822
  gr.Markdown("## 청신 - 소리로 정화하기")
823
- gr.Markdown(
824
- """
825
- 온천천의 소리를 들으며 마음을 정화해보세요.
826
-
827
- 💫 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
828
- 온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
829
- """
830
- )
831
  play_music_btn = gr.Button("온천천의 소리 듣기", variant="secondary")
832
  with gr.Row():
833
  audio = gr.Audio(
@@ -908,7 +820,6 @@ def create_interface():
908
  💫 여러분의 소원은 11월 25일 온천천 벽면에 설치될 소원나무에 전시될 예정입니다.
909
  따뜻한 마음을 담아 작성해주세요.
910
  """)
911
- status_message = gr.Markdown("")
912
  wishes_display = gr.Dataframe(
913
  headers=["시간", "소원", "이름"],
914
  label="기록된 소원들",
@@ -944,6 +855,21 @@ def create_interface():
944
  gr.update(visible=False)
945
  )
946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  def handle_enter():
948
  return gr.update(selected=1) # 청신 탭으로 이동
949
 
@@ -1008,29 +934,31 @@ def create_interface():
1008
  sentiment_text = "분석 불가"
1009
 
1010
  # DB에 저장
1011
- reflection_data = db.save_reflection(name, text, sentiment_text)
1012
 
1013
- # 현재 세션의 감상 데이터만 표시
1014
  display_data = []
1015
- session_reflections = state.get("reflections", [])
1016
- session_reflections.append([
1017
- current_time,
1018
- text,
1019
- sentiment_text
1020
- ])
1021
-
 
1022
  # 상태 업데이트
1023
- state = safe_state_update(state, {"reflections": session_reflections})
1024
 
1025
- return state, session_reflections # 세션 데이터만 반환
1026
-
1027
  except Exception as e:
1028
  print(f"Error saving reflection: {e}")
1029
- return state, state.get("reflections", [])
 
1030
 
1031
  def handle_save_wish(text, state):
1032
  if not text.strip():
1033
- return state, "소원을 입력해주세요.", []
1034
 
1035
  try:
1036
  name = state.get("user_name", "익명")
@@ -1040,11 +968,10 @@ def create_interface():
1040
  [wish["timestamp"], wish["wish"], wish["name"]]
1041
  for wish in wishes
1042
  ]
1043
- return state, "소원이 저장되었습니다.", wish_display_data
1044
  except Exception as e:
1045
  print(f"Error saving wish: {e}")
1046
- return state, "오류가 발생했습니다.", []
1047
-
1048
 
1049
  # 이벤트 연결
1050
  name_submit_btn.click(
@@ -1057,29 +984,11 @@ def create_interface():
1057
  fn=handle_continue,
1058
  outputs=[story_section, welcome_section, blessing_section, entry_guide_section]
1059
  )
1060
-
1061
  set_baseline_btn.click(
1062
  fn=handle_blessing_complete,
1063
  inputs=[baseline_audio, state],
1064
- outputs=[
1065
- state, # 상태
1066
- analysis_status, # 상태 메시지
1067
- loading_icon, # 로딩 아이콘
1068
- voice_features, # 음성 특성 분석
1069
- text_sentiment, # 기본 감정 상태
1070
- analysis_result, # 최종 분석 결과
1071
- next_button, # 다음 단계 버튼
1072
- blessing_section, # 축원 섹션
1073
- entry_guide_section # 가이드 섹션
1074
- ]
1075
- )
1076
-
1077
- next_button.click(
1078
- fn=next_section,
1079
- outputs=[
1080
- blessing_section,
1081
- entry_guide_section
1082
- ]
1083
  )
1084
 
1085
  enter_btn.click(
@@ -1118,10 +1027,8 @@ def create_interface():
1118
  save_final_btn.click(
1119
  fn=handle_save_wish,
1120
  inputs=[final_reflection, state],
1121
- outputs=[state, status_message, wishes_display]
1122
  )
1123
-
1124
-
1125
  return app
1126
 
1127
  if __name__ == "__main__":
 
53
 
54
 
55
  class SimpleDB:
56
+ def __init__(self, reflections_path="data/reflections.json", wishes_path="data/wishes.json"):
57
+ self.reflections_path = reflections_path
 
 
 
 
 
 
58
  self.wishes_path = wishes_path
59
+ os.makedirs('data', exist_ok=True)
60
+ self.reflections = self._load_json(reflections_path)
61
  self.wishes = self._load_json(wishes_path)
 
62
 
63
  def _load_json(self, file_path):
64
  if not os.path.exists(file_path):
 
71
  print(f"Error loading {file_path}: {e}")
72
  return []
73
 
74
+ def save_reflection(self, name, reflection, sentiment, timestamp=None):
75
+ if timestamp is None:
76
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
77
+
78
  reflection_data = {
79
+ "timestamp": timestamp,
80
  "name": name,
81
  "reflection": reflection,
82
  "sentiment": sentiment
83
  }
84
+
85
+ self.reflections.append(reflection_data)
86
+ self._save_json(self.reflections_path, self.reflections)
87
  return True
88
 
89
+ def save_wish(self, name, wish, emotion_data=None, timestamp=None):
90
+ if timestamp is None:
91
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
92
  wish_data = {
 
93
  "name": name,
94
  "wish": wish,
95
+ "emotion": emotion_data,
96
+ "timestamp": timestamp
97
  }
98
  self.wishes.append(wish_data)
99
  self._save_json(self.wishes_path, self.wishes)
 
108
  print(f"Error saving to {file_path}: {e}")
109
  return False
110
 
111
+ def get_all_reflections(self):
112
+ return sorted(self.reflections, key=lambda x: x["timestamp"], reverse=True)
 
113
 
114
  def get_all_wishes(self):
115
+ return self.wishes
 
 
 
 
 
116
 
117
  # API 설정
118
  HF_API_TOKEN = os.getenv("roots", "")
 
250
  return state, "음성을 먼저 녹음해주세요.", "", "", ""
251
 
252
  try:
253
+ features = calculate_baseline_features(audio_data)
254
+ if features is None:
 
 
 
 
 
 
255
  return state, "음성 분석에 실패했습니다.", "", "", ""
256
 
257
+ # 기준점과 비교하여 감정 분석
258
+ voice_emotion = map_acoustic_to_emotion(
259
+ features,
260
+ state.get("baseline_features") # 기준점 전달
261
+ )
262
+
263
+ # 음성 인식 (필요한 경우)
264
+ text = "음성이 인식되었습니다."
265
  if speech_recognizer:
266
  try:
267
+ sr, y = audio_data
268
  transcription = speech_recognizer({"sampling_rate": sr, "raw": y.astype(np.float32)})
269
  text = transcription["text"]
270
  except Exception as e:
271
  print(f"Speech recognition error: {e}")
272
+ text = "음성 인식은 실패했으나, 감정 분석은 진행합니다."
273
+
274
+ # 감정 상태 변화 분석
275
+ baseline_emotion = state.get("baseline_emotion", {})
276
+ emotion_change = ""
277
+ if baseline_emotion:
278
+ baseline_intensity = baseline_emotion.get("intensity", 0)
279
+ current_intensity = voice_emotion["intensity"]
280
+ intensity_change = current_intensity - baseline_intensity
281
+
282
+ emotion_change = f"""
283
+ 💫 기준 상태와의 비교:
284
+ 감정 변화: {baseline_emotion['primary']} → {voice_emotion['primary']}
285
+ 강도 변화: {intensity_change:+.1f}%
286
+ """
287
+
288
+ voice_result = f"""
289
+ 🎭 현재 감정 상태:
290
+ 주요 감정: {voice_emotion['primary']}
291
+ • 감정 강도: {voice_emotion['intensity']:.1f}%
292
+ 특징: {', '.join(voice_emotion['characteristics'])}
293
+
294
+ {emotion_change}
295
+ """
 
 
 
 
 
 
296
 
297
  # 프롬프트 생성
298
+ prompt = generate_detailed_prompt(text, voice_emotion, {"label": "neutral", "score": 0.5})
299
  state = {**state, "final_prompt": prompt}
300
 
301
+ return state, text, voice_result, "", prompt
302
 
303
  except Exception as e:
304
  print(f"Error in analyze_voice: {str(e)}")
305
+ return state, f"오류가 발생했습니다: {str(e)}", "", "", ""
306
 
307
 
308
  def generate_detailed_prompt(text, emotions, text_sentiment):
 
613
  .gradio-button:active {
614
  transform: scale(0.98);
615
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  """
617
 
618
  with gr.Blocks(theme=gr.themes.Soft(), css=css) as app:
 
637
  interactive=True
638
  )
639
  name_submit_btn = gr.Button("굿판 시작하기", variant="primary")
640
+
641
  # 2단계: 세계관 설명
642
  story_section = gr.Column(visible=False)
643
  with story_section:
644
  gr.Markdown(ONCHEON_STORY)
645
  continue_btn = gr.Button("준비하기", variant="primary")
646
+
647
  # 3단계: 축원 의식
648
  blessing_section = gr.Column(visible=False)
649
  with blessing_section:
 
658
  streaming=False
659
  )
660
  set_baseline_btn = gr.Button("축원 마치기", variant="primary")
661
+
662
+ # 분석 결과 표시 영역
663
+ baseline_status = gr.Markdown("") # 상태 메시지
664
+ analysis_results = gr.Markdown(visible=False) # 분석 결과
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
  def handle_blessing_complete(audio, state):
667
+ """축원 완료 기준점 설정 핸들러"""
668
  if audio is None:
669
  return (
670
+ state,
671
  "음성을 먼저 녹음해주세요.",
672
+ gr.update(visible=True),
673
+ gr.update(visible=False)
 
 
 
 
 
674
  )
675
 
676
  try:
677
+ # 기준점 설정을 위한 음성 분석
678
  features = calculate_baseline_features(audio)
679
  if features is None:
680
  return (
681
  state,
682
+ "음성 분석에 실패했습니다. 다시 시도해주세요.",
 
 
 
 
 
683
  gr.update(visible=True),
684
  gr.update(visible=False)
685
  )
686
+
687
+ # 기본 감정 상태 분석
688
  baseline_emotion = map_acoustic_to_emotion(features)
689
 
690
+ # 상태 업데이트
691
+ new_state = safe_state_update(state, {
692
+ "baseline_features": features,
693
+ "baseline_emotion": baseline_emotion
694
+ })
695
+
696
+ # 분석 결과 표시
697
+ result_text = f"""
698
+ ✨ 축원이 완료되었습니다.
699
 
700
+ 🎭 기본 감정 상태:
 
701
  • 주요 감정: {baseline_emotion['primary']}
702
  • 감정 강도: {baseline_emotion['intensity']:.1f}%
703
  • 특징: {', '.join(baseline_emotion['characteristics'])}
 
704
 
705
+ 다음 단계로 진행해주세요.
 
 
706
  """
707
 
 
 
 
 
 
 
 
708
  return (
709
  new_state,
710
+ result_text,
711
+ gr.update(visible=False),
712
+ gr.update(visible=True)
 
 
 
 
 
713
  )
714
+
715
  except Exception as e:
716
  print(f"Error in handle_blessing_complete: {str(e)}")
717
  return (
718
  state,
719
+ f"오류가 발생했습니다. 다시 시도해주세요.",
 
 
 
 
 
720
  gr.update(visible=True),
721
  gr.update(visible=False)
722
  )
723
+
724
  # 4단계: 굿판 입장 안내
725
  entry_guide_section = gr.Column(visible=False)
726
  with entry_guide_section:
727
  gr.Markdown("## 굿판으로 입장하기")
728
+ gr.Markdown("""
729
+ * 청신 탭으로 이동해 주세요.
730
+ * 부산광역시 동래구 온천장역에서 시작하면 더욱 깊은 경험을 시작할 수 있습니다.
731
+ * (본격적인 경험을 시작하기에 앞서 이동을 권장드립니다)
732
+ """)
 
 
733
  enter_btn = gr.Button("청신 의식 시작하기", variant="primary")
734
 
735
  with gr.TabItem("청신") as tab_listen:
736
  gr.Markdown("## 청신 - 소리로 정화하기")
737
+ gr.Markdown("""
738
+ 온천천의 소리를 들으며 마음을 정화해보세요.
739
+
740
+ 💫 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
741
+ 온천천 온천장역에서 장전역까지 걸으며 깊은 체험이 가능합니다.
742
+ """)
 
 
743
  play_music_btn = gr.Button("온천천의 소리 듣기", variant="secondary")
744
  with gr.Row():
745
  audio = gr.Audio(
 
820
  💫 여러분의 소원은 11월 25일 온천천 벽면에 설치될 소원나무에 전시될 예정입니다.
821
  따뜻한 마음을 담아 작성해주세요.
822
  """)
 
823
  wishes_display = gr.Dataframe(
824
  headers=["시간", "소원", "이름"],
825
  label="기록된 소원들",
 
855
  gr.update(visible=False)
856
  )
857
 
858
+ def handle_blessing_complete(audio, state):
859
+ if audio is None:
860
+ return state, "음성을 먼저 녹음해주세요.", gr.update(visible=True), gr.update(visible=False)
861
+
862
+ try:
863
+ # ... (기존 음성 처리 코드)
864
+ return (
865
+ state,
866
+ "축원이 완료되었습니다.",
867
+ gr.update(visible=False),
868
+ gr.update(visible=True)
869
+ )
870
+ except Exception as e:
871
+ return state, f"오류가 발생했습니다: {str(e)}", gr.update(visible=True), gr.update(visible=False)
872
+
873
  def handle_enter():
874
  return gr.update(selected=1) # 청신 탭으로 이동
875
 
 
934
  sentiment_text = "분석 불가"
935
 
936
  # DB에 저장
937
+ db.save_reflection(name, text, sentiment_text)
938
 
939
+ # 화면에 표시할 데이터 형식으로 변환
940
  display_data = []
941
+ all_reflections = db.get_all_reflections()
942
+ for ref in all_reflections:
943
+ display_data.append([
944
+ ref["timestamp"],
945
+ ref["reflection"],
946
+ ref["sentiment"]
947
+ ])
948
+
949
  # 상태 업데이트
950
+ state = safe_state_update(state, {"reflections": display_data})
951
 
952
+ return state, display_data
953
+
954
  except Exception as e:
955
  print(f"Error saving reflection: {e}")
956
+ return state, []
957
+
958
 
959
  def handle_save_wish(text, state):
960
  if not text.strip():
961
+ return "소원을 입력해주세요.", []
962
 
963
  try:
964
  name = state.get("user_name", "익명")
 
968
  [wish["timestamp"], wish["wish"], wish["name"]]
969
  for wish in wishes
970
  ]
971
+ return "소원이 저장되었습니다.", wish_display_data
972
  except Exception as e:
973
  print(f"Error saving wish: {e}")
974
+ return "오류가 발생했습니다.", []
 
975
 
976
  # 이벤트 연결
977
  name_submit_btn.click(
 
984
  fn=handle_continue,
985
  outputs=[story_section, welcome_section, blessing_section, entry_guide_section]
986
  )
987
+
988
  set_baseline_btn.click(
989
  fn=handle_blessing_complete,
990
  inputs=[baseline_audio, state],
991
+ outputs=[state, baseline_status, blessing_section, entry_guide_section]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
992
  )
993
 
994
  enter_btn.click(
 
1027
  save_final_btn.click(
1028
  fn=handle_save_wish,
1029
  inputs=[final_reflection, state],
1030
+ outputs=[baseline_status, wishes_display]
1031
  )
 
 
1032
  return app
1033
 
1034
  if __name__ == "__main__":