haepada commited on
Commit
0acbc79
·
verified ·
1 Parent(s): 2277770

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -156
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import gradio as gr
2
  import numpy as np
3
  import librosa
@@ -8,7 +10,7 @@ import requests
8
  import json
9
  import time
10
 
11
- # 데이터 저장을 위한 간단한 파일 기반 DB
12
  class SimpleDB:
13
  def __init__(self, file_path="wishes.json"):
14
  self.file_path = file_path
@@ -27,15 +29,12 @@ class SimpleDB:
27
  def save_wish(self, name, wish, timestamp=None):
28
  if timestamp is None:
29
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
30
-
31
  wish_data = {
32
  "name": name,
33
  "wish": wish,
34
  "timestamp": timestamp
35
  }
36
-
37
  self.wishes.append(wish_data)
38
-
39
  try:
40
  with open(self.file_path, 'w', encoding='utf-8') as f:
41
  json.dump(self.wishes, f, ensure_ascii=False, indent=2)
@@ -44,12 +43,11 @@ class SimpleDB:
44
  print(f"Error saving wish: {e}")
45
  return False
46
 
47
- # 환경변수 설정
48
  HF_API_TOKEN = os.getenv("roots")
49
  if not HF_API_TOKEN:
50
  raise ValueError("roots token not found in environment variables")
51
 
52
- # API 설정
53
  API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
54
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
55
 
@@ -64,7 +62,7 @@ text_analyzer = pipeline(
64
  )
65
 
66
  # 상수 정의
67
- IMAGE_DISPLAY_TIME = 30 # 이미지 표시 시간 (초)
68
  WELCOME_MESSAGE = """
69
  # 디지털 굿판에 오신 것을 환영합니다
70
 
@@ -85,7 +83,159 @@ WORLDVIEW_MESSAGE = """
85
  """
86
 
87
 
88
- # Part 2/3 - Core Functions and Image Generation
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  def generate_image_from_prompt(prompt):
91
  """이미지 생성 함수"""
@@ -110,7 +260,7 @@ def generate_image_from_prompt(prompt):
110
 
111
  if response.status_code == 200:
112
  print("Image generated successfully")
113
- # 이미지 저장 로직 추가
114
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
115
  image_path = f"generated_images/{timestamp}.png"
116
  os.makedirs("generated_images", exist_ok=True)
@@ -125,64 +275,6 @@ def generate_image_from_prompt(prompt):
125
  print(f"Error generating image: {str(e)}")
126
  return None, None
127
 
128
- def analyze_voice_with_retry(audio_path, state, max_retries=3):
129
- """음성 분석 함수 (재시도 로직 포함)"""
130
- for attempt in range(max_retries):
131
- try:
132
- y, sr = librosa.load(audio_path, sr=16000)
133
-
134
- acoustic_features = {
135
- "energy": float(np.mean(librosa.feature.rms(y=y))),
136
- "tempo": float(librosa.beat.tempo(y)[0]),
137
- "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
138
- "volume": float(np.mean(np.abs(y)))
139
- }
140
-
141
- transcription = speech_recognizer(y)
142
- text = transcription["text"]
143
-
144
- emotions = map_acoustic_to_emotion(acoustic_features, state.get("baseline_features"))
145
- text_sentiment = text_analyzer(text)[0]
146
-
147
- return {
148
- "text": text,
149
- "emotions": emotions,
150
- "sentiment": text_sentiment,
151
- "features": acoustic_features
152
- }
153
- except Exception as e:
154
- if attempt == max_retries - 1:
155
- raise e
156
- print(f"Attempt {attempt + 1} failed, retrying...")
157
- continue
158
-
159
- def generate_detailed_prompt(text, emotions, text_sentiment):
160
- """감정 기반 상세 프롬프트 생성"""
161
- emotion_colors = {
162
- "기쁨/열정": "밝은 노랑과 따뜻한 주황색",
163
- "분노/강조": "강렬한 빨강과 짙은 검정",
164
- "놀람/흥분": "선명한 파랑과 밝은 보라",
165
- "관심/호기심": "연한 하늘색과 민트색",
166
- "슬픔/우울": "어두운 파랑과 회색",
167
- "피로/무기력": "탁한 갈색과 짙은 회색",
168
- "평온/안정": "부드러운 초록과 베이지",
169
- "차분/진지": "차분한 남색과 깊은 보라"
170
- }
171
-
172
- if emotions["intensity"] > 70:
173
- visual_style = "역동적인 붓질과 강한 대비"
174
- elif emotions["intensity"] > 40:
175
- visual_style = "균형잡힌 구도와 중간 톤의 조화"
176
- else:
177
- visual_style = "부드러운 그라데이션과 차분한 톤"
178
-
179
- prompt = f"한국 전통 민화 스타일의 추상화, {emotion_colors.get(emotions['primary'], '자연스러운 색상')} 기반. "
180
- prompt += f"{visual_style}로 표현된 {emotions['primary']}의 감정. "
181
- prompt += f"음성의 특징({', '.join(emotions['characteristics'])})을 화면의 동적 요소로 표현. "
182
- prompt += f"발화 내용 '{text}'에서 느껴지는 감정(강도: {text_sentiment['score']}/5)을 은유적 이미지로 담아내기."
183
-
184
- return prompt
185
-
186
  def update_final_prompt(state):
187
  """청신의 감상들을 종합하여 최종 프롬프트 업데이트"""
188
  combined_prompt = "한국 전통 민화 스타일의 추상화, 온천천에서의 감상과 소원을 담아내기:\n\n"
@@ -221,9 +313,9 @@ def save_reflection(text, state):
221
  print(f"Error in save_reflection: {str(e)}")
222
  return state, []
223
 
224
- # Part 3/3 - Interface and Main
 
225
  def create_interface():
226
- # DB 초기화
227
  db = SimpleDB()
228
 
229
  with gr.Blocks(theme=gr.themes.Soft()) as app:
@@ -240,10 +332,9 @@ def create_interface():
240
  header = gr.Markdown("# 디지털 굿판")
241
  user_display = gr.Markdown("")
242
 
243
- # 탭 구성
244
  with gr.Tabs() as tabs:
245
  # 입장
246
- with gr.Tab("입장", id="intro"):
247
  gr.Markdown(WELCOME_MESSAGE)
248
  name_input = gr.Textbox(
249
  label="이름을 알려주세요",
@@ -251,17 +342,14 @@ def create_interface():
251
  )
252
  worldview_display = gr.Markdown(visible=False)
253
  start_btn = gr.Button("여정 시작하기")
254
- continue_btn = gr.Button("다음 단계로", visible=False)
255
 
256
  # 기준 설정
257
- with gr.Tab("기준 설정", id="baseline"):
258
- gr.Markdown("""
259
- ### 축원의 문장을 평온한 마음으로 읽어주세요
260
 
261
  먼저, 평온한 마음으로 축원의 문장을 읽어주세요.
262
  이 축원은 당신에게 평화와 안정을 불러일으키며,
263
- 감정을 정확히 이해하기 위한 **기준점**이 될 것입니다.
264
- """)
265
  gr.Markdown("'당신의 건강과 행복이 늘 가득하기를'")
266
  baseline_audio = gr.Audio(
267
  label="축원 문장 녹음하기",
@@ -271,14 +359,10 @@ def create_interface():
271
  baseline_status = gr.Markdown("")
272
 
273
  # 청신
274
- with gr.Tab("청신", id="cleansing"):
275
- gr.Markdown("""
276
- ## 청신 - 소리로 정화하기
277
 
278
- 온천천의 물소리에 귀 기울이며 **30분간 마음을 정화**해보세요.
279
- 장전역까지 이어지는 이 여정을 함께하며,
280
- 차분히 자연의 소리에 마음을 기울여보세요.
281
- """)
282
  play_music_btn = gr.Button("온천천의 소리 듣기")
283
  with gr.Row():
284
  audio = gr.Audio(
@@ -300,45 +384,25 @@ def create_interface():
300
  )
301
 
302
  # 기원
303
- with gr.Tab("기원", id="prayer"):
304
- gr.Markdown("""
305
- ## 기원 - 소원을 전해보세요
306
-
307
- 당신의 소원을 온전히 담아 이곳에 전해주세요.
308
- 당신의 목소리와 감정이 영적 메시지가 되어 전해지며,
309
- 이 기원이 당신에게 평화와 안정을 불러오기를 바랍니다.
310
- """)
311
  with gr.Row():
312
  with gr.Column():
313
  voice_input = gr.Audio(
314
  label="소원을 나누고 싶은 마음을 말해주세요",
315
  sources=["microphone"]
316
  )
 
317
  analyze_btn = gr.Button("소원 분석하기")
318
 
319
  with gr.Column():
320
- transcribed_text = gr.Textbox(
321
- label="인식된 텍스트",
322
- interactive=False
323
- )
324
- voice_emotion = gr.Textbox(
325
- label="음성 감정 분석",
326
- interactive=False
327
- )
328
- text_emotion = gr.Textbox(
329
- label="텍스트 감정 분석",
330
- interactive=False
331
- )
332
 
333
  # 송신
334
- with gr.Tab("송신", id="sending"):
335
- gr.Markdown("""
336
- ## 송신 - 마음의 그림을 남기고, 보내기
337
-
338
- 당신의 마음을 시각화하여 그려봅니다.
339
- 자연과 영적 교감을 통해 얻은 평온과 치유의 흔적을,
340
- 하나의 그림으로 담아보세요.
341
- """)
342
  final_prompt = gr.Textbox(
343
  label="생성된 프롬프트",
344
  interactive=False,
@@ -348,54 +412,23 @@ def create_interface():
348
  generate_btn = gr.Button("마음의 그림 그리기")
349
  save_image_btn = gr.Button("이미지 저장하기")
350
  result_image = gr.Image(label="생성된 이미지")
351
- image_timer = gr.Markdown(
352
- "이미지는 30초 후 자동으로 사라집니다...",
353
- visible=False
354
- )
355
-
356
- # 최종 감상
357
- gr.Markdown("""
358
- ## 마지막 감상을 남겨주세요
359
 
360
- 이제 당신의 여정이 마무리되었습니다.
361
- 마지막으로 느낀 감상을 한 줄로 남겨주세요.
362
- """)
363
  final_reflection = gr.Textbox(
364
  label="마지막 감상",
365
  placeholder="한 줄로 남겨주세요..."
366
  )
367
  save_final_btn = gr.Button("감상 남기기")
368
 
369
- # 이벤트 핸들러
370
- def start_journey(name):
371
- if not name.strip():
372
- return "이름을 입력해주세요", gr.update(), gr.update()
373
-
374
- state = {"user_name": name}
375
- return (
376
- WORLDVIEW_MESSAGE,
377
- gr.update(visible=True),
378
- gr.update(visible=True)
379
- )
380
-
381
- def save_wish_to_db(text, state):
382
- if text and state.get("user_name"):
383
- db.save_wish(state["user_name"], text)
384
- return "소원이 안전하게 저장되었습니다."
385
- return "저장에 실패했습니다."
386
-
387
- def handle_image_generation(prompt):
388
- image_content, image_path = generate_image_from_prompt(prompt)
389
- if image_content:
390
- gr.update(visible=True) # 타이머 표시
391
- return image_content
392
- return None
393
-
394
  # 이벤트 연결
395
  start_btn.click(
396
- fn=start_journey,
 
 
 
 
397
  inputs=[name_input],
398
- outputs=[worldview_display, continue_btn, tabs]
399
  )
400
 
401
  set_baseline_btn.click(
@@ -410,25 +443,30 @@ def create_interface():
410
  outputs=[state, reflections_display]
411
  )
412
 
 
 
 
 
 
413
  analyze_btn.click(
414
  fn=analyze_voice,
415
  inputs=[voice_input, state],
416
- outputs=[transcribed_text, voice_emotion, text_emotion, state]
417
  )
418
 
419
  generate_btn.click(
420
- fn=handle_image_generation,
421
  inputs=[final_prompt],
422
  outputs=[result_image]
423
  )
424
 
425
  save_final_btn.click(
426
- fn=save_wish_to_db,
427
  inputs=[final_reflection, state],
428
- outputs=[gr.Markdown("")] # 저장 상태 메시지
429
  )
430
 
431
- # 이미지 자동 사라짐 설정
432
  result_image.change(
433
  fn=lambda: gr.update(value=None),
434
  inputs=[],
@@ -441,4 +479,3 @@ def create_interface():
441
  if __name__ == "__main__":
442
  demo = create_interface()
443
  demo.launch(debug=True)
444
-
 
1
+
2
+ # Part 1/4 - Imports and Initial Setup
3
  import gradio as gr
4
  import numpy as np
5
  import librosa
 
10
  import json
11
  import time
12
 
13
+ # 데이터 저장을 위한 DB 클래스
14
  class SimpleDB:
15
  def __init__(self, file_path="wishes.json"):
16
  self.file_path = file_path
 
29
  def save_wish(self, name, wish, timestamp=None):
30
  if timestamp is None:
31
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
32
  wish_data = {
33
  "name": name,
34
  "wish": wish,
35
  "timestamp": timestamp
36
  }
 
37
  self.wishes.append(wish_data)
 
38
  try:
39
  with open(self.file_path, 'w', encoding='utf-8') as f:
40
  json.dump(self.wishes, f, ensure_ascii=False, indent=2)
 
43
  print(f"Error saving wish: {e}")
44
  return False
45
 
46
+ # 환경변수 및 API 설정
47
  HF_API_TOKEN = os.getenv("roots")
48
  if not HF_API_TOKEN:
49
  raise ValueError("roots token not found in environment variables")
50
 
 
51
  API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
52
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
53
 
 
62
  )
63
 
64
  # 상수 정의
65
+ IMAGE_DISPLAY_TIME = 30
66
  WELCOME_MESSAGE = """
67
  # 디지털 굿판에 오신 것을 환영합니다
68
 
 
83
  """
84
 
85
 
86
+ # Part 2/4 - Analysis Functions
87
+ def calculate_baseline_features(audio_path):
88
+ """기준점 음성 특성 분석"""
89
+ try:
90
+ y, sr = librosa.load(audio_path, sr=16000)
91
+ features = {
92
+ "energy": float(np.mean(librosa.feature.rms(y=y))),
93
+ "tempo": float(librosa.beat.tempo(y)[0]),
94
+ "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
95
+ "volume": float(np.mean(np.abs(y))),
96
+ "mfcc": librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13).mean(axis=1).tolist()
97
+ }
98
+ return features
99
+ except Exception as e:
100
+ print(f"Error calculating baseline: {str(e)}")
101
+ return None
102
+
103
+ def map_acoustic_to_emotion(features, baseline_features=None):
104
+ """음향학적 특성을 감정으로 매핑"""
105
+ energy_norm = min(features["energy"] * 100, 100)
106
+ tempo_norm = min(features["tempo"] / 200, 1)
107
+ pitch_norm = min(features["pitch"] * 2, 1)
108
+
109
+ if baseline_features:
110
+ energy_norm = (features["energy"] / baseline_features["energy"]) * 50
111
+ tempo_norm = (features["tempo"] / baseline_features["tempo"])
112
+ pitch_norm = (features["pitch"] / baseline_features["pitch"])
113
+
114
+ emotions = {
115
+ "primary": "",
116
+ "intensity": energy_norm,
117
+ "confidence": 0.0,
118
+ "secondary": "",
119
+ "characteristics": []
120
+ }
121
+
122
+ if energy_norm > 70:
123
+ if tempo_norm > 0.6:
124
+ emotions["primary"] = "기쁨/열정"
125
+ emotions["characteristics"].append("빠르고 활기찬 말하기 패턴")
126
+ else:
127
+ emotions["primary"] = "분노/강조"
128
+ emotions["characteristics"].append("강한 음성 강도")
129
+ emotions["confidence"] = energy_norm / 100
130
+ elif pitch_norm > 0.6:
131
+ if energy_norm > 50:
132
+ emotions["primary"] = "놀람/흥분"
133
+ emotions["characteristics"].append("높은 음고와 강한 강세")
134
+ else:
135
+ emotions["primary"] = "관심/호기심"
136
+ emotions["characteristics"].append("음고 변화가 큼")
137
+ emotions["confidence"] = pitch_norm
138
+ elif energy_norm < 30:
139
+ if tempo_norm < 0.4:
140
+ emotions["primary"] = "슬픔/우울"
141
+ emotions["characteristics"].append("느리고 약한 음성")
142
+ else:
143
+ emotions["primary"] = "피로/무기력"
144
+ emotions["characteristics"].append("낮은 에너지 레벨")
145
+ emotions["confidence"] = (30 - energy_norm) / 30
146
+ else:
147
+ if tempo_norm > 0.5:
148
+ emotions["primary"] = "평온/안정"
149
+ emotions["characteristics"].append("균형잡힌 말하기 패턴")
150
+ else:
151
+ emotions["primary"] = "차분/진지"
152
+ emotions["characteristics"].append("안정적인 음성 특성")
153
+ emotions["confidence"] = 0.5
154
+
155
+ emotions["details"] = {
156
+ "energy_level": f"{energy_norm:.1f}%",
157
+ "speech_rate": f"{'빠름' if tempo_norm > 0.6 else '보통' if tempo_norm > 0.4 else '느림'}",
158
+ "pitch_variation": f"{'높음' if pitch_norm > 0.6 else '보통' if pitch_norm > 0.3 else '낮음'}",
159
+ "voice_volume": f"{'큼' if features['volume'] > 0.7 else '보통' if features['volume'] > 0.3 else '작음'}"
160
+ }
161
+
162
+ return emotions
163
+
164
+ def analyze_voice(audio_path, state):
165
+ """통합 음성 분석"""
166
+ if audio_path is None:
167
+ return state, "음성을 먼저 녹음해주세요.", "", "", ""
168
+
169
+ try:
170
+ y, sr = librosa.load(audio_path, sr=16000)
171
+
172
+ # 음향학적 특성 분석
173
+ acoustic_features = {
174
+ "energy": float(np.mean(librosa.feature.rms(y=y))),
175
+ "tempo": float(librosa.beat.tempo(y)[0]),
176
+ "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
177
+ "volume": float(np.mean(np.abs(y)))
178
+ }
179
+
180
+ # 음성 감정 분석
181
+ voice_emotion = map_acoustic_to_emotion(acoustic_features, state.get("baseline_features"))
182
+
183
+ # 음성 인식
184
+ transcription = speech_recognizer(y)
185
+ text = transcription["text"]
186
+
187
+ # 텍스트 감정 분석
188
+ text_sentiment = text_analyzer(text)[0]
189
+
190
+ # 결과 포맷팅
191
+ voice_result = (
192
+ f"음성 감정: {voice_emotion['primary']} "
193
+ f"(강도: {voice_emotion['intensity']:.1f}%, 신뢰도: {voice_emotion['confidence']:.2f})\n"
194
+ f"특징: {', '.join(voice_emotion['characteristics'])}\n"
195
+ f"상세 분석:\n"
196
+ f"- 에너지 레벨: {voice_emotion['details']['energy_level']}\n"
197
+ f"- 말하기 속도: {voice_emotion['details']['speech_rate']}\n"
198
+ f"- 음높이 변화: {voice_emotion['details']['pitch_variation']}\n"
199
+ f"- 음성 크기: {voice_emotion['details']['voice_volume']}"
200
+ )
201
+
202
+ text_result = f"텍스트 감정 분석 (1-5): {text_sentiment['score']}"
203
+
204
+ # 프롬프트 생성
205
+ prompt = generate_detailed_prompt(text, voice_emotion, text_sentiment)
206
+
207
+ return state, text, voice_result, text_result, prompt
208
+ except Exception as e:
209
+ return state, f"오류 발생: {str(e)}", "", "", ""
210
+
211
+
212
+ # Part 3/4 - Generation and Processing Functions
213
+ def generate_detailed_prompt(text, emotions, text_sentiment):
214
+ """감정 기반 상세 프롬프트 생성"""
215
+ emotion_colors = {
216
+ "기쁨/열정": "밝은 노랑과 따뜻한 주황색",
217
+ "분노/강조": "강렬한 빨강과 짙은 검정",
218
+ "놀람/흥분": "선명한 파랑과 밝은 보라",
219
+ "관심/호기심": "연한 하늘색과 민트색",
220
+ "슬픔/우울": "어두운 파랑과 회색",
221
+ "피로/무기력": "탁한 갈색과 짙은 회색",
222
+ "평온/안정": "부드러운 초록과 베이지",
223
+ "차분/진지": "차분한 남색과 깊은 보라"
224
+ }
225
+
226
+ if emotions["intensity"] > 70:
227
+ visual_style = "역동적인 붓질과 강한 대비"
228
+ elif emotions["intensity"] > 40:
229
+ visual_style = "균형잡힌 구도와 중간 톤의 조화"
230
+ else:
231
+ visual_style = "부드러운 그라데이션과 차분한 톤"
232
+
233
+ prompt = f"한국 전통 민화 스타일의 추상화, {emotion_colors.get(emotions['primary'], '자연스러운 색상')} 기반. "
234
+ prompt += f"{visual_style}로 표현된 {emotions['primary']}의 감정. "
235
+ prompt += f"음성의 특징({', '.join(emotions['characteristics'])})을 화면의 동적 요소로 표현. "
236
+ prompt += f"발화 내용 '{text}'에서 느껴지는 감정(강도: {text_sentiment['score']}/5)을 은유적 이미지로 담아내기."
237
+
238
+ return prompt
239
 
240
  def generate_image_from_prompt(prompt):
241
  """이미지 생성 함수"""
 
260
 
261
  if response.status_code == 200:
262
  print("Image generated successfully")
263
+ # 이미지 저장
264
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
265
  image_path = f"generated_images/{timestamp}.png"
266
  os.makedirs("generated_images", exist_ok=True)
 
275
  print(f"Error generating image: {str(e)}")
276
  return None, None
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  def update_final_prompt(state):
279
  """청신의 감상들을 종합하여 최종 프롬프트 업데이트"""
280
  combined_prompt = "한국 전통 민화 스타일의 추상화, 온천천에서의 감상과 소원을 담아내기:\n\n"
 
313
  print(f"Error in save_reflection: {str(e)}")
314
  return state, []
315
 
316
+
317
+ # Part 4/4 - Interface and Main
318
  def create_interface():
 
319
  db = SimpleDB()
320
 
321
  with gr.Blocks(theme=gr.themes.Soft()) as app:
 
332
  header = gr.Markdown("# 디지털 굿판")
333
  user_display = gr.Markdown("")
334
 
 
335
  with gr.Tabs() as tabs:
336
  # 입장
337
+ with gr.Tab("입장"):
338
  gr.Markdown(WELCOME_MESSAGE)
339
  name_input = gr.Textbox(
340
  label="이름을 알려주세요",
 
342
  )
343
  worldview_display = gr.Markdown(visible=False)
344
  start_btn = gr.Button("여정 시작하기")
 
345
 
346
  # 기준 설정
347
+ with gr.Tab("기준 설정"):
348
+ gr.Markdown("""### 축원의 문장을 평온한 마음으로 읽어주세요
 
349
 
350
  먼저, 평온한 마음으로 축원의 문장을 읽어주세요.
351
  이 축원은 당신에게 평화와 안정을 불러일으키며,
352
+ 감정을 정확히 이해하기 위한 **기준점**이 될 것입니다.""")
 
353
  gr.Markdown("'당신의 건강과 행복이 늘 가득하기를'")
354
  baseline_audio = gr.Audio(
355
  label="축원 문장 녹음하기",
 
359
  baseline_status = gr.Markdown("")
360
 
361
  # 청신
362
+ with gr.Tab("청신"):
363
+ gr.Markdown("""## 청신 - 소리로 정화하기
 
364
 
365
+ 온천천의 물소리에 귀 기울이며 **30분간 마음을 정화**해보세요.""")
 
 
 
366
  play_music_btn = gr.Button("온천천의 소리 듣기")
367
  with gr.Row():
368
  audio = gr.Audio(
 
384
  )
385
 
386
  # 기원
387
+ with gr.Tab("기원"):
388
+ gr.Markdown("## 기원 - 소원을 전해보세요")
 
 
 
 
 
 
389
  with gr.Row():
390
  with gr.Column():
391
  voice_input = gr.Audio(
392
  label="소원을 나누고 싶은 마음을 말해주세요",
393
  sources=["microphone"]
394
  )
395
+ clear_btn = gr.Button("녹음 지우기")
396
  analyze_btn = gr.Button("소원 분석하기")
397
 
398
  with gr.Column():
399
+ transcribed_text = gr.Textbox(label="인식된 텍스트")
400
+ voice_emotion = gr.Textbox(label="음성 감정 분석")
401
+ text_emotion = gr.Textbox(label="텍스트 감정 분석")
 
 
 
 
 
 
 
 
 
402
 
403
  # 송신
404
+ with gr.Tab("송신"):
405
+ gr.Markdown("## 송신 - 마음의 그림을 남기고, 보내기")
 
 
 
 
 
 
406
  final_prompt = gr.Textbox(
407
  label="생성된 프롬프트",
408
  interactive=False,
 
412
  generate_btn = gr.Button("마음의 그림 그리기")
413
  save_image_btn = gr.Button("이미지 저장하기")
414
  result_image = gr.Image(label="생성된 이미지")
 
 
 
 
 
 
 
 
415
 
416
+ gr.Markdown("## 마지막 감상을 남겨주세요")
 
 
417
  final_reflection = gr.Textbox(
418
  label="마지막 감상",
419
  placeholder="한 줄로 남겨주세요..."
420
  )
421
  save_final_btn = gr.Button("감상 남기기")
422
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  # 이벤트 연결
424
  start_btn.click(
425
+ fn=lambda name: (
426
+ WORLDVIEW_MESSAGE if name.strip() else "이름을 입력해주세요",
427
+ gr.update(visible=True) if name.strip() else gr.update(),
428
+ {"user_name": name} if name.strip() else state
429
+ ),
430
  inputs=[name_input],
431
+ outputs=[worldview_display, tabs, state]
432
  )
433
 
434
  set_baseline_btn.click(
 
443
  outputs=[state, reflections_display]
444
  )
445
 
446
+ clear_btn.click(
447
+ fn=lambda: None,
448
+ outputs=[voice_input]
449
+ )
450
+
451
  analyze_btn.click(
452
  fn=analyze_voice,
453
  inputs=[voice_input, state],
454
+ outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt]
455
  )
456
 
457
  generate_btn.click(
458
+ fn=lambda p: generate_image_from_prompt(p)[0],
459
  inputs=[final_prompt],
460
  outputs=[result_image]
461
  )
462
 
463
  save_final_btn.click(
464
+ fn=lambda t, s: (db.save_wish(s["user_name"], t), "감상이 저장되었습니다."),
465
  inputs=[final_reflection, state],
466
+ outputs=[gr.Markdown("")]
467
  )
468
 
469
+ # 이미지 자동 사라짐
470
  result_image.change(
471
  fn=lambda: gr.update(value=None),
472
  inputs=[],
 
479
  if __name__ == "__main__":
480
  demo = create_interface()
481
  demo.launch(debug=True)