Spaces:
Runtime error
Runtime error
| 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 ( | |
| '<div class="status-box error">β Please either upload a PDF file or provide learning objectives</div>', | |
| {}, | |
| session_id | |
| ) | |
| # Update status based on input type | |
| if pdf_file is not None: | |
| status_html = '<div class="status-box processing">π Processing PDF...</div>' | |
| # Validate PDF | |
| validation = pdf_processor.validate_pdf(pdf_file) | |
| if not validation['valid']: | |
| return ( | |
| f'<div class="status-box error">β {validation["error"]}</div>', | |
| {}, | |
| session_id | |
| ) | |
| # Extract text | |
| extraction_result = pdf_processor.extract_text_from_pdf(pdf_file) | |
| if not extraction_result['success']: | |
| return ( | |
| f'<div class="status-box error">β {extraction_result["error"]}</div>', | |
| {}, | |
| session_id | |
| ) | |
| pdf_content = extraction_result['text'] | |
| pdf_data = extraction_result | |
| else: | |
| # Generate lecture from learning objectives only | |
| status_html = '<div class="status-box processing">π Generating lecture from learning objectives...</div>' | |
| 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'<div class="status-box error">β Lecture generation failed: {lecture_result["error"]}</div>', | |
| {}, | |
| 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 = '<div class="status-box success">β PDF processed successfully!</div>' | |
| 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 = '<div class="status-box success">β Lecture generated from learning objectives!</div>' | |
| 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'<div class="status-box error">β Processing failed: {str(e)}</div>', | |
| {}, | |
| 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 | |