import streamlit as st import google.generativeai as genai from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import inch from reportlab.platypus import ( SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle, Image as RLImage, ) from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY, TA_LEFT, TA_RIGHT from reportlab.lib import colors from io import BytesIO from datetime import datetime import os from dotenv import load_dotenv import re import warnings import logging import json from typing import Tuple, Dict, Any, List import requests from PIL import Image import io
warnings.filterwarnings("ignore") os.environ.setdefault("GRPC_VERBOSITY", "ERROR") os.environ.setdefault("GLOG_minloglevel", "2") logging.getLogger("google").setLevel(logging.ERROR)
load_dotenv()
MIN_UNIVERSITY_NAME_LENGTH = 3 MIN_STUDENT_NAME_LENGTH = 2 MIN_STUDENT_ID_LENGTH = 3 MIN_PROGRAM_NAME_LENGTH = 5 MIN_SUBJECT_NAME_LENGTH = 3 MIN_TOPIC_LENGTH = 20 MAX_QUESTIONS = 10 CACHE_TTL = 3600
st.set_page_config( page_title="Assignment Maker By Ethicallogix", page_icon="π", layout="wide", initial_sidebar_state="expanded", )
st.markdown( """ <style> .stButton>button { border-radius: 10px; transition: all 0.3s ease; } .stButton>button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .reportview-container .main .block-container{ padding-top:1rem; } .success-box { padding: 1rem; border-radius: 0.5rem; background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; margin: 1rem 0; } </style> """, unsafe_allow_html=True, )
def initialize_session_state(): defaults = { "assignment_generated": False, "assignment_content": None, "generation_time": None, "total_generated": 0, "generation_history": [], "is_generating": False, "student_info": None, "section_images": {} } for key, value in defaults.items(): if key not in st.session_state: st.session_state[key] = value
initialize_session_state()
st.title("π Ethicallogix Assignment Maker") st.markdown( "
Generate professional academic assignments with AI-powered content and images
", unsafe_allow_html=True, ) st.markdown("---")with st.sidebar: st.header("Powered By Ethicallogix") api_key = os.getenv("GOOGLE_API_KEY", "")
# Gamma API Configuration
st.subheader("π¨ Image Generation")
gamma_api_key = st.text_input(
"Gamma API Key (Optional)",
type="password",
placeholder="Enter your Gamma API key",
help="Get your API key from https://gamma.app/api"
)
include_images = st.checkbox("Generate Images for Sections", value=False)
image_style = st.selectbox(
"Image Style",
["Academic", "Professional", "Modern", "Minimalist", "Detailed"],
index=0
)
gemini_model = "gemini-2.0-flash-exp"
st.subheader("βοΈ Advanced Options")
num_questions = st.slider("Number of Questions", 1, MAX_QUESTIONS, 3)
assignment_type = st.selectbox(
"Assignment Type",
[
"Assignment",
"Research Paper",
"Problem Solving",
"Essay",
"Case Study",
"Technical Report",
"Literature Review",
"Project Proposal",
],
)
word_count_preference = st.selectbox(
"Answer Length",
["Concise (200-300 words)", "Standard (400-600 words)", "Detailed (800-1000 words)"],
index=1,
)
difficulty = st.selectbox(
"Difficulty Level",
["Beginner", "Intermediate", "Advanced", "Expert"],
index=1
)
include_references = st.checkbox("Include Academic References (APA)", value=True)
include_examples = st.checkbox("Include Practical Examples", value=True)
include_learning_objectives = st.checkbox("Include Learning Objectives", value=False)
include_rubric = st.checkbox("Include Grading Rubric", value=False)
st.markdown("---")
st.subheader("βΉοΈ About")
st.markdown(
"""
**Features:**
- π QβA academic assignments
- π¨ AI-generated section images
- π― Optional learning objectives & rubric
- π Professional PDF output
- π¨ Formatted cover page
- π Page numbering
- πΎ Multiple export formats
"""
)
st.markdown("---")
if st.button("π Reset App", use_container_width=True):
for k in list(st.session_state.keys()):
del st.session_state[k]
st.rerun()
def generate_image_gamma(prompt: str, api_key: str, style: str = "Academic") -> BytesIO: """ Generate an image using Gamma API """ try: # Gamma API endpoint (replace with actual endpoint) url = "https://api.gamma.app/v1/images/generate"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# Enhance prompt with style
enhanced_prompt = f"{style} style: {prompt}. Clean, professional, suitable for academic document."
payload = {
"prompt": enhanced_prompt,
"width": 800,
"height": 500,
"format": "png"
}
response = requests.post(url, headers=headers, json=payload, timeout=30)
if response.status_code == 200:
return BytesIO(response.content)
else:
st.warning(f"Image generation failed: {response.status_code}")
return None
except Exception as e:
st.warning(f"Error generating image: {str(e)}")
return None
def extract_sections(content: str) -> List[Dict[str, str]]: """ Extract main sections and their titles from the generated content """ sections = [] lines = content.splitlines() current_section = None
for line in lines:
line = line.strip()
# Match ## headings (main sections)
if line.startswith("## "):
section_title = line.replace("## ", "").replace("**", "").strip()
# Skip references section
if "REFERENCE" not in section_title.upper():
current_section = {
"title": section_title,
"content": ""
}
sections.append(current_section)
elif current_section:
current_section["content"] += line + " "
return sections
def generate_image_prompt(section_title: str, section_content: str, subject: str) -> str: """ Generate a descriptive prompt for image generation based on section """ # Truncate content to first 200 chars for context content_snippet = section_content[:200]
prompt = f"Create an educational illustration for {subject} about {section_title}. "
prompt += f"Context: {content_snippet}... "
prompt += "Make it visually clear, informative, and suitable for academic materials."
return prompt
st.subheader("π Assignment Details")
col1, col2 = st.columns(2) with col1: university_name = st.text_input( "University Name *", placeholder="University of Lahore", help=f"Minimum {MIN_UNIVERSITY_NAME_LENGTH} characters" ) student_name = st.text_input( "Student Name *", placeholder="Muhammad Haseeb", help=f"Minimum {MIN_STUDENT_NAME_LENGTH} characters" ) student_id = st.text_input( "Student ID *", placeholder="F2024332157", help=f"Minimum {MIN_STUDENT_ID_LENGTH} characters" ) program_name = st.text_input( "Program Name *", placeholder="BS Data Science", help=f"Minimum {MIN_PROGRAM_NAME_LENGTH} characters" ) with col2: st.text_input("", placeholder="", disabled=True, label_visibility="hidden") subject_name = st.text_input( "Subject Name *", placeholder="Machine Learning", help=f"Minimum {MIN_SUBJECT_NAME_LENGTH} characters" ) instructor_name = st.text_input( "Instructor Name", placeholder="Dr. Sarah Johnson" ) semester = st.text_input( "Semester/Term", placeholder="Fall 2024" )
st.markdown("### π Assignment Topic / Prompt") assignment_topic = st.text_area( "Describe the topic or assignment brief *", height=140, placeholder="Example: Explain time complexity of sorting algorithms and implement merge sort in Python with detailed analysis and complexity comparison.", help=f"Minimum {MIN_TOPIC_LENGTH} characters" ) if assignment_topic: chars = len(assignment_topic) words = len(assignment_topic.split()) st.caption(f"π {chars} characters | {words} words")
def validate_inputs() -> List[str]: errors = [] if not university_name or len(university_name.strip()) < MIN_UNIVERSITY_NAME_LENGTH: errors.append(f"β University name must be at least {MIN_UNIVERSITY_NAME_LENGTH} characters.") if not student_name or len(student_name.strip()) < MIN_STUDENT_NAME_LENGTH: errors.append(f"β Student name must be at least {MIN_STUDENT_NAME_LENGTH} characters.") if not student_id or len(student_id.strip()) < MIN_STUDENT_ID_LENGTH: errors.append(f"β Student ID must be at least {MIN_STUDENT_ID_LENGTH} characters.") if not program_name or len(program_name.strip()) < MIN_PROGRAM_NAME_LENGTH: errors.append(f"β Program name must be at least {MIN_PROGRAM_NAME_LENGTH} characters.") if not subject_name or len(subject_name.strip()) < MIN_SUBJECT_NAME_LENGTH: errors.append(f"β Subject name must be at least {MIN_SUBJECT_NAME_LENGTH} characters.") if not assignment_topic or len(assignment_topic.strip()) < MIN_TOPIC_LENGTH: errors.append(f"β Assignment topic must be at least {MIN_TOPIC_LENGTH} characters.") if include_images and not gamma_api_key: errors.append(f"β Gamma API key required for image generation.") return errors
def build_prompt( topic: str, subject: str, num_qs: int, assign_type: str, diff_level: str, include_refs: bool, include_ex: bool, include_lo: bool, include_rub: bool, word_pref: str, ) -> Tuple[str, Dict[str, Any]]: word_count = "100-150" if "Concise" in word_pref: word_count = "100-150" elif "Detailed" in word_pref: word_count = "800-1000"
examples_instruction = "\n- Include practical examples and real-world applications." if include_ex else ""
lo_block = ""
if include_lo:
lo_block = """
[List 3β5 clear, measurable learning objectives that students should achieve after completing this assignment.] """
rubric_block = ""
if include_rub:
rubric_block = """
[Provide 4β5 criteria with brief descriptors for Excellent, Good, Satisfactory, and Poor performance (concise table style).] """
references_instruction = ""
if include_refs:
references_instruction = """
"""
prompt = f"""
You are an expert university professor and academic writer. Create a professional {assign_type} assignment suitable for {diff_level}-level students.
CRITICAL FORMATTING RULES:
- Use ## for main section headings (e.g., ## INTRODUCTION)
- Use ### for subsection headings (e.g., ### Importance of Biofertilizers)
- Do NOT use any "Question" or "Answer" format.
- Maintain consistent academic formatting and spacing.
- Write in formal academic English throughout.
Topic: {topic} Subject: {subject}
INSTRUCTIONS:
- Structure the assignment with clear sections and subsections.
- Each subsection should explain key aspects of the topic in a coherent, analytical manner.
- Maintain academic flow β introduction, main discussion (divided into logical subtopics), and conclusion.
- Use discipline-appropriate terminology and theoretical insights{examples_instruction}.
- Provide depth, evidence-based reasoning, and critical reflection.
- Each major section should contain approximately {word_count} words.
[Write short paragraph of introduction on 2-3 lines:
- Provide background and significance of the topic
- Explain its relevance to the academic discipline
- Outline the key concepts or challenges explored
- State the overall purpose and learning outcomes of the assignment]
{lo_block} {rubric_block}
[Organize this section into several subheadings and this section is short paragraph on 2-3 lines, e.g.:
Each subsection should elaborate comprehensively with academic reasoning and examples.]
[Write 1 paragraphs synthesizing the key insights from all sections and reflecting on the broader academic and practical significance of the topic.] """
meta = {
"word_count_range": word_count,
"examples_instruction": examples_instruction,
"num_questions": num_qs,
}
return prompt, meta
def generate_assignment( api_key: str, topic: str, subject: str, num_qs: int, assign_type: str, diff_level: str, include_refs: bool, include_ex: bool, include_lo: bool, include_rub: bool, model_name: str, word_pref: str, ) -> Tuple[str, float]: try: genai.configure(api_key=api_key) model = genai.GenerativeModel(model_name)
prompt, meta = build_prompt(
topic,
subject,
num_qs,
assign_type,
diff_level,
include_refs,
include_ex,
include_lo,
include_rub,
word_pref,
)
start = datetime.now()
response = model.generate_content(prompt)
end = datetime.now()
gen_time = (end - start).total_seconds()
return response.text, gen_time
except Exception as e:
msg = str(e)
if "API_KEY_INVALID" in msg or "invalid" in msg.lower():
return (
"β **API Key Error**: Your API key is invalid.\n\n"
"**Solution:** Get your key at: https://makersuite.google.com/app/apikey"
), 0.0
if "quota" in msg.lower() or "resource_exhausted" in msg.lower():
return (
"β **Quota Exceeded**: You've reached your API usage limits.\n\n"
"**Solution:** Check your quota at: https://console.cloud.google.com/"
), 0.0
if "timeout" in msg.lower():
return (
"β **Timeout Error**: Request took too long.\n\n"
"**Solution:** Try reducing word count or number of questions."
), 0.0
if "PERMISSION_DENIED" in msg:
return (
"β **Permission Error**: API key doesn't have permission to use this model.\n\n"
"**Solution:** Enable the Generative AI API in Google Cloud Console."
), 0.0
return f"β **Unexpected Error**: {msg}", 0.0
@st.cache_data(ttl=CACHE_TTL) def create_pdf_with_images(student_info_json: str, assignment_content: str, include_refs: bool, section_images_json: str) -> bytes: student_info = json.loads(student_info_json) section_images = json.loads(section_images_json) if section_images_json else {} buffer = create_pdf(student_info, assignment_content, include_refs, section_images) return buffer.getvalue()
def create_pdf(student_info: Dict[str, str], assignment_content: str, include_refs: bool, section_images: Dict[str, bytes] = None) -> BytesIO: buffer = BytesIO() doc = SimpleDocTemplate( buffer, pagesize=letter, topMargin=0.8 * inch, bottomMargin=0.8 * inch, leftMargin=0.9 * inch, rightMargin=0.9 * inch, )
styles = getSampleStyleSheet()
# Enhanced styles
title_style = ParagraphStyle(
"Title",
parent=styles["Heading1"],
fontSize=26,
alignment=TA_CENTER,
textColor=colors.HexColor("#1a2384"),
fontName="Helvetica-Bold",
spaceAfter=10,
)
subtitle_style = ParagraphStyle(
"Subtitle",
parent=styles["Normal"],
fontSize=12,
alignment=TA_CENTER,
textColor=colors.HexColor("#000000"),
spaceAfter=14,
fontName="Helvetica-Bold",
)
info_style = ParagraphStyle(
"Info",
parent=styles["Normal"],
fontSize=10,
alignment=TA_LEFT,
textColor=colors.HexColor("#000000"),
)
heading_style = ParagraphStyle(
"Heading",
parent=styles["Heading2"],
fontSize=12,
alignment=TA_LEFT,
textColor=colors.HexColor("#0a0a0a"),
fontName="Helvetica-Bold",
spaceBefore=12,
spaceAfter=6,
)
main_heading_style = ParagraphStyle(
"MainHeading",
parent=styles["Heading1"],
fontSize=14,
alignment=TA_LEFT,
textColor=colors.HexColor("#000000"),
fontName="Helvetica-Bold",
spaceBefore=16,
spaceAfter=10,
)
question_style = ParagraphStyle(
"Question",
parent=styles["Normal"],
fontSize=11,
alignment=TA_LEFT,
fontName="Helvetica-Bold",
textColor=colors.HexColor("#1565c0"),
spaceBefore=10,
spaceAfter=6,
)
answer_style = ParagraphStyle(
"Answer",
parent=styles["Normal"],
fontSize=11,
alignment=TA_JUSTIFY,
leading=16,
spaceAfter=8,
textColor=colors.HexColor("#2c3e50"),
)
small_style = ParagraphStyle(
"Small",
parent=styles["Normal"],
fontSize=9,
alignment=TA_LEFT,
textColor=colors.HexColor("#020202"),
leading=12,
)
story = []
# Enhanced cover page
story.append(Spacer(1, 0.4 * inch))
story.append(Paragraph(student_info.get("university", "UNIVERSITY").upper(), title_style))
story.append(Spacer(1, 0.1 * inch))
story.append(Paragraph("ACADEMIC ASSIGNMENT", subtitle_style))
story.append(Paragraph(student_info.get("subject", ""), subtitle_style))
story.append(Spacer(1, 0.15 * inch))
# Student information table
student_table = [
["Student Name:", student_info.get("name", "")],
["Student ID:", student_info.get("id", "")],
["Program:", student_info.get("program", "")],
["Instructor:", student_info.get("instructor", "N/A")],
["Semester / Term:", student_info.get("semester", "N/A")],
["Submission Date:", datetime.now().strftime("%B %d, %Y")],
]
tbl = Table(student_table, colWidths=[2.0 * inch, 4.8 * inch], hAlign="CENTER")
tbl.setStyle(
TableStyle(
[
("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#e8f0fe")),
("TEXTCOLOR", (0, 0), (-1, -1), colors.HexColor("#2c3e50")),
("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
("FONTNAME", (1, 0), (1, -1), "Helvetica"),
("FONTSIZE", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("TOPPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#b8d4f1")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
]
)
)
story.append(tbl)
story.append(Spacer(1, 0.3 * inch))
story.append(Paragraph("_" * 80, small_style))
story.append(Spacer(1, 0.2 * inch))
story.append(PageBreak())
# Main content parsing and formatting
content_lines = assignment_content.splitlines()
in_references = False
current_main_heading = None
for raw_line in content_lines:
line = raw_line.strip()
if not line:
continue
clean_line = line.replace("**", "")
if re.match(r"^##\s*REFERENCES", clean_line.upper()):
in_references = True
# Handle main headings (##)
if line.startswith("## "):
clean_line = line.replace("## ", "").replace("**", "")
current_main_heading = clean_line
story.append(Paragraph(clean_line.upper(), main_heading_style))
# Add image if available for this section
if section_images and clean_line.upper() in section_images:
try:
img_data = section_images[clean_line.upper()]
img = RLImage(img_data, width=5*inch, height=3*inch)
story.append(Spacer(1, 0.1 * inch))
story.append(img)
story.append(Spacer(1, 0.2 * inch))
except Exception as e:
pass
continue
if line.startswith("### "):
clean_line = line.replace("### ", "").replace("**", "")
story.append(Paragraph(clean_line, heading_style))
continue
if re.match(r"^Subheading:\s*", clean_line, re.IGNORECASE):
subheading_text = re.sub(r"^Subheading:\s*", "", clean_line, flags=re.IGNORECASE).strip()
story.append(Paragraph(subheading_text, heading_style))
continue
if re.match(r"^\*\*Question:\*\*", line, re.IGNORECASE):
clean_line = re.sub(r"^\*\*Question:\*\*", "Question:", line, flags=re.IGNORECASE)
story.append(Paragraph(clean_line, question_style))
continue
if re.match(r"^\*\*Answer:\*\*", line, re.IGNORECASE):
clean_line = re.sub(r"^\*\*Answer:\*\*", "Answer:", line, flags=re.IGNORECASE)
story.append(Paragraph(clean_line, question_style))
continue
if re.match(r"^\*\*(INTRODUCTION|LEARNING OBJECTIVES|EVALUATION RUBRIC|REFERENCES|ASSIGNMENT BODY|CONCLUSION)[\*:]", clean_line.upper(), re.IGNORECASE):
clean_text = clean_line.replace("**", "").replace(":", "").strip().upper()
story.append(Paragraph(clean_text, main_heading_style))
continue
if re.match(r"^(INTRODUCTION:|LEARNING OBJECTIVES:|EVALUATION RUBRIC:|REFERENCES:|ASSIGNMENT BODY:|CONCLUSION:)", clean_line, re.IGNORECASE):
story.append(Paragraph(clean_line.upper(), main_heading_style))
continue
if re.match(r"^Q\d+[\.\)]", clean_line):
story.append(Paragraph(clean_line, question_style))
continue
if re.match(r"^Answer\s*\d+:", clean_line, re.IGNORECASE):
story.append(Paragraph(clean_line, question_style))
continue
if in_references and re.match(r"^\d+\.\s", clean_line):
story.append(Paragraph(clean_line, small_style))
continue
story.append(Paragraph(clean_line, answer_style))
# Page numbering callback
def add_page_number(canvas, doc):
page_num = canvas.getPageNumber()
text = f"Page {page_num}"
canvas.setFont("Helvetica", 9)
canvas.setFillColor(colors.HexColor("#6b7280"))
canvas.drawRightString(7.5 * inch, 0.55 * inch, text)
canvas.setStrokeColor(colors.HexColor("#d1d5db"))
canvas.setLineWidth(0.5)
canvas.line(0.9 * inch, 0.65 * inch, 7.6 * inch, 0.65 * inch)
doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
buffer.seek(0)
return buffer
st.markdown("---") generate_col1, generate_col2, generate_col3 = st.columns([1, 2, 1]) with generate_col2: generate_button = st.button( "π Generate Assignment", type="primary", use_container_width=True, disabled=st.session_state.is_generating )
if generate_button:
if not api_key or len(api_key.strip()) < 10:
st.error("β **API Key Required**: Please enter a valid Google Gemini API key in the sidebar.")
st.info("π‘ Get your free API key at: https://makersuite.google.com/app/apikey")
else:
errors = validate_inputs()
if errors:
st.error("**Please fix the following errors:**")
for err in errors:
st.markdown(err)
else:
st.session_state.is_generating = True
progress = st.progress(0, text="Initializing...")
try:
progress.progress(10, text="Building prompt...")
progress.progress(20, text=f"Connecting to {gemini_model}...")
assignment_content, gen_time = generate_assignment(
api_key=api_key,
topic=assignment_topic,
subject=subject_name,
num_qs=num_questions,
assign_type=assignment_type,
diff_level=difficulty,
include_refs=include_references,
include_ex=include_examples,
include_lo=include_learning_objectives,
include_rub=include_rubric,
model_name=gemini_model,
word_pref=word_count_preference,
)
progress.progress(60, text="Processing response...")
if assignment_content.startswith("β"):
st.error(assignment_content)
progress.empty()
else:
# Generate images if enabled
section_images = {}
if include_images and gamma_api_key:
progress.progress(70, text="Generating section images...")
sections = extract_sections(assignment_content)
for idx, section in enumerate(sections[:5]): # Limit to 5 sections
progress.progress(70 + (idx * 4), text=f"Generating image for: {section['title']}")
image_prompt = generate_image_prompt(
section['title'],
section['content'],
subject_name
)
img_buffer = generate_image_gamma(image_prompt, gamma_api_key, image_style)
if img_buffer:
section_images[section['title'].upper()] = img_buffer
st.session_state.section_images = section_images
progress.progress(90, text="Finalizing...")
# Save to session state
st.session_state.assignment_content = assignment_content
st.session_state.assignment_generated = True
st.session_state.student_info = {
"university": university_name,
"name": student_name,
"id": student_id,
"program": program_name,
"subject": subject_name,
"instructor": instructor_name or "N/A",
"semester": semester or "N/A",
}
st.session_state.total_generated += 1
st.session_state.generation_time = gen_time
# Add to history
st.session_state.generation_history.append({
"timestamp": datetime.now(),
"subject": subject_name,
"student": student_name,
"gen_time": gen_time
})
progress.progress(100, text="Complete! β¨")
st.success(f"β
**Assignment generated successfully!** (Generation time: {gen_time:.1f}s)")
if include_images and section_images:
st.info(f"π¨ Generated {len(section_images)} section images")
# Word count
word_count = len(assignment_content.split())
st.info(f"π **Content Statistics:** {word_count:,} words | {len(assignment_content):,} characters")
# Preview with enhanced display
with st.expander("π Preview Assignment Content", expanded=True):
# Show images in preview if available
if section_images:
sections = extract_sections(assignment_content)
for section in sections:
if section['title'].upper() in section_images:
st.markdown(f"### {section['title']}")
img_buffer = section_images[section['title'].upper()]
img_buffer.seek(0)
st.image(img_buffer, caption=f"Illustration for {section['title']}", use_container_width=True)
st.markdown(assignment_content)
# Create PDF and download options
st.markdown("### πΎ Download Options")
col1, col2, col3 = st.columns(3)
student_info_json = json.dumps(st.session_state.student_info)
section_images_json = json.dumps({k: v.getvalue() if hasattr(v, 'getvalue') else v for k, v in section_images.items()}) if section_images else ""
pdf_bytes = create_pdf_with_images(
student_info_json,
assignment_content,
include_references,
section_images_json
)
base_filename = f"{student_name.replace(' ', '_')}_{subject_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d')}"
with col1:
st.download_button(
"π₯ Download PDF",
data=pdf_bytes,
file_name=f"{base_filename}.pdf",
mime="application/pdf",
use_container_width=True,
)
with col2:
st.download_button(
"π₯ Download Markdown",
data=assignment_content,
file_name=f"{base_filename}.md",
mime="text/markdown",
use_container_width=True,
)
with col3:
st.download_button(
"π₯ Download Text",
data=assignment_content,
file_name=f"{base_filename}.txt",
mime="text/plain",
use_container_width=True,
)
progress.empty()
except Exception as e:
st.error(f"β **Unexpected Error**: {str(e)}")
st.info("π‘ Try refreshing the page or checking your API key.")
progress.empty()
finally:
st.session_state.is_generating = False
if st.session_state.assignment_generated and st.session_state.assignment_content and not st.session_state.is_generating: st.markdown("---") st.info("π‘ Previous assignment available below")
with st.expander("π View Previous Assignment", expanded=False):
word_count = len(st.session_state.assignment_content.split())
st.caption(f"π {word_count:,} words | Generated: {st.session_state.generation_history[-1]['timestamp'].strftime('%Y-%m-%d %H:%M')}")
# Show images if available
if st.session_state.section_images:
sections = extract_sections(st.session_state.assignment_content)
for section in sections:
if section['title'].upper() in st.session_state.section_images:
st.markdown(f"### {section['title']}")
img_buffer = st.session_state.section_images[section['title'].upper()]
if isinstance(img_buffer, BytesIO):
img_buffer.seek(0)
st.image(img_buffer, caption=f"Illustration for {section['title']}", use_container_width=True)
st.markdown(st.session_state.assignment_content)
if st.session_state.student_info:
st.markdown("### πΎ Download Previous Assignment")
col1, col2, col3 = st.columns(3)
student_info_json = json.dumps(st.session_state.student_info)
section_images_json = json.dumps({k: v.getvalue() if hasattr(v, 'getvalue') else v for k, v in st.session_state.section_images.items()}) if st.session_state.section_images else ""
pdf_bytes = create_pdf_with_images(
student_info_json,
st.session_state.assignment_content,
include_references,
section_images_json
)
prev_base_filename = f"{st.session_state.student_info['name'].replace(' ', '_')}_{st.session_state.student_info['subject'].replace(' ', '_')}_{datetime.now().strftime('%Y%m%d')}"
with col1:
st.download_button(
"π₯ PDF",
data=pdf_bytes,
file_name=f"{prev_base_filename}.pdf",
mime="application/pdf",
use_container_width=True,
)
with col2:
st.download_button(
"π₯ Markdown",
data=st.session_state.assignment_content,
file_name=f"{prev_base_filename}.md",
mime="text/markdown",
use_container_width=True,
)
with col3:
st.download_button(
"π₯ Text",
data=st.session_state.assignment_content,
file_name=f"{prev_base_filename}.txt",
mime="text/plain",
use_container_width=True,
)
if not st.session_state.assignment_generated: st.markdown("---") st.markdown("### π‘ Tips for Best Results")
tips_col1, tips_col2 = st.columns(2)
with tips_col1:
st.markdown("""
**Topic Guidelines:**
- Be specific and clear about requirements
- Include context and scope
- Mention any specific concepts to cover
- Specify format preferences (if any)
**Image Generation:**
- Enable images for visual learning enhancement
- Choose appropriate style for your subject
- Images are generated for main sections only
""")
with tips_col2:
st.markdown("""
**Quality Tips:**
- Choose appropriate difficulty level
- Enable examples for better understanding
- Include references for academic credibility
- Adjust word count based on depth needed
**API Keys:**
- Gemini API: Required for text generation
- Gamma API: Optional for image generation
- Keep your API keys secure
""")
st.markdown("---") st.markdown( """
Developed by Muhammad Haseeb
Version 2.1 | Enhanced Edition with Image Generation
with st.sidebar: st.markdown("---") st.markdown("### π Developer Details") st.markdown(""" This Application Developed by Muhammad Haseeb Raza
**Image Generation:**
- Powered by Gamma API
- Generates relevant illustrations for each section
- Enhances visual learning experience
- Optional feature - can be disabled
**Common Issues:**
- Ensure both API keys are valid
- Image generation may take longer
- Images are limited to 5 sections max
**Best Practices:**
- Save assignments immediately after generation
- Keep topics focused and specific
- Review and edit generated content
- Cite sources appropriately
- Use images to enhance understanding
""")
st.markdown("---")
st.markdown(
"""
<div style='text-align:center; font-size:0.8em; color:#9ca3af'>
Β© 2025 Ethicallogix<br>
All Rights Reserved
</div>
""",
unsafe_allow_html=True,
)