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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -31
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import time
3
  import google.generativeai as genai
@@ -46,7 +47,10 @@ generation_config = {
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(
@@ -58,6 +62,7 @@ try:
58
  # st.info(f"'{model_name}' 모델 로드 성공!") # 디버깅용
59
  except Exception as e:
60
  st.error(f"Gemini 모델 ('{model_name}') 로딩 중 오류 발생: {e}")
 
61
  st.stop()
62
 
63
  # --- 함수 정의 ---
@@ -79,13 +84,13 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
79
  - 초등학생이 이해하기 쉬운 단어와 문장 사용
80
  - 줄글 형식으로 작성, 문단 구분 명확히 (빈 줄 사용)
81
  - 각 문단의 첫 문장 또는 마지막 문장이 중심 문장이 되도록 작성
82
- - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (예: '**제목**')
83
 
84
  ## 2. 어휘 목록 작성
85
  - **선정 기준:** 본문 내용 중 초등학교 {grade}에게 어려울 수 있는 단어, 한자어, 학습 용어
86
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
87
  - **형식:** 설명문 본문 뒤에 '### 어휘 목록' 제목 아래 글머리 기호 목록으로 작성
88
- ```
89
  ### 어휘 목록
90
  * 단어1: 쉬운 설명/유의어
91
  * 단어2: 쉬운 설명/유의어
@@ -106,7 +111,7 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
106
  - **형식:**
107
  - '### 독해 문제' 제목 아래 문제 번호(1., 2., ...)와 함께 문제 제시
108
  - 모든 문제 출제 후, '### 정답' 제목 아래 문제 번호와 정답 명확히 제시
109
- ```
110
  ### 독해 문제
111
  1. [문제 내용 - 단답형 또는 5지 선다형]
112
  ① 선택지1 ② 선택지2 ③ 선택지3 ④ 선택지4 ⑤ 선택지5 (선다형의 경우)
@@ -121,53 +126,67 @@ def generate_text_and_questions(grade, num_paragraphs, sentences_per_paragraph,
121
 
122
  ## # 출력 요구사항
123
  - 위의 모든 지시사항(설명문, 어휘 목록, 독해 문제, 정답)을 **하나의 응답**으로 이어서 생성해주세요.
124
- - 각 섹션(제목, 본문, 어휘 목록, 독해 문제, 정답) 구분을 명확히 해주세요.
125
  - 모든 내용은 초등학교 {grade} 눈높이에 맞춰 작성해주세요.
126
  """
127
 
128
  full_response_text = "" # 전체 응답 저장 변수 초기화
129
  output_area = st.empty() # 스트리밍 출력을 위한 빈 공간 생성
 
130
 
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)}")
152
- # 오류 발생 시 이전 내용을 유지하기 위해 output_area 그대로
153
  return "" # 오류 시 빈 문자열 반환
154
 
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
 
@@ -190,7 +209,9 @@ with st.sidebar:
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
  # 문단 수 설정
196
  num_paragraphs = st.number_input("문단 수 (권장 3~5)", min_value=2, max_value=10, value=3)
@@ -234,19 +255,22 @@ if generate_button:
234
  with st.spinner("AI가 열심히 글을 쓰고 문제를 만들고 있어요... 잠시만 기다려주세요! 🤔✍️"):
235
  # 함수 호출 (출력은 함수 내부에서 스트리밍으로 처리됨)
236
  generated_content = generate_text_and_questions(
237
- grade,
238
  num_paragraphs,
239
  sentences_per_paragraph,
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 만 반환하게 수정 필요
 
1
+ # -*- coding: utf-8 -*-
2
  import os
3
  import time
4
  import google.generativeai as genai
 
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(
 
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
  # --- 함수 정의 ---
 
84
  - 초등학생이 이해하기 쉬운 단어와 문장 사용
85
  - 줄글 형식으로 작성, 문단 구분 명확히 (빈 줄 사용)
86
  - 각 문단의 첫 문장 또는 마지막 문장이 중심 문장이 되도록 작성
87
+ - **제목:** 글의 맨 처음에 내용을 잘 나타내는 제목을 **크고 굵은 글씨**로 작성 (Markdown 형식: '** 제목 **')
88
 
89
  ## 2. 어휘 목록 작성
90
  - **선정 기준:** 본문 내용 중 초등학교 {grade}에게 어려울 수 있는 단어, 한자어, 학습 용어
91
  - **설명 방식:** 해당 학년 수준에 맞는 쉬운 유의어 또는 풀이 제공
92
  - **형식:** 설명문 본문 뒤에 '### 어휘 목록' 제목 아래 글머리 기호 목록으로 작성
93
+ ```markdown
94
  ### 어휘 목록
95
  * 단어1: 쉬운 설명/유의어
96
  * 단어2: 쉬운 설명/유의어
 
111
  - **형식:**
112
  - '### 독해 문제' 제목 아래 문제 번호(1., 2., ...)와 함께 문제 제시
113
  - 모든 문제 출제 후, '### 정답' 제목 아래 문제 번호와 정답 명확히 제시
114
+ ```markdown
115
  ### 독해 문제
116
  1. [문제 내용 - 단답형 또는 5지 선다형]
117
  ① 선택지1 ② 선택지2 ③ 선택지3 ④ 선택지4 ⑤ 선택지5 (선다형의 경우)
 
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
 
 
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)
 
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 만 반환하게 수정 필요