Update app.py
Browse files
app.py
CHANGED
|
@@ -4,12 +4,6 @@ import streamlit as st
|
|
| 4 |
# import html # 필요 시 주석 해제
|
| 5 |
|
| 6 |
# --- OpenAI API 설정 ---
|
| 7 |
-
# 로컬 환경에서 테스트 시 .env 파일 사용 고려
|
| 8 |
-
# from dotenv import load_dotenv
|
| 9 |
-
# load_dotenv()
|
| 10 |
-
# openai_api_key = os.getenv("OPENAI_API_KEY")
|
| 11 |
-
|
| 12 |
-
# Streamlit secrets 사용
|
| 13 |
openai_api_key = st.secrets["OPENAI_API_KEY"]
|
| 14 |
|
| 15 |
|
|
@@ -23,40 +17,50 @@ openai.api_key = openai_api_key
|
|
| 23 |
# --- 함수 정의 ---
|
| 24 |
|
| 25 |
def generate_smart_system_prompt(grade_level):
|
| 26 |
-
"""
|
| 27 |
-
|
| 28 |
-
(
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
prompt = f"""
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
return prompt
|
| 61 |
|
| 62 |
def openai_chat(grade_level):
|
|
@@ -69,38 +73,26 @@ def openai_chat(grade_level):
|
|
| 69 |
if not st.session_state.messages or st.session_state.messages[0]["role"] != "system":
|
| 70 |
# 시스템 메시지가 없거나 첫번째가 아니면 맨 앞에 삽입
|
| 71 |
st.session_state.messages.insert(0, {"role": "system", "content": system_prompt})
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
# 기존 시스템 메시지 내용이 현재 선택된 학년과 다르면 업데이트
|
| 75 |
st.session_state.messages[0]["content"] = system_prompt
|
| 76 |
-
print(f"DEBUG: System prompt updated for {grade_level}.") # 디버깅 로그
|
| 77 |
-
# else: # 시스템 메시지가 이미 존재하고 내용도 같으면 변경 없음
|
| 78 |
-
# print(f"DEBUG: System prompt for {grade_level} already up-to-date.") # 디버깅 로그
|
| 79 |
-
|
| 80 |
|
| 81 |
-
# API 호출 시 시스템 메시지를 포함한 전체 대화 전달
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
# print(f"DEBUG: Sending messages to API: {messages_for_api}") # 디버깅 로그
|
| 85 |
-
|
| 86 |
-
response = openai.chat.completions.create( # 최신 SDK 사용법으로 변경
|
| 87 |
model="gpt-4o", # 또는 사용 가능한 최신 모델
|
| 88 |
-
messages=
|
| 89 |
temperature=0.7,
|
| 90 |
max_tokens=2000, # 필요에 따라 조절
|
| 91 |
top_p=0.9,
|
| 92 |
frequency_penalty=0.1,
|
| 93 |
presence_penalty=0.1
|
| 94 |
)
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
except openai.APIError as e: # 구체적인 OpenAI 에러 처리 (최신 SDK 기준)
|
| 98 |
st.error(f"OpenAI API 오류 발생: {str(e)}")
|
| 99 |
-
print(f"ERROR: OpenAI API Error: {e}") # 콘솔에도 에러 로그 출력
|
| 100 |
return None
|
| 101 |
except Exception as e: # 기타 예외 처리
|
| 102 |
st.error(f"알 수 없는 오류 발생: {str(e)}")
|
| 103 |
-
print(f"ERROR: Unknown Error: {e}") # 콘솔에도 에러 로그 출력
|
| 104 |
return None
|
| 105 |
|
| 106 |
# --- Streamlit 앱 UI 설정 ---
|
|
@@ -111,7 +103,6 @@ st.set_page_config(
|
|
| 111 |
)
|
| 112 |
|
| 113 |
# --- 페이지 스타일 (CSS) ---
|
| 114 |
-
# (기존 CSS 코드는 변경 없음)
|
| 115 |
st.markdown(
|
| 116 |
"""
|
| 117 |
<style>
|
|
@@ -201,7 +192,6 @@ st.markdown(
|
|
| 201 |
unsafe_allow_html=True
|
| 202 |
)
|
| 203 |
|
| 204 |
-
|
| 205 |
# 메인 타이틀
|
| 206 |
st.markdown("<div class='main-title'>🎯 SMART 목표 설정 도우미 ✍️</div>", unsafe_allow_html=True)
|
| 207 |
|
|
@@ -212,33 +202,28 @@ with st.sidebar:
|
|
| 212 |
grade_level_options = [
|
| 213 |
"초등학교 1학년", "초등학교 2학년", "초등학교 3학년", "초등학교 4학년", "초등학교 5학년", "초등학교 6학년",
|
| 214 |
"중학교 1학년", "중학교 2학년", "중학교 3학년",
|
| 215 |
-
"고등학교 1학년", "고등학교 2학년", "고등학교 3학년"
|
| 216 |
-
"대학생", "성인" # 다른 대상 추가 가능
|
| 217 |
]
|
| 218 |
-
#
|
| 219 |
try:
|
| 220 |
default_index = grade_level_options.index("초등학교 6학년")
|
| 221 |
except ValueError:
|
| 222 |
-
default_index = 5 #
|
| 223 |
|
| 224 |
-
# 학년 선택 selectbox - 선택 변경 시 시스템 프롬프트 업데이트
|
| 225 |
selected_grade = st.selectbox(
|
| 226 |
-
"👤
|
| 227 |
grade_level_options,
|
| 228 |
index=default_index,
|
| 229 |
-
key="grade_select" # 키 추가
|
|
|
|
|
|
|
| 230 |
)
|
| 231 |
|
| 232 |
# 초기화 버튼
|
| 233 |
if st.button("🔄 대화 초기화"):
|
| 234 |
-
# 메시지 기록 삭제
|
| 235 |
st.session_state.messages = []
|
| 236 |
-
# 시스템 프롬프트 재생성 및 추가
|
| 237 |
-
system_prompt = generate_smart_system_prompt(selected_grade)
|
| 238 |
-
st.session_state.messages.append({"role": "system", "content": system_prompt})
|
| 239 |
-
# 초기 환영 메시지 추가
|
| 240 |
-
welcome_message = "안녕! 👋 나는 네 목표 설정을 도와줄 AI 코치야. 이루고 싶은 목표나 하고 싶은 일이 있으면 나에게 이야기해 줄래? 같이 멋진 계획을 세워보자! 😊"
|
| 241 |
-
st.session_state.messages.append({"role": "assistant", "content": welcome_message})
|
| 242 |
st.success("대화 내용이 초기화되었습니다. 새로운 목표를 설정해보세요!")
|
| 243 |
# 페이지 새로고침 없이 즉시 적용되도록 rerun 사용
|
| 244 |
st.rerun()
|
|
@@ -252,34 +237,34 @@ with st.sidebar:
|
|
| 252 |
# 채팅 세션 초기화 (메시지 리스트가 없으면 생성)
|
| 253 |
if "messages" not in st.session_state:
|
| 254 |
st.session_state.messages = []
|
| 255 |
-
# 앱 첫 실행 시 시스템 프롬프트와 환영 메시지 설정
|
| 256 |
-
system_prompt = generate_smart_system_prompt(selected_grade) # 사이드바 기본값 사용
|
| 257 |
-
st.session_state.messages.append({"role": "system", "content": system_prompt})
|
| 258 |
-
welcome_message = "안녕! 👋 나는 네 목표 설정을 도와줄 AI 코치야. 이루고 싶은 목표나 하고 싶은 일이 있으면 나에게 이야기해 줄래? 같이 멋진 계획을 세워보자! 😊"
|
| 259 |
-
st.session_state.messages.append({"role": "assistant", "content": welcome_message})
|
| 260 |
-
# 초기 상태이므로 rerun 필요 없음
|
| 261 |
|
| 262 |
# 사용자와 AI 아이콘 URL 설정
|
| 263 |
user_icon_url = "https://cdn-icons-png.flaticon.com/512/1995/1995531.png" # 학생 아이콘
|
| 264 |
assistant_icon_url = "https://cdn-icons-png.flaticon.com/512/4323/4323008.png" # 튜터 아이콘
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
# --- 채팅 메시지 표시 ---
|
| 268 |
-
# 시스템 프롬프트 업데이트 확인 (사이드바 선택 변경 시)
|
| 269 |
-
# openai_chat 함수 내부에서 처리하도록 변경됨 (더 효율적)
|
| 270 |
-
|
| 271 |
# st.session_state.messages에 있는 모든 메시지를 순서대로 화면에 그림
|
| 272 |
for index, message in enumerate(st.session_state.messages):
|
| 273 |
if message["role"] == "system":
|
| 274 |
-
#
|
| 275 |
-
continue # 시스템 메시지는 화면에 표시하지 않음
|
| 276 |
|
| 277 |
role = message["role"]
|
| 278 |
content = message["content"]
|
| 279 |
-
# content = html.escape(message["content"]) # HTML 태그 문제
|
| 280 |
|
| 281 |
# 역할에 따라 다른 스타일과 구조 적용
|
| 282 |
if role == "user":
|
|
|
|
| 283 |
st.markdown(
|
| 284 |
f"""
|
| 285 |
<div class='chat-message chat-message-user' key='user_msg_{index}'>
|
|
@@ -290,6 +275,7 @@ for index, message in enumerate(st.session_state.messages):
|
|
| 290 |
unsafe_allow_html=True
|
| 291 |
)
|
| 292 |
elif role == "assistant":
|
|
|
|
| 293 |
st.markdown(
|
| 294 |
f"""
|
| 295 |
<div class='chat-message chat-message-assistant' key='assistant_msg_{index}'>
|
|
@@ -307,8 +293,7 @@ if prompt := st.chat_input("🎯 이루고 싶은 목표나 하고 싶은 일을
|
|
| 307 |
|
| 308 |
# 2. AI 응답 생성 (스피너 표시)
|
| 309 |
with st.spinner("AI 코치가 생각 중이에요... 🤔"):
|
| 310 |
-
# 사이드바에서
|
| 311 |
-
response = openai_chat(selected_grade)
|
| 312 |
|
| 313 |
# 3. AI 응답이 성공적이면 세션 상태에 추가
|
| 314 |
if response:
|
|
|
|
| 4 |
# import html # 필요 시 주석 해제
|
| 5 |
|
| 6 |
# --- OpenAI API 설정 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
openai_api_key = st.secrets["OPENAI_API_KEY"]
|
| 8 |
|
| 9 |
|
|
|
|
| 17 |
# --- 함수 정의 ---
|
| 18 |
|
| 19 |
def generate_smart_system_prompt(grade_level):
|
| 20 |
+
"""학년 수준에 맞는 SMART 목표 설정 시스템 프롬프트를 생성합니다."""
|
| 21 |
+
|
| 22 |
+
# 학년 정보 추출 (예: "초등학교 6학년" -> "6", "초등학교")
|
| 23 |
+
try:
|
| 24 |
+
parts = grade_level.split(" ")
|
| 25 |
+
if len(parts) == 2:
|
| 26 |
+
school_level = parts[0]
|
| 27 |
+
grade_num = parts[1].rstrip("학년")
|
| 28 |
+
if school_level not in ["초등학교", "중학교", "고등학교"] or not grade_num.isdigit():
|
| 29 |
+
grade_num, school_level = "알 수 없음", "알 수 없음"
|
| 30 |
+
else:
|
| 31 |
+
grade_num, school_level = "알 수 없음", "알 수 없음"
|
| 32 |
+
except Exception: # 광범위한 예외 처리 (분석 실패 시)
|
| 33 |
+
grade_num, school_level = "알 수 없음", "알 수 없음"
|
| 34 |
+
|
| 35 |
prompt = f"""
|
| 36 |
+
너는 {grade_level} 학생이 SMART 목표를 세우고 실천 계획을 만들도록 돕는 친절하고 격려하는 코치 선생님입니다.
|
| 37 |
+
학생의 이름은 부르지 않고, '학생'이라고 불러주세요. 존댓말로 정중하고 친근하게 대화해주세요.
|
| 38 |
+
학생이 이루고 싶은 목표나 상황을 이야기하면, 그 목표가 SMART 기준에 맞도록 자연스럽게 질문을 던져주세요.
|
| 39 |
+
SMART는 목표를 더 명확하고 달성 가능하게 만드는 방법입니다:
|
| 40 |
+
- S (Specific - 구체적인): 목표가 명확하고 자세한가요? 무엇을 이루고 싶은지 정확히 아는 것입니다.
|
| 41 |
+
- M (Measurable - 측정 가능한): 목표를 달성했는지 어떻게 알 수 있을까요? 숫자로 표현할 수 있으면 좋습니다.
|
| 42 |
+
- A (Achievable - 달성 가능한): 이 목표를 실제로 이룰 수 있을까요? 너무 어렵거나 쉽지 않게 설정하는 것입니다.
|
| 43 |
+
- R (Relevant - 관련성 있는): 이 목표가 왜 중요할까요? 학생에게 의미가 있는 목표여야 합니다.
|
| 44 |
+
- T (Time-bound - 시간 제한이 있는): 언제까지 이 목표를 이루고 싶으신가요? 마감일을 정하는 것입니다.
|
| 45 |
+
|
| 46 |
+
절대 선생님이 목표나 계획을 직접 제시하거나 정답을 알려주지 마세요.
|
| 47 |
+
대신, 학생 스스로 생각하고 답을 찾도록 소크라테스식 질문을 사용해주세요. 예를 들면:
|
| 48 |
+
- "우와, 좋은 생각인데요! 그 목표를 조금 더 자세하게 설명해 주실 수 있을까요?" (Specific 유도)
|
| 49 |
+
- "목표를 이루면 어떤 모습일지 상상해볼래요? 그걸 어떻게 확인할 수 있을까요?" (Measurable 유도)
|
| 50 |
+
- "그 목표를 이루려면 어떤 노력이 필요할까요? 혹시 도움이 필요한 부분이 있을까요?" (Achievable 유도)
|
| 51 |
+
- "이 목표가 학생에게 왜 그렇게 중요할까요?" (Relevant 유도)
|
| 52 |
+
- "언제까지 그 목표를 딱! 이루고 싶으신가요?" (Time-bound 유도)
|
| 53 |
+
- "좋아요, 그럼 이제 그 목표를 이루기 위해 어떤 작은 단계들을 하나씩 해볼 수 있을까요?" (실천 계획 유도)
|
| 54 |
+
|
| 55 |
+
학생이 목표를 정하는 과정에서 어려움을 느끼거나 주제에서 벗어나면 부드럽게 다시 목표 설정으로 이끌어주세요.
|
| 56 |
+
학생의 대답을 칭찬하고 격려하며 자신감을 심어주세요.
|
| 57 |
+
한 번에 너무 많은 질문을 하지 말고, 학생의 대답을 듣고 다음 질문으로 넘어가주세요.
|
| 58 |
+
|
| 59 |
+
학생이 SMART 기준에 맞춰 목표를 구체화하고, 그 목표를 달성하기 위한 실천 계획 (최소 3가지 구체적인 행동)까지 스스로 만들었다고 판단되면,
|
| 60 |
+
마지막에 학생이 직접 세운 내용을 명확하게 요약해서 보여주세요.
|
| 61 |
+
요약 예시: "정말 멋집니다! 학생이 직접 세운 SMART 목표와 실천 계획을 함께 정리해볼까요? \\n\\n**🎯 SMART 목표:** [학생이 정의한 구체적이고, 측정 가능하며, 달성 가능하고, 관련성 있고, 시간 제한이 있는 목표 요약]\\n\\n**👣 실천 계획:**\\n1. [학생이 정의한 첫 번째 실천 단계]\\n2. [학생이 정의한 두 번째 실천 단계]\\n3. [학생이 정의한 세 번째 실천 단계]\\n\\n이렇게 계획을 세우니 목표가 훨씬 가까워진 느낌이지요? 꾸준히 실천하면 꼭 이룰 수 있을 거예요! 선생님이 응원하겠습니다! 😊"
|
| 62 |
+
요약하기 전에는 반드시 "이제 목표랑 실천 계획이 다 세워진 것 같은데, 선생님이 한번 정리해봐도 괜찮을까요?" 와 같이 학생의 동의를 구하는 질문을 먼저 해주세요.
|
| 63 |
+
"""
|
| 64 |
return prompt
|
| 65 |
|
| 66 |
def openai_chat(grade_level):
|
|
|
|
| 73 |
if not st.session_state.messages or st.session_state.messages[0]["role"] != "system":
|
| 74 |
# 시스템 메시지가 없거나 첫번째가 아니면 맨 앞에 삽입
|
| 75 |
st.session_state.messages.insert(0, {"role": "system", "content": system_prompt})
|
| 76 |
+
else:
|
| 77 |
+
# 기존 시스템 메시지 내용 업데이트
|
|
|
|
| 78 |
st.session_state.messages[0]["content"] = system_prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
# API 호출 시 시스템 메시지를 포함한 전체 대화 전달
|
| 81 |
+
response = openai.ChatCompletion.create(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
model="gpt-4o", # 또는 사용 가능한 최신 모델
|
| 83 |
+
messages=st.session_state.messages,
|
| 84 |
temperature=0.7,
|
| 85 |
max_tokens=2000, # 필요에 따라 조절
|
| 86 |
top_p=0.9,
|
| 87 |
frequency_penalty=0.1,
|
| 88 |
presence_penalty=0.1
|
| 89 |
)
|
| 90 |
+
return response.choices[0].message["content"]
|
| 91 |
+
except openai.error.OpenAIError as e: # 구체적인 OpenAI 에러 처리
|
|
|
|
| 92 |
st.error(f"OpenAI API 오류 발생: {str(e)}")
|
|
|
|
| 93 |
return None
|
| 94 |
except Exception as e: # 기타 예외 처리
|
| 95 |
st.error(f"알 수 없는 오류 발생: {str(e)}")
|
|
|
|
| 96 |
return None
|
| 97 |
|
| 98 |
# --- Streamlit 앱 UI 설정 ---
|
|
|
|
| 103 |
)
|
| 104 |
|
| 105 |
# --- 페이지 스타일 (CSS) ---
|
|
|
|
| 106 |
st.markdown(
|
| 107 |
"""
|
| 108 |
<style>
|
|
|
|
| 192 |
unsafe_allow_html=True
|
| 193 |
)
|
| 194 |
|
|
|
|
| 195 |
# 메인 타이틀
|
| 196 |
st.markdown("<div class='main-title'>🎯 SMART 목표 설정 도우미 ✍️</div>", unsafe_allow_html=True)
|
| 197 |
|
|
|
|
| 202 |
grade_level_options = [
|
| 203 |
"초등학교 1학년", "초등학교 2학년", "초등학교 3학년", "초등학교 4학년", "초등학교 5학년", "초등학교 6학년",
|
| 204 |
"중학교 1학년", "중학교 2학년", "중학교 3학년",
|
| 205 |
+
"고등학교 1학년", "고등학교 2학년", "고등학교 3학년"
|
|
|
|
| 206 |
]
|
| 207 |
+
# 초등학교 6학년 인덱스 찾기 (더 안전하게)
|
| 208 |
try:
|
| 209 |
default_index = grade_level_options.index("초등학교 6학년")
|
| 210 |
except ValueError:
|
| 211 |
+
default_index = 5 # 리스트에 없으면 6번째 항목(초6)으로 가정
|
| 212 |
|
| 213 |
+
# 학년 선택 selectbox - 선택 변경 시 시스템 프롬프트 업데이트 위해 콜백 추가 가능성 고려
|
| 214 |
selected_grade = st.selectbox(
|
| 215 |
+
"👤 학생의 학년을 선택하세요:",
|
| 216 |
grade_level_options,
|
| 217 |
index=default_index,
|
| 218 |
+
key="grade_select", # 키 추가
|
| 219 |
+
on_change=openai_chat, # 학년 변경 시 openai_chat 함수를 다시 호출하여 시스템 프롬프트 업데이트
|
| 220 |
+
args=[st.session_state.grade_select] # 현재 선택된 학년 수준을 openai_chat에 전달
|
| 221 |
)
|
| 222 |
|
| 223 |
# 초기화 버튼
|
| 224 |
if st.button("🔄 대화 초기화"):
|
| 225 |
+
# 메시지 기록 삭제
|
| 226 |
st.session_state.messages = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
st.success("대화 내용이 초기화되었습니다. 새로운 목표를 설정해보세요!")
|
| 228 |
# 페이지 새로고침 없이 즉시 적용되도록 rerun 사용
|
| 229 |
st.rerun()
|
|
|
|
| 237 |
# 채팅 세션 초기화 (메시지 리스트가 없으면 생성)
|
| 238 |
if "messages" not in st.session_state:
|
| 239 |
st.session_state.messages = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
# 사용자와 AI 아이콘 URL 설정
|
| 242 |
user_icon_url = "https://cdn-icons-png.flaticon.com/512/1995/1995531.png" # 학생 아이콘
|
| 243 |
assistant_icon_url = "https://cdn-icons-png.flaticon.com/512/4323/4323008.png" # 튜터 아이콘
|
| 244 |
|
| 245 |
+
# 초기 메시지 추가 (세션이 비어있을 때만 실행)
|
| 246 |
+
if not st.session_state.messages:
|
| 247 |
+
# 현재 선택된 학년으로 시스템 프롬프트 설정
|
| 248 |
+
system_prompt = generate_smart_system_prompt(selected_grade) # 사이드바에서 선택된 값 사용
|
| 249 |
+
st.session_state.messages.append({"role": "system", "content": system_prompt})
|
| 250 |
+
# 초기 환영 메시지 추가
|
| 251 |
+
welcome_message = f"안녕하세요! 👋 저는 {selected_grade} 학생의 목표 설정을 도와줄 AI 코치 선생님입니다. 이루고 싶은 목표나 하고 싶은 일이 있으면 저에게 이야기해 주시겠어요? 함께 멋진 계획을 세워보아요! 😊" # 존댓말로 변경, 학년 수준 명시
|
| 252 |
+
st.session_state.messages.append({"role": "assistant", "content": welcome_message})
|
| 253 |
+
# 초기 메시지는 아래 메시지 표시 루프에서 자동으로 그려짐. 여기서 st.rerun() 불필요.
|
| 254 |
|
| 255 |
# --- 채팅 메시지 표시 ---
|
|
|
|
|
|
|
|
|
|
| 256 |
# st.session_state.messages에 있는 모든 메시지를 순서대로 화면에 그림
|
| 257 |
for index, message in enumerate(st.session_state.messages):
|
| 258 |
if message["role"] == "system":
|
| 259 |
+
continue # 시스템 메시지는 건너뜀
|
|
|
|
| 260 |
|
| 261 |
role = message["role"]
|
| 262 |
content = message["content"]
|
| 263 |
+
# content = html.escape(message["content"]) # HTML 태그가 문제될 경우 주석 해제
|
| 264 |
|
| 265 |
# 역할에 따라 다른 스타일과 구조 적용
|
| 266 |
if role == "user":
|
| 267 |
+
# 사용자 메시지: [내용] [아이콘]
|
| 268 |
st.markdown(
|
| 269 |
f"""
|
| 270 |
<div class='chat-message chat-message-user' key='user_msg_{index}'>
|
|
|
|
| 275 |
unsafe_allow_html=True
|
| 276 |
)
|
| 277 |
elif role == "assistant":
|
| 278 |
+
# AI 메시지: [아이콘] [내용]
|
| 279 |
st.markdown(
|
| 280 |
f"""
|
| 281 |
<div class='chat-message chat-message-assistant' key='assistant_msg_{index}'>
|
|
|
|
| 293 |
|
| 294 |
# 2. AI 응답 생성 (스피너 표시)
|
| 295 |
with st.spinner("AI 코치가 생각 중이에요... 🤔"):
|
| 296 |
+
response = openai_chat(selected_grade) # 사이드바에서 선택된 학년 정보 전달
|
|
|
|
| 297 |
|
| 298 |
# 3. AI 응답이 성공적이면 세션 상태에 추가
|
| 299 |
if response:
|