StudyLLM / app.py
Swaroop Vedula
Upload app.py
b308324 verified
Raw
History Blame Contribute Delete
8.31 kB
import asyncio.base_events
_orig_del = asyncio.base_events.BaseEventLoop.__del__
def _patched_del(self):
try:
_orig_del(self)
except ValueError as e:
if "Invalid file descriptor" not in str(e):
raise
asyncio.base_events.BaseEventLoop.__del__ = _patched_del
import os
import gradio as gr
from extractors import extract_text_dispatcher
from llm import generate_notes
from chat import chat
from rag import chunk_text, build_collection
from flashcards import (
render_card,
generate_flashcards_handler,
flip_card,
next_card,
prev_card,
)
from exam import (
render_question,
generate_exam_handler,
next_question,
prev_question,
record_answer,
grade_exam,
)
# --- Gradio Callback Handlers ---
def process_file_uploads(files, history):
"""
Rebuilds study_context AND the RAG vector store from scratch based on
whatever files are CURRENTLY in the upload widget. Runs on every change
to the file list — additions AND deletions.
"""
if not files:
# User cleared all files — context, vector store, and status all reset.
return history, "", None, "No files currently loaded."
file_list = files if isinstance(files, list) else [files]
new_extracted_text = ""
filenames = []
for file_obj in file_list:
file_path = file_obj.name if hasattr(file_obj, "name") else file_obj
filename = os.path.basename(file_path)
filenames.append(filename)
try:
extracted = extract_text_dispatcher(file_path)
new_extracted_text += f"\n--- Start of {filename} ---\n{extracted}\n--- End of {filename} ---\n"
except Exception as e:
new_extracted_text += f"\n--- Start of {filename} ---\n[Error processing file: {str(e)}]\n--- End of {filename} ---\n"
system_msg = f"📁 Currently loaded: {', '.join(filenames)}"
history = history + [{"role": "assistant", "content": system_msg}]
upload_status_text = f"✅ **Currently loaded:** {', '.join(filenames)}"
chunks = chunk_text(new_extracted_text)
collection = build_collection(chunks)
return history, new_extracted_text, collection, upload_status_text
def put_message_in_chatbot(message, history):
return "", history + [{"role": "user", "content": message}]
# --- Interface Layout ---
with gr.Blocks() as ui:
# Shared state
study_context = gr.State(value="")
vector_store_state = gr.State(value=None)
# Flashcard-specific state
flashcards_state = gr.State(value=[])
current_index = gr.State(value=0)
show_answer = gr.State(value=False)
# Practice Exam-specific state
exam_questions_state = gr.State(value=[])
exam_index = gr.State(value=0)
user_answers_state = gr.State(value={})
exam_submitted_state = gr.State(value=False)
gr.Markdown("## Study LLM")
with gr.Tab("Upload Files"):
with gr.Row():
upload = gr.File(
file_types=[".pdf", ".pptx", ".docx", ".txt", ".mp4", ".avi", ".mov", ".png", ".jpg", ".jpeg"],
file_count="multiple",
label="Drag and drop your study materials here",
)
with gr.Row():
upload_status = gr.Markdown("No files currently loaded.")
with gr.Tab("Chat"):
with gr.Row():
chatbot = gr.Chatbot(height=500)
with gr.Row():
message = gr.Textbox(label="Chat with our AI Assistant:", placeholder="Ask something about your study notes...")
with gr.Tab("Notes"):
with gr.Column():
generate_notes_button = gr.Button("Generate Study Notes", variant="primary")
notes_output = gr.Markdown(label="Generated Study Notes", latex_delimiters=[
{"left": "$$", "right": "$$", "display": True},
{"left": "$", "right": "$", "display": False},
{"left": "\\[", "right": "\\]", "display": True},
{"left": "\\(", "right": "\\)", "display": False},
])
with gr.Tab("Flashcards"):
with gr.Column():
generate_flashcards_button = gr.Button("Generate Flashcards", variant="primary")
card_display = gr.Markdown(label="Flashcard", latex_delimiters=[
{"left": "$$", "right": "$$", "display": True},
{"left": "$", "right": "$", "display": False},
{"left": "\\[", "right": "\\]", "display": True},
{"left": "\\(", "right": "\\)", "display": False},
])
with gr.Row():
prev_button = gr.Button("⬅ Previous")
flip_button = gr.Button("Flip")
next_button = gr.Button("Next ➡")
with gr.Tab("Practice Exam"):
with gr.Column():
generate_exam_button = gr.Button("Generate Practice Exam", variant="primary")
question_display = gr.Markdown(label="Question", latex_delimiters=[
{"left": "$$", "right": "$$", "display": True},
{"left": "$", "right": "$", "display": False},
{"left": "\\[", "right": "\\]", "display": True},
{"left": "\\(", "right": "\\)", "display": False},
])
answer_choices = gr.Radio(choices=[], label="Select an answer")
with gr.Row():
prev_question_button = gr.Button("⬅ Previous Question")
submit_exam_button = gr.Button("Submit Exam")
next_question_button = gr.Button("Next Question ➡")
results_display = gr.Markdown(label="Results")
# --- Event Bindings ---
# 1. Rebuild extracted text + vector store whenever the file list changes
upload.change(
fn=process_file_uploads,
inputs=[upload, chatbot],
outputs=[chatbot, study_context, vector_store_state, upload_status],
)
# 2. Submit user chat message
message.submit(
put_message_in_chatbot,
inputs=[message, chatbot],
outputs=[message, chatbot],
).then(
chat,
inputs=[chatbot, study_context, vector_store_state],
outputs=chatbot,
)
# 3. Trigger note generation from stored context
generate_notes_button.click(
fn=lambda: "⏳ Generating notes...",
outputs=[notes_output],
).then(
fn=generate_notes,
inputs=[study_context],
outputs=[notes_output],
)
# 4. Flashcards — generation + navigation.
generate_flashcards_button.click(
fn=lambda: "⏳ Generating flashcards...",
outputs=[card_display],
).then(
fn=generate_flashcards_handler,
inputs=[study_context],
outputs=[flashcards_state, current_index, show_answer, card_display],
)
flip_button.click(
fn=flip_card,
inputs=[flashcards_state, current_index, show_answer],
outputs=[show_answer, card_display],
)
next_button.click(
fn=next_card,
inputs=[flashcards_state, current_index],
outputs=[current_index, show_answer, card_display],
)
prev_button.click(
fn=prev_card,
inputs=[flashcards_state, current_index],
outputs=[current_index, show_answer, card_display],
)
# 5. Practice Exam
generate_exam_button.click(
fn=generate_exam_handler,
inputs=[study_context],
outputs=[exam_questions_state, exam_index, user_answers_state, exam_submitted_state, question_display, answer_choices, results_display]
)
next_question_button.click(
fn=next_question,
inputs=[exam_questions_state, exam_index, user_answers_state],
outputs=[exam_index, question_display, answer_choices]
)
prev_question_button.click(
fn=prev_question,
inputs=[exam_questions_state, exam_index, user_answers_state],
outputs=[exam_index, question_display, answer_choices]
)
submit_exam_button.click(
fn=grade_exam,
inputs=[exam_questions_state, user_answers_state],
outputs=[results_display]
)
answer_choices.change(
fn=record_answer,
inputs=[user_answers_state, exam_index, answer_choices],
outputs=[user_answers_state]
)
ui.launch(theme=gr.themes.Ocean())