ll7098ll commited on
Commit
ff9b005
·
verified ·
1 Parent(s): fbaa65a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +213 -58
app.py CHANGED
@@ -3,58 +3,126 @@ import openai
3
  import streamlit as st
4
 
5
  # OpenAI API 설정
6
- openai_api_key = os.getenv("OPENAI_API_KEY")
 
 
 
 
 
 
 
 
 
7
  openai.api_key = openai_api_key
8
 
9
- def openai_chat(text, grade_level):
 
 
 
10
  try:
11
- # 학년 수준에 따른 프롬프트 추가
12
- grade_prompt = ""
13
  if grade_level.startswith("초등학교"):
14
- grade_num = grade_level[5:].rstrip("학년")
15
- grade_prompt = f"이 학생은 초등학교 {grade_num}학년입니다. 질문은 이 학생의 학년 수준에 맞춰서 해주세요. 학생이 스스로 생각하고 답을 찾을 수 있도록 소크라테스 산파법을 활용하여 질문해주세요. 정답을 직접적으로 말하거나 설명을 너무 자세하게 하지 마세요. 대신 질문을 통해 학생이 스스로 답을 생각하고, 개념을 이해하도록 유도해주세요. 학생의 학년에 맞게 질문과 답하는 내용의 범위를 해당 학년의 교육과정에 맞게 범위를 조정하세요."
16
  elif grade_level.startswith("중학교"):
17
- grade_num = grade_level[3:].rstrip("학년")
18
- grade_prompt = f"이 학생은 중학교 {grade_num}학년입니다. 질문은 이 학생의 학년 수준에 맞춰서 해주세요. 학생이 스스로 생각하고 답을 찾을 수 있도록 소크라테스 산파법을 활용하여 질문해주세요. 정답을 직접적으로 말하거나 설명을 너무 자세하게 하지 마세요. 대신 질문을 통해 학생이 스스로 답을 생각하고, 개념을 이해하도록 유도해주세요. 학생의 학년에 맞게 질문과 답하는 내용의 범위를 해당 학년의 교육과정에 맞게 범위를 조정하세요."
19
  elif grade_level.startswith("고등학교"):
20
- grade_num = grade_level[4:].rstrip("학년")
21
- grade_prompt = f"이 학생은 고등학교 {grade_num}학년입니다. 질문은 이 학생의 학년 수준에 맞춰서 해주세요. 학생이 스스로 생각하고 답을 찾을 수 있도록 소크라테스 산파법을 활용하여 질문해주세요. 정답을 직접적으로 말하거나 설명을 너무 자세하게 하지 마세요. 대신 질문을 통해 학생이 스스로 답을 생각하고, 개념을 이해하도록 유도해주세요. 학생의 학년에 맞게 질문과 답하는 내용의 범위를 해당 학년의 교육과정에 맞게 범위를 조정하세요."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- # 시스템 메시지 세션 번만 추가
24
- if not any(message["role"] == "system" for message in st.session_state.messages):
25
- st.session_state.messages.append({"role": "system", "content": grade_prompt})
 
26
  else:
27
- # 기존 시스템 메시지 업데이트 (선택 사항)
28
- st.session_state.messages[0]["content"] = grade_prompt
29
 
30
  response = openai.ChatCompletion.create(
31
  model="gpt-4o",
32
  messages=st.session_state.messages, # 전체 대화 세션 전달
33
- temperature=0.7,
34
- max_tokens=3000, # max_tokens 조정
35
  top_p=0.9,
36
- frequency_penalty=0,
37
- presence_penalty=0
38
  )
39
  return response.choices[0].message["content"]
40
  except Exception as e:
41
- return f"에러 발생: {str(e)}"
 
42
 
43
- # Streamlit 앱 설정
44
- st.set_page_config(page_title="질문하는 AI 튜터", page_icon="🎓", initial_sidebar_state="expanded")
45
 
46
- # 페이지 스타일 커스터마이징
47
  st.markdown(
48
  """
49
  <style>
50
  /* 전체 배경색 설정 */
51
  .stApp {
52
- background-color: #fffafa; /* 은은한 핑크색 배경 */
53
  }
54
  /* 타이틀 스타일 */
55
  .main-title {
56
- font-size: 3rem;
57
- color: #000000; /* 검은색 */
58
  font-weight: 700;
59
  text-align: center;
60
  margin-bottom: 20px;
@@ -65,38 +133,45 @@ st.markdown(
65
  padding: 15px;
66
  margin: 10px 0;
67
  display: flex;
68
- align-items: center;
69
- flex-wrap: wrap;
70
  word-break: break-word;
 
71
  }
72
  .chat-message-user {
73
- background-color: #ffebef; /* 따뜻한 파스텔 핑크 */
74
- color: #8b4513;
75
- justify-content: flex-end;
 
76
  }
77
  .chat-message-assistant {
78
- background-color: #ffe4e6; /* 부드 파스텔 복숭아 핑크 */
79
- color: #6b4226;
80
- justify-content: flex-start;
 
81
  }
82
  .chat-avatar {
83
  width: 40px;
84
  height: 40px;
85
  border-radius: 50%;
86
  margin-right: 10px;
 
87
  }
88
  .chat-avatar-user {
89
  margin-left: 10px;
90
  margin-right: 0;
91
  }
 
 
 
92
  /* 사용자 입력 창 스타일 */
93
  .stTextInput input {
94
  border-radius: 15px;
95
- border: 2px solid #cd857f; /* 따뜻한 핑크색 */
96
  }
97
  /* 버튼 스타일 */
98
  .stButton button {
99
- background-color: #cd857f; /* 따뜻핑크색 */
100
  color: #fff;
101
  border-radius: 15px;
102
  padding: 10px 20px;
@@ -107,49 +182,129 @@ st.markdown(
107
  )
108
 
109
  # 메인 타이틀
110
- st.markdown("<div class='main-title'>질문하는 AI 튜터 🎓</div>", unsafe_allow_html=True)
111
-
112
- # 채팅 세션 초기화
113
- if "messages" not in st.session_state:
114
- st.session_state.messages = [
115
- {"role": "system", "content": "친절하고 든든한 튜터가 되어 학생이 목표를 달성하도록 안내하고, 학생이 주제에서 벗어나면 부드럽게 다시 집중하도록 도와주세요. 학생이 질문에 대해 틀린 대답을 하는 경우, 정답을 바로 알려주기보다, 스스로 오류를 깨닫고 정답을 찾아갈 수 있도록 질문을 통해 유도해주세요. 큰 개념은 단계적으로 이해할 수 있도록 돕고, 개념을 더 깊이 탐구할 수 있도록 질문을 던져 주세요. 학생이 부담을 느끼지 않도록 한 번에 한 가지 질문씩 해주세요. 학생이 개념을 이해했는지 확인되면 대화를 마무리해주세요."}
116
- ]
117
 
118
- # 사용자와 AI의 아 URL 설정
119
- user_icon_url = "https://cdn-icons-png.flaticon.com/512/1995/1995531.png" # 학생 아이콘 (책을 든 학생)
120
- assistant_icon_url = "https://cdn-icons-png.flaticon.com/512/4323/4323008.png" # 튜터 아이콘 (안경 쓴 선생님)
121
-
122
- # 학년 수준 선택
123
  with st.sidebar:
 
 
124
  grade_level_options = [
125
  "초등학교 1학년", "초등학교 2학년", "초등학교 3학년", "초등학교 4학년", "초등학교 5학년", "초등학교 6학년",
126
  "중학교 1학년", "중학교 2학년", "중학교 3학년",
127
  "고등학교 1학년", "고등학교 2학년", "고등학교 3학년"
128
  ]
129
- grade_level = st.selectbox("📚 학년 수준을 선택하세요:", grade_level_options, index=0)
 
 
 
 
 
 
 
 
 
 
 
130
  # 초기화 버튼
131
- if st.button("💡 초기화"):
132
- del st.session_state.messages # 메시지 세션 상태 삭제
 
 
 
133
  st.rerun()
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  # 채팅 메시지 표시
 
136
  for message in st.session_state.messages:
137
  if message["role"] != "system":
138
  role_class = "chat-message-user" if message["role"] == "user" else "chat-message-assistant"
139
  avatar_url = user_icon_url if message["role"] == "user" else assistant_icon_url
140
  avatar_class = "chat-avatar-user" if message["role"] == "user" else "chat-avatar"
 
 
 
141
  st.markdown(
142
- f"<div class='chat-message {role_class}'><img src='{avatar_url}' class='chat-avatar {avatar_class}'>{message['content']}</div>",
 
 
 
 
 
 
143
  unsafe_allow_html=True
144
  )
145
 
 
146
  # 사용자 입력 받기
147
- if prompt := st.chat_input("📝학습 내용을 입력하면 AI 튜터가 질문통해서 개념을 이해하도록 도와줍니다."):
148
  # 사용자의 메시지를 세션에 추가
149
  st.session_state.messages.append({"role": "user", "content": prompt})
150
- st.markdown(f"<div class='chat-message chat-message-user'>{prompt}<img src='{user_icon_url}' class='chat-avatar chat-avatar-user'></div>", unsafe_allow_html=True)
151
 
152
- # OpenAI API 호출
153
- response = openai_chat(prompt, grade_level)
154
- st.session_state.messages.append({"role": "assistant", "content": response})
155
- st.markdown(f"<div class='chat-message chat-message-assistant'><img src='{assistant_icon_url}' class='chat-avatar'>{response}</div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import streamlit as st
4
 
5
  # OpenAI API 설정
6
+ # Streamlit Cloud secrets에서 API 키 가져오기
7
+ openai_api_key = st.secrets["OPENAI_API_KEY"]
8
+ # 로컬 환경 변수에서 API 키 가져오기 (선택 사항)
9
+ # openai_api_key = os.getenv("OPENAI_API_KEY")
10
+
11
+ # API 키가 있는지 확인
12
+ if not openai_api_key:
13
+ st.error("OpenAI API 키가 설정되지 않았습니다. 환경 변수나 Streamlit secrets에 키를 추가해주세요.")
14
+ st.stop()
15
+
16
  openai.api_key = openai_api_key
17
 
18
+ def generate_smart_system_prompt(grade_level):
19
+ """학년 수준에 맞는 SMART 목표 설정 시스템 프롬프트를 생성합니다."""
20
+
21
+ # 학년 정보 추출 (예: "초등학교 6학년" -> "6")
22
  try:
 
 
23
  if grade_level.startswith("초등학교"):
24
+ grade_num = grade_level.split(" ")[1].rstrip("학년")
25
+ school_level = "초등학교"
26
  elif grade_level.startswith("중학교"):
27
+ grade_num = grade_level.split(" ")[1].rstrip("학년")
28
+ school_level = "중학교"
29
  elif grade_level.startswith("고등학교"):
30
+ grade_num = grade_level.split(" ")[1].rstrip("학년")
31
+ school_level = "고등학교"
32
+ else:
33
+ grade_num = "알 수 없음"
34
+ school_level = "알 수 없음"
35
+ except IndexError:
36
+ grade_num = "알 수 없음"
37
+ school_level = "알 수 없음"
38
+
39
+ # 초등학교 6학년에 특화된, 그리고 SMART 목표 설정에 초점을 맞춘 프롬프트
40
+ # (요청에 따라 초등학교 6학년에 더 집중하도록 수정)
41
+ if school_level == "초등학교" and grade_num == "6":
42
+ prompt = f"""
43
+ 너는 초등학교 6학년 학생이 SMART 목표를 세우고 실천 계획을 만들도록 돕는 친절하고 격려하는 코치 선생님이야.
44
+ 학생의 이름은 부르지 않고, '친구' 또는 '학생'이라고 불러줘. 반말로 친근하게 대화해줘.
45
+ 학생이 이루고 싶은 목표나 상황을 이야기하면, 그 목표가 SMART 기준에 맞도록 자연스럽게 질문을 던져줘.
46
+ SMART는 목표를 더 명확하고 달성 가능하게 만드는 방법이야:
47
+ - S (Specific - 구체적인): 목표가 명확하고 자세한가? 무엇을 이루고 싶은지 정확히 아는 거야.
48
+ - M (Measurable - 측정 가능한): 목표를 달성했는지 어떻게 알 수 있을까? 숫자로 표현할 수 있으면 좋아.
49
+ - A (Achievable - 달성 가능한): 이 목표를 실제로 이룰 수 있을까? 너무 어렵거나 쉽지 않게 설정하는 거야.
50
+ - R (Relevant - 관련성 있는): 이 목표가 왜 중요할까? 나에게 의미가 있는 목표여야 해.
51
+ - T (Time-bound - 시간 제한이 있는): 언제까지 이 목표를 이루고 싶니? 마감일을 정하는 거야.
52
+
53
+ 절대 네가 목표나 계획�� 직접 제시하거나 정답을 알려주지 마.
54
+ 대신, 학생 스스로 생각하고 답을 찾도록 소크라테스식 질문을 사용해줘. 예를 들면:
55
+ - "우와, 좋은 생각인데! 그 목표를 조금 더 자세하게 설명해 줄 수 있을까?" (Specific 유도)
56
+ - "목표를 이루면 어떤 모습일지 상상해볼래? 그걸 어떻게 확인할 수 있을까?" (Measurable 유도)
57
+ - "그 목표를 이루려면 어떤 노력이 필요할까? 혹시 도움이 필요한 부분이 있을까?" (Achievable 유도)
58
+ - "이 목표가 친구에게 왜 그렇게 중요해?" (Relevant 유도)
59
+ - "언제까지 그 목표를 딱! 이루고 싶어?" (Time-bound 유도)
60
+ - "좋아, 그럼 이제 그 목표를 이루기 위해 어떤 작은 단계들을 하나씩 해볼 수 있을까?" (실천 계획 유도)
61
+
62
+ 학생이 목표를 정하는 과정에서 어려움을 느끼거나 주제에서 벗어나면 부드럽게 다시 목표 설정으로 이끌어줘.
63
+ 학생의 대답을 칭찬하고 격려하며 자신감을 심어줘.
64
+ 한 번에 너무 많은 질문을 하지 말고, 학생의 대답을 듣고 다음 질문으로 넘어가줘.
65
+
66
+ 학생이 SMART 기준에 맞춰 목표를 구체화하고, 그 목표를 달성하기 위한 실천 계획 (최소 3가지 구체적인 행동)까지 스스로 만들었다고 판단되면,
67
+ 마지막에 학생이 직접 세운 내용을 명확하게 요약해서 보여줘.
68
+ 예시: "정말 멋지다! 친구가 직접 세운 SMART 목표와 실천 계획을 함께 정리해볼까? \n\n**🎯 SMART 목표:** [학생이 정의한 구체적이고, 측정 가능하며, 달성 가능하고, 관련성 있고, 시간 제한이 있는 목표 요약]\n\n**👣 실천 계획:**\n1. [학생이 정의한 첫 번째 실천 단계]\n2. [학생이 정의한 두 번째 실천 단계]\n3. [학생이 정의한 세 번째 실천 단계]\n\n이렇게 계획을 세우니 목표가 훨씬 가까워진 느낌이지? 꾸준히 실천하면 꼭 이룰 수 있을 거야! 선생님이 응원할게! 😊"
69
+ 요약하기 전에는 "이제 목표랑 계획이 다 세워진 것 같은데, 선생님이 한번 정리해볼까?" 와 같이 학생의 동의를 구하는 질문을 해줘.
70
+ """
71
+ else:
72
+ # 다른 학년 또는 기본 프롬프트 (SMART 목표 설정으로 유지하되, 언어 수준 조정 가능)
73
+ prompt = f"""
74
+ 너는 {grade_level} 학생이 SMART 목표를 세우고 실천 계획을 만들도록 돕는 친절하고 격려하는 코치야.
75
+ 학생이 이루고 싶은 목표나 상황을 이야기하면, 그 목표가 SMART(Specific, Measurable, Achievable, Relevant, Time-bound) 기준에 맞도록 질문을 통해 안내해줘.
76
+ 절대 직접적인 제안이나 답을 주지 말고, 학생 스스로 생각하고 답을 찾도록 유도해줘.
77
+ 학생의 학년 수준에 맞는 언어를 사용하고, 한 번에 한 가지씩 질문하며 대화를 이끌어가줘.
78
+ 학생이 SMART 목표와 실천 계획을 모두 수립했다고 판단되면, 마지막에 학생이 만든 내용을 요약해서 정리해주고 격려해줘.
79
+ 요약 전에는 학생에게 먼저 정리해도 될지 물어봐줘.
80
+ """
81
+ return prompt
82
+
83
+ def openai_chat(grade_level):
84
+ """OpenAI API를 호출하여 채팅 응답을 생성합니다."""
85
+ try:
86
+ # 현재 선택된 학년 수준에 맞는 시스템 프롬프트 생성
87
+ system_prompt = generate_smart_system_prompt(grade_level)
88
 
89
+ # 세션 상태의 첫 번째 메시지스템 지인지 확인하고 업데이트
90
+ if not st.session_state.messages or st.session_state.messages[0]["role"] != "system":
91
+ # 시스템 메시지가 없으면 맨 앞에 추가
92
+ st.session_state.messages.insert(0, {"role": "system", "content": system_prompt})
93
  else:
94
+ # 기존 시스템 메시지 내용 업데이트
95
+ st.session_state.messages[0]["content"] = system_prompt
96
 
97
  response = openai.ChatCompletion.create(
98
  model="gpt-4o",
99
  messages=st.session_state.messages, # 전체 대화 세션 전달
100
+ temperature=0.7, # 창의성과 일관성 조절
101
+ max_tokens=1000, # 응답 길이절 (SMART 목표 설 대화에 충분하도록)
102
  top_p=0.9,
103
+ frequency_penalty=0.1, # 약간의 반복 방지
104
+ presence_penalty=0.1 # 약간의 새로운 주제 도입 허용
105
  )
106
  return response.choices[0].message["content"]
107
  except Exception as e:
108
+ st.error(f"API 호출 중 오류 발생: {str(e)}")
109
+ return None # 오류 발생 시 None 반환
110
 
111
+ # --- Streamlit 앱 UI 설정 ---
112
+ st.set_page_config(page_title="SMART 목표 설정 도우미", page_icon="🎯", initial_sidebar_state="expanded")
113
 
114
+ # 페이지 스타일 커스터마이징 (기존 스타일 유지 또는 약간 수정)
115
  st.markdown(
116
  """
117
  <style>
118
  /* 전체 배경색 설정 */
119
  .stApp {
120
+ background-color: #f0f8ff; /* 부드러운 하늘색 배경 */
121
  }
122
  /* 타이틀 스타일 */
123
  .main-title {
124
+ font-size: 2.5rem; /* 약간 작게 조정 */
125
+ color: #4682b4; /* 차분한 파란색 */
126
  font-weight: 700;
127
  text-align: center;
128
  margin-bottom: 20px;
 
133
  padding: 15px;
134
  margin: 10px 0;
135
  display: flex;
136
+ align-items: flex-start; /* 아이콘과 텍스트 상단 정렬 */
137
+ flex-wrap: nowrap; /* 줄바꿈 방지 */
138
  word-break: break-word;
139
+ max-width: 85%; /* 메시지 최대 너비 설정 */
140
  }
141
  .chat-message-user {
142
+ background-color: #e0f7fa; /* 밝은 청록색 */
143
+ color: #00796b; /* 어두운 청록색 */
144
+ margin-left: auto; /* 오른쪽 정렬 */
145
+ flex-direction: row-reverse; /* 아이콘 오른쪽으로 */
146
  }
147
  .chat-message-assistant {
148
+ background-color: #fff0f5; /* 라벤더 블 (핑크계열) */
149
+ color: #c71585; /* 미디엄 바이올렛 레드 */
150
+ margin-right: auto; /* 왼쪽 정렬 */
151
+ flex-direction: row; /* 아이콘 왼쪽 (기본값) */
152
  }
153
  .chat-avatar {
154
  width: 40px;
155
  height: 40px;
156
  border-radius: 50%;
157
  margin-right: 10px;
158
+ flex-shrink: 0; /* 아이콘 크기 고정 */
159
  }
160
  .chat-avatar-user {
161
  margin-left: 10px;
162
  margin-right: 0;
163
  }
164
+ .chat-content {
165
+ flex-grow: 1; /* 텍스트 영역이 남은 공간 차지 */
166
+ }
167
  /* 사용자 입력 창 스타일 */
168
  .stTextInput input {
169
  border-radius: 15px;
170
+ border: 2px solid #add8e6; /* 밝은 파란색 */
171
  }
172
  /* 버튼 스타일 */
173
  .stButton button {
174
+ background-color: #4682b4; /* 차분파란색 */
175
  color: #fff;
176
  border-radius: 15px;
177
  padding: 10px 20px;
 
182
  )
183
 
184
  # 메인 타이틀
185
+ st.markdown("<div class='main-title'>🎯 SMART 목표 설정 도우미 ✍️</div>", unsafe_allow_html=True)
 
 
 
 
 
 
186
 
187
+ # --- 드바 설정 ---
 
 
 
 
188
  with st.sidebar:
189
+ st.header("⚙️ 설정")
190
+ # 학년 수준 선택 (초등학교 6학년 기본 선택)
191
  grade_level_options = [
192
  "초등학교 1학년", "초등학교 2학년", "초등학교 3학년", "초등학교 4학년", "초등학교 5학년", "초등학교 6학년",
193
  "중학교 1학년", "중학교 2학년", "중학교 3학년",
194
  "고등학교 1학년", "고등학교 2학년", "고등학교 3학년"
195
  ]
196
+ # 초등학교 6학년 인덱스 찾기
197
+ try:
198
+ default_index = grade_level_options.index("초등학교 6학년")
199
+ except ValueError:
200
+ default_index = 0 # 혹시 목록에 없으면 첫 번째 항목 선택
201
+
202
+ grade_level = st.selectbox(
203
+ "👤 학생의 학년을 선택하세요:",
204
+ grade_level_options,
205
+ index=default_index # 초등학교 6학년을 기본값으로 설정
206
+ )
207
+
208
  # 초기화 버튼
209
+ if st.button("🔄 대화 초기화"):
210
+ # 메시지 기록 삭제 및 시스템 프롬프트 재생성 준비
211
+ st.session_state.messages = []
212
+ st.success("대화 내용이 초기화되었습니다. 새로운 목표를 설정해보세요!")
213
+ # 페이지 새로고침 없이 즉시 적용되도록 rerun 사용
214
  st.rerun()
215
 
216
+ st.info("💡 AI 코치가 질문을 통해 스스로 SMART 목표와 실천 계획을 세우도록 도와줄 거예요!")
217
+
218
+
219
+ # --- 채팅 로직 ---
220
+
221
+ # 채팅 세션 초기화 (기본 시스템 메시지 없이 시작, openai_chat에서 동적으로 추가)
222
+ if "messages" not in st.session_state:
223
+ st.session_state.messages = []
224
+ # 필요하다면 여기에 초기 환영 메시지 추가 가능
225
+ # st.session_state.messages.append({"role": "assistant", "content": "안녕하세요! 어떤 목표를 세우고 싶나요? 함께 이야기해봐요!"})
226
+
227
+
228
+ # 사용자와 AI의 아이콘 URL 설정
229
+ user_icon_url = "https://cdn-icons-png.flaticon.com/512/1995/1995531.png" # 학생 아이콘
230
+ assistant_icon_url = "https://cdn-icons-png.flaticon.com/512/4323/4323008.png" # 튜터 아이콘
231
+
232
  # 채팅 메시지 표시
233
+ # 시스템 메시지는 화면에 표시하지 않음
234
  for message in st.session_state.messages:
235
  if message["role"] != "system":
236
  role_class = "chat-message-user" if message["role"] == "user" else "chat-message-assistant"
237
  avatar_url = user_icon_url if message["role"] == "user" else assistant_icon_url
238
  avatar_class = "chat-avatar-user" if message["role"] == "user" else "chat-avatar"
239
+ align_class = "chat-message-user" if message["role"] == "user" else "chat-message-assistant"
240
+
241
+ # HTML 구조 수정하여 아이콘과 내용 분리
242
  st.markdown(
243
+ f"""
244
+ <div class='chat-message {align_class}'>
245
+ {'<div class="chat-content">' + message['content'] + '</div>' if message["role"] == "assistant" else ""}
246
+ <img src='{avatar_url}' class='chat-avatar {avatar_class}'>
247
+ {'<div class="chat-content">' + message['content'] + '</div>' if message["role"] == "user" else ""}
248
+ </div>
249
+ """,
250
  unsafe_allow_html=True
251
  )
252
 
253
+
254
  # 사용자 입력 받기
255
+ if prompt := st.chat_input("🎯 이루고 싶은 목표나 하고 싶은 일적어보세요! (예: 수학 시험 잘 보기)"):
256
  # 사용자의 메시지를 세션에 추가
257
  st.session_state.messages.append({"role": "user", "content": prompt})
 
258
 
259
+ # 사용자 메시지 즉시 화면에 표시
260
+ st.markdown(
261
+ f"""
262
+ <div class='chat-message chat-message-user'>
263
+ <img src='{user_icon_url}' class='chat-avatar chat-avatar-user'>
264
+ <div class="chat-content">{prompt}</div>
265
+ </div>
266
+ """,
267
+ unsafe_allow_html=True
268
+ )
269
+
270
+ # AI 응답 생성 및 표시
271
+ with st.spinner("AI 코치가 생각 중이에요... 🤔"):
272
+ # OpenAI API 호출 (이제 grade_level만 전달)
273
+ response = openai_chat(grade_level)
274
+
275
+ if response: # 응답이 성공적으로 생성된 경우
276
+ st.session_state.messages.append({"role": "assistant", "content": response})
277
+ # AI 응답 화면에 표시
278
+ st.markdown(
279
+ f"""
280
+ <div class='chat-message chat-message-assistant'>
281
+ <img src='{assistant_icon_url}' class='chat-avatar'>
282
+ <div class="chat-content">{response}</div>
283
+ </div>
284
+ """,
285
+ unsafe_allow_html=True
286
+ )
287
+ else:
288
+ # API 호출 실패 시, 사용자에게 알림 (오류 메시지는 openai_chat 내부에서 st.error로 표시됨)
289
+ # 실패한 사용자 메시지를 롤백할 수도 있음 (선택 사항)
290
+ # st.session_state.messages.pop()
291
+ pass
292
+
293
+ # 초기 메시지 추가 (세션이 비어있고, 아직 사용자가 입력하지 않았을 때)
294
+ if not st.session_state.messages:
295
+ # 초기 시스템 프롬프트 설정 (필수)
296
+ system_prompt = generate_smart_system_prompt(grade_level)
297
+ st.session_state.messages.append({"role": "system", "content": system_prompt})
298
+ # 초기 환영 메시지 추가
299
+ welcome_message = "안녕! 👋 나는 네 목표 설정을 도와줄 AI 코치 선생님이야. 이루고 싶은 목표나 하고 싶은 일이 있으면 나에게 이야기해 줄래? 같이 멋진 계획을 세워보자! 😊"
300
+ st.session_state.messages.append({"role": "assistant", "content": welcome_message})
301
+ # 환영 메시지 표시
302
+ st.markdown(
303
+ f"""
304
+ <div class='chat-message chat-message-assistant'>
305
+ <img src='{assistant_icon_url}' class='chat-avatar'>
306
+ <div class="chat-content">{welcome_message}</div>
307
+ </div>
308
+ """,
309
+ unsafe_allow_html=True
310
+ )