GeneXam / app.py
Rathapoom's picture
Update app.py
58d3b83 verified
raw
history blame
10.8 kB
import streamlit as st
import openai
from openai import OpenAI
import time
import gspread
from oauth2client.service_account import ServiceAccountCredentials
# Set up OpenAI client
client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
# Google Sheets setup remains the same
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name("genexam-2c8c645ecc0d.json", scope)
client_gs = gspread.authorize(creds)
sheet = client_gs.open("GeneXam user").sheet1
def check_user_in_sheet(username):
try:
users_list = sheet.col_values(1)
if username in users_list:
return True
return False
except Exception as e:
st.error(f"Error checking user: {str(e)}")
return False
def update_api_usage(username):
try:
users_list = sheet.col_values(1)
row_number = users_list.index(username) + 1
api_usage = int(sheet.cell(row_number, 2).value)
api_usage += 1
sheet.update_cell(row_number, 2, api_usage)
except Exception as e:
st.error(f"Error updating API usage: {str(e)}")
def generate_questions_with_retry(knowledge_material, question_type, cognitive_level, extra_instructions, case_based, num_choices=None, max_retries=3):
# Adjust number of questions based on type
if question_type == "Multiple Choice":
num_questions = 3
format_instructions = f"""
For each multiple choice question:
1. Present the question clearly
2. Provide {num_choices} choices labeled with A, B, C{', D' if num_choices > 3 else ''}{', E' if num_choices > 4 else ''}
3. After all questions, provide an ANSWER KEY section with:
- The correct answer letter for each question
- A brief explanation of why this is the correct answer
- Why other options are incorrect
"""
elif question_type == "Fill in the Blank":
num_questions = 10
format_instructions = """
For each fill-in-the-blank question:
1. Present the question with a clear blank space indicated by _____
2. After all questions, provide an ANSWER KEY section with:
- The correct answer for each blank
- A brief explanation of why this answer is correct
- Any alternative acceptable answers if applicable
"""
elif question_type == "True/False":
num_questions = 5
format_instructions = """
For each true/false question:
1. Present the statement clearly
2. After all questions, provide an ANSWER KEY section with:
- Whether the statement is True or False
- A detailed explanation of why the statement is true or false
- The specific part of the source material that supports this answer
"""
else: # Open-ended
num_questions = 3
format_instructions = """
For each open-ended question:
1. Present the question clearly
2. After all questions, provide an ANSWER KEY section with:
- A structured scoring checklist of key points (minimum 3-5 points per question)
- Each key point should be worth a specific number of marks
- Total marks available for each question
- Sample answer that would receive full marks
- Common points that students might miss
"""
# Base prompt
prompt = f"""Generate {num_questions} {question_type.lower()} exam questions based on {cognitive_level.lower()} level from the following material:
{knowledge_material}
{format_instructions}
{extra_instructions}
Please format the output clearly with:
1. Questions section (numbered 1, 2, 3, etc.)
2. Answer Key section (clearly separated from questions)
3. Each answer should include explanation for better understanding
Make sure all questions and answers are directly related to the provided material."""
# Modify prompt for case-based medical situations
if case_based:
prompt = f"""Generate {num_questions} {question_type.lower()} case-based medical exam questions based on {cognitive_level.lower()} level.
Use this material as the medical knowledge base:
{knowledge_material}
Each question should:
1. Start with a medical case scenario/patient presentation
2. Include relevant clinical details
3. Ask about diagnosis, treatment, or management
4. Be at {cognitive_level.lower()} cognitive level
{format_instructions}
{extra_instructions}
Please format the output with:
1. Cases and Questions (numbered 1, 2, 3, etc.)
2. Detailed Answer Key section including:
- Correct answers
- Clinical reasoning
- Key diagnostic or treatment considerations
- Common pitfalls to avoid"""
retries = 0
while retries < max_retries:
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are an expert exam question generator with deep knowledge in medical education. Create clear, well-structured questions with detailed answer keys and explanations."},
{"role": "user", "content": prompt}
],
temperature=0.7,
max_tokens=3000 # Increased to accommodate answers and explanations
)
return response.choices[0].message.content
except Exception as e:
retries += 1
st.warning(f"Attempt {retries} failed. Retrying... Error: {str(e)}")
if retries == max_retries:
st.error(f"Failed to generate questions after {max_retries} attempts. Error: {str(e)}")
return None
time.sleep(2)
# ระบบ login
if 'username' not in st.session_state:
st.title("Login")
username_input = st.text_input("Enter your username:")
if st.button("Login"):
if username_input:
if check_user_in_sheet(username_input):
st.session_state['username'] = username_input
st.success(f"Welcome, {username_input}!")
update_api_usage(username_input)
else:
st.warning("Username not found. Please try again.")
else:
st.warning("Please enter a valid username.")
else:
# Main App after login
st.title(f"Welcome, {st.session_state['username']}! Generate your exam questions")
# Input field for knowledge material (text) with 3,000-word limit
knowledge_material = st.text_area("Enter knowledge material to generate exam questions:")
# Word count check
if len(knowledge_material.split()) > 3000:
st.warning("Please limit the knowledge material to 3,000 words or fewer.")
# File uploader for PDFs (limited to 5 MB)
uploaded_file = st.file_uploader("Upload a file (PDF)", type="pdf")
if uploaded_file is not None:
if uploaded_file.size > 5 * 1024 * 1024: # 5 MB limit
st.warning("File size exceeds 5 MB. Please upload a smaller file.")
else:
st.success("File uploaded successfully! (Text extraction not implemented yet.)")
# Select question type
question_type = st.selectbox("Select question type:",
["Multiple Choice", "Fill in the Blank", "Open-ended", "True/False"])
# For multiple choice, let users select the number of choices
num_choices = None
if question_type == "Multiple Choice":
num_choices = st.selectbox("Select the number of choices for each question:", [3, 4, 5])
# Select cognitive level
cognitive_level = st.selectbox("Select cognitive level:",
["Recall", "Understanding", "Application", "Analysis", "Synthesis", "Evaluation"])
# Checkbox for Case-Based Medical Situations
case_based = st.checkbox("Generate case-based medical exam questions")
# Extra input field for additional instructions (placed below cognitive level)
extra_instructions = st.text_area("Enter additional instructions (e.g., how you want the questions to be phrased):")
# Generate questions button
if 'previous_questions' not in st.session_state:
st.session_state['previous_questions'] = []
if st.button("Generate Questions"):
if len(knowledge_material.split()) <= 3000:
# Generate questions with retry logic
questions = generate_questions_with_retry(
knowledge_material,
question_type,
cognitive_level,
extra_instructions,
case_based,
num_choices
)
if questions:
st.write("Generated Exam Questions:")
st.write(questions)
# Avoid showing repeated content in future requests
st.session_state['previous_questions'].append(questions)
# Option to download the questions as a text file
st.download_button(
label="Download Questions",
data=questions,
file_name='generated_questions.txt',
mime='text/plain'
)
else:
st.warning("Please reduce the word count to 3,000 or fewer.")
# Button to generate more questions based on the same material
if st.button("Generate More Questions"):
if len(knowledge_material.split()) <= 3000:
# Regenerate new questions, trying to avoid repeated content
questions = generate_questions_with_retry(
knowledge_material,
question_type,
cognitive_level,
extra_instructions,
case_based,
num_choices
)
# Check if the new set of questions is not the same as the previous set
if questions and questions not in st.session_state['previous_questions']:
st.write("Generated More Exam Questions:")
st.write(questions)
# Append the new questions to the session state
st.session_state['previous_questions'].append(questions)
# Option to download the new set of questions
st.download_button(
label="Download More Questions",
data=questions,
file_name='more_generated_questions.txt',
mime='text/plain'
)
else:
st.warning("New questions seem to overlap with the previous ones. Try adjusting the instructions.")
else:
st.warning("Please reduce the word count to 3,000 or fewer.")