import streamlit as st import tempfile import json import random from pathlib import Path from PyPDF2 import PdfReader from openai import OpenAI import os from ast import literal_eval # Import video processing functions from video import ( process_uploaded_video, generate_video_summary, chat_with_video ) # Initialize the OpenAI client api_key = os.getenv("OPENAI_API_KEY") client = OpenAI(api_key = api_key) # --------------------------- # Helper Function: Extract text from PDF # --------------------------- def extract_text(uploaded_file): # Check file size (max 10MB) uploaded_file.seek(0, os.SEEK_END) file_size = uploaded_file.tell() uploaded_file.seek(0) if file_size > 10 * 1024 * 1024: st.error("File size exceeds 10MB limit.") return "" pdf_reader = PdfReader(uploaded_file) text = "" for page in pdf_reader.pages: page_text = page.extract_text() if page_text: text += page_text + "\n" return text # --------------------------- # OpenAI Response Functions (using new style) # --------------------------- def generate_summary_from_text(text): prompt = ( f"Summarize the following document in a concise manner, highlighting the key points that a student should know:\n\n{text}" ) messages = [ {"role": "system", "content": "You are an educational assistant."}, {"role": "user", "content": prompt} ] completion = client.chat.completions.create( model="gpt-4o-mini", messages=messages ) return completion.choices[0].message.content.strip() def chat_with_document(text, conversation_history, user_query): messages = conversation_history + [ {"role": "user", "content": f"Based on the following document:\n\n{text}\n\nQuestion: {user_query}"} ] completion = client.chat.completions.create( model="gpt-4o-mini", messages=messages ) return completion.choices[0].message.content.strip() def generate_questions_from_text(text, num_questions): prompt = ( f"Generate up to {num_questions} study questions with answers based on the following document.\n" f"Return the output as a table with two columns: 'Question' and 'Answer'.\n\nDocument:\n\n{text}" ) messages = [ {"role": "system", "content": "You are an educational assistant that generates study questions."}, {"role": "user", "content": prompt} ] completion = client.chat.completions.create( model="gpt-4o-mini", messages=messages ) return completion.choices[0].message.content.strip() def generate_flashcards_from_text(text, num_cards): prompt = ( f"Generate exactly {num_cards} flashcards based on the following document.\n\n" f"Document:\n\n{text}\n\n" "Return ONLY a valid JSON object (not a code block) where each key is a flashcard question and its value is the answer. " "Format: {\"Question 1?\": \"Answer 1\", \"Question 2?\": \"Answer 2\"}. " "Do not include ```json or any other text, just the raw JSON object." ) messages = [ {"role": "system", "content": "You are an educational assistant that creates study flashcards. Always return valid JSON only, without code blocks or additional text."}, {"role": "user", "content": prompt} ] completion = client.chat.completions.create( model="gpt-4o-mini", messages=messages ) output = completion.choices[0].message.content.strip() # Clean up the output - remove code block markers if present if output.startswith("```json"): output = output[7:] # Remove ```json elif output.startswith("```"): output = output[3:] # Remove ``` if output.endswith("```"): output = output[:-3] # Remove trailing ``` output = output.strip() try: # Try JSON first (more reliable) flashcards = json.loads(output) if isinstance(flashcards, dict) and len(flashcards) > 0: return flashcards else: st.error("Generated flashcards are empty or invalid format.") return {} except json.JSONDecodeError: # Fallback to literal_eval for Python dict syntax try: flashcards = literal_eval(output) if isinstance(flashcards, dict) and len(flashcards) > 0: return flashcards else: st.error("Generated flashcards are empty or invalid format.") return {} except Exception as e: st.error(f"Error parsing flashcards: {e}") st.error(f"Received output: {output[:200]}...") # Show first 200 chars for debugging return {} # --------------------------- # Sidebar: File Upload & Mode Selection # --------------------------- st.sidebar.markdown(""" """, unsafe_allow_html=True) # Input source selection input_source = st.sidebar.radio("📥 Select Input Source", ("PDF Document", "Video")) st.sidebar.markdown("---") # Initialize variables uploaded_pdf = None uploaded_video = None if input_source == "PDF Document": uploaded_pdf = st.sidebar.file_uploader("📄 Upload your study PDF (max 10MB)", type="pdf") mode = st.sidebar.radio("🎯 Select Mode", ("Chat", "Test Your Knowledge", "Flashcards")) elif input_source == "Video": uploaded_video = st.sidebar.file_uploader("🎥 Upload your video (MP4, max 200MB)", type=["mp4", "mkv", "mov", "avi"]) mode = st.sidebar.radio("🎯 Select Mode", ("Transcript", "Summary", "Chat")) st.sidebar.markdown("---") # For Test Your Knowledge and Flashcards modes, allow number input. num_questions = None num_flashcards = None if input_source == "PDF Document": if mode == "Test Your Knowledge": num_questions = st.sidebar.number_input("Number of questions to generate (max 50):", min_value=1, max_value=50, value=10, step=1) elif mode == "Flashcards": num_flashcards = st.sidebar.number_input("Number of flashcards to generate (max 20):", min_value=1, max_value=20, value=5, step=1) # --------------------------- # Session State Initialization # --------------------------- if "pdf_text" not in st.session_state: st.session_state.pdf_text = None if "summary" not in st.session_state: st.session_state.summary = None if "chat_history" not in st.session_state: st.session_state.chat_history = [{"role": "assistant", "content": "Hi, how can I help you with your study material?"}] if "questions_table" not in st.session_state: st.session_state.questions_table = None if "flashcards" not in st.session_state: st.session_state.flashcards = {} if "current_card" not in st.session_state: st.session_state.current_card = 0 if "score" not in st.session_state: st.session_state.score = 0 if "show_answer" not in st.session_state: st.session_state.show_answer = False if "flashcard_keys" not in st.session_state: st.session_state.flashcard_keys = [] if "user_answers" not in st.session_state: st.session_state.user_answers = {} if "shuffle_cards" not in st.session_state: st.session_state.shuffle_cards = False # Video-related session states if "video_transcript" not in st.session_state: st.session_state.video_transcript = None if "video_chat_history" not in st.session_state: st.session_state.video_chat_history = [{"role": "assistant", "content": "Hi, how can I help you with the video content?"}] if "processed_video_path" not in st.session_state: st.session_state.processed_video_path = None if "video_summary" not in st.session_state: st.session_state.video_summary = None # --------------------------- # Process PDF Upload # --------------------------- if uploaded_pdf is not None: st.session_state.pdf_text = extract_text(uploaded_pdf) if st.session_state.pdf_text: st.sidebar.success("PDF uploaded and processed successfully!") else: st.sidebar.error("Failed to extract text. Please check your PDF file.") # --------------------------- # Process Video Upload # --------------------------- if uploaded_video is not None: # Generate a unique identifier for the video video_id = f"{uploaded_video.name}_{uploaded_video.size}" # Only process if it's a new video if st.session_state.processed_video_path != video_id: transcript, video_path = process_uploaded_video(uploaded_video) if transcript: st.session_state.video_transcript = transcript st.session_state.processed_video_path = video_id st.session_state.video_summary = None # Reset summary for new video st.sidebar.success("✅ Video processed and transcribed successfully!") else: st.sidebar.error("Failed to process video.") else: st.sidebar.info("Using cached video transcript.") # --------------------------- # Main Area: Mode-Based Display (all functions via side menu) # --------------------------- # Page configuration and custom styling st.markdown(""" """, unsafe_allow_html=True) st.markdown("""

📚 Study Companion

Your AI-powered learning assistant

""", unsafe_allow_html=True) # --------------------------- # Main Display - Handle PDF and Video inputs # --------------------------- if input_source == "PDF Document": if st.session_state.pdf_text is None: st.info("Please upload a PDF from the sidebar to begin.") else: if mode == "Chat": st.header("💬 Chat with Your Study Companion") # Display persistent chat history for msg in st.session_state.chat_history: st.chat_message(msg["role"]).write(msg["content"]) user_question = st.chat_input("💭 Ask a question about the document...") if user_question: st.session_state.chat_history.append({"role": "user", "content": user_question}) st.chat_message("user").write(user_question) with st.spinner("🤔 Thinking..."): response = chat_with_document(st.session_state.pdf_text, st.session_state.chat_history, user_question) st.session_state.chat_history.append({"role": "assistant", "content": response}) st.chat_message("assistant").write(response) # Add a clear chat button if len(st.session_state.chat_history) > 1: if st.button("🗑️ Clear Chat History"): st.session_state.chat_history = [{"role": "assistant", "content": "Hi, how can I help you with your study material?"}] st.rerun() elif mode == "Test Your Knowledge": st.header("📝 Test Your Knowledge") if num_questions is None: st.info("Please specify the number of questions in the sidebar.") else: if st.button("📋 Generate Questions", use_container_width=True, type="primary"): with st.spinner("✨ Generating questions..."): questions_output = generate_questions_from_text(st.session_state.pdf_text, num_questions) st.session_state.questions_table = questions_output if st.session_state.questions_table: st.markdown(st.session_state.questions_table) # Download button for questions st.download_button( label="📥 Download Questions", data=st.session_state.questions_table, file_name="study_questions.md", mime="text/markdown" ) else: st.info("💡 Click 'Generate Questions' to create a quiz based on your document.") elif mode == "Flashcards": st.header("🎴 Interactive Flashcards") # Custom CSS for flashcard styling with flip effect st.markdown(""" """, unsafe_allow_html=True) # Sidebar controls for flashcards with st.sidebar: st.markdown("---") st.subheader("🎴 Flashcard Options") shuffle_option = st.checkbox("Shuffle cards", value=st.session_state.shuffle_cards) if shuffle_option != st.session_state.shuffle_cards: st.session_state.shuffle_cards = shuffle_option if st.session_state.flashcards and st.session_state.flashcard_keys: if shuffle_option: st.session_state.flashcard_keys = list(st.session_state.flashcards.keys()) random.shuffle(st.session_state.flashcard_keys) else: st.session_state.flashcard_keys = list(st.session_state.flashcards.keys()) if num_flashcards is None: #st.info("Please specify the number of flashcards in the sidebar.") pass else: col1, col2 = st.columns([3, 1]) with col1: if st.button("🎯 Generate Flashcards", use_container_width=True): with st.spinner("✨ Creating your flashcards..."): flashcards = generate_flashcards_from_text(st.session_state.pdf_text, num_flashcards) if flashcards: st.session_state.flashcards = flashcards st.session_state.flashcard_keys = list(flashcards.keys()) if st.session_state.shuffle_cards: random.shuffle(st.session_state.flashcard_keys) st.session_state.current_card = 0 st.session_state.score = 0 st.session_state.show_answer = False st.session_state.user_answers = {} st.success("✅ Flashcards generated successfully!") st.rerun() else: st.error("Failed to generate flashcards. Please try again.") with col2: if st.session_state.flashcards and st.button("🔄 Reset", use_container_width=True): st.session_state.current_card = 0 st.session_state.score = 0 st.session_state.show_answer = False st.session_state.user_answers = {} st.rerun() if not st.session_state.flashcards: st.info("💡 No flashcards available. Click 'Generate Flashcards' to create a study set based on your document.") else: total_cards = len(st.session_state.flashcards) if st.session_state.current_card >= total_cards: # Completion screen st.balloons() st.markdown(f"""
🎉 Congratulations! You've completed all flashcards!
Final Score: {st.session_state.score} / {total_cards} ({int(st.session_state.score/total_cards*100)}%)
""", unsafe_allow_html=True) # Show review of incorrect answers if st.session_state.user_answers: st.subheader("📝 Review Your Answers") for i, (q, is_correct) in enumerate(st.session_state.user_answers.items(), 1): if not is_correct: with st.expander(f"❌ Card {i}: {q[:50]}..."): st.write(f"**Question:** {q}") st.write(f"**Answer:** {st.session_state.flashcards[q]}") col1, col2 = st.columns(2) with col1: if st.button("🔄 Practice Again", use_container_width=True): st.session_state.current_card = 0 st.session_state.score = 0 st.session_state.show_answer = False st.session_state.user_answers = {} if st.session_state.shuffle_cards: random.shuffle(st.session_state.flashcard_keys) st.rerun() with col2: if st.button("📚 New Set", use_container_width=True): st.session_state.flashcards = {} st.session_state.flashcard_keys = [] st.session_state.current_card = 0 st.session_state.score = 0 st.session_state.show_answer = False st.session_state.user_answers = {} st.rerun() else: # Progress bar with percentage progress = (st.session_state.current_card / total_cards) * 100 st.markdown(f"""
{int(progress)}%
""", unsafe_allow_html=True) # Display current card with flip effect current_key = st.session_state.flashcard_keys[st.session_state.current_card] current_answer = st.session_state.flashcards[current_key] # Flip card display flip_class = "flipped" if st.session_state.show_answer else "" st.markdown(f"""
Card {st.session_state.current_card + 1} of {total_cards}
{current_key}
👆 Click below to flip
Card {st.session_state.current_card + 1} of {total_cards}
Answer:

{current_answer}
""", unsafe_allow_html=True) # Flip button col_flip1, col_flip2, col_flip3 = st.columns([1, 2, 1]) with col_flip2: if st.button("🔄 Flip Card", use_container_width=True, key="flip_btn"): st.session_state.show_answer = not st.session_state.show_answer st.rerun() # Navigation buttons (Previous/Next) nav_col1, nav_col2, nav_col3 = st.columns([1, 1, 1]) with nav_col1: if st.button("⬅️ Previous", use_container_width=True, key="prev_btn", disabled=(st.session_state.current_card == 0)): st.session_state.current_card -= 1 st.session_state.show_answer = False st.rerun() with nav_col2: # Show current position st.markdown(f"""
{st.session_state.current_card + 1} / {total_cards}
""", unsafe_allow_html=True) with nav_col3: if st.button("Next ➡️", use_container_width=True, key="next_btn", disabled=(st.session_state.current_card >= total_cards - 1)): st.session_state.current_card += 1 st.session_state.show_answer = False st.rerun() # Self-assessment buttons (only when answer is shown) if st.session_state.show_answer: st.markdown("### Did you get it right?") assess_col1, assess_col2, assess_col3 = st.columns([1, 1, 1]) with assess_col1: if st.button("✅ Got it!", use_container_width=True, type="primary", key="correct_btn"): st.session_state.score += 1 st.session_state.user_answers[current_key] = True if st.session_state.current_card < total_cards - 1: st.session_state.current_card += 1 st.session_state.show_answer = False else: st.session_state.current_card += 1 st.rerun() with assess_col2: if st.button("❌ Missed it", use_container_width=True, key="wrong_btn"): st.session_state.user_answers[current_key] = False if st.session_state.current_card < total_cards - 1: st.session_state.current_card += 1 st.session_state.show_answer = False else: st.session_state.current_card += 1 st.rerun() with assess_col3: if st.button("⏭️ Skip", use_container_width=True, key="skip_btn"): if st.session_state.current_card < total_cards - 1: st.session_state.current_card += 1 st.session_state.show_answer = False else: st.session_state.current_card += 1 st.rerun() # Current score display st.markdown(f"""
Current Score: {st.session_state.score} / {st.session_state.current_card} {f"({int(st.session_state.score/st.session_state.current_card*100)}%)" if st.session_state.current_card > 0 else ""}
""", unsafe_allow_html=True) # --------------------------- # Video Input Display # --------------------------- elif input_source == "Video": if st.session_state.video_transcript is None: st.info("🎥 Please upload a video from the sidebar to begin.") else: if mode == "Transcript": st.header("📄 Video Transcript") st.write("Below is the complete transcript of the video:") st.text_area("Transcript", st.session_state.video_transcript, height=400, disabled=True, label_visibility="collapsed") # Add download button for transcript st.download_button( label="📥 Download Transcript", data=st.session_state.video_transcript, file_name="video_transcript.txt", mime="text/plain" ) elif mode == "Summary": st.header("📝 Video Summary & Key Points") # Generate summary only once and cache it if st.session_state.video_summary is None: with st.spinner("✨ Generating summary..."): summary = generate_video_summary(st.session_state.video_transcript) st.session_state.video_summary = summary st.markdown(st.session_state.video_summary) # Download button for summary st.download_button( label="📥 Download Summary", data=st.session_state.video_summary, file_name="video_summary.md", mime="text/markdown" ) elif mode == "Chat": st.header("💬 Chat About the Video") # Display persistent chat history for video for msg in st.session_state.video_chat_history: st.chat_message(msg["role"]).write(msg["content"]) user_question = st.chat_input("💭 Ask a question about the video...") if user_question: st.session_state.video_chat_history.append({"role": "user", "content": user_question}) st.chat_message("user").write(user_question) with st.spinner("🤔 Thinking..."): response = chat_with_video( st.session_state.video_transcript, st.session_state.video_chat_history, user_question ) st.session_state.video_chat_history.append({"role": "assistant", "content": response}) st.chat_message("assistant").write(response) # Add a clear chat button if len(st.session_state.video_chat_history) > 1: if st.button("🗑️ Clear Chat History", key="clear_video_chat"): st.session_state.video_chat_history = [{"role": "assistant", "content": "Hi, how can I help you with the video content?"}] st.rerun()