ahm14 commited on
Commit
f724766
·
verified ·
1 Parent(s): 80ec719

Rename final_test3.py to app.py

Browse files
Files changed (1) hide show
  1. final_test3.py → app.py +316 -246
final_test3.py → app.py RENAMED
@@ -1,246 +1,316 @@
1
- import streamlit as st
2
- from langchain_groq import ChatGroq
3
- from langchain_core.output_parsers import StrOutputParser
4
- from langchain_core.prompts import ChatPromptTemplate
5
- from dotenv import load_dotenv
6
- import pytesseract
7
- from PIL import Image
8
- import pdfplumber
9
- import docx
10
- from io import BytesIO
11
- import logging
12
-
13
- # Load environment variables
14
- load_dotenv()
15
-
16
- # Initialize logging
17
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
18
-
19
- # Initialize LLM
20
- llm = ChatGroq(temperature=0.5, groq_api_key="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", model_name="llama3-8b-8192")
21
-
22
- # OCR Configuration for Pytesseract
23
- pytesseract.pytesseract.tesseract_cmd = r"/usr/bin/tesseract" # Adjust to your system's path
24
-
25
- # Enhanced OCR with configurable language option
26
- def extract_text_from_images(images, lang="eng"):
27
- ocr_text = ""
28
- for image in images:
29
- try:
30
- ocr_text += pytesseract.image_to_string(image, lang=lang).strip() + "\n"
31
- except Exception as e:
32
- logging.error(f"Error in OCR: {e}")
33
- return ocr_text.strip()
34
-
35
- # Function to extract text, images, tables, and formulas from PDF
36
- def extract_pdf_data(pdf_path):
37
- data = {"text": "", "tables": [], "images": []}
38
- try:
39
- with pdfplumber.open(pdf_path) as pdf:
40
- for page in pdf.pages:
41
- data["text"] += page.extract_text() or ""
42
- tables = page.extract_tables()
43
- for table in tables:
44
- data["tables"].append(table)
45
- for image in page.images:
46
- base_image = pdf.extract_image(image["object_number"])
47
- image_obj = Image.open(BytesIO(base_image["image"]))
48
- data["images"].append(image_obj)
49
- except Exception as e:
50
- logging.error(f"Error processing PDF: {e}")
51
- return data
52
-
53
- # Function to extract text from DOCX files
54
- def extract_docx_data(docx_file):
55
- try:
56
- doc = docx.Document(docx_file)
57
- text = "\n".join([para.text.strip() for para in doc.paragraphs if para.text.strip()])
58
- return text
59
- except Exception as e:
60
- logging.error(f"Error extracting DOCX content: {e}")
61
- return ""
62
-
63
- # Function to extract text from plain text files
64
- def extract_text_file_data(text_file):
65
- try:
66
- return text_file.read().decode("utf-8").strip()
67
- except Exception as e:
68
- logging.error(f"Error extracting TXT content: {e}")
69
- return ""
70
-
71
- # Function to process extracted content (PDF, DOCX, etc.)
72
- def process_content(file_data, file_type, lang="eng"):
73
- text = ""
74
- images = []
75
- if file_type == "pdf":
76
- pdf_data = extract_pdf_data(file_data)
77
- text = process_pdf_content(pdf_data)
78
- images = pdf_data["images"]
79
- elif file_type == "docx":
80
- text = extract_docx_data(file_data)
81
- elif file_type == "txt":
82
- text = extract_text_file_data(file_data)
83
- elif file_type in ["png", "jpg", "jpeg"]:
84
- image = Image.open(file_data)
85
- images.append(image)
86
-
87
- ocr_text = extract_text_from_images(images, lang)
88
- return text + "\n" + ocr_text
89
-
90
- # Function to process PDF content
91
- def process_pdf_content(pdf_data):
92
- ocr_text = extract_text_from_images(pdf_data["images"])
93
- combined_text = pdf_data["text"] + ocr_text
94
-
95
- table_text = ""
96
- for table in pdf_data["tables"]:
97
- table_rows = [" | ".join(str(cell) if cell else "" for cell in row) for row in table]
98
- table_text += "\n".join(table_rows) + "\n"
99
-
100
- return (combined_text + "\n" + table_text).strip()
101
-
102
- # Function to generate questions
103
- def generate_questions(question_type, subject_name, instructor, class_name, institution, syllabus_context, num_questions, difficulty_level):
104
- prompt_template = f"""
105
- Based on the following syllabus content, generate {num_questions} {question_type} questions. Ensure the questions are directly derived from the provided syllabus content.
106
-
107
- Subject: {subject_name}
108
- Instructor: {instructor}
109
- Class: {class_name}
110
- Institution: {institution}
111
- Syllabus Content: {syllabus_context}
112
-
113
- Difficulty Levels:
114
- - Remember: {difficulty_level.get('Remember', 0)}
115
- - Understand: {difficulty_level.get('Understand', 0)}
116
- - Apply: {difficulty_level.get('Apply', 0)}
117
- - Analyze: {difficulty_level.get('Analyze', 0)}
118
- - Evaluate: {difficulty_level.get('Evaluate', 0)}
119
- - Create: {difficulty_level.get('Create', 0)}
120
-
121
- Format questions as follows:
122
- Q1. ________________
123
-
124
- Q2. ________________
125
-
126
- ...
127
- """
128
- chain = (ChatPromptTemplate.from_template(prompt_template) | llm | StrOutputParser())
129
- try:
130
- return chain.invoke({})
131
- except Exception as e:
132
- logging.error(f"Error generating {question_type} questions: {e}")
133
- return ""
134
-
135
- # Function to generate answers
136
- def generate_answers(questions, syllabus_context):
137
- prompt = f"""
138
- Based on the provided syllabus content, generate detailed answers for the following questions. The answers must only be based on the syllabus content.
139
-
140
- Syllabus Content: {syllabus_context}
141
-
142
- Questions:
143
- {questions}
144
-
145
- Format answers as follows:
146
- Answer 1: ________________
147
- Answer 2: ________________
148
- ...
149
- """
150
- chain = (ChatPromptTemplate.from_template(prompt) | llm | StrOutputParser())
151
- try:
152
- return chain.invoke({})
153
- except Exception as e:
154
- logging.error(f"Error generating answers: {e}")
155
- return ""
156
-
157
- # Streamlit app
158
- st.title("Bloom's Taxonomy Based Exam Paper Developer")
159
-
160
- # Sidebar Clear Data Button
161
- if st.sidebar.button("Clear All Data"):
162
- st.session_state.clear()
163
- st.success("All data has been cleared. You can now upload a new syllabus.")
164
-
165
- # Syllabus Upload with Automatic Clearing
166
- uploaded_file = st.sidebar.file_uploader(
167
- "Upload Syllabus (PDF, DOCX, TXT, Image)",
168
- type=["pdf", "docx", "txt", "png", "jpg"]
169
- )
170
-
171
- # Sidebar Inputs for Subject Name, Instructor, Class, and Institution
172
- subject_name = st.sidebar.text_input("Enter Subject Name", "Subject Name")
173
- instructor_name = st.sidebar.text_input("Enter Instructor Name", "Instructor Name")
174
- class_name = st.sidebar.text_input("Enter Class Name", "Class Name")
175
- institution_name = st.sidebar.text_input("Enter Institution Name", "Institution Name")
176
-
177
- # Language Option for OCR
178
- ocr_lang = st.sidebar.selectbox("Select OCR Language", ["eng", "spa", "fra", "deu", "ita"])
179
-
180
- if uploaded_file:
181
- # Clear session state when a new file is uploaded
182
- if "uploaded_filename" in st.session_state and st.session_state.uploaded_filename != uploaded_file.name:
183
- st.session_state.clear()
184
- st.success("Previous data cleared. Processing new file...")
185
-
186
- st.session_state.uploaded_filename = uploaded_file.name
187
- file_type = uploaded_file.type.split("/")[-1]
188
-
189
- # Validate file type
190
- if file_type not in ["pdf", "docx", "txt", "png", "jpg"]:
191
- st.error("Unsupported file type. Please upload PDF, DOCX, TXT, or image files.")
192
- else:
193
- syllabus_text = process_content(uploaded_file, file_type, lang=ocr_lang)
194
- st.session_state.syllabus_text = syllabus_text
195
-
196
- # Preview of Syllabus
197
- if "syllabus_text" in st.session_state:
198
- st.subheader("Syllabus Preview:")
199
- st.text_area("Extracted Content", st.session_state.syllabus_text[:1000], height=300)
200
- else:
201
- st.warning("Please upload a syllabus to begin.")
202
-
203
- # Question Type Selection
204
- question_type = st.sidebar.radio("Select Question Type", ("MCQs", "Short Questions", "Long Questions", "Fill in the Blanks", "Case Studies", "Diagram-based"))
205
- difficulty_levels = ["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]
206
- difficulty = {level: st.sidebar.slider(level, 0, 5, 1) for level in difficulty_levels}
207
- num_questions = st.sidebar.number_input("Number of Questions", min_value=1, max_value=50, value=10)
208
-
209
- if st.sidebar.button("Generate Questions"):
210
- if "syllabus_text" in st.session_state:
211
- with st.spinner(f"Generating {question_type}..."):
212
- syllabus_context = st.session_state.syllabus_text
213
- st.session_state.generated_questions = generate_questions(question_type, subject_name, instructor_name, class_name, institution_name, syllabus_context, num_questions, difficulty)
214
- st.text_area(f"Generated {question_type}", value=st.session_state.generated_questions, height=400)
215
- else:
216
- st.error("Please upload a syllabus before generating questions.")
217
-
218
- if st.sidebar.button("Generate Answers for Questions"):
219
- if "generated_questions" in st.session_state:
220
- with st.spinner("Generating answers..."):
221
- syllabus_context = st.session_state.syllabus_text
222
- st.session_state.generated_answers = generate_answers(st.session_state.generated_questions, syllabus_context)
223
- st.text_area("Generated Answers", value=st.session_state.generated_answers, height=400)
224
- else:
225
- st.error("Generate questions first before generating answers.")
226
-
227
- if "generated_questions" in st.session_state:
228
- st.sidebar.download_button(
229
- label="Download Questions",
230
- data=st.session_state.generated_questions,
231
- file_name=f"{subject_name}_questions.txt",
232
- mime="text/plain",
233
- )
234
-
235
- if "generated_answers" in st.session_state:
236
- st.sidebar.download_button(
237
- label="Download Answers",
238
- data=st.session_state.generated_answers,
239
- file_name=f"{subject_name}_answers.txt",
240
- mime="text/plain",
241
- )
242
-
243
- st.markdown("""
244
- ---
245
- **Advanced Test Paper Generator** - powered by LangChain, Pinecone, and Streamlit.
246
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from langchain_groq import ChatGroq
3
+ from langchain_core.output_parsers import StrOutputParser
4
+ from langchain_core.prompts import ChatPromptTemplate
5
+ from dotenv import load_dotenv
6
+ import pytesseract
7
+ from PIL import Image
8
+ import pdfplumber
9
+ import docx
10
+ from io import BytesIO
11
+ import logging
12
+ from docx import Document
13
+ from fpdf import FPDF
14
+
15
+ # Load environment variables
16
+ load_dotenv()
17
+
18
+ # Initialize logging
19
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
20
+
21
+ # Initialize LLM
22
+ llm = ChatGroq(temperature=0.5, groq_api_key="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", model_name="llama3-8b-8192")
23
+
24
+ # OCR Configuration for Pytesseract
25
+ pytesseract.pytesseract.tesseract_cmd = r"/usr/bin/tesseract" # Adjust to your system's path
26
+
27
+ # Enhanced OCR with configurable language option and multi-image support
28
+ def extract_text_from_images(images, lang="eng"):
29
+ ocr_text = ""
30
+ for image in images:
31
+ try:
32
+ ocr_text += pytesseract.image_to_string(image, lang=lang).strip() + "\n"
33
+ except Exception as e:
34
+ logging.error(f"Error in OCR: {e}")
35
+ return ocr_text.strip()
36
+
37
+ # Function to extract text, images, tables, and formulas from PDF
38
+ def extract_pdf_data(pdf_path):
39
+ data = {"text": "", "tables": [], "images": []}
40
+ try:
41
+ with pdfplumber.open(pdf_path) as pdf:
42
+ for page in pdf.pages:
43
+ data["text"] += page.extract_text() or ""
44
+ tables = page.extract_tables()
45
+ for table in tables:
46
+ data["tables"].append(table)
47
+ for image in page.images:
48
+ base_image = pdf.extract_image(image["object_number"])
49
+ image_obj = Image.open(BytesIO(base_image["image"]))
50
+ data["images"].append(image_obj)
51
+ except Exception as e:
52
+ logging.error(f"Error processing PDF: {e}")
53
+ return data
54
+
55
+ # Function to extract text from DOCX files
56
+ def extract_docx_data(docx_file):
57
+ try:
58
+ doc = docx.Document(docx_file)
59
+ text = "\n".join([para.text.strip() for para in doc.paragraphs if para.text.strip()])
60
+ return text
61
+ except Exception as e:
62
+ logging.error(f"Error extracting DOCX content: {e}")
63
+ return ""
64
+
65
+ # Function to extract text from plain text files
66
+ def extract_text_file_data(text_file):
67
+ try:
68
+ return text_file.read().decode("utf-8").strip()
69
+ except Exception as e:
70
+ logging.error(f"Error extracting TXT content: {e}")
71
+ return ""
72
+
73
+ # Function to process extracted content (PDF, DOCX, etc.)
74
+ def process_content(file_data, file_type, lang="eng"):
75
+ text = ""
76
+ images = []
77
+ if file_type == "pdf":
78
+ pdf_data = extract_pdf_data(file_data)
79
+ text = process_pdf_content(pdf_data)
80
+ images = pdf_data["images"]
81
+ elif file_type == "docx":
82
+ text = extract_docx_data(file_data)
83
+ elif file_type == "txt":
84
+ text = extract_text_file_data(file_data)
85
+ elif file_type in ["png", "jpg", "jpeg"]:
86
+ image = Image.open(file_data)
87
+ images.append(image)
88
+
89
+ ocr_text = extract_text_from_images(images, lang)
90
+ return text + "\n" + ocr_text
91
+
92
+ # Function to process PDF content
93
+ def process_pdf_content(pdf_data):
94
+ ocr_text = extract_text_from_images(pdf_data["images"])
95
+ combined_text = pdf_data["text"] + ocr_text
96
+
97
+ table_text = ""
98
+ for table in pdf_data["tables"]:
99
+ table_rows = [" | ".join(str(cell) if cell else "" for cell in row) for row in table]
100
+ table_text += "\n".join(table_rows) + "\n"
101
+
102
+ return (combined_text + "\n" + table_text).strip()
103
+
104
+ # Function to generate questions
105
+ def generate_questions(question_type, subject_name, instructor, class_name, institution, syllabus_context, num_questions, difficulty_level):
106
+ prompt_template = f"""
107
+ Based on the following syllabus content, generate {num_questions} {question_type} questions. Ensure the questions are directly derived from the provided syllabus content.
108
+ Subject: {subject_name}
109
+ Instructor: {instructor}
110
+ Class: {class_name}
111
+ Institution: {institution}
112
+ Syllabus Content: {syllabus_context}
113
+ Difficulty Levels:
114
+ - Remember: {difficulty_level.get('Remember', 0)}
115
+ - Understand: {difficulty_level.get('Understand', 0)}
116
+ - Apply: {difficulty_level.get('Apply', 0)}
117
+ - Analyze: {difficulty_level.get('Analyze', 0)}
118
+ - Evaluate: {difficulty_level.get('Evaluate', 0)}
119
+ - Create: {difficulty_level.get('Create', 0)}
120
+ Format questions as follows:
121
+ Q1. ________________
122
+ Q2. ________________
123
+ ...
124
+ """
125
+ chain = (ChatPromptTemplate.from_template(prompt_template) | llm | StrOutputParser())
126
+ try:
127
+ return chain.invoke({})
128
+ except Exception as e:
129
+ logging.error(f"Error generating {question_type} questions: {e}")
130
+ return ""
131
+
132
+ # Function to generate answers
133
+ def generate_answers(questions, syllabus_context):
134
+ prompt = f"""
135
+ Based on the provided syllabus content, generate detailed answers for the following questions. The answers must only be based on the syllabus content.
136
+ Syllabus Content: {syllabus_context}
137
+ Questions:
138
+ {questions}
139
+ Format answers as follows:
140
+ Answer 1: ________________
141
+ Answer 2: ________________
142
+ ...
143
+ """
144
+ chain = (ChatPromptTemplate.from_template(prompt) | llm | StrOutputParser())
145
+ try:
146
+ return chain.invoke({})
147
+ except Exception as e:
148
+ logging.error(f"Error generating answers: {e}")
149
+ return ""
150
+
151
+ # Function to download as DOCX
152
+ def download_as_docx(content, file_name="output.docx"):
153
+ doc = Document()
154
+ for line in content.split("\n"):
155
+ doc.add_paragraph(line)
156
+ buffer = BytesIO()
157
+ doc.save(buffer)
158
+ buffer.seek(0)
159
+ return buffer
160
+
161
+ # Function to download as PDF
162
+ def download_as_pdf(content, file_name="output.pdf"):
163
+ pdf = FPDF()
164
+ pdf.add_page()
165
+ pdf.set_font("Arial", size=12)
166
+ for line in content.split("\n"):
167
+ pdf.cell(200, 10, txt=line, ln=True)
168
+ buffer = BytesIO()
169
+ pdf.output(buffer)
170
+ buffer.seek(0)
171
+ return buffer
172
+
173
+ # Streamlit app with enhanced UI and multi-image upload support
174
+ st.title("Bloom's Taxonomy Based Exam Paper Developer")
175
+ st.markdown("""
176
+ ### A powerful tool to generate exam questions and answers using AI, based on syllabus content and Bloom's Taxonomy principles.
177
+ """)
178
+
179
+ # Sidebar Clear Data Button
180
+ if st.sidebar.button("Clear All Data"):
181
+ st.session_state.clear()
182
+ st.success("All data has been cleared. You can now upload a new syllabus.")
183
+
184
+ # Upload Syllabus and Multiple Images
185
+ uploaded_file = st.sidebar.file_uploader(
186
+ "Upload Syllabus (PDF, DOCX, TXT)",
187
+ type=["pdf", "docx", "txt"]
188
+ )
189
+
190
+ uploaded_images = st.sidebar.file_uploader(
191
+ "Upload Supplementary Images (PNG, JPG, JPEG)",
192
+ type=["png", "jpg", "jpeg"],
193
+ accept_multiple_files=True
194
+ )
195
+
196
+ # Sidebar Inputs for Subject Name, Instructor, Class, and Institution
197
+ subject_name = st.sidebar.text_input("Enter Subject Name", "Subject Name")
198
+ instructor_name = st.sidebar.text_input("Enter Instructor Name", "Instructor Name")
199
+ class_name = st.sidebar.text_input("Enter Class Name", "Class Name")
200
+ institution_name = st.sidebar.text_input("Enter Institution Name", "Institution Name")
201
+
202
+ # Language Option for OCR
203
+ ocr_lang = st.sidebar.selectbox("Select OCR Language", ["eng", "spa", "fra", "deu", "ita"])
204
+
205
+ # Process uploaded file and images
206
+ if uploaded_file or uploaded_images:
207
+ # Clear session state when new files are uploaded
208
+ if "uploaded_filename" in st.session_state and st.session_state.uploaded_filename != uploaded_file.name:
209
+ st.session_state.clear()
210
+ st.success("Previous data cleared. Processing new file...")
211
+
212
+ st.session_state.uploaded_filename = uploaded_file.name if uploaded_file else None
213
+
214
+ # Process syllabus file
215
+ if uploaded_file:
216
+ file_type = uploaded_file.type.split("/")[-1]
217
+ if file_type in ["pdf", "docx", "txt"]:
218
+ syllabus_text = process_content(uploaded_file, file_type, lang=ocr_lang)
219
+ st.session_state.syllabus_text = syllabus_text
220
+ else:
221
+ st.error("Unsupported file type. Please upload PDF, DOCX, or TXT files.")
222
+
223
+ # Process images
224
+ if uploaded_images:
225
+ image_text = extract_text_from_images([Image.open(img) for img in uploaded_images], lang=ocr_lang)
226
+ st.session_state.syllabus_text = st.session_state.get("syllabus_text", "") + "\n" + image_text
227
+
228
+ # Preview of Syllabus
229
+ if "syllabus_text" in st.session_state:
230
+ st.markdown("### Preview of Extracted Syllabus Content")
231
+ st.text_area("Extracted Syllabus Content", st.session_state.syllabus_text, height=300)
232
+
233
+ # Inputs for Question Generation
234
+ if "syllabus_text" in st.session_state:
235
+ st.markdown("### Generate Questions")
236
+ question_type = st.selectbox("Select Question Type", ["Multiple Choice", "Short Answer", "Essay"])
237
+ num_questions = st.number_input("Number of Questions", min_value=1, max_value=50, value=10)
238
+ difficulty_levels = {
239
+ "Remember": st.slider("Remember (%)", 0, 100, 20),
240
+ "Understand": st.slider("Understand (%)", 0, 100, 20),
241
+ "Apply": st.slider("Apply (%)", 0, 100, 20),
242
+ "Analyze": st.slider("Analyze (%)", 0, 100, 20),
243
+ "Evaluate": st.slider("Evaluate (%)", 0, 100, 10),
244
+ "Create": st.slider("Create (%)", 0, 100, 10),
245
+ }
246
+
247
+ if st.button("Generate Questions"):
248
+ with st.spinner("Generating questions..."):
249
+ questions = generate_questions(
250
+ question_type,
251
+ subject_name,
252
+ instructor_name,
253
+ class_name,
254
+ institution_name,
255
+ st.session_state.syllabus_text,
256
+ num_questions,
257
+ difficulty_levels,
258
+ )
259
+ st.session_state.generated_questions = questions
260
+ st.success("Questions generated successfully!")
261
+
262
+ # Display Generated Questions
263
+ if "generated_questions" in st.session_state:
264
+ st.markdown("### Generated Questions")
265
+ st.text_area("Questions", st.session_state.generated_questions, height=300)
266
+
267
+ if st.button("Generate Answers"):
268
+ with st.spinner("Generating answers..."):
269
+ answers = generate_answers(
270
+ st.session_state.generated_questions,
271
+ st.session_state.syllabus_text,
272
+ )
273
+ st.session_state.generated_answers = answers
274
+ st.success("Answers generated successfully!")
275
+
276
+ # Display Generated Answers
277
+ if "generated_answers" in st.session_state:
278
+ st.markdown("### Generated Answers")
279
+ st.text_area("Answers", st.session_state.generated_answers, height=300)
280
+
281
+ # Download Options
282
+ if "generated_questions" in st.session_state or "generated_answers" in st.session_state:
283
+ st.markdown("### Download Options")
284
+ download_choice = st.radio("Select Download Format", ["DOCX", "PDF", "TXT"])
285
+
286
+ content_to_download = ""
287
+ if "generated_questions" in st.session_state:
288
+ content_to_download += "Generated Questions:\n" + st.session_state.generated_questions + "\n\n"
289
+ if "generated_answers" in st.session_state:
290
+ content_to_download += "Generated Answers:\n" + st.session_state.generated_answers
291
+
292
+ if st.button("Download"):
293
+ if download_choice == "DOCX":
294
+ buffer = download_as_docx(content_to_download)
295
+ st.download_button(
296
+ label="Download as DOCX",
297
+ data=buffer,
298
+ file_name="exam_content.docx",
299
+ mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
300
+ )
301
+ elif download_choice == "PDF":
302
+ buffer = download_as_pdf(content_to_download)
303
+ st.download_button(
304
+ label="Download as PDF",
305
+ data=buffer,
306
+ file_name="exam_content.pdf",
307
+ mime="application/pdf",
308
+ )
309
+ elif download_choice == "TXT":
310
+ buffer = BytesIO(content_to_download.encode("utf-8"))
311
+ st.download_button(
312
+ label="Download as TXT",
313
+ data=buffer,
314
+ file_name="exam_content.txt",
315
+ mime="text/plain",
316
+ )