Spaces:
Running
Running
| 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(""" | |
| <style> | |
| .sidebar-title { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 20px; | |
| border-radius: 10px; | |
| color: white; | |
| text-align: center; | |
| margin-bottom: 20px; | |
| font-size: 1.3em; | |
| font-weight: bold; | |
| } | |
| </style> | |
| <div class="sidebar-title"> | |
| βοΈ Study Companion Setup | |
| </div> | |
| """, 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(""" | |
| <style> | |
| /* Global styling */ | |
| .main { | |
| background-color: #f5f7fa; | |
| } | |
| /* Header styling */ | |
| .main-header { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 20px 15px; | |
| border-radius: 15px; | |
| text-align: center; | |
| color: white; | |
| margin-bottom: 20px; | |
| box-shadow: 0 5px 20px rgba(0,0,0,0.2); | |
| } | |
| .main-header h1 { | |
| margin: 0; | |
| font-size: 1.8em; | |
| } | |
| .main-header p { | |
| margin: 8px 0 0 0; | |
| font-size: 0.95em; | |
| opacity: 0.9; | |
| } | |
| /* Desktop view - larger header */ | |
| @media (min-width: 768px) { | |
| .main-header { | |
| padding: 30px; | |
| margin-bottom: 30px; | |
| } | |
| .main-header h1 { | |
| font-size: 2.5em; | |
| } | |
| .main-header p { | |
| font-size: 1.1em; | |
| margin: 10px 0 0 0; | |
| } | |
| } | |
| /* Chat message styling */ | |
| .stChatMessage { | |
| background-color: white; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin: 10px 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| /* Ensure chat text is visible on mobile */ | |
| .stChatMessage, .stChatMessage p, .stChatMessage div { | |
| color: #333333 !important; | |
| } | |
| /* Chat mode header - smaller on mobile */ | |
| h2 { | |
| font-size: 1.3em; | |
| } | |
| @media (min-width: 768px) { | |
| h2 { | |
| font-size: 1.75em; | |
| } | |
| } | |
| /* Info box styling */ | |
| .stInfo { | |
| background-color: #e3f2fd; | |
| border-left: 5px solid #2196f3; | |
| } | |
| /* Success box styling */ | |
| .stSuccess { | |
| background-color: #e8f5e9; | |
| border-left: 5px solid #4caf50; | |
| } | |
| /* Questions table styling */ | |
| table { | |
| background-color: white; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| th { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 15px; | |
| } | |
| td { | |
| padding: 12px; | |
| border-bottom: 1px solid #e0e0e0; | |
| color: #333333 !important; | |
| } | |
| /* Ensure all text in tables is visible */ | |
| table, table p, table div, table span { | |
| color: #333333 !important; | |
| } | |
| /* Question container - transparent background */ | |
| .question-container { | |
| background: transparent; | |
| border-radius: 10px; | |
| padding: 0; | |
| margin: 15px 0; | |
| } | |
| /* Make markdown text in questions visible */ | |
| .question-container, .question-container p, .question-container div { | |
| color: #333333 !important; | |
| } | |
| /* Sidebar styling */ | |
| .css-1d391kg { | |
| background-color: #f8f9fa; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="main-header"> | |
| <h1>π Study Companion</h1> | |
| <p>Your AI-powered learning assistant</p> | |
| </div> | |
| """, 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(""" | |
| <style> | |
| .flashcard-3d-container { | |
| perspective: 1000px; | |
| margin: 20px 0 30px 0; | |
| } | |
| .flashcard-flip { | |
| position: relative; | |
| min-height: 300px; | |
| transition: transform 0.6s; | |
| transform-style: preserve-3d; | |
| margin-bottom: 20px; | |
| } | |
| .flashcard-flip.flipped { | |
| transform: rotateY(180deg); | |
| } | |
| .flashcard-face { | |
| position: absolute; | |
| width: 100%; | |
| min-height: 300px; | |
| backface-visibility: hidden; | |
| border-radius: 15px; | |
| padding: 40px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| overflow-y: auto; | |
| max-height: 400px; | |
| } | |
| .flashcard-front { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .flashcard-back { | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| transform: rotateY(180deg); | |
| } | |
| .flashcard-question { | |
| font-size: 22px; | |
| font-weight: bold; | |
| line-height: 1.4; | |
| } | |
| .flashcard-answer { | |
| font-size: 18px; | |
| line-height: 1.6; | |
| } | |
| .card-number { | |
| position: absolute; | |
| top: 15px; | |
| left: 20px; | |
| font-size: 14px; | |
| opacity: 0.9; | |
| } | |
| .flip-instruction { | |
| position: absolute; | |
| bottom: 15px; | |
| width: 100%; | |
| text-align: center; | |
| font-size: 14px; | |
| opacity: 0.8; | |
| } | |
| .score-card { | |
| background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | |
| border-radius: 10px; | |
| padding: 20px; | |
| color: white; | |
| font-size: 18px; | |
| font-weight: bold; | |
| text-align: center; | |
| margin: 20px 0; | |
| } | |
| .progress-bar-container { | |
| background: #e0e0e0; | |
| border-radius: 10px; | |
| height: 25px; | |
| margin: 20px 0; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .progress-bar { | |
| background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%); | |
| height: 100%; | |
| transition: width 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-weight: bold; | |
| font-size: 14px; | |
| } | |
| .navigation-buttons { | |
| display: flex; | |
| gap: 10px; | |
| margin: 30px 0 20px 0; | |
| } | |
| .stButton > button { | |
| border-radius: 8px; | |
| font-weight: 600; | |
| padding: 10px 24px; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton > button:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |
| } | |
| /* Mobile responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .flashcard-face { | |
| padding: 30px 20px; | |
| min-height: 250px; | |
| } | |
| .flashcard-question { | |
| font-size: 18px; | |
| } | |
| .flashcard-answer { | |
| font-size: 16px; | |
| } | |
| } | |
| </style> | |
| """, 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""" | |
| <div class="score-card"> | |
| π Congratulations! You've completed all flashcards!<br> | |
| Final Score: {st.session_state.score} / {total_cards} ({int(st.session_state.score/total_cards*100)}%) | |
| </div> | |
| """, 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""" | |
| <div class="progress-bar-container"> | |
| <div class="progress-bar" style="width: {progress}%">{int(progress)}%</div> | |
| </div> | |
| """, 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""" | |
| <div class="flashcard-3d-container"> | |
| <div class="flashcard-flip {flip_class}"> | |
| <div class="flashcard-face flashcard-front"> | |
| <div> | |
| <div class="card-number">Card {st.session_state.current_card + 1} of {total_cards}</div> | |
| <div class="flashcard-question">{current_key}</div> | |
| <div class="flip-instruction">π Click below to flip</div> | |
| </div> | |
| </div> | |
| <div class="flashcard-face flashcard-back"> | |
| <div> | |
| <div class="card-number">Card {st.session_state.current_card + 1} of {total_cards}</div> | |
| <div class="flashcard-answer"><strong>Answer:</strong><br><br>{current_answer}</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """, 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""" | |
| <div style="text-align: center; padding: 10px; font-size: 16px; font-weight: bold;"> | |
| {st.session_state.current_card + 1} / {total_cards} | |
| </div> | |
| """, 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""" | |
| <div class="score-card"> | |
| 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 ""} | |
| </div> | |
| """, 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() | |