import os import streamlit as st import datetime import google.generativeai as genai import time import google.api_core.exceptions # 오류 처리를 위해 추가 # --- Streamlit 설정 --- st.set_page_config( page_title="마음 건강 다이어리 💖", page_icon="💖", layout="wide", ) # --- Custom CSS (스타일링) --- st.markdown( """ """, unsafe_allow_html=True, ) # --- API 키 설정 (Streamlit Secrets 사용 권장) --- api_key = os.environ.get("GEMINI_API_KEY") or st.secrets.get("GEMINI_API_KEY") if not api_key: st.error("Gemini API 키가 설정되지 않았습니다. 환경 변수 또는 Streamlit Secrets에 `GEMINI_API_KEY`를 설정해주세요.") st.stop() try: genai.configure(api_key=api_key) except Exception as e: st.error(f"API 키 설정 중 오류 발생: {e}") st.stop() # --- Gemini 모델 설정 --- generation_config = { "temperature": 0.75, # 약간 더 창의적인 답변 유도 "top_p": 0.95, "top_k": 64, "max_output_tokens": 8192, "response_mime_type": "text/plain", } MODEL_NAME = "gemini-2.0-flash" try: model = genai.GenerativeModel( model_name=MODEL_NAME, generation_config=generation_config, # safety_settings= # 안전 설정 필요시 추가 ) except Exception as e: st.error(f"Gemini 모델 로딩 중 오류 발생: {e}") st.stop() # --- 세션 상태 초기화 --- # (이전과 동일하게 유지) if "daily_emotions" not in st.session_state: st.session_state["daily_emotions"] = {} if "chat_session" not in st.session_state: st.session_state["chat_session"] = None if "emotion_analysis_result" not in st.session_state: st.session_state["emotion_analysis_result"] = None if "counseling_messages" not in st.session_state: st.session_state["counseling_messages"] = [] # UI 표시용 if "analysis_to_chatbot" not in st.session_state: st.session_state["analysis_to_chatbot"] = "" if "counseling_history" not in st.session_state: st.session_state["counseling_history"] = [] # API 전달용 if "section_index" not in st.session_state: st.session_state["section_index"] = 0 # --- 감정 분석 함수 --- # (이전과 동일하게 유지) def analyze_emotion(emotion, reason): """Gemini를 사용하여 감정과 이유를 분석하고 격려 메시지를 생성합니다.""" prompt = f""" **사용자 정보:** * 오늘의 감정: {emotion} * 감정 이유: {reason if reason else "특별한 이유는 없음"} **당신의 역할:** 따뜻하고 공감 능력이 뛰어난 심리 상담가입니다. 사용자의 감정을 심리학적인 관점에서 분석하고, 긍정적인 지지와 격려를 보내주세요. **지시사항:** 1. **감정 분석 (2-3문장):** 사용자의 감정과 그 이유(있다면)를 바탕으로 현재 심리 상태를 간결하게 분석해주세요. 전문 용어보다는 쉽고 이해하기 좋은 표현을 사용하세요. 2. **공감 및 격려 (1-2문장):** 분석 내용을 바탕으로 사용자의 감정에 깊이 공감하고, 따뜻한 위로와 격려의 메시지를 전달해주세요. 친근하고 부드러운 말투(~해요, ~군요)를 사용해주세요. **출력 형식:** 분석과 공감/격려 메시지를 자연스럽게 이어서 한 문단으로 작성해주세요. """ try: # 분석은 일회성이므로 새 모델 인스턴스 사용 가능 temp_model = genai.GenerativeModel(MODEL_NAME, generation_config=generation_config) response = temp_model.generate_content(prompt) # Markdown 형식 제거 (볼드 등) analysis_text = response.text.strip().replace("**", "") return analysis_text except google.api_core.exceptions.ResourceExhausted as e: st.error(f"API 할당량 초과 오류가 발생했습니다. 잠시 후 다시 시도해주세요. 오류: {e}") return "죄송합니다. 지금은 감정 분석을 처리할 수 없습니다. 잠시 후 다시 시도해주세요." except Exception as e: st.error(f"감정 분석 중 오류가 발생했습니다: {e}") return "죄송합니다. 감정 분석 중 예상치 못한 오류가 발생했습니다." # --- 감정 기록 함수 --- # (이전과 동일하게 유지) def record_emotion(emotion, reason): """선택된 감정과 이유를 세션 상태에 기록합니다.""" now = datetime.datetime.now() timestamp_str = now.strftime("%Y-%m-%d %H:%M:%S") # 날짜 및 시간 today = str(datetime.date.today()) reason_to_save = reason if reason else "" st.session_state["daily_emotions"][today] = { "emotion": emotion, "reason": reason_to_save, "timestamp": timestamp_str } st.toast(f"{timestamp_str} 감정: {emotion} 😊 기록 완료!", icon="✅") # 다음 단계를 위해 관련 상태 초기화 st.session_state["emotion_analysis_result"] = None st.session_state["analysis_to_chatbot"] = "" st.session_state["counseling_messages"] = [] st.session_state["counseling_history"] = [] st.session_state["chat_session"] = None # 채팅 세션도 초기화 st.session_state["section_index"] += 1 # 다음 섹션으로 이동 # --- 감정 이력 표시 함수 --- # (이전과 동일하게 유지, expander 활용) def display_emotion_history(): """기록된 감정 이력을 날짜 역순으로 표시합니다.""" st.subheader("📊 감정 이력") st.markdown("지난 감정 기록을 통해 마음의 변화를 살펴보세요.") if not st.session_state["daily_emotions"]: st.info("아직 기록된 감정이 없어요. '💖 감정 체크인' 섹션에서 오늘 하루의 감정을 기록해보세요.") else: sorted_dates = sorted(st.session_state["daily_emotions"].keys(), reverse=True) for date_str in sorted_dates: emotion_data = st.session_state["daily_emotions"][date_str] display_date = datetime.datetime.strptime(date_str, "%Y-%m-%d").strftime("%Y년 %m월 %d일") with st.expander(f"{display_date} ({emotion_data['timestamp']}) - 감정: {emotion_data['emotion']}"): st.markdown(f"**감정:** {emotion_data['emotion']}") if emotion_data['reason']: st.markdown(f"**이유:**\n{emotion_data['reason']}") # 줄바꿈 추가 else: st.markdown("**이유:** (기록되지 않음)") st.markdown("---") # --- AI 감정 분석 및 상담 준비 함수 --- # (이전과 동일하게 유지) def start_emotion_analysis(): """오늘 기록된 감정을 분석하고 결과를 세션 상태에 저장합니다.""" today = str(datetime.date.today()) emotion_data = st.session_state["daily_emotions"].get(today) if emotion_data: with st.spinner("AI가 당신의 감정을 분석하고 있어요... 잠시만 기다려주세요 ✨"): emotion_analysis_result = analyze_emotion(emotion_data['emotion'], emotion_data['reason']) if emotion_analysis_result and not emotion_analysis_result.startswith("죄송합니다"): # 분석 성공 시 st.session_state["emotion_analysis_result"] = emotion_analysis_result st.session_state["analysis_to_chatbot"] = emotion_analysis_result st.success("AI 감정 분석 완료! 아래에서 결과를 확인하고 상담을 시작하세요.") # section_index는 버튼 클릭으로 이동 else: # analyze_emotion 함수 내에서 이미 에러 메시지 표시됨 또는 실패 메시지 반환 st.warning(emotion_analysis_result or "감정 분석에 실패했습니다. 잠시 후 다시 시도해주세요.") else: st.warning("오늘의 감정이 아직 기록되지 않았어요. '감정 체크인' 섹션에서 먼저 기록해주세요.") # --- 상담 안내 함수 --- # (이전과 동일하게 유지) def display_counseling_info(): """학교, 학부모, 전문기관 상담 안내 정보를 표시합니다.""" st.subheader("📢 도움이 필요하다면 언제든 요청하세요") st.markdown("마음이 힘들거나 어려운 일이 있을 때, 혼자 고민하지 마세요. 기댈 수 있는 곳들이 있습니다.") # expander 내용 생략 (이전과 동일) with st.expander("🏫 학교 Wee클래스 / 상담 선생님"): st.markdown(""" * 학교에는 여러분의 이야기를 들어주고 도와줄 상담 선생님(Wee클래스)이 계세요. * 교내 상담실 위치나 상담 신청 방법을 모른다면 담임 선생님께 여쭤보세요. * 비밀은 보장되니 안심하고 이야기할 수 있어요. """) with st.expander("👨👩👧👦 부모님 또는 보호자"): st.markdown(""" * 가장 가까이 있는 부모님이나 보호자께 솔직하게 마음을 이야기하는 것도 좋은 방법이에요. * 어떻게 말을 꺼내야 할지 어렵다면, '요즘 마음이 좀 힘들어요'라고 시작해보세요. * 여러분의 마음을 이해하고 지지해주실 거예요. """) with st.expander("📞 청소년 상담 채널 (1388)"): st.markdown(""" * **전화:** 1388 (24시간 운영, 무료) * **문자:** #1388 * **카카오톡:** '청소년상담1388' 채널 추가 후 상담 * **온라인:** [청소년사이버상담센터](https://www.cyber1388.kr) * 혼자 해결하기 어려운 고민이 있을 때, 전문가의 도움을 받을 수 있어요. 익명으로도 상담 가능해요. """) st.markdown("---") st.markdown("**🌟 중요한 건 혼자 힘들어하지 않고 도움을 요청하는 용기입니다. 당신은 혼자가 아니에요!**") st.markdown("---") # --- 메인 화면 구성 함수 --- def main(): col_empty_left, col_main, col_empty_right = st.columns([0.5, 3, 0.5]) with st.sidebar: # 사이드바 내용 (이전과 동일) st.markdown("# 💖 마음 건강 다이어리") st.markdown("### 당신의 마음을 기록하고 AI와 대화해요") st.markdown("매일의 감정을 기록하고, AI 상담 친구와 편안하게 이야기하며 마음을 돌보세요. 🥰") with st.expander("🚀 앱 사용 가이드", expanded=False): st.markdown( """ **1단계: 감정 체크인 💖** - '💖 감정 체크인'에서 오늘의 감정을 선택하고, 이유를 간단히 적어주세요 (선택 사항). - '✅ 감정 기록 완료' 버튼을 누르면 다음 단계로 이동합니다. **2단계: AI 감정 분석 🔮** - '🔮 AI 감정 분석'에서 '✨ AI 감정 분석 시작하기' 버튼을 눌러주세요. - AI가 당신의 감정을 분석하고 따뜻한 메시지를 줄 거예요. - 분석 결과를 확인 후 '💬 AI 상담 챗봇 시작 👉' 버튼을 누르세요. **3단계: AI 상담 챗봇 💬** - '💬 AI 상담 챗봇'에서 AI와 자유롭게 대화하며 마음을 나눠보세요. - 대화 내용은 현재 세션 동안만 유지됩니다. (브라우저를 닫으면 사라짐) - 대화 후 '📊 감정 이력 확인 👉' 버튼을 눌러 다음 단계로 가세요. **4단계: 감정 이력 확인 📊** - '📊 감정 이력'에서 지난 감정 기록들을 날짜별로 확인할 수 있어요. - '📢 상담 안내 확인 👉' 버튼을 눌러 마지막 단계로 이동하세요. **5단계: 상담 안내 📢** - '📢 상담 안내'에서 도움이 필요할 때 연락할 수 있는 곳들의 정보를 확인하세요. - '💖 처음으로 돌아가기' 버튼을 누르면 다시 감정 체크인부터 시작할 수 있습니다. **💖 마음 건강 다이어리와 함께 당신의 마음을 더 깊이 이해하고 돌보는 시간을 가져보세요! 💪** """ ) st.markdown("---") st.markdown("
Version 1.2
", unsafe_allow_html=True) # 버전 업데이트 with col_main: # --- 섹션 이동 버튼 (상단) --- if st.session_state.section_index > 0: nav_cols = st.columns(2) with nav_cols[0]: if st.button("👈 이전 단계", key="prev_button_top", use_container_width=True): st.session_state.section_index -= 1 st.rerun() # --- ❤️ 감정 체크인 섹션 (index 0) --- if st.session_state.section_index == 0: with st.container(border=True): st.markdown('💖 감정 체크인
', unsafe_allow_html=True) st.markdown('오늘 하루, 당신의 마음은 어땠나요? 솔직한 감정을 기록해보세요.
', unsafe_allow_html=True) emotion_options = ["😄 행복해요", "😊 좋아요", "😶 평범해요", "🙁 슬퍼요", "😭 매우 슬퍼요", "😠 화나요", "😡 매우 화나요", "😥 불안해요", "😨 두려워요"] emotion = st.radio( "**오늘 나의 감정은?**", emotion_options, index=2, horizontal=True, key="emotion_radio" ) reason = st.text_area( "**✍️ 왜 그렇게 느꼈나요? (선택 사항)**", height=100, placeholder="어떤 일이 있었는지, 어떤 생각이 들었는지 자유롭게 적어보세요.", key="emotion_reason_input" ) if st.button("✅ 감정 기록 완료", use_container_width=True, key="record_button", type="primary"): record_emotion(emotion, reason) st.rerun() # --- 🔮 AI 감정 분석 섹션 (index 1) --- elif st.session_state.section_index == 1: with st.container(border=True): st.markdown('🔮 AI 감정 분석
', unsafe_allow_html=True) st.markdown('AI가 당신의 감정을 이해하고 따뜻한 위로를 건네줄 거예요.
', unsafe_allow_html=True) today = str(datetime.date.today()) if today not in st.session_state.daily_emotions: st.warning("먼저 '감정 체크인' 단계에서 오늘의 감정을 기록해주세요.") if st.button("💖 감정 체크인으로 돌아가기", key="goto_record"): st.session_state.section_index = 0 st.rerun() else: if not st.session_state.get("emotion_analysis_result"): if st.button("✨ AI 감정 분석 시작하기", use_container_width=True, key="analyze_button", type="primary"): start_emotion_analysis() st.rerun() if st.session_state.get("emotion_analysis_result"): st.markdown("---") st.markdown("##### 📝 AI 감정 분석 결과") st.info(st.session_state["emotion_analysis_result"]) # info box에 결과 표시 st.markdown("---") if st.button("💬 AI 상담 챗봇 시작 👉", use_container_width=True, key="next_chat_button"): st.session_state.section_index += 1 st.rerun() # --- 💬 AI 상담 챗봇 섹션 (index 2) --- elif st.session_state.section_index == 2: with st.container(border=True): st.markdown('💬 AI 상담 챗봇
', unsafe_allow_html=True) st.markdown('AI 상담 친구와 편안하게 대화하며 마음을 돌보세요.
', unsafe_allow_html=True) st.markdown("AI 챗봇은 당신의 이야기에 귀 기울이고 공감하며, 긍정적인 방향으로 나아갈 수 있도록 도울 거예요. 대화는 현재 세션 동안만 유지됩니다.") # --- 채팅 메시지 표시 영역 --- message_container = st.container() with message_container: # CSS 클래스 적용 st.markdown('', unsafe_allow_html=True) # --- 채팅 입력 --- if prompt := st.chat_input("AI 챗봇에게 이야기해보세요 (무엇이든 괜찮아요 😊)", key="chat_input"): # 사용자 메시지 추가 (UI용 & 히스토리용) st.session_state["counseling_messages"].append({"role": "user", "content": prompt}) st.session_state["counseling_history"].append({"role": "user", "parts": [prompt]}) # AI 응답 생성 및 표시 with st.chat_message("assistant"): with st.spinner("AI 챗봇이 답장을 준비하고 있어요..."): try: # 채팅 세션 가져오기 또는 시작 if st.session_state["chat_session"] is None: st.session_state["chat_session"] = model.start_chat( # 현재까지의 히스토리 전달 (마지막 사용자 입력 포함) history=st.session_state["counseling_history"][:-1] ) chat_session = st.session_state["chat_session"] # Gemini 모델에 메시지 전송 (history 자동 관리) response = chat_session.send_message(prompt) ai_response = response.text.strip() # AI 응답 추가 (UI용 & 히스토리용) st.session_state["counseling_messages"].append({"role": "assistant", "content": ai_response}) st.session_state["counseling_history"].append({"role": "model", "parts": [ai_response]}) # 화면에 AI 응답 표시 (st.write 대신 chat_message 컨텍스트 내에서 자동으로 처리됨) st.write(ai_response) # Streamlit 1.33+ 에서는 이 줄 없어도 됨 except google.api_core.exceptions.ResourceExhausted as e: st.error(f"API 할당량 초과로 응답을 받을 수 없습니다. 잠시 후 다시 시도해주세요. 오류: {e}") error_message = "죄송합니다. API 사용량 제한으로 지금은 답장을 드릴 수 없어요. 잠시 후 다시 시도해주세요." st.session_state["counseling_messages"].append({"role": "assistant", "content": error_message}) st.write(error_message) except Exception as e: st.error(f"챗봇 응답 생성 중 오류가 발생했습니다: {e}") error_message = "죄송합니다. 지금은 답장을 드릴 수 없어요. 잠시 후 다시 시도해주세요." st.session_state["counseling_messages"].append({"role": "assistant", "content": error_message}) st.write(error_message) # 채팅 입력 후 UI 업데이트 (메시지 목록 새로고침 및 스크롤 하단 이동 유도) st.rerun() st.markdown("---") # 다음 단계 버튼 if st.button("📊 감정 이력 확인 👉", use_container_width=True, key="history_button"): st.session_state.section_index += 1 st.rerun() # --- 📊 감정 이력 섹션 (index 3) --- elif st.session_state.section_index == 3: with st.container(border=True): st.markdown('📊 감정 이력
', unsafe_allow_html=True) display_emotion_history() if st.button("📢 상담 안내 확인 👉", use_container_width=True, key="counseling_button"): st.session_state.section_index += 1 st.rerun() # --- 📢 상담 안내 섹션 (index 4) --- elif st.session_state.section_index == 4: with st.container(border=True): st.markdown('📢 상담 안내
', unsafe_allow_html=True) display_counseling_info() if st.button("💖 처음으로 돌아가기", use_container_width=True, key="reset_button", type="primary"): # 상태 초기화 st.session_state["emotion_analysis_result"] = None st.session_state["analysis_to_chatbot"] = "" st.session_state["counseling_messages"] = [] st.session_state["counseling_history"] = [] st.session_state["chat_session"] = None st.session_state.section_index = 0 st.rerun() if __name__ == "__main__": main()