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