{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "7ce0a47c-1c4f-44a4-a9d8-9ea6399a8f84", "metadata": {}, "outputs": [], "source": [ "#| default_exp learning_interface" ] }, { "cell_type": "markdown", "id": "55331735-898e-411b-b751-5b380605be36", "metadata": {}, "source": [ "# Learning Interface\n", "\n", "> Gradio interface" ] }, { "cell_type": "markdown", "id": "dd401991-b919-423e-9da7-961387faf11e", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "code", "execution_count": null, "id": "edc3fbb1-13ef-408a-b5fe-eb7a6821915b", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "from nbdev.showdoc import *" ] }, { "cell_type": "code", "execution_count": null, "id": "4be213cf-89b4-48c8-9592-f509332da485", "metadata": {}, "outputs": [ { "ename": "ImportError", "evalue": "cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[4], line 7\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path\n\u001b[0;32m 6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01masyncio\u001b[39;00m\n\u001b[1;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mclinical_tutor\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ClinicalTutor\n\u001b[0;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m format_response\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlearning_context\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m setup_logger\n", "\u001b[1;31mImportError\u001b[0m: cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)" ] } ], "source": [ "#| export\n", "from typing import Dict, List, Optional, Tuple, Any\n", "import gradio as gr\n", "from pathlib import Path\n", "import asyncio\n", "from datetime import datetime\n", "import pandas as pd\n", "from wardbuddy.clinical_tutor import ClinicalTutor\n", "from wardbuddy.learning_context import setup_logger\n", "\n", "logger = setup_logger(__name__)" ] }, { "cell_type": "markdown", "id": "c39da1db-e630-4296-93f6-03b9188320cc", "metadata": {}, "source": [ "## Learning Interface" ] }, { "cell_type": "code", "execution_count": null, "id": "aff3d321-2116-475f-b906-f74889e76d66", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def create_dashboard_css() -> str:\n", " \"\"\"Create custom CSS for dashboard styling\"\"\"\n", " return \"\"\"\n", " /* Global styles */\n", " .gradio-container {\n", " background-color: #0f172a !important; /* slate-900 */\n", " }\n", " \n", " /* Card styling */\n", " .dashboard-card {\n", " background-color: #1e293b !important; /* slate-800 */\n", " border: 1px solid #334155 !important; /* slate-700 */\n", " border-radius: 0.5rem !important;\n", " padding: 1rem !important;\n", " margin: 0.5rem 0 !important;\n", " color: #f1f5f9 !important; /* slate-100 */\n", " }\n", " \n", " /* Chat container */\n", " .chatbot {\n", " background-color: #1e293b !important; /* slate-800 */\n", " border-color: #334155 !important; /* slate-700 */\n", " }\n", " \n", " /* Message bubbles */\n", " .chatbot .message.user {\n", " background-color: #334155 !important; /* slate-700 */\n", " border: 1px solid #475569 !important; /* slate-600 */\n", " color: #f1f5f9 !important; /* slate-100 */\n", " }\n", " \n", " .chatbot .message.bot {\n", " background-color: #1e40af !important; /* blue-800 */\n", " border: 1px solid #1e3a8a !important; /* blue-900 */\n", " color: #f1f5f9 !important; /* slate-100 */\n", " }\n", " \n", " /* Input fields */\n", " textarea, input[type=\"text\"] {\n", " background-color: #334155 !important; /* slate-700 */\n", " color: #f1f5f9 !important; /* slate-100 */\n", " border: 1px solid #475569 !important; /* slate-600 */\n", " }\n", " \n", " textarea:focus, input[type=\"text\"]:focus {\n", " border-color: #3b82f6 !important; /* blue-500 */\n", " box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2) !important;\n", " }\n", " \n", " /* Buttons */\n", " button.primary {\n", " background-color: #2563eb !important; /* blue-600 */\n", " color: white !important;\n", " }\n", " \n", " button.primary:hover {\n", " background-color: #3b82f6 !important; /* blue-500 */\n", " }\n", " \n", " button.secondary {\n", " background-color: #475569 !important; /* slate-600 */\n", " color: white !important;\n", " }\n", " \n", " button.secondary:hover {\n", " background-color: #64748b !important; /* slate-500 */\n", " }\n", " \n", " /* Tabs */\n", " .tab-nav {\n", " background-color: #1e293b !important; /* slate-800 */\n", " border-bottom: 1px solid #334155 !important; /* slate-700 */\n", " }\n", " \n", " .tab-nav button {\n", " color: #f1f5f9 !important; /* slate-100 */\n", " }\n", " \n", " .tab-nav button.selected {\n", " border-bottom-color: #3b82f6 !important; /* blue-500 */\n", " }\n", " \n", " /* Status indicators */\n", " .status-active {\n", " color: #22c55e !important; /* green-500 */\n", " font-weight: 500 !important;\n", " }\n", " \n", " .status-completed {\n", " color: #94a3b8 !important; /* slate-400 */\n", " }\n", " \n", " /* Headers */\n", " .dashboard-header {\n", " color: #f1f5f9 !important; /* slate-100 */\n", " font-size: 1.5rem !important;\n", " font-weight: 600 !important;\n", " margin-bottom: 1rem !important;\n", " }\n", " \n", " /* Tables */\n", " table {\n", " background-color: #1e293b !important; /* slate-800 */\n", " color: #f1f5f9 !important; /* slate-100 */\n", " }\n", " \n", " th, td {\n", " border-color: #334155 !important; /* slate-700 */\n", " }\n", " \"\"\"" ] }, { "cell_type": "markdown", "id": "de7ace04-4841-461d-89bb-234b5f8b48e1", "metadata": {}, "source": [ "This module provides the user interface for the clinical learning system, including:\n", " * Case presentation and feedback\n", " * Learning preference configuration\n", " * Session management\n", " * Progress visualization" ] }, { "cell_type": "code", "execution_count": null, "id": "675c20cc-43aa-4897-89ba-4fa26dd37c20", "metadata": {}, "outputs": [], "source": [ "#| export\n", "\n", "class LearningInterface:\n", " \"\"\"\n", " Gradio interface for clinical learning interactions.\n", " \n", " Features:\n", " - Natural case discussion chat\n", " - Dynamic learning dashboard\n", " - Post-discussion analysis\n", " - Progress tracking\n", " \"\"\"\n", " \n", " def __init__(\n", " self,\n", " context_path: Optional[Path] = None,\n", " theme: str = \"default\"\n", " ):\n", " \"\"\"Initialize learning interface.\"\"\"\n", " self.tutor = ClinicalTutor(context_path)\n", " self.theme = theme\n", " self.context_path = context_path\n", " \n", " # Track current discussion state\n", " self.current_discussion = {\n", " \"started\": None,\n", " \"case_type\": None,\n", " \"messages\": []\n", " }\n", " \n", " logger.info(\"Learning interface initialized\")\n", " \n", " async def process_chat(\n", " self,\n", " message: str,\n", " history: List[List[str]],\n", " state: Dict[str, Any]\n", " ) -> Tuple[List[List[str]], str, Dict[str, Any]]: \n", " \"\"\"\n", " Process chat messages with state management.\n", " \n", " Args:\n", " message: User input message\n", " history: Chat history\n", " state: Current interface state\n", " \n", " Returns:\n", " tuple: (updated history, cleared message, updated state)\n", " \"\"\"\n", " try:\n", " if not message.strip():\n", " return history, \"\", state\n", " \n", " # Start new discussion if none active\n", " if not state.get(\"discussion_active\"):\n", " state[\"discussion_active\"] = True\n", " state[\"discussion_start\"] = datetime.now().isoformat()\n", " \n", " # Get tutor response\n", " response = await self.tutor.discuss_case(message)\n", " \n", " # Update history - now using list pairs instead of dicts\n", " if history is None:\n", " history = []\n", " history.append([message, response]) # Changed from dict format to list pair\n", " \n", " state[\"last_message\"] = datetime.now().isoformat()\n", " \n", " return history, \"\", state\n", " \n", " except Exception as e:\n", " logger.error(f\"Error in chat: {str(e)}\")\n", " return history or [], \"\", state\n", "\n", " async def end_discussion(\n", " self,\n", " history: List[List[str]],\n", " state: Dict[str, Any]\n", " ) -> Tuple[Dict[str, Any], Dict[str, Any]]:\n", " \"\"\"\n", " Analyze completed discussion and prepare summary.\n", " \n", " Args:\n", " history: Chat history as list of [user_message, assistant_message] pairs\n", " state: Current interface state\n", " \n", " Returns:\n", " tuple: (analysis results, updated state)\n", " \"\"\"\n", " try:\n", " if not history:\n", " return {\n", " \"learning_points\": [],\n", " \"gaps\": {},\n", " \"strengths\": [],\n", " \"suggested_objectives\": []\n", " }, state\n", " \n", " # Convert history format for analysis\n", " formatted_history = []\n", " for user_msg, assistant_msg in history:\n", " formatted_history.extend([\n", " {\"role\": \"user\", \"content\": user_msg},\n", " {\"role\": \"assistant\", \"content\": assistant_msg}\n", " ])\n", " \n", " # Get analysis\n", " analysis = await self.tutor.analyze_discussion(formatted_history)\n", " \n", " # Reset discussion state\n", " state[\"discussion_active\"] = False\n", " state[\"discussion_start\"] = None\n", " state[\"last_message\"] = None\n", " \n", " return analysis, state\n", " \n", " except Exception as e:\n", " logger.error(f\"Error analyzing discussion: {str(e)}\")\n", " return {\n", " \"learning_points\": [],\n", " \"gaps\": {},\n", " \"strengths\": [],\n", " \"suggested_objectives\": []\n", " }, state \n", " \n", " def update_rotation(\n", " self,\n", " specialty: str,\n", " start_date: str,\n", " end_date: str,\n", " focus_areas: str\n", " ) -> Tuple[str, str, str, str]:\n", " \"\"\"\n", " Update rotation details and return updated values.\n", " \n", " Args:\n", " specialty: Rotation specialty\n", " start_date: Start date string\n", " end_date: End date string\n", " focus_areas: Comma-separated focus areas\n", " \n", " Returns:\n", " tuple: Updated field values\n", " \"\"\"\n", " try:\n", " # Parse focus areas\n", " focus_list = [\n", " area.strip() \n", " for area in focus_areas.split(\",\") \n", " if area.strip()\n", " ]\n", " \n", " # Update context\n", " rotation = {\n", " \"specialty\": specialty,\n", " \"start_date\": start_date,\n", " \"end_date\": end_date,\n", " \"key_focus_areas\": focus_list\n", " }\n", " self.tutor.learning_context.update_rotation(rotation)\n", " \n", " # Return updated values\n", " return (\n", " specialty,\n", " start_date,\n", " end_date,\n", " \",\".join(focus_list)\n", " )\n", " \n", " except Exception as e:\n", " logger.error(f\"Error updating rotation: {str(e)}\")\n", " current = self.tutor.learning_context.current_rotation\n", " return (\n", " current[\"specialty\"],\n", " current[\"start_date\"] or \"\",\n", " current[\"end_date\"] or \"\",\n", " \",\".join(current[\"key_focus_areas\"])\n", " )\n", "\n", " def add_objective(\n", " self,\n", " objective: str,\n", " objectives_df: pd.DataFrame\n", " ) -> pd.DataFrame:\n", " \"\"\"\n", " Add new learning objective and return updated dataframe.\n", " \n", " Args:\n", " objective: New objective text\n", " objectives_df: Current objectives dataframe\n", " \n", " Returns:\n", " pd.DataFrame: Updated objectives list\n", " \"\"\"\n", " try:\n", " if not objective.strip():\n", " return objectives_df\n", " \n", " # Add to context\n", " self.tutor.learning_context.add_learning_objective(objective)\n", " \n", " # Convert to dataframe\n", " return pd.DataFrame([\n", " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n", " for obj in self.tutor.learning_context.learning_objectives\n", " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n", " \n", " except Exception as e:\n", " logger.error(f\"Error adding objective: {str(e)}\")\n", " return objectives_df\n", "\n", " def toggle_objective_status(\n", " self,\n", " evt: gr.SelectData, # Updated to use gr.SelectData\n", " objectives_df: pd.DataFrame\n", " ) -> pd.DataFrame:\n", " \"\"\"\n", " Toggle objective status between active and completed.\n", " \n", " Args:\n", " evt: Gradio select event containing row index\n", " objectives_df: Current objectives dataframe\n", " \n", " Returns:\n", " pd.DataFrame: Updated objectives list\n", " \"\"\"\n", " try:\n", " objective_idx = evt.index[0] # Get selected row index\n", " if objective_idx >= len(objectives_df):\n", " return objectives_df\n", " \n", " # Get objective\n", " objective = objectives_df.iloc[objective_idx][\"Objective\"]\n", " current_status = objectives_df.iloc[objective_idx][\"Status\"]\n", " \n", " # Toggle in context\n", " if current_status == \"active\":\n", " self.tutor.learning_context.complete_objective(objective)\n", " else:\n", " self.tutor.learning_context.add_learning_objective(objective)\n", " \n", " # Update dataframe\n", " return pd.DataFrame([\n", " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n", " for obj in self.tutor.learning_context.learning_objectives\n", " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n", " \n", " except Exception as e:\n", " logger.error(f\"Error toggling objective: {str(e)}\")\n", " return objectives_df\n", "\n", " def add_feedback_focus(\n", " self,\n", " focus: str,\n", " feedback_df: pd.DataFrame\n", " ) -> pd.DataFrame:\n", " \"\"\"Add new feedback focus area.\"\"\"\n", " try:\n", " if not focus.strip():\n", " return feedback_df\n", " \n", " # Add to context\n", " self.tutor.learning_context.toggle_feedback_focus(focus, True)\n", " \n", " # Update dataframe\n", " return pd.DataFrame([\n", " [pref[\"focus\"], pref[\"active\"]]\n", " for pref in self.tutor.learning_context.feedback_preferences\n", " ], columns=[\"Focus Area\", \"Active\"])\n", " \n", " except Exception as e:\n", " logger.error(f\"Error adding feedback focus: {str(e)}\")\n", " return feedback_df\n", "\n", " def toggle_feedback_status(\n", " self,\n", " evt: gr.SelectData, # Updated to use gr.SelectData\n", " feedback_df: pd.DataFrame\n", " ) -> pd.DataFrame:\n", " \"\"\"Toggle feedback focus active status.\"\"\"\n", " try:\n", " focus_idx = evt.index[0] # Get selected row index\n", " if focus_idx >= len(feedback_df):\n", " return feedback_df\n", " \n", " # Get focus area\n", " focus = feedback_df.iloc[focus_idx][\"Focus Area\"]\n", " current_status = feedback_df.iloc[focus_idx][\"Active\"]\n", " \n", " # Toggle in context\n", " self.tutor.learning_context.toggle_feedback_focus(\n", " focus, \n", " not current_status\n", " )\n", " \n", " # Update dataframe\n", " return pd.DataFrame([\n", " [pref[\"focus\"], pref[\"active\"]]\n", " for pref in self.tutor.learning_context.feedback_preferences\n", " ], columns=[\"Focus Area\", \"Active\"])\n", " \n", " except Exception as e:\n", " logger.error(f\"Error toggling feedback: {str(e)}\")\n", " return feedback_df\n", "\n", " def create_interface(self) -> gr.Blocks:\n", " \"\"\"Create and configure the Gradio interface\"\"\"\n", " with gr.Blocks(\n", " title=\"Clinical Learning Assistant\",\n", " theme=self.theme,\n", " css=create_dashboard_css()\n", " ) as interface:\n", " # State management\n", " state = gr.State({\n", " \"discussion_active\": False,\n", " \"discussion_start\": None,\n", " \"last_message\": None\n", " })\n", " \n", " # Header\n", " with gr.Row():\n", " gr.Markdown(\n", " \"# Clinical Learning Assistant\",\n", " elem_classes=[\"dashboard-header\"]\n", " )\n", " \n", " with gr.Row():\n", " # Left column - Chat interface\n", " with gr.Column(scale=2):\n", " # Active discussion indicator\n", " discussion_status = gr.Markdown(\n", " \"Start a new case discussion\",\n", " elem_classes=[\"dashboard-card\"]\n", " )\n", " \n", " # Chat interface\n", " chatbot = gr.Chatbot(\n", " height=500,\n", " label=\"Case Discussion\",\n", " show_label=True,\n", " elem_classes=[\"dashboard-card\"]\n", " )\n", " \n", " with gr.Row():\n", " msg = gr.Textbox(\n", " label=\"Present your case or ask questions\",\n", " placeholder=(\n", " \"Present your case as you would to your supervisor:\\n\"\n", " \"- Start with the chief complaint\\n\"\n", " \"- Include relevant history and findings\\n\"\n", " \"- Share your assessment and plan\"\n", " ),\n", " lines=5\n", " )\n", " \n", " # Add voice input with updated syntax\n", " audio_msg = gr.Audio(\n", " label=\"Or speak your case\",\n", " sources=[\"microphone\"],\n", " type=\"numpy\",\n", " streaming=True\n", " )\n", " \n", " with gr.Row():\n", " clear = gr.Button(\"Clear Discussion\")\n", " end_discussion = gr.Button(\n", " \"End Discussion & Review\",\n", " variant=\"primary\"\n", " )\n", " \n", " # Right column - Learning dashboard\n", " with gr.Column(scale=1):\n", " with gr.Tabs():\n", " # Current Rotation tab\n", " with gr.Tab(\"Current Rotation\"):\n", " with gr.Column(elem_classes=[\"dashboard-card\"]):\n", " specialty = gr.Textbox(\n", " label=\"Specialty\",\n", " value=self.tutor.learning_context.current_rotation[\"specialty\"]\n", " )\n", " start_date = gr.Textbox(\n", " label=\"Start Date (YYYY-MM-DD)\",\n", " value=self.tutor.learning_context.current_rotation[\"start_date\"]\n", " )\n", " end_date = gr.Textbox(\n", " label=\"End Date (YYYY-MM-DD)\",\n", " value=self.tutor.learning_context.current_rotation[\"end_date\"]\n", " )\n", " focus_areas = gr.Textbox(\n", " label=\"Key Focus Areas (comma-separated)\",\n", " value=\",\".join(\n", " self.tutor.learning_context.current_rotation[\"key_focus_areas\"]\n", " )\n", " )\n", " update_rotation_btn = gr.Button(\n", " \"Update Rotation\",\n", " variant=\"secondary\"\n", " )\n", " \n", " # Learning Objectives tab\n", " with gr.Tab(\"Learning Objectives\"):\n", " with gr.Column(elem_classes=[\"dashboard-card\"]):\n", " objectives_df = gr.DataFrame(\n", " headers=[\"Objective\", \"Status\", \"Date Added\"],\n", " value=[[\n", " obj[\"objective\"],\n", " obj[\"status\"],\n", " obj[\"added\"]\n", " ] for obj in self.tutor.learning_context.learning_objectives],\n", " interactive=True,\n", " wrap=True\n", " )\n", " \n", " with gr.Row():\n", " new_objective = gr.Textbox(\n", " label=\"New Learning Objective\",\n", " placeholder=\"Enter objective...\"\n", " )\n", " add_objective_btn = gr.Button(\n", " \"Add\",\n", " variant=\"secondary\"\n", " )\n", " \n", " # Feedback Preferences tab\n", " with gr.Tab(\"Feedback Focus\"):\n", " with gr.Column(elem_classes=[\"dashboard-card\"]):\n", " feedback_df = gr.DataFrame(\n", " headers=[\"Focus Area\", \"Active\"],\n", " value=[[\n", " pref[\"focus\"],\n", " pref[\"active\"]\n", " ] for pref in self.tutor.learning_context.feedback_preferences],\n", " interactive=True,\n", " wrap=True\n", " )\n", " \n", " with gr.Row():\n", " new_feedback = gr.Textbox(\n", " label=\"New Feedback Focus\",\n", " placeholder=\"Enter focus area...\"\n", " )\n", " add_feedback_btn = gr.Button(\n", " \"Add\",\n", " variant=\"secondary\"\n", " )\n", " \n", " # Knowledge Profile tab\n", " with gr.Tab(\"Knowledge Profile\"):\n", " with gr.Column(elem_classes=[\"dashboard-card\"]):\n", " # Knowledge Gaps\n", " gr.Markdown(\"### Knowledge Gaps\")\n", " gaps_display = gr.DataFrame(\n", " headers=[\"Topic\", \"Confidence\"],\n", " value=[[\n", " topic, confidence\n", " ] for topic, confidence in \n", " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n", " ],\n", " interactive=False\n", " )\n", " \n", " # Strengths Display\n", " gr.Markdown(\"### Strengths\")\n", " strengths_display = gr.DataFrame(\n", " headers=[\"Area\"],\n", " value=[[strength] for strength in \n", " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n", " ],\n", " interactive=False\n", " )\n", " \n", " # Recent Progress\n", " gr.Markdown(\"### Recent Progress\")\n", " progress_display = gr.DataFrame(\n", " headers=[\"Topic\", \"Improvement\", \"Date\"],\n", " value=[[\n", " prog[\"topic\"],\n", " f\"{prog['improvement']:.2f}\",\n", " prog[\"date\"]\n", " ] for prog in \n", " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n", " ],\n", " interactive=False\n", " )\n", " \n", " # Discussion summary section\n", " summary_section = gr.Column(visible=False)\n", " with summary_section:\n", " gr.Markdown(\"## Discussion Summary\")\n", " \n", " # Overview section\n", " with gr.Row():\n", " with gr.Column():\n", " gr.Markdown(\"### Session Overview\")\n", " session_overview = gr.JSON(\n", " label=\"Discussion Details\",\n", " value={\n", " \"duration\": \"0 minutes\",\n", " \"messages\": 0,\n", " \"topics_covered\": []\n", " }\n", " )\n", " \n", " # Learning Points and Gaps\n", " with gr.Row():\n", " with gr.Column():\n", " gr.Markdown(\"### Key Learning Points\")\n", " learning_points = gr.JSON(label=\"Points to Remember\")\n", " \n", " with gr.Column():\n", " gr.Markdown(\"### Knowledge Profile Updates\")\n", " with gr.Row():\n", " gaps = gr.JSON(label=\"Areas for Improvement\")\n", " strengths = gr.JSON(label=\"Demonstrated Strengths\")\n", " \n", " # Future Learning section\n", " gr.Markdown(\"### Planning Ahead\")\n", " with gr.Row():\n", " with gr.Column():\n", " gr.Markdown(\"#### Suggested Learning Objectives\")\n", " objectives = gr.JSON(label=\"Consider Adding\")\n", " \n", " with gr.Column():\n", " gr.Markdown(\"#### Recommended Focus Areas\")\n", " recommendations = gr.JSON(label=\"Next Steps\")\n", " \n", " # Action buttons\n", " with gr.Row():\n", " add_selected_objectives = gr.Button(\n", " \"Add Selected Objectives\",\n", " variant=\"primary\"\n", " )\n", " close_summary = gr.Button(\"Close Summary\")\n", " \n", " # Event handlers\n", " # Add new event handler for voice input\n", " def process_audio(audio):\n", " if audio is None:\n", " return None\n", " # Convert audio to text using your preferred method\n", " # For example, you could use transformers pipeline here\n", " try:\n", " from transformers import pipeline\n", " transcriber = pipeline(\"automatic-speech-recognition\", model=\"openai/whisper-small\")\n", " text = transcriber(audio)[\"text\"]\n", " return text\n", " except Exception as e:\n", " logger.error(f\"Error transcribing audio: {str(e)}\")\n", " return None\n", " \n", " # Update the event handler:\n", " audio_msg.stop_recording(\n", " fn=process_audio,\n", " outputs=[msg]\n", " ).then(\n", " fn=self.process_chat,\n", " inputs=[msg, chatbot, state],\n", " outputs=[chatbot, msg, state]\n", " ).then(\n", " fn=self._update_discussion_status,\n", " inputs=[state],\n", " outputs=[discussion_status]\n", " ) \n", "\n", " msg.submit(\n", " self.process_chat,\n", " inputs=[msg, chatbot, state],\n", " outputs=[chatbot, msg, state]\n", " ).then(\n", " self._update_discussion_status,\n", " inputs=[state],\n", " outputs=[discussion_status]\n", " )\n", " \n", " clear.click(\n", " lambda: ([], \"\", {\n", " \"discussion_active\": False,\n", " \"discussion_start\": None,\n", " \"last_message\": None\n", " }),\n", " outputs=[chatbot, msg, state]\n", " ).then(\n", " lambda: \"Start a new case discussion\",\n", " outputs=[discussion_status]\n", " )\n", " \n", " end_discussion.click(\n", " self.end_discussion,\n", " inputs=[chatbot, state],\n", " outputs=[\n", " session_overview,\n", " learning_points,\n", " gaps,\n", " strengths,\n", " objectives,\n", " recommendations\n", " ]\n", " ).then(\n", " lambda: gr.update(visible=True),\n", " None,\n", " summary_section\n", " ).then(\n", " self._refresh_knowledge_profile,\n", " outputs=[gaps_display, strengths_display, progress_display]\n", " )\n", " \n", " close_summary.click(\n", " lambda: gr.update(visible=False),\n", " None,\n", " summary_section\n", " )\n", " \n", " # Rotation management\n", " update_rotation_btn.click(\n", " self.update_rotation,\n", " inputs=[specialty, start_date, end_date, focus_areas],\n", " outputs=[specialty, start_date, end_date, focus_areas]\n", " )\n", " \n", " # Learning objectives management\n", " add_objective_btn.click(\n", " self.add_objective,\n", " inputs=[new_objective, objectives_df],\n", " outputs=[objectives_df]\n", " ).then(\n", " lambda: \"\",\n", " None,\n", " new_objective\n", " )\n", " \n", " objectives_df.select(\n", " self.toggle_objective_status,\n", " inputs=[objectives_df],\n", " outputs=[objectives_df]\n", " )\n", " \n", " # Feedback preferences management\n", " add_feedback_btn.click(\n", " self.add_feedback_focus,\n", " inputs=[new_feedback, feedback_df],\n", " outputs=[feedback_df]\n", " ).then(\n", " lambda: \"\",\n", " None,\n", " new_feedback\n", " )\n", " \n", " feedback_df.select(\n", " self.toggle_feedback_status,\n", " inputs=[feedback_df],\n", " outputs=[feedback_df]\n", " )\n", " \n", " # Add selected objectives from summary\n", " add_selected_objectives.click(\n", " self._add_suggested_objectives,\n", " inputs=[objectives],\n", " outputs=[objectives_df]\n", " )\n", " \n", " return interface\n", " \n", " def _update_discussion_status(self, state: Dict[str, Any]) -> str:\n", " \"\"\"Update discussion status display\"\"\"\n", " try:\n", " if not state.get(\"discussion_active\"):\n", " return \"Start a new case discussion\"\n", " \n", " start = datetime.fromisoformat(state[\"discussion_start\"])\n", " duration = datetime.now() - start\n", " minutes = int(duration.total_seconds() / 60)\n", " \n", " return f\"Active discussion ({minutes} minutes)\"\n", " \n", " except Exception as e:\n", " logger.error(f\"Error updating status: {str(e)}\")\n", " return \"Discussion status unknown\"\n", " \n", " def _refresh_knowledge_profile(\n", " self\n", " ) -> Tuple[List[List[str]], List[List[str]], List[List[str]]]:\n", " \"\"\"Refresh knowledge profile displays\"\"\"\n", " try:\n", " # Gaps\n", " gaps_data = [[\n", " topic, f\"{confidence:.2f}\"\n", " ] for topic, confidence in \n", " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n", " ]\n", " \n", " # Strengths\n", " strengths_data = [[\n", " strength\n", " ] for strength in \n", " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n", " ]\n", " \n", " # Progress\n", " progress_data = [[\n", " prog[\"topic\"],\n", " f\"{prog['improvement']:.2f}\",\n", " prog[\"date\"]\n", " ] for prog in \n", " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n", " ]\n", " \n", " return gaps_data, strengths_data, progress_data\n", " \n", " except Exception as e:\n", " logger.error(f\"Error refreshing profile: {str(e)}\")\n", " return [], [], []\n", " \n", " def _add_suggested_objectives(\n", " self,\n", " evt: gr.SelectData, # Updated to use gr.SelectData\n", " suggested_objectives: List[str]\n", " ) -> pd.DataFrame:\n", " \"\"\"Add selected suggested objectives to learning objectives\"\"\"\n", " try:\n", " selected_indices = [evt.index[0]] # Get selected row index\n", " \n", " for idx in selected_indices:\n", " if idx < len(suggested_objectives):\n", " objective = suggested_objectives[idx]\n", " self.tutor.learning_context.add_learning_objective(objective)\n", " \n", " return pd.DataFrame([\n", " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n", " for obj in self.tutor.learning_context.learning_objectives\n", " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n", " \n", " except Exception as e:\n", " logger.error(f\"Error adding objectives: {str(e)}\")\n", " return pd.DataFrame()" ] }, { "cell_type": "markdown", "id": "30c0f121-5d5f-4dc0-b897-f6e2067a63b2", "metadata": {}, "source": [ "## Launch Function" ] }, { "cell_type": "code", "execution_count": null, "id": "65f97529-b221-4a19-9856-fb20d7f7316e", "metadata": {}, "outputs": [], "source": [ "#| export\n", "async def launch_learning_interface(\n", " port: Optional[int] = None,\n", " context_path: Optional[Path] = None,\n", " share: bool = False,\n", " theme: str = \"default\"\n", ") -> None:\n", " \"\"\"Launch the learning interface application.\"\"\"\n", " try:\n", " interface = LearningInterface(context_path, theme)\n", " app = interface.create_interface()\n", " app.launch(\n", " server_port=port,\n", " share=share\n", " )\n", " logger.info(f\"Interface launched on port: {port}\")\n", " except Exception as e:\n", " logger.error(f\"Error launching interface: {str(e)}\")\n", " raise" ] }, { "cell_type": "markdown", "id": "5c75de88-f6d5-4a5d-92b5-1ebe85895a84", "metadata": {}, "source": [ "## Tests" ] }, { "cell_type": "code", "execution_count": null, "id": "365bc95a-d189-4ab2-aa30-022d0286b5ba", "metadata": {}, "outputs": [], "source": [ "async def test_learning_interface():\n", " \"\"\"Test learning interface functionality\"\"\"\n", " interface = LearningInterface()\n", " \n", " # Test chat processing\n", " history = []\n", " test_input = \"28yo M with chest pain\"\n", " \n", " new_history, msg = await interface.process_chat(test_input, history)\n", " assert isinstance(new_history, list)\n", " assert len(new_history) == 2 # User message + response\n", " assert new_history[0][\"role\"] == \"user\"\n", " assert new_history[0][\"content\"] == test_input\n", " \n", " # Test discussion analysis\n", " analysis = await interface.end_discussion(new_history)\n", " assert isinstance(analysis, dict)\n", " assert all(k in analysis for k in [\n", " 'learning_points', 'gaps', 'strengths', 'suggested_objectives'\n", " ])\n", " \n", " # Test rotation updates\n", " rotation = interface.update_rotation(\n", " \"Emergency Medicine\",\n", " \"2025-01-01\",\n", " \"2025-03-31\",\n", " [\"Resuscitation\", \"Procedures\"]\n", " )\n", " assert rotation[\"specialty\"] == \"Emergency Medicine\"\n", " assert \"Resuscitation\" in rotation[\"key_focus_areas\"]\n", " \n", " # Test objective management\n", " objectives = interface.toggle_objective(\"Improve chest pain assessment\", False)\n", " assert len(objectives) == 1\n", " assert objectives[0][\"status\"] == \"active\"\n", " \n", " objectives = interface.toggle_objective(\"Improve chest pain assessment\", True)\n", " assert objectives[0][\"status\"] == \"completed\"\n", " \n", " # Test feedback preferences\n", " preferences = interface.toggle_feedback(\"Include more ddx\", True)\n", " assert len(preferences) == 1\n", " assert preferences[0][\"active\"] == True\n", " \n", " print(\"Interface tests passed!\")\n", "\n", "# Run tests\n", "if __name__ == \"__main__\":\n", " import asyncio\n", " if not asyncio.get_event_loop().is_running():\n", " asyncio.run(test_learning_interface())" ] } ], "metadata": { "kernelspec": { "display_name": "python3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }