ll7098ll commited on
Commit
7fbccf5
·
verified ·
1 Parent(s): 34845b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -183
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
  import os
3
  import time
4
  import google.generativeai as genai
@@ -6,75 +5,31 @@ import streamlit as st
6
  from streamlit_extras.colored_header import colored_header
7
  from streamlit_extras.add_vertical_space import add_vertical_space
8
  import markdown
9
- import pyperclip # pyperclip 임포트 추가
10
 
11
- # --- 설정 ---
12
-
13
- # Google Gemini API 값 설정 (Streamlit secrets 또는 환경 변수 사용 권장)
14
- # 로컬 테스트 시: os.environ["GEMINI_API_KEY"] = "YOUR_API_KEY"
15
- api_key_configured = False
16
- try:
17
- # secrets에서 먼저 시도
18
- gemini_api_key = st.secrets["GEMINI_API_KEY"]
19
- genai.configure(api_key=gemini_api_key)
20
- api_key_configured = True
21
- # st.success("Secrets에서 Gemini API 키 로드 성공!") # 디버깅용
22
- except (KeyError, FileNotFoundError):
23
- # secrets에 없으면 환경 변수에서 시도
24
- try:
25
- gemini_api_key = os.environ["GEMINI_API_KEY"]
26
- genai.configure(api_key=gemini_api_key)
27
- api_key_configured = True
28
- # st.info("환경 변수에서 Gemini API 키 로드 성공!") # 디버깅용
29
- except KeyError:
30
- # 둘 다 없으면 오류 메시지 표시
31
- st.error("⚠️ Gemini API 키가 설정되지 않았습니다. Streamlit secrets 또는 환경 변수에 'GEMINI_API_KEY'를 설정해주세요.")
32
- st.stop() # API 키 없으면 앱 중지
33
-
34
- # API 키 설정 확인 (만약을 위한 추가 검사)
35
- if not api_key_configured:
36
- st.error("API 키 설정에 실패했습니다. 애플리케이션을 중지합니다.")
37
- st.stop()
38
 
39
  # 모델 설정
40
  generation_config = {
41
  "temperature": 0.7,
42
  "top_p": 0.95,
43
  "top_k": 64,
44
- "max_output_tokens": 8192, # 충분한 토큰 확보 (본문 + 문제 + 어휘)
45
  "response_mime_type": "text/plain",
46
  }
47
 
48
- # 사용할 모델 이름 설정 (변경 가능)
49
- # 예: "gemini-1.5-flash-latest", "gemini-1.5-pro-latest"
50
- # gemini-2.5-flash-preview-04-17 모델 이름이 유효하지 않을 수 있습니다.
51
- # 최신 flash 모델인 "gemini-1.5-flash-latest" 또는 pro 모델 "gemini-1.5-pro-latest" 사용을 권장합니다.
52
- # model_name = "gemini-2.5-flash-preview-04-17" # -> 유효하지 않을 가능성 있음
53
- model_name = "gemini-2.5-flash-preview-04-17" # 안정적인 최신 flash 모델로 변경
54
-
55
- try:
56
- model = genai.GenerativeModel(
57
- model_name=model_name,
58
- generation_config=generation_config,
59
- # safety_settings = Adjust safety settings
60
- # See https://ai.google.dev/gemini-api/docs/safety-settings
61
- )
62
- # st.info(f"'{model_name}' 모델 로드 성공!") # 디버깅용
63
- except Exception as e:
64
- st.error(f"Gemini 모델 ('{model_name}') 로딩 중 오류 발생: {e}")
65
- st.error("올바른 모델 이름인지 확인하거나 Google AI Studio에서 사용 가능한 모델 목록을 확인하세요.")
66
- st.stop()
67
-
68
- # --- 함수 정의 ---
69
 
70
- def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph, structure, topic):
71
  """
72
  주어진 조건에 따라 설명문 텍스트, 어휘 목록, 독해 문제를 생성하고 스트리밍으로 출력합니다.
73
  """
74
  # 상세화된 프롬프트: 설명문 작성 + 어휘 목록 + 독해 문제 출제 요청 추가
75
  prompt = f"""
76
  # 지시사항
77
-
78
  ## 1. 설명문 작성
79
  - **대상 독자:** 초등학교 {grade} 학생
80
  - **주제:** '{topic}'
@@ -85,7 +40,6 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
85
  - 줄글 형식으로 작성, 문단 구분 명확히 (빈 줄 사용)
86
  - 각 문단의 첫 문장 또는 마지막 문장이 중심 문장이 되도록 작성
87
  - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (Markdown 형식: '** 제목 **')
88
-
89
  ## 2. 어휘 목록 작성
90
  - **선정 기준:** 본문 내용 중 초등학교 {grade}에게 어려울 수 있는 단어, 한자어, 학습 용어
91
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
@@ -96,7 +50,6 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
96
  * 단어2: 쉬운 설명/유의어
97
  ...
98
  ```
99
-
100
  ## 3. 독해 문제 및 정답 출제
101
  - **출제 기반:** **반드시 위에서 생성된 설명문 내용만을 바탕으로** 출제
102
  - **문제 수:** 총 7~8개
@@ -117,161 +70,62 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
117
  ① 선택지1 ② 선택지2 ③ 선택지3 ④ 선택지4 ⑤ 선택지5 (선다형의 경우)
118
  2. [문제 내용 - 단답형 또는 5지 선다형]
119
  ... (총 7~8개)
120
-
121
  ### 정답
122
  1. [정답 내용 또는 번호]
123
  2. [정답 내�� 또는 번호]
124
  ...
125
  ```
126
-
127
  ## # 출력 요구사항
128
  - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
129
  - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 Markdown 제목(##, ###)과 빈 줄로 명확히 해주세요.
130
  - 모든 내용은 초등학교 {grade} 눈높이에 맞춰 작성해주세요.
131
- """
132
-
133
- full_response_text = "" # 전체 응답 저장 변수 초기화
134
- output_area = st.empty() # 스트리밍 출력을 위한 빈 공간 생성
135
- blocked = False # 콘텐츠 차단 플래그
136
 
 
137
  try:
138
  response = model.generate_content(prompt, stream=True)
139
  for chunk in response:
140
- # 프롬프트 피드백 확인 (차단 여부)
141
- if hasattr(chunk, 'prompt_feedback') and chunk.prompt_feedback.block_reason:
142
- block_reason = chunk.prompt_feedback.block_reason
143
- st.error(f"콘텐츠 생성 요청이 안전상의 이유로 차단되었습니다. 이유: {block_reason}")
144
- output_area.error(f"⚠️ 콘텐츠 생성 중단됨 (이유: {block_reason}). 프롬프트나 주제를 수정하여 다시 시도해 보세요.")
145
- blocked = True
146
- break # 차단 시 스트리밍 중단
147
-
148
- # 응답 텍스트 처리
149
- try:
150
- # 최신 API는 chunk.text로 바로 접근 가능할 수 있음
151
- chunk_text = chunk.text
152
- except AttributeError:
153
- # 이전 방식 호환 (candidates 확인)
154
- if chunk.candidates and chunk.candidates[0].content and chunk.candidates[0].content.parts and chunk.candidates[0].content.parts[0].text:
155
- chunk_text = chunk.candidates[0].content.parts[0].text
156
- else:
157
- # 예상치 못한 청크 형식일 경우 건너뛰기 또는 로깅
158
- # st.warning(f"Unexpected chunk format: {chunk}") # 디버깅용
159
- chunk_text = "" # 또는 continue
160
-
161
- if chunk_text:
162
- full_response_text += chunk_text
163
- # Markdown을 HTML로 변환하여 스트리밍 출력 (표, 코드 블록, 줄바꿈 지원)
164
- html_text = markdown.markdown(full_response_text, extensions=['markdown.extensions.tables', 'markdown.extensions.fenced_code', 'markdown.extensions.nl2br'])
165
- output_area.markdown(html_text, unsafe_allow_html=True)
166
- time.sleep(0.05) # 스트리밍 지연 (조절 가능)
167
 
168
  except Exception as e:
169
- st.error(f"텍스트 생성 중 에러 발생: {str(e)}")
170
- # 오류 발생 시 현재까지 생성된 내용을 유지하도록 output_area 수정하지 않음
171
- return "" # 오류 시 빈 문자열 반환
172
-
173
- # 스트리밍 완료 후 처리 (차단되지 않았을 경우)
174
- if not blocked and full_response_text:
175
- st.markdown("---") # 구분선 추가
176
- copy_button = st.button("📄 생성된 내용 전체 복사")
177
- if copy_button:
178
- try:
179
- # 마크다운 형식으로 복사
180
- pyperclip.copy(full_response_text)
181
- st.success("클립보드에 복사되었습니다! (마크다운 형식)")
182
- except Exception as e:
183
- # pyperclip 에러 처리 (예: 서버 환경 등에서 클립보드 접근 불가 시)
184
- st.warning(f"클립보드 자동 복사 중 오류 발생: {e}\n서버 환경에서는 클립보드 복사가 지원되지 않을 수 있습니다.\n아래 텍스트 영역에서 수동으로 복사해주세요.")
185
- # 복사 실패 시 수동 복사를 위해 텍스트 영역 추가 표시
186
- st.text_area("복사할 내용 (마크다운):", full_response_text, height=300)
187
 
188
- elif blocked:
189
- return "" # 차단 시 빈 문자열 반환
190
 
191
- return full_response_text # 전체 생성된 텍스트 반환 (마크다운 원본)
 
 
 
 
192
 
193
- # --- Streamlit 인터페이스 설정 ---
194
 
195
- st.set_page_config(layout="wide") # 넓은 레이아웃 사용
196
 
197
- # 헤더 설정
198
  colored_header(
199
- label="📜 초등학생 맞춤 읽기 자료 생성기+",
200
- description="주제와 조건을 입력하면 설명문, 어휘 학습, 독해 문제까지 한번에 만들어줍니다.",
201
- color_name="blue-70", # 색상 변경
202
  )
 
203
  add_vertical_space(1)
204
 
205
- # 사이드바: 옵션 설정
206
  with st.sidebar:
207
- st.header("⚙️ 생성 옵션 설정")
208
- add_vertical_space(1)
209
-
210
- # 학년 선택 (초1 ~ 초6)
211
- grade_options = [f"{i}학년" for i in range(1, 7)]
212
- # 문자열에서 숫자만 추출하도록 수정
213
- grade_str = st.selectbox("대상 학년", grade_options, index=2) # 기본값 3학년
214
- grade = grade_str.replace("학년", "") # '3학년' -> '3'
215
-
216
- # 문단 수 설정
217
- num_paragraphs = st.number_input("문단 수 (권장 3~5)", min_value=2, max_value=10, value=3)
218
 
219
- # 문단 문장 수 설정
220
- sentences_per_paragraph = st.number_input("문단 당 문장 수 (권장 3~5)", min_value=2, max_value=10, value=4)
221
 
222
- # 설명문 구조 선택
223
- structure = st.selectbox(
224
- "설명 방식",
225
- ["정의와 예시", "비교와 대조", "분류와 구분", "분석 (구성 요소)", "인과 (원인과 결과)", "서사 (시간 순서 또는 과정)"],
226
- index=0 # 기본값 '정의와 예시'
227
- )
228
 
229
- # 주제 입력
230
- topic = st.text_area(
231
- "✏️ 글의 주제를 입력하세요",
232
- height=150,
233
- placeholder="예) 지구 온난화, 스마트폰의 장단점, 우리나라의 사계절, 재활용의 중요성, 좋아하는 동물 설명하기, 독도의 중요성"
234
- )
235
 
236
- add_vertical_space(2)
237
- st.caption("ℹ️ 주제가 명확하고 구체적일수록 좋은 결과가 나옵니다.")
238
- st.caption(f"현재 사용 모델: {model_name}") # 사용 중인 모델 표시
239
-
240
-
241
- # 메인 화면: 생성 버튼 및 출력 영역
242
- st.subheader("✨ 읽기 자료 생성 결과")
243
- add_vertical_space(1)
244
-
245
- # 생성 버튼
246
- generate_button = st.button("🚀 읽기 자료 생성하기", type="primary", use_container_width=True)
247
-
248
- # 생성 버튼 클릭 시 로직 실행
249
  if generate_button:
250
- if not topic:
251
- st.warning("⚠️ 주제를 입력해주세요!")
252
- # API 키 존재 여부는 시작 시점에 이미 확인 및 처리되었으므로 여기서 다시 확인할 필요 없음
253
- else:
254
- # 주제 입력 확인 후 진행
255
- with st.spinner("AI가 열심히 글을 쓰고 문제를 만들고 있어요... 잠시만 기다려주세요! 🤔✍️"):
256
- # 함수 호출 (출력은 함수 내부에서 스트리밍으로 처리됨)
257
- generated_content = generate_text_and_questions(
258
- grade, # 숫자 학년 전달
259
- num_paragraphs,
260
- sentences_per_paragraph,
261
- structure,
262
- topic
263
- )
264
- # 생성 완료 후 별도 메시지는 함수 내에서 처리 (복사 버튼 표시 등)
265
- # 또는 스피너 완료 자체가 성공 표시가 됨
266
- # 주석 처리된 부분의 pass 삭제: IndentationError 수정
267
- # if generated_content:
268
- # st.success("읽기 자료 생성이 완료되었습니다!") # 성공 메시지 필요 시 주석 해제
269
- # elif not generated_content: # 생성된 내용이 없는 경우 (오류 또는 차단)
270
- # 오류 메시지는 함수 내부에서 이미 표시됨
271
- # st.error("읽기 자료 생성에 실패했습니다.")
272
- # pass # <- 이 줄이 IndentationError의 원인. 삭제됨.
273
- pass # 이 pass는 else 블록에 속하며, 현재는 아무 작업도 하지 않음을 나타냄 (필요시 로직 추가 가능)
274
-
275
- # 만약 스트리밍 출력을 함수 밖에서 제어하고 싶다면,
276
- # output_area = st.empty() 를 여기 두고, 함수는 text chunk 만 반환하게 수정 필요
277
- # 하지만 현재 구조(함수 내에서 스트리밍 처리 및 버튼 표시)가 더 간결함.
 
 
1
  import os
2
  import time
3
  import google.generativeai as genai
 
5
  from streamlit_extras.colored_header import colored_header
6
  from streamlit_extras.add_vertical_space import add_vertical_space
7
  import markdown
 
8
 
9
+ # Google Gemini API 값 설정
10
+ genai.configure(api_key=os.environ["GEMINI_API_KEY"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # 모델 설정
13
  generation_config = {
14
  "temperature": 0.7,
15
  "top_p": 0.95,
16
  "top_k": 64,
17
+ "max_output_tokens": 8000,
18
  "response_mime_type": "text/plain",
19
  }
20
 
21
+ model = genai.GenerativeModel(
22
+ model_name="gemini-2.0-flash-thinking-exp-01-21",
23
+ generation_config=generation_config,
24
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ def generate_text(grade, num_paragraphs, sentences_per_paragraph, structure, topic):
27
  """
28
  주어진 조건에 따라 설명문 텍스트, 어휘 목록, 독해 문제를 생성하고 스트리밍으로 출력합니다.
29
  """
30
  # 상세화된 프롬프트: 설명문 작성 + 어휘 목록 + 독해 문제 출제 요청 추가
31
  prompt = f"""
32
  # 지시사항
 
33
  ## 1. 설명문 작성
34
  - **대상 독자:** 초등학교 {grade} 학생
35
  - **주제:** '{topic}'
 
40
  - 줄글 형식으로 작성, 문단 구분 명확히 (빈 줄 사용)
41
  - 각 문단의 첫 문장 또는 마지막 문장이 중심 문장이 되도록 작성
42
  - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (Markdown 형식: '** 제목 **')
 
43
  ## 2. 어휘 목록 작성
44
  - **선정 기준:** 본문 내용 중 초등학교 {grade}에게 어려울 수 있는 단어, 한자어, 학습 용어
45
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
 
50
  * 단어2: 쉬운 설명/유의어
51
  ...
52
  ```
 
53
  ## 3. 독해 문제 및 정답 출제
54
  - **출제 기반:** **반드시 위에서 생성된 설명문 내용만을 바탕으로** 출제
55
  - **문제 수:** 총 7~8개
 
70
  ① 선택지1 ② 선택지2 ③ 선택지3 ④ 선택지4 ⑤ 선택지5 (선다형의 경우)
71
  2. [문제 내용 - 단답형 또는 5지 선다형]
72
  ... (총 7~8개)
 
73
  ### 정답
74
  1. [정답 내용 또는 번호]
75
  2. [정답 내�� 또는 번호]
76
  ...
77
  ```
 
78
  ## # 출력 요구사항
79
  - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
80
  - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 Markdown 제목(##, ###)과 빈 줄로 명확히 해주세요.
81
  - 모든 내용은 초등학교 {grade} 눈높이에 맞춰 작성해주세요.
 
 
 
 
 
82
 
83
+ full_text = "" # 전체 텍스트 저장 변수 초기화
84
  try:
85
  response = model.generate_content(prompt, stream=True)
86
  for chunk in response:
87
+ full_text += chunk.text
88
+ # Markdown to HTML 변환
89
+ html_text = markdown.markdown(full_text, extensions=['tables'])
90
+ output_area.markdown(html_text, unsafe_allow_html=True)
91
+ time.sleep(0.1) # 지연 추가 (필요에 따라 조절)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  except Exception as e:
94
+ st.error(f"에러 발생: {str(e)}")
95
+ return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
 
 
97
 
98
+ # 출력 복사 기능 추가
99
+ copy_button = st.button("출력 내용 복사")
100
+ if copy_button:
101
+ pyperclip.copy(full_text)
102
+ st.success("클립보드에 복사되었습니다!")
103
 
104
+ return full_text
105
 
 
106
 
107
+ # Streamlit 인터페이스 설정
108
  colored_header(
109
+ label="📜 초등학생을 위한 읽기 자료 생성기",
110
+ description="주제를 입력하면 초등학생이 이해하기 쉬운 읽기 자료를 만들어줍니다.",
111
+ color_name="red-70",
112
  )
113
+
114
  add_vertical_space(1)
115
 
 
116
  with st.sidebar:
117
+ st.header("옵션 설정")
118
+ grade = st.selectbox("학년", [f"초등학교 {i}학년" for i in range(1, 7)]) # 드롭다운 수정
119
+ num_paragraphs = st.number_input("문단 수", min_value=1, value=3)
120
+ sentences_per_paragraph = st.number_input("문단 당 문장 수", min_value=1, value=3) # 드롭다운 수정
121
+ structure = st.selectbox("설명문 구조", ["정의와 예시", "비교와 대조", "분류", "분석", "인과", "순서"], index=0)
122
+ topic = st.text_area("✏️ 주제 및 내용을 입력하세요 ", height=200)
 
 
 
 
 
123
 
124
+ generate_button = st.button("읽기 자료 생성")
 
125
 
126
+ # 출력 영역을 함수 외부에 정의
127
+ output_area = st.empty()
 
 
 
 
128
 
 
 
 
 
 
 
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  if generate_button:
131
+ generate_text(grade, num_paragraphs, sentences_per_paragraph, structure, topic)