|  | import gradio as gr | 
					
						
						|  | import numpy as np | 
					
						
						|  | import librosa | 
					
						
						|  | from transformers import pipeline | 
					
						
						|  | from datetime import datetime | 
					
						
						|  | import os | 
					
						
						|  | import requests | 
					
						
						|  | import json | 
					
						
						|  | import time | 
					
						
						|  | import threading | 
					
						
						|  | from dotenv import load_dotenv | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | load_dotenv() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | WELCOME_MESSAGE = """ | 
					
						
						|  | # 디지털 굿판에 오신 것을 환영합니다 | 
					
						
						|  |  | 
					
						
						|  | 디지털 굿판은 현대 도시 속에서 잊혀진 전통 굿의 정수를 담아낸 **디지털 의례의 공간**입니다. | 
					
						
						|  | 이곳에서는 사람들의 목소리와 감정을 통해 **영적 교감**을 나누고, **자연과 도시의 에너지가 연결**됩니다. | 
					
						
						|  | 이제, 평온함과 치유의 여정을 시작해보세요. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | WORLDVIEW_MESSAGE = """ | 
					
						
						|  | ## 굿판의 세계관 🌌 | 
					
						
						|  |  | 
					
						
						|  | 온천천의 물줄기는 신성한 금샘에서 시작됩니다. 금샘은 생명과 창조의 원천이며, | 
					
						
						|  | 천상의 생명이 지상에서 숨을 틔우는 자리입니다. 도시의 소음 속에서도 신성한 생명력을 느껴보세요. | 
					
						
						|  | 이곳에서 영적인 교감을 경험하며, 자연과 하나 되는 순간을 맞이해 보시기 바랍니다. | 
					
						
						|  |  | 
					
						
						|  | 이 앱은 온천천의 사운드스케이프를 녹음하여 제작되었으며, | 
					
						
						|  | 온천천 온천장역에서 장전역까지 걸으며 더 깊은 체험이 가능합니다. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | class SimpleDB: | 
					
						
						|  | def __init__(self, file_path="wishes.json"): | 
					
						
						|  | self.file_path = file_path | 
					
						
						|  | self.wishes = self._load_wishes() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if not os.path.exists(self.file_path): | 
					
						
						|  | with open(self.file_path, 'w', encoding='utf-8') as f: | 
					
						
						|  | json.dump([], f, ensure_ascii=False, indent=2) | 
					
						
						|  |  | 
					
						
						|  | def _load_wishes(self): | 
					
						
						|  | try: | 
					
						
						|  | if os.path.exists(self.file_path): | 
					
						
						|  | with open(self.file_path, 'r', encoding='utf-8') as f: | 
					
						
						|  | return json.load(f) | 
					
						
						|  | return [] | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error loading wishes: {e}") | 
					
						
						|  | return [] | 
					
						
						|  |  | 
					
						
						|  | def save_wish(self, name, wish, timestamp=None): | 
					
						
						|  | if timestamp is None: | 
					
						
						|  | timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | 
					
						
						|  | wish_data = { | 
					
						
						|  | "name": name, | 
					
						
						|  | "wish": wish, | 
					
						
						|  | "timestamp": timestamp | 
					
						
						|  | } | 
					
						
						|  | self.wishes.append(wish_data) | 
					
						
						|  | try: | 
					
						
						|  | with open(self.file_path, 'w', encoding='utf-8') as f: | 
					
						
						|  | json.dump(self.wishes, f, ensure_ascii=False, indent=2) | 
					
						
						|  | return True | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error saving wish: {e}") | 
					
						
						|  | return False | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | HF_API_TOKEN = os.getenv("roots", "") | 
					
						
						|  | if not HF_API_TOKEN: | 
					
						
						|  | print("Warning: HuggingFace API token not found. Some features may be limited.") | 
					
						
						|  |  | 
					
						
						|  | API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" | 
					
						
						|  | headers = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {} | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | try: | 
					
						
						|  | speech_recognizer = pipeline( | 
					
						
						|  | "automatic-speech-recognition", | 
					
						
						|  | model="kresnik/wav2vec2-large-xlsr-korean" | 
					
						
						|  | ) | 
					
						
						|  | text_analyzer = pipeline( | 
					
						
						|  | "sentiment-analysis", | 
					
						
						|  | model="nlptown/bert-base-multilingual-uncased-sentiment" | 
					
						
						|  | ) | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error initializing AI models: {e}") | 
					
						
						|  |  | 
					
						
						|  | speech_recognizer = None | 
					
						
						|  | text_analyzer = None | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | os.makedirs("generated_images", exist_ok=True) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def calculate_baseline_features(audio_data): | 
					
						
						|  | """기준점 음성 특성 분석""" | 
					
						
						|  | try: | 
					
						
						|  | if isinstance(audio_data, tuple): | 
					
						
						|  | sr, y = audio_data | 
					
						
						|  | elif isinstance(audio_data, str): | 
					
						
						|  | y, sr = librosa.load(audio_data, sr=16000) | 
					
						
						|  | else: | 
					
						
						|  | print("Unsupported audio format") | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if len(y) == 0: | 
					
						
						|  | print("Empty audio data") | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  | features = { | 
					
						
						|  | "energy": float(np.mean(librosa.feature.rms(y=y))), | 
					
						
						|  | "tempo": float(librosa.beat.tempo(y, sr=sr)[0]), | 
					
						
						|  | "pitch": float(np.mean(librosa.feature.zero_crossing_rate(y))), | 
					
						
						|  | "volume": float(np.mean(np.abs(y))), | 
					
						
						|  | "mfcc": librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13).mean(axis=1).tolist() | 
					
						
						|  | } | 
					
						
						|  | return features | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error calculating baseline: {str(e)}") | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  | def map_acoustic_to_emotion(features, baseline_features=None): | 
					
						
						|  | """음향학적 특성을 감정으로 매핑""" | 
					
						
						|  | if features is None: | 
					
						
						|  | return { | 
					
						
						|  | "primary": "알 수 없음", | 
					
						
						|  | "intensity": 0, | 
					
						
						|  | "confidence": 0.0, | 
					
						
						|  | "secondary": "", | 
					
						
						|  | "characteristics": ["음성 분석 실패"], | 
					
						
						|  | "details": { | 
					
						
						|  | "energy_level": "0%", | 
					
						
						|  | "speech_rate": "알 수 없음", | 
					
						
						|  | "pitch_variation": "알 수 없음", | 
					
						
						|  | "voice_volume": "알 수 없음" | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | energy_norm = min(features["energy"] * 100, 100) | 
					
						
						|  | tempo_norm = min(features["tempo"] / 200, 1) | 
					
						
						|  | pitch_norm = min(features["pitch"] * 2, 1) | 
					
						
						|  |  | 
					
						
						|  | if baseline_features: | 
					
						
						|  | energy_norm = (features["energy"] / baseline_features["energy"]) * 50 | 
					
						
						|  | tempo_norm = (features["tempo"] / baseline_features["tempo"]) | 
					
						
						|  | pitch_norm = (features["pitch"] / baseline_features["pitch"]) | 
					
						
						|  |  | 
					
						
						|  | emotions = { | 
					
						
						|  | "primary": "", | 
					
						
						|  | "intensity": energy_norm, | 
					
						
						|  | "confidence": 0.0, | 
					
						
						|  | "secondary": "", | 
					
						
						|  | "characteristics": [] | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if energy_norm > 70: | 
					
						
						|  | if tempo_norm > 0.6: | 
					
						
						|  | emotions["primary"] = "기쁨/열정" | 
					
						
						|  | emotions["characteristics"].append("빠르고 활기찬 말하기 패턴") | 
					
						
						|  | else: | 
					
						
						|  | emotions["primary"] = "분노/강조" | 
					
						
						|  | emotions["characteristics"].append("강한 음성 강도") | 
					
						
						|  | emotions["confidence"] = energy_norm / 100 | 
					
						
						|  | elif pitch_norm > 0.6: | 
					
						
						|  | if energy_norm > 50: | 
					
						
						|  | emotions["primary"] = "놀람/흥분" | 
					
						
						|  | emotions["characteristics"].append("높은 음고와 강한 강세") | 
					
						
						|  | else: | 
					
						
						|  | emotions["primary"] = "관심/호기심" | 
					
						
						|  | emotions["characteristics"].append("음고 변화가 큼") | 
					
						
						|  | emotions["confidence"] = pitch_norm | 
					
						
						|  | elif energy_norm < 30: | 
					
						
						|  | if tempo_norm < 0.4: | 
					
						
						|  | emotions["primary"] = "슬픔/우울" | 
					
						
						|  | emotions["characteristics"].append("느리고 약한 음성") | 
					
						
						|  | else: | 
					
						
						|  | emotions["primary"] = "피로/무기력" | 
					
						
						|  | emotions["characteristics"].append("낮은 에너지 레벨") | 
					
						
						|  | emotions["confidence"] = (30 - energy_norm) / 30 | 
					
						
						|  | else: | 
					
						
						|  | if tempo_norm > 0.5: | 
					
						
						|  | emotions["primary"] = "평온/안정" | 
					
						
						|  | emotions["characteristics"].append("균형잡힌 말하기 패턴") | 
					
						
						|  | else: | 
					
						
						|  | emotions["primary"] = "차분/진지" | 
					
						
						|  | emotions["characteristics"].append("안정적인 음성 특성") | 
					
						
						|  | emotions["confidence"] = 0.5 | 
					
						
						|  |  | 
					
						
						|  | emotions["details"] = { | 
					
						
						|  | "energy_level": f"{energy_norm:.1f}%", | 
					
						
						|  | "speech_rate": f"{'빠름' if tempo_norm > 0.6 else '보통' if tempo_norm > 0.4 else '느림'}", | 
					
						
						|  | "pitch_variation": f"{'높음' if pitch_norm > 0.6 else '보통' if pitch_norm > 0.3 else '낮음'}", | 
					
						
						|  | "voice_volume": f"{'큼' if features['volume'] > 0.7 else '보통' if features['volume'] > 0.3 else '작음'}" | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | return emotions | 
					
						
						|  |  | 
					
						
						|  | def analyze_voice(audio_data, state): | 
					
						
						|  | """통합 음성 분석""" | 
					
						
						|  | if audio_data is None: | 
					
						
						|  | return state, "음성을 먼저 녹음해주세요.", "", "", "" | 
					
						
						|  |  | 
					
						
						|  | try: | 
					
						
						|  | if isinstance(audio_data, tuple): | 
					
						
						|  | sr, y = audio_data | 
					
						
						|  | elif isinstance(audio_data, str): | 
					
						
						|  | y, sr = librosa.load(audio_data, sr=16000) | 
					
						
						|  | else: | 
					
						
						|  | print("Unsupported audio format") | 
					
						
						|  | return state, "오디오 형식을 지원하지 않습니다.", "", "", "" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | acoustic_features = calculate_baseline_features(audio_data) | 
					
						
						|  | if acoustic_features is None: | 
					
						
						|  | return state, "음성 분석에 실패했습니다.", "", "", "" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | voice_emotion = map_acoustic_to_emotion(acoustic_features, state.get("baseline_features")) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if speech_recognizer: | 
					
						
						|  | transcription = speech_recognizer({"sampling_rate": sr, "raw": y}) | 
					
						
						|  | text = transcription["text"] | 
					
						
						|  | else: | 
					
						
						|  | text = "음성 인식 모델을 불러올 수 없습니다." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if text_analyzer and text: | 
					
						
						|  | text_sentiment = text_analyzer(text)[0] | 
					
						
						|  | text_result = f"텍스트 감정 분석: {text_sentiment['label']} (점수: {text_sentiment['score']:.2f})" | 
					
						
						|  | else: | 
					
						
						|  | text_sentiment = {"label": "unknown", "score": 0.0} | 
					
						
						|  | text_result = "텍스트 감정 분석을 수행할 수 없습니다." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | voice_result = ( | 
					
						
						|  | f"음성 감정: {voice_emotion['primary']} " | 
					
						
						|  | f"(강도: {voice_emotion['intensity']:.1f}%, 신뢰도: {voice_emotion['confidence']:.2f})\n" | 
					
						
						|  | f"특징: {', '.join(voice_emotion['characteristics'])}\n" | 
					
						
						|  | f"상세 분석:\n" | 
					
						
						|  | f"- 에너지 레벨: {voice_emotion['details']['energy_level']}\n" | 
					
						
						|  | f"- 말하기 속도: {voice_emotion['details']['speech_rate']}\n" | 
					
						
						|  | f"- 음높이 변화: {voice_emotion['details']['pitch_variation']}\n" | 
					
						
						|  | f"- 음성 크기: {voice_emotion['details']['voice_volume']}" | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | prompt = generate_detailed_prompt(text, voice_emotion, text_sentiment) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | state = {**state, "final_prompt": prompt} | 
					
						
						|  |  | 
					
						
						|  | return state, text, voice_result, text_result, prompt | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error in analyze_voice: {str(e)}") | 
					
						
						|  | return state, f"오류 발생: {str(e)}", "", "", "" | 
					
						
						|  |  | 
					
						
						|  | def generate_detailed_prompt(text, emotions, text_sentiment): | 
					
						
						|  | """감정 기반 상세 프롬프트 생성""" | 
					
						
						|  | emotion_colors = { | 
					
						
						|  | "기쁨/열정": "밝은 노랑과 따뜻한 주황색", | 
					
						
						|  | "분노/강조": "강렬한 빨강과 짙은 검정", | 
					
						
						|  | "놀람/흥분": "선명한 파랑과 밝은 보라", | 
					
						
						|  | "관심/호기심": "연한 하늘색과 민트색", | 
					
						
						|  | "슬픔/우울": "어두운 파랑과 회색", | 
					
						
						|  | "피로/무기력": "탁한 갈색과 짙은 회색", | 
					
						
						|  | "평온/안정": "부드러운 초록과 베이지", | 
					
						
						|  | "차분/진지": "차분한 남색과 깊은 보라" | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if emotions["intensity"] > 70: | 
					
						
						|  | visual_style = "역동적인 붓질과 강한 대비" | 
					
						
						|  | elif emotions["intensity"] > 40: | 
					
						
						|  | visual_style = "균형잡힌 구도와 중간 톤의 조화" | 
					
						
						|  | else: | 
					
						
						|  | visual_style = "부드러운 그라데이션과 차분한 톤" | 
					
						
						|  |  | 
					
						
						|  | prompt = f"한국 전통 민화 스타일의 추상화, {emotion_colors.get(emotions['primary'], '자연스러운 색상')} 기반. " | 
					
						
						|  | prompt += f"{visual_style}로 표현된 {emotions['primary']}의 감정. " | 
					
						
						|  | prompt += f"음성의 특징({', '.join(emotions['characteristics'])})을 화면의 동적 요소로 표현. " | 
					
						
						|  | prompt += f"발화 내용 '{text}'에서 느껴지는 감정({text_sentiment['label']} - 점수: {text_sentiment['score']:.2f})을 은유적 이미지로 담아내기." | 
					
						
						|  |  | 
					
						
						|  | return prompt | 
					
						
						|  |  | 
					
						
						|  | def generate_image_from_prompt(prompt): | 
					
						
						|  | """이미지 생성 함수""" | 
					
						
						|  | if not prompt: | 
					
						
						|  | print("No prompt provided") | 
					
						
						|  | return None, None | 
					
						
						|  |  | 
					
						
						|  | try: | 
					
						
						|  | response = requests.post( | 
					
						
						|  | API_URL, | 
					
						
						|  | headers=headers, | 
					
						
						|  | json={ | 
					
						
						|  | "inputs": prompt, | 
					
						
						|  | "parameters": { | 
					
						
						|  | "negative_prompt": "ugly, blurry, poor quality, distorted", | 
					
						
						|  | "num_inference_steps": 30, | 
					
						
						|  | "guidance_scale": 7.5 | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | if response.status_code == 200: | 
					
						
						|  | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | 
					
						
						|  | image_path = f"generated_images/{timestamp}.png" | 
					
						
						|  | with open(image_path, "wb") as f: | 
					
						
						|  | f.write(response.content) | 
					
						
						|  | return response.content, image_path | 
					
						
						|  | else: | 
					
						
						|  | print(f"Error: {response.status_code}") | 
					
						
						|  | print(f"Response: {response.text}") | 
					
						
						|  | return None, None | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error generating image: {str(e)}") | 
					
						
						|  | return None, None | 
					
						
						|  |  | 
					
						
						|  | def save_reflection(text, state): | 
					
						
						|  | """감상 저장""" | 
					
						
						|  | if not text.strip(): | 
					
						
						|  | return state, [] | 
					
						
						|  |  | 
					
						
						|  | try: | 
					
						
						|  | current_time = datetime.now().strftime("%H:%M:%S") | 
					
						
						|  | sentiment = text_analyzer(text)[0] if text_analyzer else {"label": "unknown", "score": 0.0} | 
					
						
						|  | new_reflection = [current_time, text, f"{sentiment['label']} ({sentiment['score']:.2f})"] | 
					
						
						|  |  | 
					
						
						|  | reflections = state.get("reflections", []) | 
					
						
						|  | reflections.append(new_reflection) | 
					
						
						|  | state = {**state, "reflections": reflections} | 
					
						
						|  | return state, reflections | 
					
						
						|  | except Exception as e: | 
					
						
						|  | print(f"Error in save_reflection: {str(e)}") | 
					
						
						|  | return state, [] | 
					
						
						|  |  | 
					
						
						|  | def create_interface(): | 
					
						
						|  | db = SimpleDB() | 
					
						
						|  |  | 
					
						
						|  | initial_state = { | 
					
						
						|  | "user_name": "", | 
					
						
						|  | "baseline_features": None, | 
					
						
						|  | "reflections": [], | 
					
						
						|  | "wish": None, | 
					
						
						|  | "final_prompt": "", | 
					
						
						|  | "image_path": None | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | with gr.Blocks(theme=gr.themes.Soft()) as app: | 
					
						
						|  | state = gr.State(value=initial_state) | 
					
						
						|  |  | 
					
						
						|  | gr.Markdown("# 디지털 굿판") | 
					
						
						|  |  | 
					
						
						|  | with gr.Tabs(selected=0) as tabs: | 
					
						
						|  | with gr.TabItem("입장") as tab_entrance: | 
					
						
						|  | gr.Markdown(WELCOME_MESSAGE) | 
					
						
						|  | name_input = gr.Textbox( | 
					
						
						|  | label="이름을 알려주세요", | 
					
						
						|  | placeholder="이름을 입력해주세요", | 
					
						
						|  | interactive=True | 
					
						
						|  | ) | 
					
						
						|  | worldview_display = gr.Markdown(visible=False) | 
					
						
						|  | start_btn = gr.Button("여정 시작하기", variant="primary") | 
					
						
						|  |  | 
					
						
						|  | with gr.TabItem("기준 설정") as tab_baseline: | 
					
						
						|  | gr.Markdown("""### 축원의 문장을 평온한 마음으로 읽어주세요""") | 
					
						
						|  | gr.Markdown("'당신의 건강과 행복이 늘 가득하기를'") | 
					
						
						|  | baseline_audio = gr.Audio( | 
					
						
						|  | label="축원 문장 녹음하기", | 
					
						
						|  | sources="microphone", | 
					
						
						|  | streaming=False, | 
					
						
						|  | type="numpy" | 
					
						
						|  | ) | 
					
						
						|  | set_baseline_btn = gr.Button("기준점 설정 완료", variant="primary") | 
					
						
						|  | baseline_status = gr.Markdown("") | 
					
						
						|  |  | 
					
						
						|  | with gr.TabItem("청신") as tab_listen: | 
					
						
						|  | gr.Markdown("## 청신 - 소리로 정화하기") | 
					
						
						|  | play_music_btn = gr.Button("온천천의 소리 듣기", variant="secondary") | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | audio = gr.Audio( | 
					
						
						|  | value="oncheoncheon_sound.wav", | 
					
						
						|  | type="filepath", | 
					
						
						|  | label="온천천의 소리", | 
					
						
						|  | interactive=False, | 
					
						
						|  | show_download_button=True, | 
					
						
						|  | visible=True | 
					
						
						|  | ) | 
					
						
						|  | with gr.Column(): | 
					
						
						|  | reflection_input = gr.Textbox( | 
					
						
						|  | label="지금 이 순간의 감상을 자유롭게 적어보세요", | 
					
						
						|  | lines=3, | 
					
						
						|  | max_lines=5 | 
					
						
						|  | ) | 
					
						
						|  | save_btn = gr.Button("감상 저장하기", variant="secondary") | 
					
						
						|  | reflections_display = gr.Dataframe( | 
					
						
						|  | headers=["시간", "감상", "감정 분석"], | 
					
						
						|  | label="기록된 감상들", | 
					
						
						|  | value=[[]], | 
					
						
						|  | interactive=False, | 
					
						
						|  | wrap=True | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | with gr.TabItem("기원") as tab_wish: | 
					
						
						|  | gr.Markdown("## 기원 - 소원을 전해보세요") | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | with gr.Column(): | 
					
						
						|  | voice_input = gr.Audio( | 
					
						
						|  | label="소원을 나누고 싶은 마음을 말해주세요", | 
					
						
						|  | sources="microphone", | 
					
						
						|  | streaming=False, | 
					
						
						|  | type="numpy" | 
					
						
						|  | ) | 
					
						
						|  | with gr.Row(): | 
					
						
						|  | clear_btn = gr.Button("녹음 지우기", variant="secondary") | 
					
						
						|  | analyze_btn = gr.Button("소원 분석하기", variant="primary") | 
					
						
						|  |  | 
					
						
						|  | with gr.Column(): | 
					
						
						|  | transcribed_text = gr.Textbox( | 
					
						
						|  | label="인식된 텍스트", | 
					
						
						|  | interactive=False | 
					
						
						|  | ) | 
					
						
						|  | voice_emotion = gr.Textbox( | 
					
						
						|  | label="음성 감정 분석", | 
					
						
						|  | interactive=False | 
					
						
						|  | ) | 
					
						
						|  | text_emotion = gr.Textbox( | 
					
						
						|  | label="텍스트 감정 분석", | 
					
						
						|  | interactive=False | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | with gr.TabItem("송신") as tab_send: | 
					
						
						|  | gr.Markdown("## 송신 - 마음의 그림을 남기고, 보내기") | 
					
						
						|  | final_prompt = gr.Textbox( | 
					
						
						|  | label="생성된 프롬프트", | 
					
						
						|  | interactive=False, | 
					
						
						|  | lines=3 | 
					
						
						|  | ) | 
					
						
						|  | generate_btn = gr.Button("마음의 그림 그리기", variant="primary") | 
					
						
						|  | result_image = gr.Image( | 
					
						
						|  | label="생성된 이미지", | 
					
						
						|  | show_download_button=True | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | gr.Markdown("## 마지막 감상을 남겨주세요") | 
					
						
						|  | final_reflection = gr.Textbox( | 
					
						
						|  | label="마지막 감상", | 
					
						
						|  | placeholder="한 줄로 남겨주세요...", | 
					
						
						|  | max_lines=3 | 
					
						
						|  | ) | 
					
						
						|  | save_final_btn = gr.Button("감상 남기기", variant="primary") | 
					
						
						|  | save_final_status = gr.Markdown("") | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | start_btn.click( | 
					
						
						|  | fn=lambda name, state: ( | 
					
						
						|  | WORLDVIEW_MESSAGE if name.strip() else "이름을 입력해주세요", | 
					
						
						|  | gr.update(visible=True) if name.strip() else gr.update(visible=False), | 
					
						
						|  | {**state, "user_name": name} if name.strip() else state | 
					
						
						|  | ), | 
					
						
						|  | inputs=[name_input, state], | 
					
						
						|  | outputs=[worldview_display, tabs, state] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | set_baseline_btn.click( | 
					
						
						|  | fn=lambda x, s: ( | 
					
						
						|  | {**s, "baseline_features": calculate_baseline_features(x)}, | 
					
						
						|  | "기준점이 설정되었습니다." | 
					
						
						|  | ), | 
					
						
						|  | inputs=[baseline_audio, state], | 
					
						
						|  | outputs=[state, baseline_status] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | save_btn.click( | 
					
						
						|  | fn=save_reflection, | 
					
						
						|  | inputs=[reflection_input, state], | 
					
						
						|  | outputs=[state, reflections_display] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | clear_btn.click( | 
					
						
						|  | fn=lambda: gr.update(value=None), | 
					
						
						|  | outputs=[voice_input] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | analyze_btn.click( | 
					
						
						|  | fn=analyze_voice, | 
					
						
						|  | inputs=[voice_input, state], | 
					
						
						|  | outputs=[state, transcribed_text, voice_emotion, text_emotion, final_prompt] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | generate_btn.click( | 
					
						
						|  | fn=lambda prompt: generate_image_from_prompt(prompt)[0], | 
					
						
						|  | inputs=[final_prompt], | 
					
						
						|  | outputs=[result_image] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | save_final_btn.click( | 
					
						
						|  | fn=lambda t, s: ( | 
					
						
						|  | db.save_wish(s.get("user_name", "익명"), t), | 
					
						
						|  | "감상이 저장되었습니다." | 
					
						
						|  | ), | 
					
						
						|  | inputs=[final_reflection, state], | 
					
						
						|  | outputs=[save_final_status] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | play_music_btn.click( | 
					
						
						|  | fn=lambda: "oncheoncheon_sound.wav", | 
					
						
						|  | outputs=[audio] | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | return app | 
					
						
						|  |  | 
					
						
						|  | if __name__ == "__main__": | 
					
						
						|  | demo = create_interface() | 
					
						
						|  | demo.launch( | 
					
						
						|  | debug=True, | 
					
						
						|  | share=True, | 
					
						
						|  | show_error=True, | 
					
						
						|  | server_name="0.0.0.0", | 
					
						
						|  | server_port=7860 | 
					
						
						|  | ) | 
					
						
						|  |  |