Spaces:
Sleeping
Sleeping
| """ | |
| AI Study Assistant - Main Application | |
| Gradio UI for the intelligent study assistant | |
| """ | |
| import gradio as gr | |
| import os | |
| from typing import Tuple | |
| from config import ( | |
| APP_TITLE, | |
| APP_DESCRIPTION, | |
| SUPPORTED_FILE_TYPES, | |
| MAX_INPUT_LENGTH, | |
| ) | |
| from core.utils import ( | |
| extract_text_from_file, | |
| truncate_text, | |
| format_error_message, | |
| log_event, | |
| create_directory, | |
| ) | |
| from core.validator import InputValidator, PromptValidator | |
| from features.summarizer import Summarizer | |
| from features.quiz_generator import QuizGenerator | |
| from features.explainer import Explainer | |
| from features.doubt_solver import DoubtSolver | |
| # Initialize features | |
| summarizer = Summarizer() | |
| quiz_gen = QuizGenerator() | |
| explainer = Explainer() | |
| doubt_solver = DoubtSolver() | |
| # Initialize validator | |
| validator = InputValidator() | |
| # =========================== | |
| # CORE PROCESSING FUNCTIONS | |
| # =========================== | |
| def process_file_upload(file_obj) -> Tuple[str, str]: | |
| """ | |
| Process uploaded file and extract text. | |
| Args: | |
| file_obj: Gradio file object | |
| Returns: | |
| Tuple of (file_text, status_message) | |
| """ | |
| if not file_obj: | |
| return "", "No file selected" | |
| try: | |
| file_path = file_obj | |
| # Validate file extension | |
| file_ext = os.path.splitext(file_path)[1].lower() | |
| if file_ext not in SUPPORTED_FILE_TYPES: | |
| return "", f"Unsupported file type: {file_ext}" | |
| # Extract text | |
| text = extract_text_from_file(file_path) | |
| log_event("FILE_PROCESSED", f"File type: {file_ext}, Size: {len(text)}") | |
| return text, f"β File processed ({len(text)} characters)" | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FILE_ERROR", str(e)) | |
| return "", error_msg | |
| def generate_summary( | |
| input_text: str, | |
| mode: str, | |
| use_file: bool, | |
| uploaded_file | |
| ) -> str: | |
| """Generate summary based on input.""" | |
| try: | |
| text = input_text | |
| if use_file and uploaded_file: | |
| text, status = process_file_upload(uploaded_file) | |
| if not text: | |
| return f"Error: {status}" | |
| if not text: | |
| return "Error: No input provided" | |
| # Validate | |
| is_valid, msg = validator.validate_input(text) | |
| if not is_valid: | |
| return f"Validation Error: {msg}" | |
| log_event("FEATURE_START", f"Generating summary in {mode} mode") | |
| success, result = summarizer.summarize(text, mode=mode) | |
| if success: | |
| return f""" | |
| **Summary ({mode.upper()} MODE)** | |
| {result} | |
| --- | |
| β Generation successful | |
| """ | |
| else: | |
| return f"Error: {result}" | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FEATURE_ERROR", f"Summary: {str(e)}") | |
| return error_msg | |
| def generate_quiz( | |
| input_text: str, | |
| num_questions: int, | |
| mode: str, | |
| use_file: bool, | |
| uploaded_file | |
| ) -> str: | |
| """Generate quiz based on input.""" | |
| try: | |
| text = input_text | |
| if use_file and uploaded_file: | |
| text, status = process_file_upload(uploaded_file) | |
| if not text: | |
| return f"Error: {status}" | |
| if not text: | |
| return "Error: No input provided" | |
| is_valid, msg = validator.validate_input(text) | |
| if not is_valid: | |
| return f"Validation Error: {msg}" | |
| log_event("FEATURE_START", f"Generating {num_questions} quiz questions in {mode} mode") | |
| success, result = quiz_gen.generate_quiz(text, num_questions=num_questions, mode=mode) | |
| if success: | |
| return f""" | |
| **QUIZ ({mode.upper()} MODE)** | |
| {result} | |
| --- | |
| β Quiz generated successfully | |
| """ | |
| else: | |
| return f"Error: {result}" | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FEATURE_ERROR", f"Quiz: {str(e)}") | |
| return error_msg | |
| def explain_concept( | |
| concept: str, | |
| context_text: str, | |
| mode: str, | |
| use_file: bool, | |
| uploaded_file | |
| ) -> str: | |
| """Explain a concept.""" | |
| try: | |
| context = context_text | |
| if use_file and uploaded_file: | |
| context, status = process_file_upload(uploaded_file) | |
| if not context: | |
| context = context_text | |
| if not concept: | |
| return "Error: Please enter a concept to explain" | |
| is_valid, msg = validator.validate_input(concept) | |
| if not is_valid: | |
| return f"Validation Error: {msg}" | |
| log_event("FEATURE_START", f"Explaining '{concept}' in {mode} mode") | |
| success, result = explainer.explain(concept, context=context, mode=mode) | |
| if success: | |
| return f""" | |
| **EXPLANATION - {concept.upper()} ({mode.upper()} MODE)** | |
| {result} | |
| --- | |
| β Explanation generated | |
| """ | |
| else: | |
| return f"Error: {result}" | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FEATURE_ERROR", f"Explainer: {str(e)}") | |
| return error_msg | |
| def solve_doubt( | |
| question: str, | |
| context_text: str, | |
| mode: str, | |
| use_file: bool, | |
| uploaded_file | |
| ) -> str: | |
| """Solve a student's doubt.""" | |
| try: | |
| context = context_text | |
| if use_file and uploaded_file: | |
| context, status = process_file_upload(uploaded_file) | |
| if not context: | |
| context = context_text | |
| if not question: | |
| return "Error: Please enter your doubt/question" | |
| is_valid, msg = validator.validate_input(question) | |
| if not is_valid: | |
| return f"Validation Error: {msg}" | |
| log_event("FEATURE_START", f"Solving doubt in {mode} mode") | |
| success, result = doubt_solver.solve(question, context=context, mode=mode) | |
| if success: | |
| return f""" | |
| **DOUBT SOLVER ({mode.upper()} MODE)** | |
| {result} | |
| --- | |
| β Doubt solved successfully | |
| """ | |
| else: | |
| return f"Error: {result}" | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FEATURE_ERROR", f"DoubtSolver: {str(e)}") | |
| return error_msg | |
| def compare_modes(input_text: str, feature_type: str, extra_input: str = "") -> str: | |
| """Compare outputs across different modes.""" | |
| try: | |
| if not input_text and not extra_input: | |
| return "Error: Please provide input" | |
| modes = ["normal", "detailed", "teacher", "exam"] | |
| results = {} | |
| log_event("FEATURE_START", f"Comparing modes for {feature_type}") | |
| if feature_type == "Summary": | |
| for mode in modes: | |
| success, result = summarizer.summarize(input_text, mode=mode) | |
| results[mode] = result if success else f"Error: {result}" | |
| elif feature_type == "Explainer": | |
| concept = extra_input or input_text | |
| for mode in modes: | |
| success, result = explainer.explain(concept, mode=mode) | |
| results[mode] = result if success else f"Error: {result}" | |
| elif feature_type == "Doubt Solver": | |
| question = extra_input or input_text | |
| for mode in modes: | |
| success, result = doubt_solver.solve(question, mode=mode) | |
| results[mode] = result if success else f"Error: {result}" | |
| # Format comparison | |
| output = f"**MODE COMPARISON - {feature_type.upper()}**\n\n" | |
| for mode, result in results.items(): | |
| output += f"## {mode.upper()} Mode\n{result}\n\n---\n\n" | |
| return output | |
| except Exception as e: | |
| error_msg = format_error_message(e) | |
| log_event("FEATURE_ERROR", f"Mode comparison: {str(e)}") | |
| return error_msg | |
| # =========================== | |
| # CREATE DIRECTORIES | |
| # =========================== | |
| create_directory("uploads") | |
| create_directory("data/outputs") | |
| create_directory("prompts/modes") | |
| create_directory("core") | |
| create_directory("features") | |
| # =========================== | |
| # BUILD GRADIO INTERFACE | |
| # =========================== | |
| with gr.Blocks( | |
| title=APP_TITLE | |
| ) as demo: | |
| # Header | |
| gr.Markdown(f"# π {APP_TITLE}") | |
| gr.Markdown(APP_DESCRIPTION) | |
| # Tabs for different features | |
| with gr.Tabs(): | |
| # ===== TAB 1: SUMMARIZER ===== | |
| with gr.TabItem("π Summary Generator"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Input Options") | |
| use_summary_file = gr.Checkbox( | |
| label="Upload File", | |
| value=False | |
| ) | |
| summary_uploaded_file = gr.File( | |
| label="Choose File (.txt, .pdf, .md)", | |
| visible=False, | |
| file_types=SUPPORTED_FILE_TYPES | |
| ) | |
| summary_text_input = gr.Textbox( | |
| label="Or paste text here", | |
| lines=10, | |
| placeholder="Paste your notes or topic here...", | |
| max_lines=20 | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Settings") | |
| summary_mode = gr.Radio( | |
| label="Select Mode", | |
| choices=["normal", "detailed", "teacher", "exam"], | |
| value="normal" | |
| ) | |
| summary_btn = gr.Button( | |
| "Generate Summary", | |
| variant="primary" | |
| ) | |
| summary_output = gr.Textbox( | |
| label="β¨ Your Summary", | |
| lines=15, | |
| interactive=False | |
| ) | |
| # Toggle file upload visibility | |
| use_summary_file.change( | |
| lambda x: gr.File(visible=x), | |
| use_summary_file, | |
| summary_uploaded_file | |
| ) | |
| # Generate on click | |
| summary_btn.click( | |
| generate_summary, | |
| inputs=[ | |
| summary_text_input, | |
| summary_mode, | |
| use_summary_file, | |
| summary_uploaded_file | |
| ], | |
| outputs=summary_output | |
| ) | |
| # ===== TAB 2: QUIZ GENERATOR ===== | |
| with gr.TabItem("β Quiz Generator"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Input") | |
| use_quiz_file = gr.Checkbox( | |
| label="Upload File", | |
| value=False | |
| ) | |
| quiz_uploaded_file = gr.File( | |
| label="Choose File", | |
| visible=False, | |
| file_types=SUPPORTED_FILE_TYPES | |
| ) | |
| quiz_text_input = gr.Textbox( | |
| label="Or paste text", | |
| lines=10, | |
| placeholder="Paste your study material..." | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Quiz Settings") | |
| quiz_num_questions = gr.Slider( | |
| label="Number of Questions", | |
| minimum=1, | |
| maximum=20, | |
| value=5, | |
| step=1 | |
| ) | |
| quiz_mode = gr.Radio( | |
| label="Mode", | |
| choices=["normal", "detailed", "teacher", "exam"], | |
| value="normal" | |
| ) | |
| quiz_btn = gr.Button( | |
| "Generate Quiz", | |
| variant="primary" | |
| ) | |
| quiz_output = gr.Textbox( | |
| label="π Your Quiz", | |
| lines=20, | |
| interactive=False | |
| ) | |
| use_quiz_file.change( | |
| lambda x: gr.File(visible=x), | |
| use_quiz_file, | |
| quiz_uploaded_file | |
| ) | |
| quiz_btn.click( | |
| generate_quiz, | |
| inputs=[ | |
| quiz_text_input, | |
| quiz_num_questions, | |
| quiz_mode, | |
| use_quiz_file, | |
| quiz_uploaded_file | |
| ], | |
| outputs=quiz_output | |
| ) | |
| # ===== TAB 3: EXPLAINER ===== | |
| with gr.TabItem("π Concept Explainer"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### What to Explain") | |
| concept_input = gr.Textbox( | |
| label="Concept to Explain", | |
| placeholder="e.g., Photosynthesis, Recursion, etc." | |
| ) | |
| gr.Markdown("### Optional Context") | |
| use_explain_file = gr.Checkbox( | |
| label="Add Context from File", | |
| value=False | |
| ) | |
| explain_uploaded_file = gr.File( | |
| label="Choose File", | |
| visible=False, | |
| file_types=SUPPORTED_FILE_TYPES | |
| ) | |
| context_input = gr.Textbox( | |
| label="Or paste context", | |
| lines=8, | |
| placeholder="Paste related notes for context..." | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Explanation Mode") | |
| explain_mode = gr.Radio( | |
| label="Select Mode", | |
| choices=["normal", "detailed", "teacher", "exam"], | |
| value="teacher" | |
| ) | |
| explain_btn = gr.Button( | |
| "Explain Concept", | |
| variant="primary" | |
| ) | |
| explain_output = gr.Textbox( | |
| label="π Explanation", | |
| lines=20, | |
| interactive=False | |
| ) | |
| use_explain_file.change( | |
| lambda x: gr.File(visible=x), | |
| use_explain_file, | |
| explain_uploaded_file | |
| ) | |
| explain_btn.click( | |
| explain_concept, | |
| inputs=[ | |
| concept_input, | |
| context_input, | |
| explain_mode, | |
| use_explain_file, | |
| explain_uploaded_file | |
| ], | |
| outputs=explain_output | |
| ) | |
| # ===== TAB 4: DOUBT SOLVER ===== | |
| with gr.TabItem("π§ Doubt Solver"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Your Doubt") | |
| question_input = gr.Textbox( | |
| label="Ask Your Question", | |
| lines=8, | |
| placeholder="Type your doubt or question here...", | |
| max_lines=15 | |
| ) | |
| gr.Markdown("### Add Context (Optional)") | |
| use_doubt_file = gr.Checkbox( | |
| label="Add Notes from File", | |
| value=False | |
| ) | |
| doubt_uploaded_file = gr.File( | |
| label="Choose File", | |
| visible=False, | |
| file_types=SUPPORTED_FILE_TYPES | |
| ) | |
| doubt_context_input = gr.Textbox( | |
| label="Or paste notes", | |
| lines=8, | |
| placeholder="Paste relevant notes..." | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Solution Mode") | |
| doubt_mode = gr.Radio( | |
| label="Explanation Style", | |
| choices=["normal", "detailed", "teacher", "exam"], | |
| value="teacher" | |
| ) | |
| doubt_btn = gr.Button( | |
| "Solve Doubt", | |
| variant="primary" | |
| ) | |
| doubt_output = gr.Textbox( | |
| label="π‘ Solution", | |
| lines=20, | |
| interactive=False | |
| ) | |
| use_doubt_file.change( | |
| lambda x: gr.File(visible=x), | |
| use_doubt_file, | |
| doubt_uploaded_file | |
| ) | |
| doubt_btn.click( | |
| solve_doubt, | |
| inputs=[ | |
| question_input, | |
| doubt_context_input, | |
| doubt_mode, | |
| use_doubt_file, | |
| doubt_uploaded_file | |
| ], | |
| outputs=doubt_output | |
| ) | |
| # ===== TAB 5: COMPARE MODES ===== | |
| with gr.TabItem("π Mode Comparison"): | |
| gr.Markdown("### Compare how different modes respond to your input") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| compare_feature = gr.Radio( | |
| label="Select Feature", | |
| choices=["Summary", "Explainer", "Doubt Solver"], | |
| value="Summary" | |
| ) | |
| compare_input = gr.Textbox( | |
| label="Input Text", | |
| lines=10, | |
| placeholder="Paste text or ask a question..." | |
| ) | |
| compare_extra = gr.Textbox( | |
| label="Extra Input (if needed)", | |
| placeholder="For Explainer: concept to explain", | |
| visible=False | |
| ) | |
| compare_btn = gr.Button( | |
| "Compare All Modes", | |
| variant="primary" | |
| ) | |
| compare_output = gr.Textbox( | |
| label="π Mode Comparison Results", | |
| lines=25, | |
| interactive=False | |
| ) | |
| # Show/hide extra input based on feature selection | |
| compare_feature.change( | |
| lambda x: gr.Textbox(visible=(x != "Summary")), | |
| compare_feature, | |
| compare_extra | |
| ) | |
| compare_btn.click( | |
| compare_modes, | |
| inputs=[ | |
| compare_input, | |
| compare_feature, | |
| compare_extra | |
| ], | |
| outputs=compare_output | |
| ) | |
| # Footer | |
| gr.Markdown(""" | |
| --- | |
| ### π‘ Tips: | |
| - **Upload files** (PDF, TXT, MD) for faster processing | |
| - **Try different modes** to see how prompts affect responses | |
| - **Use Doubt Solver** with context for better answers | |
| - **Compare modes** to understand prompt engineering | |
| ### π Features Powered By: | |
| - **Prompt Engineering**: Dynamic prompts adapt to your needs | |
| - **Chain-of-Thought**: Step-by-step reasoning for complex problems | |
| - **Few-Shot Learning**: Consistent, high-quality outputs | |
| - **Input Validation**: Safety & security built-in | |
| *Built with β€οΈ for smarter studying* | |
| """) | |
| # =========================== | |
| # RUN APPLICATION | |
| # =========================== | |
| if __name__ == "__main__": | |
| log_event("APP_START", "AI Study Assistant launched") | |
| demo.launch( | |
| share=False, | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_error=True, | |
| theme=gr.themes.Soft(), | |
| ) | |