import gradio as gr
import os
import uuid
import tempfile
from typing import Dict, Any, Optional, Tuple
import logging
from datetime import datetime
from app.pdf_processor import PDFProcessor
from app.lecture_generator import LectureGenerator
from app.voice_synthesizer import VoiceSynthesizer
from app.chatbot import RAGChatbot
logger = logging.getLogger(__name__)
# Initialize components
openai_api_key = os.getenv("OPENAI_API_KEY", "")
pdf_processor = PDFProcessor()
lecture_generator = LectureGenerator()
voice_synthesizer = VoiceSynthesizer(openai_api_key=openai_api_key)
chatbot = RAGChatbot(openai_api_key=openai_api_key)
# Global state for sessions
current_session = None
session_data = {}
def create_gradio_interface():
"""Create and configure the Gradio interface"""
# Custom CSS for better styling
css = """
.container {
max-width: 1200px;
margin: 0 auto;
}
.status-box {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.processing {
background-color: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
"""
with gr.Blocks(css=css, title="AI Tutor") as interface:
gr.Markdown("# 🎓 AI Tutor")
gr.Markdown("Convert PDFs into interactive lectures with voice narration and chat with your AI tutor about any topic!")
# Session state
session_id_state = gr.State(value=str(uuid.uuid4()))
openai_key_state = gr.State(value="")
with gr.Tab("🔑 API Key Setup"):
openai_key_input = gr.Textbox(
label="OpenAI API Key",
placeholder="Enter your OpenAI API key here",
type="password"
)
save_key_btn = gr.Button("Save API Key")
with gr.Tab("📄 PDF Upload & Processing"):
with gr.Row():
with gr.Column(scale=1):
pdf_upload = gr.File(
label="Upload PDF Document (Optional)",
file_types=[".pdf"],
type="binary"
)
lecture_style = gr.Dropdown(
choices=["academic", "casual", "detailed"],
value="academic",
label="Lecture Style"
)
include_examples = gr.Checkbox(
value=True,
label="Include Examples"
)
learning_objectives = gr.Textbox(
label="Learning Objectives & Topic",
placeholder="What do you want to learn? e.g., 'Machine Learning basics', 'Python programming fundamentals', 'Explain quantum physics concepts'",
lines=3,
max_lines=5
)
gr.Markdown("**Note:** You can generate a lecture with just learning objectives, or upload a PDF for content-based lectures.")
process_btn = gr.Button("🚀 Generate Lecture", variant="primary")
with gr.Column(scale=2):
processing_status = gr.HTML()
pdf_info = gr.JSON(label="PDF Information")
with gr.Tab("📚 Generated Lecture"):
with gr.Row():
with gr.Column():
lecture_title = gr.Textbox(label="Lecture Title", interactive=False)
lecture_content = gr.Textbox(
label="Lecture Content",
lines=20,
max_lines=30,
interactive=False
)
with gr.Row():
download_pdf_btn = gr.Button("📄 Download PDF")
download_audio_btn = gr.Button("🎤 Generate & Download Audio")
pdf_download = gr.File(label="Download Lecture PDF")
audio_download = gr.File(label="Download Audio Lecture")
with gr.Tab("💬 Tutor Chat"):
with gr.Row():
with gr.Column(scale=3):
chatbot_interface = gr.Chatbot(
label="Chat with your AI Tutor about your content",
height=400,
type="messages"
)
with gr.Row():
msg_input = gr.Textbox(
label="Your Message",
placeholder="Ask your AI tutor about any topic, PDF content, or lecture...",
scale=4
)
send_btn = gr.Button("Send", scale=1)
clear_chat_btn = gr.Button("Clear Chat History")
with gr.Column(scale=1):
chat_stats = gr.JSON(label="Session Statistics")
refresh_stats_btn = gr.Button("Refresh Stats")
# Event handlers
def process_pdf_handler(pdf_file, style, examples, learning_objectives, session_id, openai_key):
"""Handle PDF processing or topic-based lecture generation"""
global session_data
# Pass the OpenAI key to the chatbot or other components
chatbot.set_api_key(openai_key)
try:
# Check if we have either PDF or learning objectives
if pdf_file is None and not learning_objectives.strip():
return (
'
❌ Please either upload a PDF file or provide learning objectives
',
{},
session_id
)
# Update status based on input type
if pdf_file is not None:
status_html = '🔄 Processing PDF...
'
# Validate PDF
validation = pdf_processor.validate_pdf(pdf_file)
if not validation['valid']:
return (
f'❌ {validation["error"]}
',
{},
session_id
)
# Extract text
extraction_result = pdf_processor.extract_text_from_pdf(pdf_file)
if not extraction_result['success']:
return (
f'❌ {extraction_result["error"]}
',
{},
session_id
)
pdf_content = extraction_result['text']
pdf_data = extraction_result
else:
# Generate lecture from learning objectives only
status_html = '🔄 Generating lecture from learning objectives...
'
pdf_content = ""
pdf_data = {
'success': True,
'text': "",
'metadata': {'total_pages': 0, 'title': learning_objectives[:50], 'author': '', 'subject': ''},
'word_count': 0,
'character_count': 0
}
# Generate lecture
lecture_result = lecture_generator.generate_lecture(
pdf_content,
style=style,
include_examples=examples,
learning_objectives=learning_objectives
)
if not lecture_result['success']:
return (
f'❌ Lecture generation failed: {lecture_result["error"]}
',
{},
session_id
)
# Store session data
session_data[session_id] = {
'pdf_data': pdf_data,
'lecture_data': lecture_result,
'processed_at': datetime.now().isoformat()
}
# Create chatbot session
chatbot.create_session(
session_id,
pdf_content=pdf_content,
lecture_content=lecture_result['content']
)
if pdf_file is not None:
success_html = '✅ PDF processed successfully!
'
info = {
'filename': getattr(pdf_file, 'name', 'uploaded_file.pdf'),
'pages': pdf_data['metadata']['total_pages'],
'word_count': pdf_data['word_count'],
'lecture_title': lecture_result['title'],
'estimated_duration': f"{lecture_result['estimated_duration']} minutes"
}
else:
success_html = '✅ Lecture generated from learning objectives!
'
info = {
'source': 'Learning Objectives',
'topic': learning_objectives[:100] + "..." if len(learning_objectives) > 100 else learning_objectives,
'lecture_title': lecture_result['title'],
'estimated_duration': f"{lecture_result['estimated_duration']} minutes"
}
return success_html, info, session_id
except Exception as e:
logger.error(f"PDF processing error: {str(e)}")
return (
f'❌ Processing failed: {str(e)}
',
{},
session_id
)
def update_lecture_display(session_id):
"""Update lecture display with generated content"""
global session_data
if session_id not in session_data:
return "", ""
lecture_data = session_data[session_id]['lecture_data']
return lecture_data['title'], lecture_data['content']
def generate_pdf_download(session_id):
"""Generate PDF download"""
global session_data
try:
if session_id not in session_data:
return None
lecture_data = session_data[session_id]['lecture_data']
# Generate PDF
output_path = os.path.join("output", f"lecture_{session_id}.pdf")
success = lecture_generator.generate_pdf(lecture_data, output_path)
if success:
return output_path
else:
return None
except Exception as e:
logger.error(f"PDF generation error: {str(e)}")
return None
def generate_audio_download(session_id):
"""Generate audio download"""
global session_data
try:
if session_id not in session_data:
return None
lecture_data = session_data[session_id]['lecture_data']
# Generate audio
output_path = os.path.join("output", f"lecture_audio_{session_id}.mp3")
result = voice_synthesizer.synthesize_lecture(
lecture_data['content'],
voice="nova",
output_path=output_path
)
if result['success']:
return result['file_path']
else:
return None
except Exception as e:
logger.error(f"Audio generation error: {str(e)}")
return None
def chat_handler(message, history, session_id, openai_key):
"""Handle chat messages"""
if not message.strip():
return history, ""
try:
# Pass the OpenAI key to the chatbot
chatbot.set_api_key(openai_key)
response_result = chatbot.get_response(session_id, message)
if response_result['success']:
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": response_result['response']})
else:
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": f"Error: {response_result['error']}"})
return history, ""
except Exception as e:
logger.error(f"Chat error: {str(e)}")
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": f"Sorry, I encountered an error: {str(e)}"})
return history, ""
def clear_chat_handler(session_id):
"""Clear chat history"""
chatbot.clear_session(session_id)
new_session_id = str(uuid.uuid4())
# Recreate session with existing content if available
if session_id in session_data:
pdf_content = session_data[session_id]['pdf_data']['text']
lecture_content = session_data[session_id]['lecture_data']['content']
chatbot.create_session(new_session_id, pdf_content, lecture_content)
session_data[new_session_id] = session_data[session_id]
del session_data[session_id]
return [], new_session_id
def get_chat_stats(session_id):
"""Get chat statistics"""
return chatbot.get_session_stats(session_id)
def save_openai_key(key):
"""Save the OpenAI API key to the session state"""
return key
# Wire up event handlers
save_key_btn.click(
fn=save_openai_key,
inputs=[openai_key_input],
outputs=[openai_key_state]
)
process_btn.click(
fn=process_pdf_handler,
inputs=[pdf_upload, lecture_style, include_examples, learning_objectives, session_id_state, openai_key_state],
outputs=[processing_status, pdf_info, session_id_state]
).then(
fn=update_lecture_display,
inputs=[session_id_state],
outputs=[lecture_title, lecture_content]
)
download_pdf_btn.click(
fn=generate_pdf_download,
inputs=[session_id_state],
outputs=[pdf_download]
)
download_audio_btn.click(
fn=generate_audio_download,
inputs=[session_id_state],
outputs=[audio_download]
)
send_btn.click(
fn=chat_handler,
inputs=[msg_input, chatbot_interface, session_id_state, openai_key_state],
outputs=[chatbot_interface, msg_input]
)
msg_input.submit(
fn=chat_handler,
inputs=[msg_input, chatbot_interface, session_id_state, openai_key_state],
outputs=[chatbot_interface, msg_input]
)
clear_chat_btn.click(
fn=clear_chat_handler,
inputs=[session_id_state],
outputs=[chatbot_interface, session_id_state]
)
refresh_stats_btn.click(
fn=get_chat_stats,
inputs=[session_id_state],
outputs=[chat_stats]
)
return interface