Spaces:
Sleeping
Sleeping
| """ | |
| Gradio Interface Module | |
| Creates the main Gradio web interface for the RAG system | |
| Compatible with Gradio 4.x and 6.x | |
| """ | |
| import gradio as gr | |
| from typing import Optional | |
| # Gradio 6.0 compatibility: TabItem was renamed to Tab | |
| # Use Tab for Gradio 6.0, fallback to TabItem for older versions | |
| try: | |
| # Try to use Tab (Gradio 6.0+) | |
| if hasattr(gr, 'Tab'): | |
| Tab = gr.Tab | |
| else: | |
| # Fallback to TabItem (Gradio 4.x) | |
| Tab = gr.TabItem | |
| except AttributeError: | |
| # If neither exists, use Tab (shouldn't happen) | |
| Tab = gr.Tab if hasattr(gr, 'Tab') else gr.TabItem | |
| from .rag_query import RAGQueryEngine | |
| from .question_generator import QuestionGenerator | |
| from .knowledge_graph import KnowledgeGraphGenerator | |
| from .config import Config | |
| # Import cold start onboarding functions if available | |
| try: | |
| from modules.cold_start_onboarding import check_and_show_onboarding | |
| COLD_START_AVAILABLE = True | |
| except ImportError: | |
| COLD_START_AVAILABLE = False | |
| def check_and_show_onboarding(user_profiling, user_id): | |
| """Fallback function if module not available""" | |
| if not user_profiling: | |
| return False | |
| return user_profiling.is_cold_start(user_id) | |
| class GradioInterfaceBuilder: | |
| """Builds the Gradio interface for the RAG system""" | |
| def __init__(self, rag_engine: RAGQueryEngine, question_generator: QuestionGenerator, | |
| knowledge_graph: KnowledgeGraphGenerator, config: Config, | |
| user_profiling=None, adaptive_engine=None, proactive_engine=None, enhanced_rag_engine=None): | |
| self.rag_engine = rag_engine | |
| self.question_generator = question_generator | |
| self.knowledge_graph = knowledge_graph | |
| self.config = config | |
| self.user_profiling = user_profiling | |
| self.adaptive_engine = adaptive_engine | |
| self.proactive_engine = proactive_engine | |
| self.enhanced_rag_engine = enhanced_rag_engine # Enhanced RAG engine (scenario feature) | |
| def create_interface(self): | |
| """Create the main Gradio interface""" | |
| with gr.Blocks(title="Mercedes E-class ADAS Manual Interface") as demo: | |
| gr.Markdown("# π Mercedes E-class ADAS Manual Interface") | |
| gr.Markdown("Ask questions, explore knowledge maps, and test your understanding!") | |
| # Create tabs with proper order: Setup -> Ask Questions -> Knowledge Map -> Test -> Personalized Learning | |
| # Ask Questions is set as default selected tab | |
| # Gradio 6.0 compatibility: Use Tab (which is gr.Tab for 6.0, gr.TabItem for 4.x) | |
| # Note: In Gradio 6.0, selected parameter might be deprecated, try both ways | |
| def create_tabs(): | |
| """Create tabs with compatibility for Gradio 4.x and 6.x""" | |
| try: | |
| # Try with selected parameter (Gradio 4.x style) | |
| return gr.Tabs(selected=1 if self.user_profiling else 0) | |
| except (TypeError, ValueError): | |
| # If selected parameter not supported, create without it (Gradio 6.0+) | |
| return gr.Tabs() | |
| with create_tabs(): | |
| # Tab 1: Setup (Cold Start/Onboarding) - only shown if user_profiling is available | |
| if self.user_profiling: | |
| with Tab("Setup"): | |
| self._create_onboarding_tab() | |
| # Tab 2: Ask Questions (Default selected tab) | |
| with Tab("Ask Questions"): | |
| self._create_qa_tab() | |
| # Tab 3: Knowledge Map | |
| with Tab("Knowledge Map"): | |
| self._create_knowledge_map_tab() | |
| # Tab 4: Test Your Knowledge | |
| with Tab("Test Your Knowledge"): | |
| self._create_test_tab() | |
| # Tab 5: Personalized Learning Path (if available) | |
| if self.adaptive_engine: | |
| with Tab("Personalized Learning"): | |
| self._create_learning_path_tab() | |
| return demo | |
| def _create_qa_tab(self): | |
| """Create the Q&A tab""" | |
| gr.Markdown("Ask questions about your car's advanced driver assistance systems") | |
| # User ID input (if user profiling is available) | |
| user_id_input = None | |
| if self.user_profiling: | |
| with gr.Row(): | |
| user_id_input = gr.Textbox( | |
| label="User ID", | |
| placeholder="Enter your user ID (e.g., default_user)", | |
| value="default_user", | |
| scale=3 | |
| ) | |
| load_suggestions_btn = gr.Button("π‘ Get Suggestions", variant="secondary", scale=1) | |
| # Prompt suggestions area | |
| suggestions_container = gr.Column(visible=False) | |
| suggestions_display = None | |
| refresh_suggestions_btn = None | |
| cancel_suggestions_btn = None | |
| regenerate_suggestions_btn = None | |
| if self.proactive_engine: | |
| with suggestions_container: | |
| gr.Markdown("### π‘ Suggested Questions for You:") | |
| suggestions_display = gr.HTML() | |
| with gr.Row(): | |
| refresh_suggestions_btn = gr.Button("π Refresh Suggestions", variant="secondary", size="sm") | |
| cancel_suggestions_btn = gr.Button("βΉοΈ Stop", variant="stop", size="sm") | |
| regenerate_suggestions_btn = gr.Button("π Regenerate", variant="secondary", size="sm") | |
| with gr.Row(): | |
| query_input = gr.Textbox( | |
| lines=2, | |
| placeholder="Enter your question here...", | |
| label="Your Question" | |
| ) | |
| with gr.Row(): | |
| submit_btn = gr.Button("Get Answer", variant="primary") | |
| cancel_answer_btn = gr.Button("βΉοΈ Stop", variant="stop") | |
| regenerate_answer_btn = gr.Button("π Regenerate", variant="secondary") | |
| with gr.Column(): | |
| answer_output = gr.Markdown(label="Answer") | |
| footnotes_output = gr.Markdown(label="Sources") | |
| # Scenario contextualization area (collapsible) | |
| scenarios_container = gr.Column(visible=False) | |
| with scenarios_container: | |
| scenarios_header = gr.Markdown("### π― Related Scenarios") | |
| scenarios_display = gr.HTML() | |
| # Follow-up questions area | |
| followup_container = gr.Column(visible=False) | |
| cancel_followup_btn = None | |
| regenerate_followup_btn = None | |
| if self.proactive_engine: | |
| with followup_container: | |
| gr.Markdown("### π‘ Want to learn more? Try these questions:") | |
| followup_questions_display = gr.HTML() | |
| with gr.Row(): | |
| cancel_followup_btn = gr.Button("βΉοΈ Stop", variant="stop", size="sm") | |
| regenerate_followup_btn = gr.Button("π Regenerate", variant="secondary", size="sm") | |
| else: | |
| followup_questions_display = gr.HTML() | |
| def process_query(query, user_id="default_user"): | |
| """Process query and generate follow-up questions""" | |
| # Use enhanced RAG engine if available, otherwise use standard | |
| if self.enhanced_rag_engine: | |
| try: | |
| enhanced_answer = self.enhanced_rag_engine.query(query, user_id=user_id) | |
| answer = enhanced_answer.answer | |
| footnotes = enhanced_answer.sources | |
| scenarios_html = enhanced_answer.scenarios_html | |
| show_scenarios = enhanced_answer.scenario_count > 0 | |
| except Exception as e: | |
| print(f"β οΈ Error in enhanced RAG engine: {e}, falling back to standard") | |
| answer, footnotes = self.rag_engine.query(query) | |
| scenarios_html = "" | |
| show_scenarios = False | |
| else: | |
| answer, footnotes = self.rag_engine.query(query) | |
| scenarios_html = "" | |
| show_scenarios = False | |
| # Update user profile with question | |
| if self.user_profiling and user_id: | |
| try: | |
| self.user_profiling.update_from_question(user_id, query) | |
| except Exception as e: | |
| print(f"Error updating user profile: {e}") | |
| # Generate follow-up questions | |
| followup_html = "" | |
| followup_visible = False | |
| if self.proactive_engine and user_id: | |
| try: | |
| followup_questions = self.proactive_engine.get_follow_up_questions( | |
| user_id, answer, max_questions=5 | |
| ) | |
| if followup_questions: | |
| followup_visible = True | |
| followup_html = "<div style='margin-top: 15px;'>" | |
| for i, q_data in enumerate(followup_questions, 1): | |
| question = q_data.get("question", "") | |
| bloom_level = q_data.get("bloom_level", "") | |
| # Escape quotes for JavaScript | |
| question_escaped = question.replace("'", "\\'").replace('"', '\\"') | |
| followup_html += f""" | |
| <div style='margin: 10px 0; padding: 12px; background-color: #f5f5f5; border-radius: 5px; border-left: 3px solid #4CAF50; display: flex; justify-content: space-between; align-items: center;'> | |
| <div style='flex: 1;'> | |
| <div style='font-weight: 500; margin-bottom: 4px;'>{question}</div> | |
| <small style='color: #666;'>Bloom Level: {bloom_level.title()}</small> | |
| </div> | |
| <button onclick="document.querySelector('textarea[label=\\'Your Question\\']').value='{question_escaped}'; this.style.backgroundColor='#4CAF50'; this.style.color='white';" | |
| style='margin-left: 15px; padding: 8px 16px; background-color: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; white-space: nowrap;'> | |
| Use | |
| </button> | |
| </div> | |
| """ | |
| followup_html += "</div>" | |
| except Exception as e: | |
| print(f"Error generating follow-up questions: {e}") | |
| # Prepare return values | |
| outputs = [answer, footnotes] | |
| # Add scenarios output | |
| if self.enhanced_rag_engine: | |
| outputs.append(gr.update(visible=show_scenarios)) | |
| outputs.append(scenarios_html if scenarios_html else "") | |
| # Add follow-up questions output | |
| if self.proactive_engine: | |
| outputs.append(gr.update(visible=followup_visible)) | |
| outputs.append(followup_html) | |
| return tuple(outputs) | |
| def load_suggestions(user_id="default_user"): | |
| """Load prompt suggestions""" | |
| if not self.proactive_engine or not user_id: | |
| return gr.update(visible=False), "" | |
| try: | |
| suggestions = self.proactive_engine.get_prompt_suggestions(user_id, max_suggestions=5) | |
| if not suggestions: | |
| return gr.update(visible=False), "" | |
| suggestions_html = "<div style='margin-top: 10px;'>" | |
| for i, suggestion in enumerate(suggestions, 1): | |
| question = suggestion.get("question", "") | |
| reason = suggestion.get("reason", "") | |
| priority = suggestion.get("priority", "low") | |
| priority_color = {"high": "#f44336", "medium": "#ff9800", "low": "#4CAF50"}.get(priority, "#666") | |
| # Escape quotes for JavaScript | |
| question_escaped = question.replace("'", "\\'").replace('"', '\\"') | |
| suggestions_html += f""" | |
| <div style='margin: 10px 0; padding: 12px; background-color: #f9f9f9; border-radius: 5px; border-left: 4px solid {priority_color};'> | |
| <div style='display: flex; justify-content: space-between; align-items: start;'> | |
| <div style='flex: 1;'> | |
| <strong style='color: #333;'>{i}. {question}</strong> | |
| <br><small style='color: #666;'>{reason}</small> | |
| </div> | |
| <button onclick="document.querySelector('textarea[label=\\'Your Question\\']').value='{question_escaped}'; this.style.backgroundColor='#4CAF50'; this.style.color='white';" | |
| style='margin-left: 10px; padding: 8px 15px; background-color: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; white-space: nowrap;'> | |
| Use | |
| </button> | |
| </div> | |
| </div> | |
| """ | |
| suggestions_html += "</div>" | |
| return gr.update(visible=True), suggestions_html | |
| except Exception as e: | |
| print(f"Error loading suggestions: {e}") | |
| return gr.update(visible=False), "" | |
| # Set up event handlers | |
| if self.proactive_engine and user_id_input and suggestions_display: | |
| # Suggestions event handlers | |
| suggestions_event = load_suggestions_btn.click( | |
| load_suggestions, | |
| inputs=[user_id_input], | |
| outputs=[suggestions_container, suggestions_display] | |
| ) | |
| if refresh_suggestions_btn: | |
| refresh_suggestions_btn.click( | |
| load_suggestions, | |
| inputs=[user_id_input], | |
| outputs=[suggestions_container, suggestions_display] | |
| ) | |
| if regenerate_suggestions_btn: | |
| regenerate_suggestions_btn.click( | |
| load_suggestions, | |
| inputs=[user_id_input], | |
| outputs=[suggestions_container, suggestions_display] | |
| ) | |
| if cancel_suggestions_btn: | |
| cancel_suggestions_btn.click(fn=None, cancels=suggestions_event) | |
| # Build outputs list for query | |
| outputs_list = [answer_output, footnotes_output] | |
| if self.enhanced_rag_engine: | |
| outputs_list.extend([scenarios_container, scenarios_display]) | |
| outputs_list.extend([followup_container, followup_questions_display]) | |
| # Query event handlers | |
| query_event = submit_btn.click( | |
| process_query, | |
| inputs=[query_input, user_id_input], | |
| outputs=outputs_list | |
| ) | |
| regenerate_answer_btn.click( | |
| process_query, | |
| inputs=[query_input, user_id_input], | |
| outputs=outputs_list | |
| ) | |
| if cancel_answer_btn: | |
| cancel_answer_btn.click(fn=None, cancels=query_event) | |
| # Follow-up questions event handlers (regenerate only, cancel is handled by query cancel) | |
| if self.proactive_engine and regenerate_followup_btn: | |
| def regenerate_followup(query, user_id, answer_text): | |
| """Regenerate follow-up questions based on the current answer""" | |
| if not self.proactive_engine or not user_id or not answer_text: | |
| return gr.update(visible=False), "" | |
| try: | |
| followup_questions = self.proactive_engine.get_follow_up_questions( | |
| user_id, answer_text, max_questions=5 | |
| ) | |
| if followup_questions: | |
| followup_html = "<div style='margin-top: 15px;'>" | |
| for i, q_data in enumerate(followup_questions, 1): | |
| question = q_data.get("question", "") | |
| bloom_level = q_data.get("bloom_level", "") | |
| question_escaped = question.replace("'", "\\'").replace('"', '\\"') | |
| followup_html += f""" | |
| <div style='margin: 10px 0; padding: 12px; background-color: #f5f5f5; border-radius: 5px; border-left: 3px solid #4CAF50; display: flex; justify-content: space-between; align-items: center;'> | |
| <div style='flex: 1;'> | |
| <div style='font-weight: 500; margin-bottom: 4px;'>{question}</div> | |
| <small style='color: #666;'>Bloom Level: {bloom_level.title()}</small> | |
| </div> | |
| <button onclick="document.querySelector('textarea[label=\\'Your Question\\']').value='{question_escaped}'; this.style.backgroundColor='#4CAF50'; this.style.color='white';" | |
| style='margin-left: 15px; padding: 8px 16px; background-color: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; white-space: nowrap;'> | |
| Use | |
| </button> | |
| </div> | |
| """ | |
| followup_html += "</div>" | |
| return gr.update(visible=True), followup_html | |
| else: | |
| return gr.update(visible=False), "" | |
| except Exception as e: | |
| print(f"Error regenerating follow-up questions: {e}") | |
| return gr.update(visible=False), "" | |
| followup_event = regenerate_followup_btn.click( | |
| regenerate_followup, | |
| inputs=[query_input, user_id_input, answer_output], | |
| outputs=[followup_container, followup_questions_display] | |
| ) | |
| if cancel_followup_btn: | |
| cancel_followup_btn.click(fn=None, cancels=followup_event) | |
| else: | |
| # Build outputs list (must match process_query return values) | |
| outputs_list = [answer_output, footnotes_output] | |
| if self.enhanced_rag_engine: | |
| outputs_list.extend([scenarios_container, scenarios_display]) | |
| if self.proactive_engine: | |
| outputs_list.extend([followup_container, followup_questions_display]) | |
| query_event = submit_btn.click( | |
| process_query, | |
| inputs=[query_input], | |
| outputs=outputs_list | |
| ) | |
| regenerate_answer_btn.click( | |
| process_query, | |
| inputs=[query_input], | |
| outputs=outputs_list | |
| ) | |
| if cancel_answer_btn: | |
| cancel_answer_btn.click(fn=None, cancels=query_event) | |
| def _create_knowledge_map_tab(self): | |
| """Create the knowledge map tab""" | |
| gr.Markdown("## π Car Manual Knowledge Map") | |
| gr.Markdown("This visualization shows how different concepts in the car manual are related.") | |
| knowledge_map_img = gr.Image( | |
| value=str(self.config.output_dir / "knowledge_graph.png"), | |
| label="Knowledge Graph" | |
| ) | |
| gr.Markdown("## π₯ Document Similarity Heatmap") | |
| gr.Markdown("This heatmap shows how similar different ADAS features are to each other.") | |
| similarity_heatmap_img = gr.Image( | |
| value=str(self.config.output_dir / "similarity_heatmap.png"), | |
| label="Similarity Heatmap" | |
| ) | |
| with gr.Row(): | |
| refresh_btn = gr.Button("π Refresh Visualizations", variant="secondary") | |
| def refresh_images(): | |
| graph_path, heatmap_path = self.knowledge_graph.generate_visualizations() | |
| return graph_path, heatmap_path | |
| refresh_btn.click( | |
| refresh_images, | |
| inputs=[], | |
| outputs=[knowledge_map_img, similarity_heatmap_img] | |
| ) | |
| def _create_test_tab(self): | |
| """Create the test tab""" | |
| gr.Markdown("## π Test Your Understanding of Mercedes E-class ADAS") | |
| gr.Markdown("Select a topic to test your knowledge with multiple-choice questions based on Bloom's taxonomy levels.") | |
| topic_files = self.rag_engine.get_files_from_vector_store() | |
| with gr.Row(): | |
| test_questions = gr.State(None) | |
| current_level_idx = gr.State(0) | |
| selected_topic = gr.State(None) | |
| test_results = gr.State([]) | |
| topic_dropdown = gr.Dropdown( | |
| label="Select a Topic", | |
| choices=topic_files, | |
| value=topic_files[0] if topic_files else None, | |
| interactive=True | |
| ) | |
| start_test_btn = gr.Button("Start Test", variant="primary") | |
| # Progress indicator | |
| with gr.Column(visible=False) as progress_container: | |
| progress_html = gr.HTML() | |
| # Test container | |
| with gr.Column(visible=False) as test_container: | |
| taxonomy_level = gr.Markdown("Level: Remember") | |
| level_description = gr.Markdown() | |
| question_display = gr.Markdown() | |
| option_radio = gr.Radio( | |
| choices=["A", "B", "C", "D"], | |
| label="Select your answer", | |
| interactive=True | |
| ) | |
| submit_answer_btn = gr.Button("Submit Answer", variant="primary") | |
| feedback_display = gr.Markdown(visible=False) | |
| next_question_btn = gr.Button("Next Question", visible=False) | |
| show_summary_btn = gr.Button("Show Summary", visible=False) | |
| # Summary container | |
| with gr.Column(visible=False) as summary_container: | |
| summary_topic = gr.Markdown() | |
| summary_results = gr.HTML() | |
| summary_recommendation = gr.Markdown() | |
| restart_btn = gr.Button("Start Another Test") | |
| # Connect handlers (simplified - full implementation would include all handlers) | |
| # This is a placeholder structure - full implementation would be quite long | |
| def _create_onboarding_tab(self): | |
| """Create onboarding tab for cold start""" | |
| gr.Markdown("## π― Welcome! Let's Get Started") | |
| gr.Markdown("Complete your profile to get a personalized learning experience.") | |
| if not self.user_profiling: | |
| gr.Markdown("β οΈ Personalized Learning System not initialized.") | |
| return | |
| user_id_input = gr.Textbox( | |
| label="User ID", | |
| placeholder="Enter your user ID", | |
| value="default_user" | |
| ) | |
| with gr.Accordion("π Step 1: Background Information", open=True): | |
| background_input = gr.Radio( | |
| label="What's your experience with ADAS systems?", | |
| choices=[ | |
| ("Beginner - I'm new to ADAS systems", "beginner"), | |
| ("Intermediate - I know some basics", "intermediate"), | |
| ("Experienced - I have good knowledge", "experienced") | |
| ], | |
| value="beginner" | |
| ) | |
| with gr.Accordion("π¨ Step 2: Learning Preferences", open=True): | |
| learning_style_input = gr.Radio( | |
| label="How do you prefer to learn?", | |
| choices=[ | |
| ("Visual - I like diagrams and illustrations", "visual"), | |
| ("Textual - I prefer reading and explanations", "textual"), | |
| ("Practical - I learn by doing", "practical"), | |
| ("Mixed - I like a combination", "mixed") | |
| ], | |
| value="mixed" | |
| ) | |
| learning_pace_input = gr.Radio( | |
| label="What's your preferred learning pace?", | |
| choices=[ | |
| ("Slow - I like to take my time", "slow"), | |
| ("Medium - Normal pace is fine", "medium"), | |
| ("Fast - I want to learn quickly", "fast") | |
| ], | |
| value="medium" | |
| ) | |
| with gr.Accordion("π― Step 3: Learning Goals", open=True): | |
| learning_goals_input = gr.CheckboxGroup( | |
| label="What are your learning goals?", | |
| choices=[ | |
| "Understand basic ADAS functions", | |
| "Learn how to operate ADAS features", | |
| "Master advanced ADAS capabilities", | |
| "Troubleshoot ADAS issues", | |
| "Prepare for certification", | |
| "General knowledge improvement" | |
| ], | |
| value=["Understand basic ADAS functions"] | |
| ) | |
| with gr.Accordion("π Step 4: Initial Knowledge Assessment", open=True): | |
| gr.Markdown("Rate your familiarity with each topic (0 = No knowledge, 1 = Expert)") | |
| knowledge_sliders = {} | |
| for topic in self.config.available_topics: | |
| display_name = topic.replace("Function of ", "").replace(" Assist", "") | |
| knowledge_sliders[topic] = gr.Slider( | |
| label=display_name, | |
| minimum=0.0, | |
| maximum=1.0, | |
| value=0.0, | |
| step=0.1 | |
| ) | |
| submit_btn = gr.Button("Complete Setup", variant="primary") | |
| output_result = gr.JSON(label="Setup Result") | |
| def submit_onboarding(user_id, background, learning_style, learning_pace, | |
| learning_goals, *knowledge_values): | |
| """Submit cold start data""" | |
| if not self.user_profiling: | |
| return {"status": "error", "message": "System not initialized"} | |
| # Convert knowledge_values tuple to dictionary | |
| knowledge_survey = {} | |
| for i, topic in enumerate(self.config.available_topics): | |
| if i < len(knowledge_values): | |
| knowledge_survey[topic] = knowledge_values[i] | |
| else: | |
| knowledge_survey[topic] = 0.0 | |
| # Handle tuple values from Radio components | |
| if isinstance(background, tuple): | |
| background = background[1] if len(background) > 1 else background[0] | |
| if isinstance(learning_style, tuple): | |
| learning_style = learning_style[1] if len(learning_style) > 1 else learning_style[0] | |
| if isinstance(learning_pace, tuple): | |
| learning_pace = learning_pace[1] if len(learning_pace) > 1 else learning_pace[0] | |
| onboarding_data = { | |
| 'learning_style': learning_style, | |
| 'learning_pace': learning_pace, | |
| 'background_experience': background, | |
| 'learning_goals': learning_goals if learning_goals else [], | |
| 'initial_knowledge_survey': knowledge_survey, | |
| 'initial_assessment_completed': True | |
| } | |
| try: | |
| profile = self.user_profiling.complete_onboarding(user_id, onboarding_data) | |
| return { | |
| "status": "success", | |
| "message": f"Onboarding completed for {user_id}", | |
| "profile_summary": self.user_profiling.get_profile_summary(user_id) | |
| } | |
| except Exception as e: | |
| import traceback | |
| error_details = traceback.format_exc() | |
| return {"status": "error", "message": f"Error: {str(e)}\nDetails: {error_details}"} | |
| inputs = [user_id_input, background_input, learning_style_input, | |
| learning_pace_input, learning_goals_input] + list(knowledge_sliders.values()) | |
| submit_btn.click(submit_onboarding, inputs=inputs, outputs=output_result) | |
| def _create_learning_path_tab(self): | |
| """Create personalized learning path tab""" | |
| gr.Markdown("## πΊοΈ Your Personalized Learning Journey") | |
| gr.Markdown("Get a customized learning path based on your knowledge profile.") | |
| if not self.adaptive_engine or not self.user_profiling: | |
| gr.Markdown("β οΈ Personalized Learning System not initialized.") | |
| return | |
| # User ID input | |
| with gr.Row(): | |
| user_id_input = gr.Textbox( | |
| label="User ID", | |
| placeholder="Enter your user ID", | |
| value="default_user" | |
| ) | |
| load_profile_btn = gr.Button("Load My Profile", variant="primary") | |
| # User profile display | |
| with gr.Column(visible=False) as profile_container: | |
| profile_summary = gr.Markdown() | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Knowledge Profile") | |
| knowledge_level_display = gr.JSON() | |
| with gr.Column(): | |
| gr.Markdown("### π Learning Statistics") | |
| learning_stats = gr.JSON() | |
| # Learning path generation | |
| with gr.Row(): | |
| focus_areas_input = gr.CheckboxGroup( | |
| label="Focus Areas (Optional)", | |
| choices=self.config.available_topics, | |
| value=[], | |
| interactive=True | |
| ) | |
| generate_path_btn = gr.Button("Generate Learning Path", variant="primary") | |
| # Learning path display | |
| with gr.Column(visible=False) as path_container: | |
| gr.Markdown("### πΊοΈ Your Learning Path") | |
| path_progress = gr.HTML() | |
| path_visualization = gr.HTML() | |
| with gr.Row(): | |
| with gr.Column(): | |
| current_node_info = gr.Markdown() | |
| recommendations_display = gr.JSON() | |
| def check_and_show_onboarding_wrapper(user_id): | |
| """Check if user needs onboarding""" | |
| if not user_id: | |
| return False | |
| return check_and_show_onboarding(self.user_profiling, user_id) | |
| def load_user_profile(user_id): | |
| """Load the user profile""" | |
| if not self.user_profiling or not user_id: | |
| return (gr.update(visible=False), "", {}, {}, []) | |
| if check_and_show_onboarding_wrapper(user_id): | |
| return ( | |
| gr.update(visible=False), | |
| f"## β οΈ Onboarding Required\n\nPlease complete onboarding first.", | |
| {}, | |
| {}, | |
| self.config.available_topics | |
| ) | |
| profile = self.user_profiling.get_or_create_profile(user_id) | |
| summary = self.user_profiling.get_profile_summary(user_id) | |
| summary_text = f""" | |
| ### π€ User Profile: {user_id} | |
| **Learning Style:** {summary['learning_style'].title()} | |
| **Learning Pace:** {summary['learning_pace'].title()} | |
| **Overall Progress:** {summary['overall_progress']:.1%} | |
| **Total Questions:** {summary['total_questions']} | |
| **Total Tests:** {summary['total_tests']} | |
| **Strong Areas:** {', '.join(summary['strong_areas']) if summary['strong_areas'] else 'None'} | |
| **Weak Areas:** {', '.join(summary['weak_areas']) if summary['weak_areas'] else 'None'} | |
| """ | |
| knowledge_data = summary['knowledge_level'] or {"No topics learned yet": 0.0} | |
| stats_data = { | |
| "Total Questions": summary['total_questions'], | |
| "Total Tests": summary['total_tests'], | |
| "Preferred Topics": summary['preferred_topics'][:5] if summary['preferred_topics'] else [], | |
| "Overall Progress": f"{summary['overall_progress']:.1%}" | |
| } | |
| return ( | |
| gr.update(visible=True), | |
| summary_text, | |
| knowledge_data, | |
| stats_data, | |
| self.config.available_topics | |
| ) | |
| def generate_learning_path(user_id, focus_areas): | |
| """Generate learning paths""" | |
| if not self.adaptive_engine or not user_id: | |
| return (gr.update(visible=False), "β οΈ System not initialized.", "", "", {}) | |
| if check_and_show_onboarding_wrapper(user_id): | |
| return (gr.update(visible=False), "β οΈ Please complete onboarding first.", "", "", {}) | |
| path = self.adaptive_engine.create_or_update_path(user_id, focus_areas if focus_areas else None) | |
| progress_html = f""" | |
| <div style="width:100%; background-color:#f0f0f0; border-radius:5px; overflow:hidden; margin:20px 0;"> | |
| <div style="width:{path.completion_percentage*100}%; background-color:#4CAF50; height:30px; border-radius:5px; display:flex; align-items:center; justify-content:center; color:white; font-weight:bold;"> | |
| {path.completion_percentage*100:.1f}% Complete | |
| </div> | |
| </div> | |
| <p><strong>Total Nodes:</strong> {len(path.nodes)} | <strong>Completed:</strong> {sum(1 for n in path.nodes if n.status == 'completed')} | <strong>Estimated Time:</strong> {path.estimated_total_time} minutes</p> | |
| """ | |
| path_html = "<div style='margin:20px 0;'><h4>Learning Path:</h4><div style='display:flex; flex-direction:column; gap:10px;'>" | |
| for i, node in enumerate(path.nodes): | |
| status_color = {"completed": "#4CAF50", "in_progress": "#2196F3", "pending": "#9E9E9E", "skipped": "#FF9800"}.get(node.status, "#9E9E9E") | |
| is_current = i == path.current_node_index | |
| highlight = "border: 3px solid #FF5722; padding: 10px;" if is_current else "padding: 10px;" | |
| path_html += f""" | |
| <div style='{highlight} background-color:white; border-left: 5px solid {status_color}; border-radius:5px; margin:5px 0;'> | |
| <div style='display:flex; justify-content:space-between; align-items:center;'> | |
| <div><strong>{node.topic}</strong> - {node.bloom_level.title()} ({node.content_type})<br><small>Difficulty: {node.difficulty:.2f} | Time: {node.estimated_time} min</small></div> | |
| <div style='color:{status_color}; font-weight:bold;'>{node.status.title()}</div> | |
| </div> | |
| </div> | |
| """ | |
| path_html += "</div></div>" | |
| if path.current_node_index < len(path.nodes): | |
| current_node = path.nodes[path.current_node_index] | |
| current_node_info_text = f""" | |
| ### Current Learning Node | |
| **Topic:** {current_node.topic} | |
| **Bloom Level:** {current_node.bloom_level.title()} | |
| **Content Type:** {current_node.content_type.title()} | |
| **Difficulty:** {current_node.difficulty:.2f} | |
| **Estimated Time:** {current_node.estimated_time} minutes | |
| """ | |
| else: | |
| current_node_info_text = "### Learning Path Complete! π" | |
| recommendations = self.adaptive_engine.get_recommendations(user_id) | |
| return ( | |
| gr.update(visible=True), | |
| progress_html, | |
| path_html, | |
| current_node_info_text, | |
| recommendations | |
| ) | |
| load_profile_btn.click( | |
| load_user_profile, | |
| inputs=[user_id_input], | |
| outputs=[profile_container, profile_summary, knowledge_level_display, learning_stats, focus_areas_input] | |
| ) | |
| generate_path_btn.click( | |
| generate_learning_path, | |
| inputs=[user_id_input, focus_areas_input], | |
| outputs=[path_container, path_progress, path_visualization, current_node_info, recommendations_display] | |
| ) | |