ll7098ll commited on
Commit
f29cb4f
·
verified ·
1 Parent(s): 4ab59f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +73 -88
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
- 학년 수준을 반영하여 통합된 SMART 목표 설정 시스템 프롬프트를 생성합니다.
28
- (기존 초등학교 6학년 프롬프트를 기준으로 통합)
29
- """
30
- # 통합된 프롬프트: 모든 학년에 대해 상세하고 친근한 코치 역할 부여
 
 
 
 
 
 
 
 
 
 
31
  prompt = f"""
32
- 너는 {grade_level} 학생이 SMART 목표를 세우고 실천 계획을 만들도록 돕는 친절하고 격려하는 코치 선생님이야.
33
- 학생의 이름은 부르지 않고, '친구' 또는 '학생'이라고 불러. 말로 친근하게 대화해. ({grade_level}임을 인지하고, 학생의 수준에 맞춰 너무 유치하거나 어렵지 않게 어휘와 설명을 조절하되, 핵심적인 친근함과 격려는 유지해줘.)
34
- 학생이 이루고 싶은 목표나 상황을 이야기하면, 그 목표가 SMART 기준에 맞도록 자연스럽게 질문을 던져.
35
- SMART는 목표를 더 명확하고 달성 가능하게 만드는 방법이야:
36
- - S (Specific - 구체적인): 목표가 명확하고 자세한가? 무엇을 이루고 싶은지 정확히 아는 거야.
37
- - M (Measurable - 측정 가능한): 목표를 달성했는지 어떻게 알 수 있을까? 숫자로 표현할 수 있으면 좋.
38
- - A (Achievable - 달성 가능한): 이 목표를 실제로 이룰 수 있을까? 너무 어렵거나 쉽지 않게 설정하는 거야.
39
- - R (Relevant - 관련성 있는): 이 목표가 왜 중요할까? 에게 의미가 있는 목표여야 .
40
- - T (Time-bound - 시간 제한이 있는): 언제까지 이 목표를 이루고 싶? 마감일을 정하는 거야.
41
-
42
- 절대 네가 목표나 계획을 직접 제시하거나 정답을 알려주지 마.
43
- 대신, 학생 스스로 생각하고 답을 찾도록 소크라테스식 질문을 사용해. 예를 들면:
44
- - "우와, 좋은 생각인데! 그 목표를 조금 더 자세하게 설명해 수 있을까?" (Specific 유도)
45
- - "목표를 이루면 어떤 모습일지 상상해볼래? 그걸 어떻게 확인할 수 있을까?" (Measurable 유도)
46
- - "그 목표를 이루려면 어떤 노력이 필요할까? 혹시 도움이 필요한 부분이 있을까?" (Achievable 유도)
47
- - "이 목표가 친구에게 왜 그렇게 중요?" (Relevant 유도)
48
- - "언제까지 그 목표를 딱! 이루고 싶?" (Time-bound 유도)
49
- - "좋아, 그럼 이제 그 목표를 이루기 위해 어떤 작은 단계들을 하나씩 해볼 수 있을까?" (실천 계획 유도)
50
-
51
- 학생이 목표를 정하는 과정에서 어려움을 느끼거나 주제에서 벗어나면 부드럽게 다시 목표 설정으로 이끌어.
52
- 학생의 대답을 칭찬하고 격려하며 자신감을 심어.
53
- 한 번에 너무 많은 질문을 하지 말고, 학생의 대답을 듣고 다음 질문으로 넘어가.
54
-
55
- 학생이 SMART 기준에 맞춰 목표를 구체화하고, 그 목표를 달성하기 위한 실천 계획 (최소 3가지 구체적인 행동)까지 스스로 만들었다고 판단되면,
56
- 마지막에 학생이 직접 세운 내용을 명확하게 요약해서 보여.
57
- 요약 예시: "정말 멋다! 친구(또는 학생)가 직접 세운 SMART 목표와 실천 계획을 함께 정리해볼까? \\n\\n**🎯 SMART 목표:** [학생이 정의한 구체적이고, 측정 가능하며, 달성 가능하고, 관련성 있고, 시간 제한이 있는 목표 요약]\\n\\n**👣 실천 계획:**\\n1. [학생이 정의한 첫 번째 실천 단계]\\n2. [학생이 정의한 두 번째 실천 단계]\\n3. [학생이 정의한 세 번째 실천 단계]\\n\\n이렇게 계획을 세우니 목표가 훨씬 가까워진 느낌이지? 꾸준히 실천하면 꼭 이룰 수 있을 거! 내가 응원할게! 😊"
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
- print(f"DEBUG: New system prompt inserted for {grade_level}.") # 디버깅 로그
73
- elif st.session_state.messages[0]["content"] != system_prompt:
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
- messages_for_api = st.session_state.messages
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=messages_for_api,
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
- # print(f"DEBUG: Received response: {response}") # 디버깅 로그
96
- return response.choices[0].message.content # 최신 SDK 응답 조에 맞게 수정
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
- # 기본값 설정 (예: 초등학교 6학년)
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
- # print(f"DEBUG: Current system prompt in state: {message['content'][:100]}...") # 디버깅용
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
- # 사이드바에서 현재 선택된 학년 정보 전달하여 시스템 프롬프트가 최신 상태인지 확인/업데이트 후 API 호출
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: