ll7098ll commited on
Commit
7a656d6
·
verified ·
1 Parent(s): fd8a968

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -24
app.py CHANGED
@@ -11,15 +11,30 @@ import pyperclip # pyperclip 임포트 추가
11
 
12
  # Google Gemini API 값 설정 (Streamlit secrets 또는 환경 변수 사용 권장)
13
  # 로컬 테스트 시: os.environ["GEMINI_API_KEY"] = "YOUR_API_KEY"
 
14
  try:
15
- genai.configure(api_key=st.secrets["GEMINI_API_KEY"])
16
- except Exception: # 로컬 테스트 또는 secrets 미설정 시 환경변수 확인
 
 
 
 
 
17
  try:
18
- genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
 
 
19
  except KeyError:
20
- st.error("GEMINI_API_KEY가 설정되지 않았습니다. Streamlit secrets 또는 환경 변수를 확인하세요.")
 
21
  st.stop() # API 키 없으면 앱 중지
22
 
 
 
 
 
 
23
  # 모델 설정
24
  generation_config = {
25
  "temperature": 0.7,
@@ -29,17 +44,20 @@ generation_config = {
29
  "response_mime_type": "text/plain",
30
  }
31
 
32
- # 최신 안정 모델 사용 권장 (필요시 모델명 변경)
33
- model_name = "gemini-2.5-flash-preview-04-17"
 
34
 
35
  try:
36
  model = genai.GenerativeModel(
37
  model_name=model_name,
38
  generation_config=generation_config,
39
- # safety_settings 설정 가능 (필요시)
 
40
  )
 
41
  except Exception as e:
42
- st.error(f"Gemini 모델 로딩 중 오류 발생: {e}")
43
  st.stop()
44
 
45
  # --- 함수 정의 ---
@@ -53,7 +71,7 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
53
  # 지시사항
54
 
55
  ## 1. 설명문 작성
56
- - **대상 독자:** 초등학교 {grade}학년 학생
57
  - **주제:** '{topic}'
58
  - **글의 구조:** '{structure}' 구조를 명확히 따를 것
59
  - **분량:** 전체 {num_paragraphs}개 문단 내외, 문단 당 {sentences_per_paragraph}개 문장 내외
@@ -64,7 +82,7 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
64
  - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (예: '**제목**')
65
 
66
  ## 2. 어휘 목록 작성
67
- - **선정 기준:** 본문 내용 중 초등학교 {grade}학년에게 어려울 수 있는 단어, 한자어, 학습 용어
68
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
69
  - **형식:** 설명문 본문 뒤에 '### 어휘 목록' 제목 아래 글머리 기호 목록으로 작성
70
  ```
@@ -104,7 +122,7 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
104
  ## # 출력 요구사항
105
  - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
106
  - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 명확히 해주세요.
107
- - 모든 내용은 초등학교 {grade}학년 눈높이에 맞춰 작성해주세요.
108
  """
109
 
110
  full_response_text = "" # 전체 응답 저장 변수 초기화
@@ -113,13 +131,21 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
113
  try:
114
  response = model.generate_content(prompt, stream=True)
115
  for chunk in response:
116
- if chunk.text: # 청크가 아닌 경우에만 처리
117
- full_response_text += chunk.text
 
 
118
  # Markdown을 HTML로 변환하여 스트리밍 출력 (표 확장 기능 포함)
119
- html_text = markdown.markdown(full_response_text, extensions=['markdown.extensions.tables', 'markdown.extensions.fenced_code', 'markdown.extensions.nl2br'])
120
  # nl2br 확장 기능으로 마크다운 줄바꿈(\n)을 <br>로 변환
 
121
  output_area.markdown(html_text, unsafe_allow_html=True)
122
  time.sleep(0.05) # 스트리밍 지연 (조절 가능)
 
 
 
 
 
 
123
 
124
  except Exception as e:
125
  st.error(f"텍스트 생성 중 에러 발생: {str(e)}")
@@ -129,20 +155,26 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
129
  # 생성 완료 후 복사 버튼 표시
130
  st.markdown("---") # 구분선 추가
131
  if full_response_text: # 생성된 내용이 있을 때만 복사 버튼 표시
 
 
 
132
  copy_button = st.button("📄 생성된 내용 전체 복사")
133
  if copy_button:
134
  try:
 
135
  pyperclip.copy(full_response_text)
136
- st.success("클립보드에 복사되었습니다!")
137
  except Exception as e:
138
- st.warning(f"클립보드 복사 중 오류 발생: {e}\n수동으로 복사해주세요.")
139
  # 복사 실패 시 수동 복사를 위해 텍스트 영역 추가 표시
140
- st.text_area("복사할 내용:", full_response_text, height=200)
141
 
142
- return full_response_text # 전체 생성된 텍스트 반환
143
 
144
  # --- Streamlit 인터페이스 설정 ---
145
 
 
 
146
  # 헤더 설정
147
  colored_header(
148
  label="📜 초등학생 맞춤 읽기 자료 생성기+",
@@ -158,8 +190,6 @@ with st.sidebar:
158
 
159
  # 학년 선택 (초1 ~ 초6)
160
  grade_options = [f"{i}학년" for i in range(1, 7)]
161
- # grade_full = st.selectbox("대상 학년", [f"초등학교 {i}학년" for i in range(1, 7)])
162
- # grade_number = grade_full.split(" ")[1][0] # 숫자만 추출 ('1', '2', ... '6') -> 모델에게 전달할 때는 'N학년' 형태가 더 자연스러울 수 있음
163
  grade = st.selectbox("대상 학년", grade_options, index=2) # 기본값 3학년
164
 
165
  # 문단 수 설정
@@ -179,11 +209,12 @@ with st.sidebar:
179
  topic = st.text_area(
180
  "✏️ 글의 주제를 입력하세요",
181
  height=150,
182
- placeholder="예) 지구 온난화, 스마트폰의 장단점, 우리나라의 사계절, 재활용의 중요성"
183
  )
184
 
185
  add_vertical_space(2)
186
  st.caption("ℹ️ 주제가 명확하고 구체적일수록 좋은 결과가 나옵니다.")
 
187
 
188
 
189
  # 메인 화면: 생성 버튼 및 출력 영역
@@ -197,9 +228,9 @@ generate_button = st.button("🚀 읽기 자료 생성하기", type="primary", u
197
  if generate_button:
198
  if not topic:
199
  st.warning("⚠️ 주제를 입력해주세요!")
200
- elif not genai.api_key:
201
- st.error("API 키가 설정되지 않았습니다. 사이드바 안내를 확인하세요.")
202
  else:
 
203
  with st.spinner("AI가 열심히 글을 쓰고 문제를 만들고 있어요... 잠시만 기다려주세요! 🤔✍️"):
204
  # 함수 호출 (출력은 함수 내부에서 스트리밍으로 처리됨)
205
  generated_content = generate_text_and_questions(
@@ -209,9 +240,13 @@ if generate_button:
209
  structure,
210
  topic
211
  )
212
- # 생성 완료 후 별도 메시지 (필요시)
213
  # if generated_content:
214
  # st.success("읽기 자료 생성이 완료되었습니다!")
 
 
 
 
215
 
216
  # 만약 스트리밍 출력을 함수 밖에서 제어하고 싶다면,
217
  # output_area = st.empty() 를 여기 두고, 함수는 text chunk 만 반환하게 수정 필요
 
11
 
12
  # Google Gemini API 값 설정 (Streamlit secrets 또는 환경 변수 사용 권장)
13
  # 로컬 테스트 시: os.environ["GEMINI_API_KEY"] = "YOUR_API_KEY"
14
+ api_key_configured = False
15
  try:
16
+ # secrets에서 먼저 시도
17
+ gemini_api_key = st.secrets["GEMINI_API_KEY"]
18
+ genai.configure(api_key=gemini_api_key)
19
+ api_key_configured = True
20
+ # st.success("Secrets에서 Gemini API 키 로드 성공!") # 디버깅용
21
+ except (KeyError, FileNotFoundError):
22
+ # secrets에 없으면 환경 변수에서 시도
23
  try:
24
+ gemini_api_key = os.environ["GEMINI_API_KEY"]
25
+ genai.configure(api_key=gemini_api_key)
26
+ api_key_configured = True
27
+ # st.info("환경 변수에서 Gemini API 키 로드 성공!") # 디버깅용
28
  except KeyError:
29
+ # 없으면 오류 메시지 표시
30
+ st.error("⚠️ Gemini API 키가 설정되지 않았습니다. Streamlit secrets 또는 환경 변수에 'GEMINI_API_KEY'를 설정해주세요.")
31
  st.stop() # API 키 없으면 앱 중지
32
 
33
+ # API 키 설정 확인 (만약을 위한 추가 검사)
34
+ if not api_key_configured:
35
+ st.error("API 키 설정에 실패했습니다. 애플리케이션을 중지합니다.")
36
+ st.stop()
37
+
38
  # 모델 설정
39
  generation_config = {
40
  "temperature": 0.7,
 
44
  "response_mime_type": "text/plain",
45
  }
46
 
47
+ # 사용할 모델 이름 설정 (변경 가능)
48
+ # 예: "gemini-1.5-flash-latest", "gemini-1.5-pro-latest"
49
+ model_name = "gemini-2.5-flash-preview-04-17" # 최신 flash 모델 사용 권장
50
 
51
  try:
52
  model = genai.GenerativeModel(
53
  model_name=model_name,
54
  generation_config=generation_config,
55
+ # safety_settings = Adjust safety settings
56
+ # See https://ai.google.dev/gemini-api/docs/safety-settings
57
  )
58
+ # st.info(f"'{model_name}' 모델 로드 성공!") # 디버깅용
59
  except Exception as e:
60
+ st.error(f"Gemini 모델 ('{model_name}') 로딩 중 오류 발생: {e}")
61
  st.stop()
62
 
63
  # --- 함수 정의 ---
 
71
  # 지시사항
72
 
73
  ## 1. 설명문 작성
74
+ - **대상 독자:** 초등학교 {grade} 학생
75
  - **주제:** '{topic}'
76
  - **글의 구조:** '{structure}' 구조를 명확히 따를 것
77
  - **분량:** 전체 {num_paragraphs}개 문단 내외, 문단 당 {sentences_per_paragraph}개 문장 내외
 
82
  - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (예: '**제목**')
83
 
84
  ## 2. 어휘 목록 작성
85
+ - **선정 기준:** 본문 내용 중 초등학교 {grade}에게 어려울 수 있는 단어, 한자어, 학습 용어
86
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
87
  - **형식:** 설명문 본문 뒤에 '### 어휘 목록' 제목 아래 글머리 기호 목록으로 작성
88
  ```
 
122
  ## # 출력 요구사항
123
  - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
124
  - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 명확히 해주세요.
125
+ - 모든 내용은 초등학교 {grade} 눈높이에 맞춰 작성해주세요.
126
  """
127
 
128
  full_response_text = "" # 전체 응답 저장 변수 초기화
 
131
  try:
132
  response = model.generate_content(prompt, stream=True)
133
  for chunk in response:
134
+ # 응답 후보(candidates)가 있고, 안에 내용(content)이 있고, 그 안에 부분(parts)이 있고, 그 안에 텍스트(text)가 있는지 확인
135
+ if chunk.candidates and chunk.candidates[0].content and chunk.candidates[0].content.parts and chunk.candidates[0].content.parts[0].text:
136
+ chunk_text = chunk.candidates[0].content.parts[0].text
137
+ full_response_text += chunk_text
138
  # Markdown을 HTML로 변환하여 스트리밍 출력 (표 확장 기능 포함)
 
139
  # nl2br 확장 기능으로 마크다운 줄바꿈(\n)을 <br>로 변환
140
+ html_text = markdown.markdown(full_response_text, extensions=['markdown.extensions.tables', 'markdown.extensions.fenced_code', 'markdown.extensions.nl2br'])
141
  output_area.markdown(html_text, unsafe_allow_html=True)
142
  time.sleep(0.05) # 스트리밍 지연 (조절 가능)
143
+ elif hasattr(chunk, 'prompt_feedback') and chunk.prompt_feedback.block_reason:
144
+ # 콘텐츠 차단 발생 시 사용자에게 알림
145
+ block_reason = chunk.prompt_feedback.block_reason
146
+ st.error(f"콘텐츠 생성 중 안전상의 이유로 차단되었습니다. 이유: {block_reason}")
147
+ output_area.error(f"⚠️ 콘텐츠 생성 중단됨 (이유: {block_reason}). 프롬프트나 주제를 수정하여 다시 시도해 보세요.")
148
+ return "" # 차단 시 빈 문자열 반환
149
 
150
  except Exception as e:
151
  st.error(f"텍스트 생성 중 에러 발생: {str(e)}")
 
155
  # 생성 완료 후 복사 버튼 표시
156
  st.markdown("---") # 구분선 추가
157
  if full_response_text: # 생성된 내용이 있을 때만 복사 버튼 표시
158
+ # full_response_text 를 직접 사용 (html_text는 렌더링용)
159
+ # st.code(full_response_text, language='markdown') # 디버깅용: 원본 마크다운 확인
160
+
161
  copy_button = st.button("📄 생성된 내용 전체 복사")
162
  if copy_button:
163
  try:
164
+ # 마크다운 형식으로 복사 (HTML 태그 제외)
165
  pyperclip.copy(full_response_text)
166
+ st.success("클립보드에 복사되었습니다! (마크다운 형식)")
167
  except Exception as e:
168
+ st.warning(f"클립보드 자동 복사 중 오류 발생: {e}\n아래 텍스트 영역에서 수동으로 복사해주세요.")
169
  # 복사 실패 시 수동 복사를 위해 텍스트 영역 추가 표시
170
+ st.text_area("복사할 내용 (마크다운):", full_response_text, height=200)
171
 
172
+ return full_response_text # 전체 생성된 텍스트 반환 (마크다운 원본)
173
 
174
  # --- Streamlit 인터페이스 설정 ---
175
 
176
+ st.set_page_config(layout="wide") # 넓은 레이아웃 사용
177
+
178
  # 헤더 설정
179
  colored_header(
180
  label="📜 초등학생 맞춤 읽기 자료 생성기+",
 
190
 
191
  # 학년 선택 (초1 ~ 초6)
192
  grade_options = [f"{i}학년" for i in range(1, 7)]
 
 
193
  grade = st.selectbox("대상 학년", grade_options, index=2) # 기본값 3학년
194
 
195
  # 문단 수 설정
 
209
  topic = st.text_area(
210
  "✏️ 글의 주제를 입력하세요",
211
  height=150,
212
+ placeholder="예) 지구 온난화, 스마트폰의 장단점, 우리나라의 사계절, 재활용의 중요성, 좋아하는 동물 설명하기, 독도의 중요성"
213
  )
214
 
215
  add_vertical_space(2)
216
  st.caption("ℹ️ 주제가 명확하고 구체적일수록 좋은 결과가 나옵니다.")
217
+ st.caption(f"현재 사용 모델: {model_name}") # 사용 중인 모델 표시
218
 
219
 
220
  # 메인 화면: 생성 버튼 및 출력 영역
 
228
  if generate_button:
229
  if not topic:
230
  st.warning("⚠️ 주제를 입력해주세요!")
231
+ # API 키 존재 여부는 시작 시점에 이미 확인 및 처리되었으므로 여기서 다시 확인할 필요 없음
 
232
  else:
233
+ # 주제 입력 확인 후 진행
234
  with st.spinner("AI가 열심히 글을 쓰고 문제를 만들고 있어요... 잠시만 기다려주세요! 🤔✍️"):
235
  # 함수 호출 (출력은 함수 내부에서 스트리밍으로 처리됨)
236
  generated_content = generate_text_and_questions(
 
240
  structure,
241
  topic
242
  )
243
+ # 생성 완료 후 별도 메시지 (필요시, 함수 내 복사 버튼 표시로 대체 가능)
244
  # if generated_content:
245
  # st.success("읽기 자료 생성이 완료되었습니다!")
246
+ # elif not generated_content: # 생성된 내용이 없는 경우 (오류 또는 차단)
247
+ # 오류 메시지는 함수 내부에서 이미 표시됨
248
+ # st.error("읽기 자료 생성에 실패했습니다.")
249
+ pass # 함수 내에서 이미 에러 또는 차단 메시지 표시
250
 
251
  # 만약 스트리밍 출력을 함수 밖에서 제어하고 싶다면,
252
  # output_area = st.empty() 를 여기 두고, 함수는 text chunk 만 반환하게 수정 필요