haepada commited on
Commit
6000375
·
verified ·
1 Parent(s): 0f6de85

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -235
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # Part 1/3 - Setup and Utilities
2
  import gradio as gr
3
  import numpy as np
4
  import librosa
@@ -6,13 +5,51 @@ from transformers import pipeline
6
  from datetime import datetime
7
  import os
8
  import requests
 
 
9
 
10
- # 환경변수에서 토큰 가져오기
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  HF_API_TOKEN = os.getenv("roots")
12
  if not HF_API_TOKEN:
13
  raise ValueError("roots token not found in environment variables")
14
 
15
- # Inference API 설정
16
  API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
17
  headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
18
 
@@ -26,86 +63,30 @@ text_analyzer = pipeline(
26
  model="nlptown/bert-base-multilingual-uncased-sentiment"
27
  )
28
 
29
- def calculate_baseline_features(audio_path):
30
- """기준점 음성 특성 분석"""
31
- try:
32
- y, sr = librosa.load(audio_path, sr=16000)
33
- features = {
34
- "energy": float(np.mean(librosa.feature.rms(y=y))),
35
- "tempo": float(librosa.beat.tempo(y)[0]),
36
- "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
37
- "volume": float(np.mean(np.abs(y))),
38
- "mfcc": librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13).mean(axis=1).tolist()
39
- }
40
- return features
41
- except Exception as e:
42
- print(f"Error calculating baseline: {str(e)}")
43
- return None
44
-
45
- def map_acoustic_to_emotion(features, baseline_features=None):
46
- """음향학적 특성을 감정으로 매핑 (기준점 대비)"""
47
- # 음성 특성 정규화
48
- energy_norm = min(features["energy"] * 100, 100)
49
- tempo_norm = min(features["tempo"] / 200, 1)
50
- pitch_norm = min(features["pitch"] * 2, 1)
51
-
52
- if baseline_features:
53
- energy_norm = (features["energy"] / baseline_features["energy"]) * 50
54
- tempo_norm = (features["tempo"] / baseline_features["tempo"])
55
- pitch_norm = (features["pitch"] / baseline_features["pitch"])
56
-
57
- emotions = {
58
- "primary": "",
59
- "intensity": energy_norm,
60
- "confidence": 0.0,
61
- "secondary": "",
62
- "characteristics": []
63
- }
64
 
65
- # 감정 매핑 로직
66
- if energy_norm > 70:
67
- if tempo_norm > 0.6:
68
- emotions["primary"] = "기쁨/열정"
69
- emotions["characteristics"].append("빠르고 활기찬 말하기 패턴")
70
- else:
71
- emotions["primary"] = "분노/강조"
72
- emotions["characteristics"].append("강한 음성 강도")
73
- emotions["confidence"] = energy_norm / 100
74
- elif pitch_norm > 0.6:
75
- if energy_norm > 50:
76
- emotions["primary"] = "놀람/흥분"
77
- emotions["characteristics"].append("높은 음고와 강한 강세")
78
- else:
79
- emotions["primary"] = "관심/호기심"
80
- emotions["characteristics"].append("음고 변화가 큼")
81
- emotions["confidence"] = pitch_norm
82
- elif energy_norm < 30:
83
- if tempo_norm < 0.4:
84
- emotions["primary"] = "슬픔/우울"
85
- emotions["characteristics"].append("느리고 약한 음성")
86
- else:
87
- emotions["primary"] = "피로/무기력"
88
- emotions["characteristics"].append("낮은 에너지 레벨")
89
- emotions["confidence"] = (30 - energy_norm) / 30
90
- else:
91
- if tempo_norm > 0.5:
92
- emotions["primary"] = "평온/안정"
93
- emotions["characteristics"].append("균형잡힌 말하기 패턴")
94
- else:
95
- emotions["primary"] = "차분/진지"
96
- emotions["characteristics"].append("안정적인 음성 특성")
97
- emotions["confidence"] = 0.5
98
-
99
- emotions["details"] = {
100
- "energy_level": f"{energy_norm:.1f}%",
101
- "speech_rate": f"{'빠름' if tempo_norm > 0.6 else '보통' if tempo_norm > 0.4 else '느림'}",
102
- "pitch_variation": f"{'높음' if pitch_norm > 0.6 else '보통' if pitch_norm > 0.3 else '낮음'}",
103
- "voice_volume": f"{'큼' if features['volume'] > 0.7 else '보통' if features['volume'] > 0.3 else '작음'}"
104
- }
105
 
106
- return emotions
107
 
108
- # Part 2/3 - Core Functions
 
109
  def generate_image_from_prompt(prompt):
110
  """이미지 생성 함수"""
111
  print(f"Generating image with prompt: {prompt}")
@@ -129,14 +110,51 @@ def generate_image_from_prompt(prompt):
129
 
130
  if response.status_code == 200:
131
  print("Image generated successfully")
132
- return response.content
 
 
 
 
 
 
133
  else:
134
  print(f"Error: {response.status_code}")
135
  print(f"Response: {response.text}")
136
- return None
137
  except Exception as e:
138
  print(f"Error generating image: {str(e)}")
139
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  def generate_detailed_prompt(text, emotions, text_sentiment):
142
  """감정 기반 상세 프롬프트 생성"""
@@ -167,86 +185,80 @@ def generate_detailed_prompt(text, emotions, text_sentiment):
167
 
168
  def update_final_prompt(state):
169
  """청신의 감상들을 종합하여 최종 프롬프트 업데이트"""
170
- if not state["reflections"]:
171
- return ""
 
 
 
 
172
 
173
- combined_prompt = "한국 전통 민화 스타일의 추상화, 온천천에서의 다음 감상들을 담아내기:\n"
174
- for time, text, sentiment in state["reflections"]:
175
- combined_prompt += f"- {time}: {text} ({sentiment})\n"
176
 
177
  return combined_prompt
178
 
179
- def analyze_voice_with_retry(audio_path, state, max_retries=3):
180
- """음성 분석 함수 (재시도 로직 포함)"""
181
- for attempt in range(max_retries):
182
- try:
183
- y, sr = librosa.load(audio_path, sr=16000)
184
-
185
- acoustic_features = {
186
- "energy": float(np.mean(librosa.feature.rms(y=y))),
187
- "tempo": float(librosa.beat.tempo(y)[0]),
188
- "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))),
189
- "volume": float(np.mean(np.abs(y)))
190
- }
191
-
192
- transcription = speech_recognizer(y)
193
- text = transcription["text"]
194
-
195
- emotions = map_acoustic_to_emotion(acoustic_features, state.get("baseline_features"))
196
- text_sentiment = text_analyzer(text)[0]
197
-
198
- return {
199
- "text": text,
200
- "emotions": emotions,
201
- "sentiment": text_sentiment
202
- }
203
- except Exception as e:
204
- if attempt == max_retries - 1:
205
- raise e
206
- print(f"Attempt {attempt + 1} failed, retrying...")
207
- continue
208
 
209
  # Part 3/3 - Interface and Main
210
  def create_interface():
 
 
 
211
  with gr.Blocks(theme=gr.themes.Soft()) as app:
212
  state = gr.State({
213
  "user_name": "",
214
  "baseline_features": None,
215
  "reflections": [],
216
- "voice_analysis": None,
217
- "final_prompt": ""
 
218
  })
219
 
220
- # 헤더 및 네비게이션
221
  header = gr.Markdown("# 디지털 굿판")
222
  user_display = gr.Markdown("")
223
 
 
224
  with gr.Tabs() as tabs:
225
  # 입장
226
- with gr.Tab("입장"):
227
- gr.Markdown("### 디지털 굿판에 오신 것을 환영합니다")
228
- name_input = gr.Textbox(label="이름을 알려주세요", placeholder="이름을 입력해주세요")
 
 
 
 
229
  start_btn = gr.Button("여정 시작하기")
 
230
 
231
  # 기준 설정
232
- with gr.Tab("기준 설정", visible=False) as baseline_tab:
233
- gr.Markdown("""### 축원의 문장을 평온한 마음으로 읽어주세요
234
- '당신의 건강과 행복이 가득하기를'""")
 
 
 
 
 
 
235
  baseline_audio = gr.Audio(
236
  label="축원 문장 녹음하기",
237
- sources=["microphone"],
238
- type="filepath"
239
  )
240
  set_baseline_btn = gr.Button("기준점 설정 완료")
241
  baseline_status = gr.Markdown("")
242
 
243
  # 청신
244
- with gr.Tab("청신", visible=False) as cleansing_tab:
245
- gr.Markdown("""## 청신 - 소리로 정화하기
 
246
 
247
- 앱은 어디서나 실행 가능하지만, 온천천의 사운드스케이프를 기반으로 제작되었습니다.
248
- 온천장역에서 장전역까지의 구간을 걸으며 경험하면,
249
- 보다 자연과 하나 온천천의 신화를 느낄 수 있습니다.
250
  """)
251
  play_music_btn = gr.Button("온천천의 소리 듣기")
252
  with gr.Row():
@@ -259,7 +271,7 @@ def create_interface():
259
  )
260
  with gr.Column():
261
  reflection_input = gr.Textbox(
262
- label="현재 순간의 감상을 적어주세요",
263
  lines=3
264
  )
265
  save_btn = gr.Button("감상 저장하기")
@@ -267,132 +279,147 @@ def create_interface():
267
  headers=["시간", "감상", "감정 분석"],
268
  label="기록된 감상들"
269
  )
270
- continue_to_prayer_btn = gr.Button("기원으로 이동하기")
271
 
272
  # 기원
273
- with gr.Tab("기원", visible=False) as prayer_tab:
 
 
 
 
 
 
 
274
  with gr.Row():
275
  with gr.Column():
276
  voice_input = gr.Audio(
277
- label="나누고 싶은 이야기를 들려주세요",
278
- sources=["microphone"],
279
- type="filepath"
280
  )
281
- clear_btn = gr.Button("녹음 지우기")
282
- analyze_btn = gr.Button("분석하기")
283
 
284
  with gr.Column():
285
- transcribed_text = gr.Textbox(label="인식된 텍스트")
286
- voice_emotion = gr.Textbox(label="음성 감정 분석")
287
- text_emotion = gr.Textbox(label="텍스트 감정 분석")
288
- final_prompt = gr.Textbox(label="생성된 프롬프트", lines=3)
 
 
 
 
 
 
 
 
289
 
290
  # 송신
291
- with gr.Tab("송신", visible=False) as sending_tab:
292
- gr.Markdown("## 송신 - 시각화 결과")
293
- combined_prompt = gr.Textbox(
294
- label="종합 프롬프트",
 
 
 
 
 
 
295
  interactive=False,
296
  lines=3
297
  )
298
- generate_btn = gr.Button("이미지 생성하기")
299
  with gr.Row():
300
- result_image = gr.Image(label="생성된 이미지", type="pil")
301
- with gr.Column():
302
- share_btn = gr.Button("이미지 공유하기")
303
- download_btn = gr.Button("이미지 저장하기")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
 
305
  def start_journey(name):
306
  if not name.strip():
307
- return "이름을 입력해주세요", gr.update()
308
 
309
- welcome_text = f"""
310
- # 환영합니다, {name}님의 디지털 굿판
311
-
312
- ## 굿판의 세계관 🌌
313
- 디지털 굿판은 현대 도시 속에서 잊혀진 전통 굿의 정수를 담아낸 **디지털 의례의 공간**입니다.
314
- 이곳에서는 사람들의 목소리와 감정을 통해 **영적 교감**을 나누고, **자연과 도시의 에너지**를 연결하며,
315
- 평온함과 치유를 경험하게 됩니다.
316
-
317
- ## 위치 안내 📍
318
- 앱은 어디서나 실행 가능하지만, 온천천의 사운드스케이프를 녹음하고 이를 기반으로 제작되어,
319
- 온천천 온천장역에서 장전역까지의 구간을 걸으며 경험하면
320
- 보다 자연과 하나 된 온천천의 신화를 느낄 수 있습니다.
321
-
322
- ## 여정을 시작하며 🚀
323
- 먼저, 평온한 마음으로 축원의 문장을 읽어주세요.
324
- 이는 당신의 감정을 더 정확하게 이해하기 위한 기준점이 될 것입니다.
325
- """
326
- return welcome_text, gr.update(selected="기준 설정")
 
327
 
328
- def play_music():
329
- audio_path = os.path.abspath(os.path.join("assets", "main_music.mp3"))
330
- return gr.update(value=audio_path, autoplay=True)
 
 
 
331
 
 
 
 
 
 
332
 
333
- def save_reflection(text, state):
334
- if not text.strip():
335
- return state, state["reflections"]
336
-
337
- try:
338
- current_time = datetime.now().strftime("%H:%M:%S")
339
- sentiment = text_analyzer(text)[0]
340
- new_reflection = [current_time, text, f"{sentiment['label']} ({sentiment['score']:.2f})"]
341
-
342
- state = state.copy()
343
- if "reflections" not in state:
344
- state["reflections"] = []
345
-
346
- state["reflections"].append(new_reflection)
347
- return state, state["reflections"]
348
- except Exception as e:
349
- print(f"Error in save_reflection: {str(e)}")
350
- return state, []
351
-
352
- def analyze_voice(audio_path, state):
353
- if audio_path is None:
354
- return state, "음성을 먼저 녹음해주세요.", "", "", ""
355
-
356
- try:
357
- result = analyze_voice_with_retry(audio_path, state)
358
-
359
- voice_result = (
360
- f"음성 감정: {result['emotions']['primary']} "
361
- f"(강도: {result['emotions']['intensity']:.1f}%, "
362
- f"신뢰도: {result['emotions']['confidence']:.2f})\n"
363
- f"특징: {', '.join(result['emotions']['characteristics'])}"
364
- )
365
-
366
- text_result = f"텍스트 감정: {result['sentiment']['label']} "
367
- f"(강도: {result['sentiment']['score']}/5)"
368
-
369
- prompt = generate_detailed_prompt(
370
- result['text'],
371
- result['emotions'],
372
- result['sentiment']
373
- )
374
-
375
- return state, result['text'], voice_result, text_result, prompt
376
- except Exception as e:
377
- return state, f"오류 발생: {str(e)}", "", "", ""
378
 
379
- def update_sending_tab(state):
380
- """청신의 감상과 기원의 음성을 종합하여 송신 탭 업데이트"""
381
- combined = "청신과 기원의 여정을 담은 이미지:\n\n"
382
- combined += update_final_prompt(state)
383
- return combined
384
 
385
- # 이벤트 연결
386
- start_btn.click(fn=start_journey, inputs=[name_input], outputs=[user_display, tabs])
387
- play_music_btn.click(fn=play_music, outputs=[audio])
388
- save_btn.click(fn=save_reflection, inputs=[reflection_input, state], outputs=[state, reflections_display])
389
- analyze_btn.click(fn=analyze_voice, inputs=[voice_input, state], outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt])
390
- continue_to_prayer_btn.click(fn=lambda: gr.update(selected="기원"), outputs=[tabs])
391
- tabs.change(fn=update_sending_tab, inputs=[state], outputs=[combined_prompt])
392
- generate_btn.click(fn=generate_image_from_prompt, inputs=[combined_prompt], outputs=[result_image])
 
 
 
 
 
 
 
 
 
 
 
393
 
394
  return app
395
 
396
  if __name__ == "__main__":
397
  demo = create_interface()
398
- demo.launch(debug=True)
 
 
 
1
  import gradio as gr
2
  import numpy as np
3
  import librosa
 
5
  from datetime import datetime
6
  import os
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
15
+ self.wishes = self._load_wishes()
16
+
17
+ def _load_wishes(self):
18
+ try:
19
+ if os.path.exists(self.file_path):
20
+ with open(self.file_path, 'r', encoding='utf-8') as f:
21
+ return json.load(f)
22
+ return []
23
+ except Exception as e:
24
+ print(f"Error loading wishes: {e}")
25
+ return []
26
+
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)
42
+ return True
43
+ except Exception as e:
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
 
 
63
  model="nlptown/bert-base-multilingual-uncased-sentiment"
64
  )
65
 
66
+ # 상수 정의
67
+ IMAGE_DISPLAY_TIME = 30 # 이미지 표시 시간 (초)
68
+ WELCOME_MESSAGE = """
69
+ # 디지털 굿판에 오신 것을 환영합니다
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ 디지털 굿판은 현대 도시 속에서 잊혀진 전통 굿의 정수를 담아낸 **디지털 의례의 공간**입니다.
72
+ 이곳에서는 사람들의 목소리와 감정을 통해 **영적 교감**을 나누고, **자연과 도시의 에너지가 연결**됩니다.
73
+ 이제, 평온함과 치유의 여정을 시작해보세요.
74
+ """
75
+
76
+ WORLDVIEW_MESSAGE = """
77
+ ## 굿판의 세계관 🌌
78
+
79
+ 온천천의 물줄기는 신성한 금샘에서 시작됩니다. 금샘은 생명과 창조의 원천이며,
80
+ 천상의 생명이 지상에서 숨을 틔우는 자리입니다. 도시의 소음 속에서도 신성한 생명력을 느껴보세요.
81
+ 이곳에서 영적인 교감을 경험하며, 자연과 하나 되는 순간을 맞이해 보시기 바랍니다.
82
+
83
+ 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며,
84
+ 온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다.
85
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
 
87
 
88
+ # Part 2/3 - Core Functions and Image Generation
89
+
90
  def generate_image_from_prompt(prompt):
91
  """이미지 생성 함수"""
92
  print(f"Generating image with 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)
117
+ with open(image_path, "wb") as f:
118
+ f.write(response.content)
119
+ return response.content, image_path
120
  else:
121
  print(f"Error: {response.status_code}")
122
  print(f"Response: {response.text}")
123
+ return None, None
124
  except Exception as e:
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
  """감정 기반 상세 프롬프트 생성"""
 
185
 
186
  def update_final_prompt(state):
187
  """청신의 감상들을 종합하여 최종 프롬프트 업데이트"""
188
+ combined_prompt = "한국 전통 민화 스타일의 추상화, 온천천에서의 감상과 소원을 담아내기:\n\n"
189
+
190
+ if state.get("reflections"):
191
+ combined_prompt += "청신의 감상들:\n"
192
+ for time, text, sentiment in state["reflections"]:
193
+ combined_prompt += f"- {time}: {text} ({sentiment})\n"
194
 
195
+ if state.get("wish"):
196
+ combined_prompt += f"\n소원:\n{state['wish']}"
 
197
 
198
  return combined_prompt
199
 
200
+ def handle_image_timeout(result_image, delay=IMAGE_DISPLAY_TIME):
201
+ """이미지 자동 사라짐 처리"""
202
+ time.sleep(delay)
203
+ return gr.update(value=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  # Part 3/3 - Interface and Main
206
  def create_interface():
207
+ # DB 초기화
208
+ db = SimpleDB()
209
+
210
  with gr.Blocks(theme=gr.themes.Soft()) as app:
211
  state = gr.State({
212
  "user_name": "",
213
  "baseline_features": None,
214
  "reflections": [],
215
+ "wish": None,
216
+ "final_prompt": "",
217
+ "image_path": None
218
  })
219
 
220
+ # 헤더
221
  header = gr.Markdown("# 디지털 굿판")
222
  user_display = gr.Markdown("")
223
 
224
+ # 탭 구성
225
  with gr.Tabs() as tabs:
226
  # 입장
227
+ with gr.Tab("입장", id="intro"):
228
+ gr.Markdown(WELCOME_MESSAGE)
229
+ name_input = gr.Textbox(
230
+ label="이름을 알려주세요",
231
+ placeholder="이름을 입력해주세요"
232
+ )
233
+ worldview_display = gr.Markdown(visible=False)
234
  start_btn = gr.Button("여정 시작하기")
235
+ continue_btn = gr.Button("다음 단계로", visible=False)
236
 
237
  # 기준 설정
238
+ with gr.Tab("기준 설정", id="baseline"):
239
+ gr.Markdown("""
240
+ ### 축원의 문장을 평온한 마음으로 읽어주세요
241
+
242
+ 먼저, 평온한 마음으로 축원의 문장을 읽어주세요.
243
+ 이 축원은 당신에게 평화와 안정을 불러일으키며,
244
+ 감정을 정확히 이해하기 위한 **기준점**이 될 것입니다.
245
+ """)
246
+ gr.Markdown("'당신의 건강과 행복이 늘 가득하기를'")
247
  baseline_audio = gr.Audio(
248
  label="축원 문장 녹음하기",
249
+ sources=["microphone"]
 
250
  )
251
  set_baseline_btn = gr.Button("기준점 설정 완료")
252
  baseline_status = gr.Markdown("")
253
 
254
  # 청신
255
+ with gr.Tab("청신", id="cleansing"):
256
+ gr.Markdown("""
257
+ ## 청신 - 소리로 정화하기
258
 
259
+ 온천천의 물소리에 기울이며 **30분간 마음을 정화**해보세요.
260
+ 장전역까지 이어지는 여정을 함께하며,
261
+ 차분히 자연의 소리에 마음을 기울여보세요.
262
  """)
263
  play_music_btn = gr.Button("온천천의 소리 듣기")
264
  with gr.Row():
 
271
  )
272
  with gr.Column():
273
  reflection_input = gr.Textbox(
274
+ label="지금 순간의 감상을 자유롭게 적어보세요",
275
  lines=3
276
  )
277
  save_btn = gr.Button("감상 저장하기")
 
279
  headers=["시간", "감상", "감정 분석"],
280
  label="기록된 감상들"
281
  )
 
282
 
283
  # 기원
284
+ with gr.Tab("기원", id="prayer"):
285
+ gr.Markdown("""
286
+ ## 기원 - 소원을 전해보세요
287
+
288
+ 당신의 소원을 온전히 담아 이곳에 전해주세요.
289
+ 당신의 목소리와 감정이 영적 메시지가 되어 전해지며,
290
+ 이 기원이 당신에게 평화와 안정을 불러오기를 바랍니다.
291
+ """)
292
  with gr.Row():
293
  with gr.Column():
294
  voice_input = gr.Audio(
295
+ label="소원을 나누고 싶은 마음을 말해주세요",
296
+ sources=["microphone"]
 
297
  )
298
+ analyze_btn = gr.Button("소원 분석하기")
 
299
 
300
  with gr.Column():
301
+ transcribed_text = gr.Textbox(
302
+ label="인식된 텍스트",
303
+ interactive=False
304
+ )
305
+ voice_emotion = gr.Textbox(
306
+ label="음성 감정 분석",
307
+ interactive=False
308
+ )
309
+ text_emotion = gr.Textbox(
310
+ label="텍스트 감정 분석",
311
+ interactive=False
312
+ )
313
 
314
  # 송신
315
+ with gr.Tab("송신", id="sending"):
316
+ gr.Markdown("""
317
+ ## 송신 - 마음의 그림을 남기고, 보내기
318
+
319
+ 당신의 마음을 시각화하여 그려봅니다.
320
+ 자연과 영적 교감을 통해 얻은 평온과 치유의 흔적을,
321
+ 하나의 그림으로 담아보세요.
322
+ """)
323
+ final_prompt = gr.Textbox(
324
+ label="생성된 프롬프트",
325
  interactive=False,
326
  lines=3
327
  )
 
328
  with gr.Row():
329
+ generate_btn = gr.Button("마음의 그림 그리기")
330
+ save_image_btn = gr.Button("이미지 저장하기")
331
+ result_image = gr.Image(label="생성된 이미지")
332
+ image_timer = gr.Markdown(
333
+ "이미지는 30초 후 자동으로 사라집니다...",
334
+ visible=False
335
+ )
336
+
337
+ # 최종 감상
338
+ gr.Markdown("""
339
+ ## 마지막 감상을 남겨주세요
340
+
341
+ 이제 당신의 여정이 마무리되었습니다.
342
+ 마지막으로 느낀 감상을 한 줄로 남겨주세요.
343
+ """)
344
+ final_reflection = gr.Textbox(
345
+ label="마지막 감상",
346
+ placeholder="한 줄로 남겨주세요..."
347
+ )
348
+ save_final_btn = gr.Button("감상 남기기")
349
 
350
+ # 이벤트 핸들러
351
  def start_journey(name):
352
  if not name.strip():
353
+ return "이름을 입력해주세요", gr.update(), gr.update()
354
 
355
+ state = {"user_name": name}
356
+ return (
357
+ WORLDVIEW_MESSAGE,
358
+ gr.update(visible=True),
359
+ gr.update(visible=True)
360
+ )
361
+
362
+ def save_wish_to_db(text, state):
363
+ if text and state.get("user_name"):
364
+ db.save_wish(state["user_name"], text)
365
+ return "소원이 안전하게 저장되었습니다."
366
+ return "저장에 실패했습니다."
367
+
368
+ def handle_image_generation(prompt):
369
+ image_content, image_path = generate_image_from_prompt(prompt)
370
+ if image_content:
371
+ gr.update(visible=True) # 타이머 표시
372
+ return image_content
373
+ return None
374
 
375
+ # 이벤트 연결
376
+ start_btn.click(
377
+ fn=start_journey,
378
+ inputs=[name_input],
379
+ outputs=[worldview_display, continue_btn, tabs]
380
+ )
381
 
382
+ set_baseline_btn.click(
383
+ fn=lambda x, s: ({"baseline_features": calculate_baseline_features(x)}, "기준점이 설정되었습니다."),
384
+ inputs=[baseline_audio, state],
385
+ outputs=[state, baseline_status]
386
+ )
387
 
388
+ save_btn.click(
389
+ fn=save_reflection,
390
+ inputs=[reflection_input, state],
391
+ outputs=[state, reflections_display]
392
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
+ analyze_btn.click(
395
+ fn=analyze_voice,
396
+ inputs=[voice_input, state],
397
+ outputs=[transcribed_text, voice_emotion, text_emotion, state]
398
+ )
399
 
400
+ generate_btn.click(
401
+ fn=handle_image_generation,
402
+ inputs=[final_prompt],
403
+ outputs=[result_image]
404
+ )
405
+
406
+ save_final_btn.click(
407
+ fn=save_wish_to_db,
408
+ inputs=[final_reflection, state],
409
+ outputs=[gr.Markdown("")] # 저장 상태 메시지
410
+ )
411
+
412
+ # 이미지 자동 사라짐 설정
413
+ result_image.change(
414
+ fn=lambda: gr.update(value=None),
415
+ inputs=[],
416
+ outputs=[result_image],
417
+ _js=f"() => setTimeout(() => {{document.querySelector('#result_image image').src = ''}}, {IMAGE_DISPLAY_TIME*1000})"
418
+ )
419
 
420
  return app
421
 
422
  if __name__ == "__main__":
423
  demo = create_interface()
424
+ demo.launch(debug=True)
425
+