ll7098ll commited on
Commit
591d161
·
verified ·
1 Parent(s): 50e276f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -48
app.py CHANGED
@@ -5,81 +5,214 @@ import streamlit as st
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
  prompt = f"""
28
- 초등학교 {grade}학년 학생이 이해할 수 있는 수준의 설명문을 작성.
29
- 설명문의 주제는 {topic}이며, 주제를 중심으로 {structure} 구조에 따른 설명문을 작성할 것.
30
- 전체 문단 수는 {num_paragraphs}개 문단 내외, 문단 당 문장 수는 {sentences_per_paragraph}개 문장 내외.
31
- 설명문은 줄글로 작성하고 문단 구분을 명확히 할 것. 지정된 문단 수와 문단 당 문장 수를 준수하기.
32
- 문단마다, 문단의 첫 문장 또는 마지막 문장이 문단의 중심 문장이 되도록 할 것. (중심 문장이란 문단의 내용을 모두 포함할 수 있는 문장)
33
- 처음에 전체 글의 제목을 작성하고, 제목은 글자 크기가 본문보다는 크고 굵게 설정.
34
- 초등학교 {grade}학년 수준에 어려운 단어와 한자어, 학습 용어는 마지막에 어휘 목록과 어휘의 뜻을 초등학교 {grade}학년 수준에 맞게 유의어로 또는 뜻을 풀어 설명하는 부분을 따로 추가할 것.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  """
36
 
37
- full_text = "" # 전체 텍스트 저장 변수 초기화
 
 
38
  try:
39
  response = model.generate_content(prompt, stream=True)
40
  for chunk in response:
41
- full_text += chunk.text
42
- # Markdown to HTML 변환
43
- html_text = markdown.markdown(full_text, extensions=['tables'])
44
- output_area.markdown(html_text, unsafe_allow_html=True)
45
- time.sleep(0.1) # 지연 추가 (필요에 따라 조절)
 
 
46
 
47
  except Exception as e:
48
- st.error(f"에러 발생: {str(e)}")
49
- return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
 
 
 
 
51
 
52
- # 출력 복사 기능 추가
53
- copy_button = st.button("출력 내용 복사")
54
- if copy_button:
55
- pyperclip.copy(full_text)
56
- st.success("클립보드에 복사되었습니다!")
57
 
58
- return full_text
 
59
 
 
 
60
 
61
- # Streamlit 인터페이스 설정
62
- colored_header(
63
- label="📜 초등학생을 위한 읽기 자료 생성기",
64
- description="주제를 입력하면 초등학생이 이해하기 쉬운 읽기 자료를 만들어줍니다.",
65
- color_name="red-70",
66
- )
67
 
68
- add_vertical_space(1)
 
 
 
 
 
69
 
70
- with st.sidebar:
71
- st.header("옵션 설정")
72
- grade = st.selectbox("학년", [f"초등학교 {i}학년" for i in range(1, 7)]) # 드롭다운 수정
73
- num_paragraphs = st.number_input("문단 수", min_value=1, value=3)
74
- sentences_per_paragraph = st.number_input("문단 당 문장 수", min_value=1, value=3) # 드롭다운 수정
75
- structure = st.selectbox("설명문 구조", ["정의와 예시", "비교와 대조", "분류", "분석", "인과", "순서"], index=0)
76
- topic = st.text_area("✏️ 주제 및 내용을 입력하세요 ", height=200)
77
 
78
- generate_button = st.button("읽기 자료 생성")
79
 
80
- # 출력 영역을 함수 외부에 정의
81
- output_area = st.empty()
 
82
 
 
 
83
 
 
84
  if generate_button:
85
- generate_text(grade, num_paragraphs, sentences_per_paragraph, structure, topic)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from streamlit_extras.colored_header import colored_header
6
  from streamlit_extras.add_vertical_space import add_vertical_space
7
  import markdown
8
+ import pyperclip # pyperclip 임포트 추가
9
 
10
+ # --- 설정 ---
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,
26
  "top_p": 0.95,
27
  "top_k": 64,
28
+ "max_output_tokens": 8192, # 충분한 토큰 확보 (본문 + 문제 + 어휘)
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
+ # --- 함수 정의 ---
46
+
47
+ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph, structure, topic):
48
+ """
49
+ 주어진 조건에 따라 설명문 텍스트, 어휘 목록, 독해 문제를 생성하고 스트리밍으로 출력합니다.
50
+ """
51
+ # 상세화된 프롬프트: 설명문 작성 + 어휘 목록 + 독해 문제 출제 요청 추가
52
  prompt = f"""
53
+ # 지시사항
54
+
55
+ ## 1. 설명문 작성
56
+ - **대상 독자:** 초등학교 {grade}학년 학생
57
+ - **주제:** '{topic}'
58
+ - **글의 구조:** '{structure}' 구조를 명확히 따를
59
+ - **분량:** 전체 {num_paragraphs} 문단 내외, 문단 {sentences_per_paragraph} 문장 내외
60
+ - **스타일:**
61
+ - 초등학생이 이해하기 쉬운 단어와 문장 사용
62
+ - 줄글 형식으로 작성, 문단 구분 명확히 (빈 줄 사용)
63
+ - 각 문단의 첫 문장 또는 마지막 문장이 중심 문장이 되도록 작성
64
+ - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (예: '**제목**')
65
+
66
+ ## 2. 어휘 목록 작성
67
+ - **선정 기준:** 본문 내용 중 초등학교 {grade}학년에게 어려울 수 있는 단어, 한자어, 학습 용어
68
+ - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
69
+ - **형식:** 설명문 본문 뒤에 '### 어휘 목록' 제목 아래 글머리 기호 목록으로 작성
70
+ ```
71
+ ### 어휘 목록
72
+ * 단어1: 쉬운 설명/유의어
73
+ * 단어2: 쉬운 설명/유의어
74
+ ...
75
+ ```
76
+
77
+ ## 3. 독해 문제 및 정답 출제
78
+ - **출제 기반:** **반드시 위에서 생성된 설명문 내용만을 바탕으로** 출제
79
+ - **문제 수:** 총 7~8개
80
+ - **문제 유형 (균형 있게 포함):**
81
+ - **사실적 이해:** 글에 명시된 정보 확인 (누가, 무엇을, 언제, 어디서 등)
82
+ - **추론적 이해:** 글에 명시되지 않았지만 내용을 바탕으로 논리적으로 판단 가능한 내용 (왜, 그래서 어떻게 될까 등)
83
+ - **어휘:** 본문 또는 어휘 목록에 나온 단어의 문맥상 의미 파악
84
+ - **글의 구조:** 글 전체의 짜임, 문단 간 관계, 사용된 설명 방식 등 파악
85
+ - **문제 형식 (혼��� 사용):**
86
+ - **단답형:** 간단한 단어, 구, 짧은 문장으로 답하는 문제
87
+ - **5지 선다형:** 5개의 선택지(①, ②, ③, ④, ⑤) 중 정답을 고르는 문제 (오답 선택지도 그럴듯하게 구성)
88
+ - **형식:**
89
+ - '### 독해 문제' 제목 아래 문제 번호(1., 2., ...)와 함께 문제 제시
90
+ - 모든 문제 출제 후, '### 정답' 제목 아래 문제 번호와 정답 명확히 제시
91
+ ```
92
+ ### 독해 문제
93
+ 1. [문제 내용 - 단답형 또는 5지 선다형]
94
+ ① 선택지1 ② 선택지2 ③ 선택지3 ④ 선택지4 ⑤ 선택지5 (선다형의 경우)
95
+ 2. [문제 내용 - 단답형 또는 5지 선다형]
96
+ ... (총 7~8개)
97
+
98
+ ### 정답
99
+ 1. [정답 내용 또는 번호]
100
+ 2. [정답 내용 또는 번호]
101
+ ...
102
+ ```
103
+
104
+ ## # 출력 요구사항
105
+ - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
106
+ - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 명확히 해주세요.
107
+ - 모든 내용은 초등학교 {grade}학년 눈높이에 맞춰 작성해주세요.
108
  """
109
 
110
+ full_response_text = "" # 전체 응답 저장 변수 초기화
111
+ output_area = st.empty() # 스트리밍 출력을 위한 빈 공간 생성
112
+
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)}")
126
+ # 오류 발생 시 이전 내용을 유지하기 위해 output_area를 그대로 둠
127
+ return "" # 오류 시 빈 문자열 반환
128
+
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="📜 초등학생 맞춤 읽기 자료 생성기+",
149
+ description="주제와 조건을 입력하면 설명문, 어휘 학습, 독해 문제까지 한번에 만들어줍니다.",
150
+ color_name="blue-70", # 색상 변경
151
+ )
152
+ add_vertical_space(1)
153
 
154
+ # 사이드바: 옵션 설정
155
+ with st.sidebar:
156
+ st.header("⚙️ 생성 옵션 설정")
157
+ add_vertical_space(1)
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
+ # 문단 수 설정
166
+ num_paragraphs = st.number_input("문단 수 (권장 3~5)", min_value=2, max_value=10, value=3)
167
 
168
+ # 문단 당 문장 수 설정
169
+ sentences_per_paragraph = st.number_input("문단 당 문장 수 (권장 3~5)", min_value=2, max_value=10, value=4)
170
 
171
+ # 설명문 구조 선택
172
+ structure = st.selectbox(
173
+ "설명 방식",
174
+ ["정의와 예시", "비교와 대조", "분류와 구분", "분석 (구성 요소)", "인과 (원인과 결과)", "서사 (시간 순서 또는 과정)"],
175
+ index=0 # 기본값 '정의와 예시'
176
+ )
177
 
178
+ # 주제 입력
179
+ topic = st.text_area(
180
+ "✏️ 글의 주제를 입력하세요",
181
+ height=150,
182
+ placeholder="예) 지구 온난화, 스마트폰의 장단점, 우리나라의 사계절, 재활용의 중요성"
183
+ )
184
 
185
+ add_vertical_space(2)
186
+ st.caption("ℹ️ 주제가 명확하고 구체적일수록 좋은 결과가 나옵니다.")
 
 
 
 
 
187
 
 
188
 
189
+ # 메인 화면: 생성 버튼 및 출력 영역
190
+ st.subheader("✨ 읽기 자료 생성 결과")
191
+ add_vertical_space(1)
192
 
193
+ # 생성 버튼
194
+ generate_button = st.button("🚀 읽기 자료 생성하기", type="primary", use_container_width=True)
195
 
196
+ # 생성 버튼 클릭 시 로직 실행
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(
206
+ grade,
207
+ num_paragraphs,
208
+ sentences_per_paragraph,
209
+ structure,
210
+ topic
211
+ )
212
+ # 생성 완료 후 별도 메시지 (필요시)
213
+ # if generated_content:
214
+ # st.success("읽기 자료 생성이 완료되었습니다!")
215
+
216
+ # 만약 스트리밍 출력을 함수 밖에서 제어하고 싶다면,
217
+ # output_area = st.empty() 를 여기 두고, 함수는 text chunk 만 반환하게 수정 필요
218
+ # 하지만 현재 구조(함수 내에서 스트리밍 처리 및 버튼 표시)가 더 간결함.