GeneXam / app(backup2).py
Rathapoom's picture
Update app(backup2).py
0ccda80 verified
raw
history blame
14.9 kB
import streamlit as st
import openai
from openai import OpenAI
import time
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import PyPDF2
import io
from datetime import datetime
from PIL import Image
# Add some custom CSS to improve the layout
st.markdown("""
<style>
.stImage {
text-align: center;
display: block;
margin-left: auto;
margin-right: auto;
}
.stTitle {
text-align: center;
padding-bottom: 20px;
}
div[data-testid="stVerticalBlock"] > div:has(div.stButton) {
text-align: center;
padding: 10px 0;
}
</style>
""", unsafe_allow_html=True)
# Constants
WORD_LIMIT = 11000
DAILY_API_LIMIT = 30 # Set your desired limit per user per day
# Set up OpenAI client
client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
# Google Sheets setup
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):
"""Check if user exists in sheet"""
try:
users_list = sheet.col_values(1) # UserID column
if username in users_list:
return True
return False
except Exception as e:
st.error(f"Error checking user: {str(e)}")
return False
def get_user_stats(username):
"""Get user's current API usage statistics"""
try:
users_list = sheet.col_values(1) # UserID column
row_number = users_list.index(username) + 1
daily_count = int(sheet.cell(row_number, 2).value) # DailyAPICount
total_count = int(sheet.cell(row_number, 3).value) # TotalAPICount
last_used = sheet.cell(row_number, 4).value # LastUsedDate
return {
'daily_count': daily_count,
'total_count': total_count,
'last_used': last_used
}
except Exception as e:
st.error(f"Error getting user stats: {str(e)}")
return None
def update_api_usage(username):
"""Update both daily and total API usage counts"""
try:
users_list = sheet.col_values(1)
row_number = users_list.index(username) + 1
today = datetime.now().strftime('%Y-%m-%d')
# Get current values
stats = get_user_stats(username)
if not stats:
return False, "Error retrieving user statistics"
# Reset daily count if it's a new day
daily_count = stats['daily_count']
if stats['last_used'] != today:
daily_count = 0
# Check daily limit
if daily_count >= DAILY_API_LIMIT:
return False, f"You have reached your daily limit of {DAILY_API_LIMIT} generations. Please try again tomorrow."
# Update counts
new_daily_count = daily_count + 1
new_total_count = stats['total_count'] + 1
# Update all values in sheet
sheet.update_cell(row_number, 2, new_daily_count) # Update DailyAPICount
sheet.update_cell(row_number, 3, new_total_count) # Update TotalAPICount
sheet.update_cell(row_number, 4, today) # Update LastUsedDate
return True, None
except Exception as e:
return False, f"Error updating API usage: {str(e)}"
def extract_text_from_pdf(pdf_file):
"""Simple PDF text extraction with word limit check"""
try:
pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file.read()))
text_content = ""
for page in pdf_reader.pages:
text_content += page.extract_text() + "\n"
word_count = len(text_content.split())
if word_count > WORD_LIMIT:
return None, f"PDF content exceeds {WORD_LIMIT:,} words (contains {word_count:,} words). Please use a shorter document."
return text_content, None
except Exception as e:
return None, f"Error processing PDF: {str(e)}"
def generate_questions_with_retry(username, knowledge_material, question_type, cognitive_level, extra_instructions, case_based, num_choices=None, max_retries=3):
"""Generate questions and update API usage"""
# Check and update API usage before generating
can_generate, error_message = update_api_usage(username)
if not can_generate:
st.error(error_message)
return None
# 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 ''} after get new line from question
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
"""
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)
# Main Streamlit interface
# Initialize session state variables
if 'login_step' not in st.session_state:
st.session_state.login_step = 'username'
if 'username' not in st.session_state:
st.session_state.username = None
# Login system
if st.session_state.username is None:
# Center align the content
col1, col2, col3 = st.columns([1,2,1])
with col2:
# Display logo
st.image("GenExam.png", width=200) # Assuming the image is saved as logo.png
st.title("Login")
username_input = st.text_input("Enter your username:")
if st.session_state.login_step == 'username' and st.button("Login", use_container_width=True):
if username_input:
if check_user_in_sheet(username_input):
stats = get_user_stats(username_input)
if stats:
st.success(f"Welcome, {username_input}! 👋")
st.info(f"""
📊 Your API Usage Statistics:
- Today's Usage: {stats['daily_count']}/{DAILY_API_LIMIT} generations
- Total All-Time Usage: {stats['total_count']} generations
""")
st.session_state.login_step = 'enter_app'
else:
st.warning("Username not found. Please try again.")
else:
st.warning("Please enter a valid username.")
if st.session_state.login_step == 'enter_app':
if st.button("🎯 Enter GeneXam Application", use_container_width=True):
st.session_state.username = username_input
st.rerun()
# Show instructions
if st.session_state.login_step == 'username':
st.markdown("""
### How to Login:
1. Enter your username and click 'Login' to verify your account
2. After verification, click 'Enter GeneXam Application' to start using the system
""")
else:
# Main application code (ส่วนที่เหลือเหมือนเดิม)
st.title(f"Welcome to GeneXam, {st.session_state.username}! 🎓")
# Show current usage stats
stats = get_user_stats(st.session_state.username)
if stats:
remaining = DAILY_API_LIMIT - stats['daily_count']
st.info(f"""
📊 Usage Statistics:
- Daily Generations Remaining: {remaining}/{DAILY_API_LIMIT}
- Total All-Time Generations: {stats['total_count']}
""")
# Create tabs for input methods
tab1, tab2 = st.tabs(["Text Input", "PDF Upload"])
with tab1:
knowledge_material = st.text_area("Enter knowledge material to generate exam questions:")
word_count = len(knowledge_material.split())
if word_count > WORD_LIMIT:
st.error(f"Text exceeds {WORD_LIMIT:,} words. Please shorten your content.")
with tab2:
st.info(f"Maximum content length: {WORD_LIMIT:,} words")
uploaded_file = st.file_uploader("Upload a PDF file", type="pdf")
if uploaded_file is not None:
pdf_content, error = extract_text_from_pdf(uploaded_file)
if error:
st.error(error)
else:
st.success("PDF processed successfully!")
knowledge_material = pdf_content
# Question generation options
col1, col2 = st.columns(2)
with col1:
question_type = st.selectbox(
"Select question type:",
["Multiple Choice", "Fill in the Blank", "Open-ended", "True/False"]
)
if question_type == "Multiple Choice":
num_choices = st.selectbox("Select number of choices:", [3, 4, 5])
cognitive_level = st.selectbox(
"Select cognitive level:",
["Recall", "Understanding", "Application", "Analysis", "Synthesis", "Evaluation"]
)
with col2:
case_based = st.checkbox("Generate case-based medical exam questions")
extra_instructions = st.text_area("Additional instructions (optional):")
# Generate questions button
if st.button("Generate Questions"):
if 'knowledge_material' in locals() and knowledge_material.strip():
with st.spinner("Generating questions..."):
questions = generate_questions_with_retry(
st.session_state['username'],
knowledge_material,
question_type,
cognitive_level,
extra_instructions,
case_based,
num_choices if question_type == "Multiple Choice" else None
)
if questions:
st.write("### Generated Exam Questions:")
st.write(questions)
# Update displayed stats after generation
new_stats = get_user_stats(st.session_state['username'])
if new_stats:
remaining = DAILY_API_LIMIT - new_stats['daily_count']
st.info(f"""
📊 Updated Usage Statistics:
- Daily Generations Remaining: {remaining}/{DAILY_API_LIMIT}
- Total All-Time Generations: {new_stats['total_count']}
""")
# Download button
st.download_button(
label="Download Questions",
data=questions,
file_name='generated_questions.txt',
mime='text/plain'
)
else:
st.warning("Please enter knowledge material or upload a PDF file first.")