dyadd commited on
Commit
77d740a
·
verified ·
1 Parent(s): f06968e

Upload folder using huggingface_hub

Browse files
nbs/02_learning_interface.ipynb CHANGED
@@ -178,7 +178,7 @@
178
  " Features:\n",
179
  " - Context selection\n",
180
  " - Goal management\n",
181
- " - Case discussion\n",
182
  " - Progress tracking\n",
183
  " \"\"\"\n",
184
  " \n",
@@ -187,13 +187,7 @@
187
  " context_path: Optional[Path] = None,\n",
188
  " model: str = \"anthropic/claude-3.5-sonnet\"\n",
189
  " ):\n",
190
- " \"\"\"\n",
191
- " Initialize interface.\n",
192
- " \n",
193
- " Args:\n",
194
- " context_path: Path for context persistence\n",
195
- " model: OpenRouter model identifier\n",
196
- " \"\"\"\n",
197
  " self.tutor = ClinicalTutor(context_path, model)\n",
198
  " \n",
199
  " # Available options\n",
@@ -208,7 +202,7 @@
208
  " self.settings = [\"Clinic\", \"Wards\", \"ED\"]\n",
209
  " \n",
210
  " logger.info(\"Learning interface initialized\")\n",
211
- "\n",
212
  " async def process_chat(\n",
213
  " self,\n",
214
  " message: str,\n",
@@ -252,9 +246,116 @@
252
  " history = []\n",
253
  " history.append([message, \"I apologize, but I encountered an error. Please try again.\"])\n",
254
  " yield history, \"\", state\n",
255
- " \n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  " def create_interface(self) -> gr.Blocks:\n",
257
- " \"\"\"Create Gradio interface with streaming support.\"\"\"\n",
258
  " with gr.Blocks(title=\"Clinical Learning Assistant\", css=create_css()) as interface:\n",
259
  " # State management\n",
260
  " state = gr.State({\n",
@@ -263,10 +364,10 @@
263
  " \"discussion_start\": None,\n",
264
  " \"last_message\": None\n",
265
  " })\n",
266
- " \n",
267
  " # Header\n",
268
  " gr.Markdown(\"# Clinical Learning Assistant\")\n",
269
- " \n",
270
  " with gr.Row():\n",
271
  " # Context selection\n",
272
  " specialty = gr.Dropdown(\n",
@@ -279,20 +380,20 @@
279
  " label=\"Setting\",\n",
280
  " value=self.tutor.learning_context.rotation.setting or None\n",
281
  " )\n",
282
- " \n",
283
  " # Main content\n",
284
  " with gr.Row():\n",
285
  " # Left: Discussion interface\n",
286
  " with gr.Column(scale=2):\n",
287
  " # Active goal display\n",
288
  " goal_display = gr.Markdown(value=\"No active learning goal\")\n",
289
- " \n",
290
  " # Discussion status\n",
291
  " discussion_status = gr.Markdown(\n",
292
  " value=\"Start a new case discussion\",\n",
293
  " elem_classes=[\"discussion-status\"]\n",
294
  " )\n",
295
- " \n",
296
  " # Chat interface\n",
297
  " chatbot = gr.Chatbot(\n",
298
  " height=400,\n",
@@ -300,7 +401,7 @@
300
  " type=\"messages\", # Use newer message format\n",
301
  " elem_classes=[\"chat-window\"]\n",
302
  " )\n",
303
- " \n",
304
  " with gr.Row():\n",
305
  " msg = gr.Textbox(\n",
306
  " label=\"Present your case or ask questions\",\n",
@@ -312,21 +413,21 @@
312
  " ),\n",
313
  " lines=4\n",
314
  " )\n",
315
- " \n",
316
  " audio_input = gr.Audio(\n",
317
  " sources=[\"microphone\"],\n",
318
  " type=\"numpy\",\n",
319
  " label=\"Or speak your case\",\n",
320
  " streaming=True\n",
321
  " )\n",
322
- " \n",
323
  " with gr.Row():\n",
324
  " clear = gr.Button(\"Clear Discussion\")\n",
325
  " end = gr.Button(\n",
326
  " \"End Discussion & Review\",\n",
327
  " variant=\"primary\"\n",
328
  " )\n",
329
- " \n",
330
  " # Right: Progress & Goals\n",
331
  " with gr.Column(scale=1):\n",
332
  " with gr.Tab(\"Learning Goals\"):\n",
@@ -338,7 +439,7 @@
338
  " placeholder=\"What do you want to get better at?\"\n",
339
  " )\n",
340
  " add_goal = gr.Button(\"Add Goal\")\n",
341
- " \n",
342
  " # Goals list\n",
343
  " goals_list = gr.DataFrame(\n",
344
  " headers=[\"Goal\", \"Category\", \"Status\"],\n",
@@ -347,7 +448,7 @@
347
  " interactive=True,\n",
348
  " wrap=True\n",
349
  " )\n",
350
- " \n",
351
  " with gr.Tab(\"Progress\"):\n",
352
  " # Category progress\n",
353
  " progress_display = gr.DataFrame(\n",
@@ -355,14 +456,14 @@
355
  " value=[],\n",
356
  " label=\"Progress by Category\"\n",
357
  " )\n",
358
- " \n",
359
  " # Recent completions\n",
360
  " recent_display = gr.DataFrame(\n",
361
  " headers=[\"Goal\", \"Category\", \"Completed\"],\n",
362
  " value=[],\n",
363
  " label=\"Recently Completed\"\n",
364
  " )\n",
365
- " \n",
366
  " # Event handlers\n",
367
  " async def update_context(spec: str, set: str, state: Dict) -> List:\n",
368
  " \"\"\"Update rotation context.\"\"\"\n",
@@ -373,14 +474,13 @@
373
  " state[\"suggested_goals\"] = goals\n",
374
  " return self._update_displays(state)\n",
375
  " return [[], [], [], \"No active learning goal\"]\n",
376
- " \n",
377
  " def process_audio(audio: np.ndarray) -> str:\n",
378
  " \"\"\"Convert audio to text.\"\"\"\n",
379
  " if audio is None:\n",
380
  " return \"\"\n",
381
- " # Would integrate with speech-to-text here\n",
382
  " return \"Audio transcription would appear here\"\n",
383
- " \n",
384
  " def clear_discussion() -> Tuple[List, str, Dict]:\n",
385
  " \"\"\"Clear chat history.\"\"\"\n",
386
  " return [], \"\", {\n",
@@ -389,20 +489,20 @@
389
  " \"discussion_start\": None,\n",
390
  " \"last_message\": None\n",
391
  " }\n",
392
- " \n",
393
  " # Wire up events\n",
394
  " specialty.change(\n",
395
  " update_context,\n",
396
  " inputs=[specialty, setting, state],\n",
397
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
398
  " )\n",
399
- " \n",
400
  " setting.change(\n",
401
  " update_context,\n",
402
  " inputs=[specialty, setting, state],\n",
403
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
404
  " )\n",
405
- " \n",
406
  " # Chat events with streaming\n",
407
  " msg.submit(\n",
408
  " self.process_chat,\n",
@@ -414,7 +514,7 @@
414
  " inputs=[state],\n",
415
  " outputs=[discussion_status]\n",
416
  " )\n",
417
- " \n",
418
  " # Voice input handling\n",
419
  " audio_input.stream(\n",
420
  " process_audio,\n",
@@ -425,7 +525,7 @@
425
  " inputs=[msg, chatbot, state],\n",
426
  " outputs=[chatbot, msg, state]\n",
427
  " )\n",
428
- " \n",
429
  " # Button handlers\n",
430
  " clear.click(\n",
431
  " clear_discussion,\n",
@@ -434,333 +534,33 @@
434
  " lambda: \"Start a new case discussion\",\n",
435
  " outputs=[discussion_status]\n",
436
  " )\n",
437
- " \n",
438
  " end.click(\n",
439
  " self.end_discussion,\n",
440
  " inputs=[state],\n",
441
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
442
  " )\n",
443
- " \n",
444
  " generate.click(\n",
445
  " self.generate_goals,\n",
446
  " inputs=[state],\n",
447
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
448
  " )\n",
449
- " \n",
450
  " # Goal management\n",
451
  " add_goal.click(\n",
452
  " self.add_user_goal,\n",
453
  " inputs=[new_goal, state],\n",
454
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
455
  " )\n",
456
- " \n",
457
  " goals_list.select(\n",
458
  " self.select_goal,\n",
459
  " inputs=[state],\n",
460
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
461
  " )\n",
462
- " \n",
463
- " return interface\n",
464
- " \n",
465
- " # Event handlers\n",
466
- " async def update_context(self, spec: str, set: str, state: Dict) -> List:\n",
467
- " \"\"\"Update rotation context.\n",
468
- " \n",
469
- " Args:\n",
470
- " spec: Specialty\n",
471
- " set: Setting\n",
472
- " state: Interface state\n",
473
- " \n",
474
- " Returns:\n",
475
- " list: Updated display data\n",
476
- " \"\"\"\n",
477
- " try:\n",
478
- " if spec and set:\n",
479
- " self.tutor.learning_context.update_rotation(spec, set)\n",
480
- " \n",
481
- " # Initialize goals list if needed\n",
482
- " if not state.get(\"suggested_goals\"):\n",
483
- " goals = await self.tutor.generate_smart_goals(spec, set)\n",
484
- " state[\"suggested_goals\"] = goals\n",
485
- " \n",
486
- " # Return all display updates\n",
487
- " goals_data = []\n",
488
- " context = self.tutor.learning_context\n",
489
- " \n",
490
- " # Update goals list\n",
491
- " for goal in state.get(\"suggested_goals\", []):\n",
492
- " status = \"Active\" if (\n",
493
- " context.active_goal and \n",
494
- " context.active_goal.id == goal.id\n",
495
- " ) else \"Available\"\n",
496
- " \n",
497
- " goals_data.append([\n",
498
- " goal.smart_version,\n",
499
- " goal.category.value,\n",
500
- " status\n",
501
- " ])\n",
502
- " \n",
503
- " # Update progress display\n",
504
- " summary = context.get_category_summary()\n",
505
- " progress_data = [\n",
506
- " [cat, data[\"completed\"], data[\"total\"]]\n",
507
- " for cat, data in summary.items()\n",
508
- " ]\n",
509
- " \n",
510
- " # Update recent completions\n",
511
- " recent_data = []\n",
512
- " for cat, data in summary.items():\n",
513
- " for goal in data[\"recent\"]:\n",
514
- " recent_data.append([\n",
515
- " goal[\"smart_version\"],\n",
516
- " cat,\n",
517
- " goal[\"completed_at\"]\n",
518
- " ])\n",
519
- " \n",
520
- " # Update active goal display\n",
521
- " goal_text = (\n",
522
- " f\"Current Goal: {context.active_goal.smart_version}\"\n",
523
- " if context.active_goal else\n",
524
- " \"No active learning goal\"\n",
525
- " )\n",
526
- " \n",
527
- " return [\n",
528
- " goals_data,\n",
529
- " progress_data,\n",
530
- " recent_data,\n",
531
- " goal_text\n",
532
- " ]\n",
533
- " \n",
534
- " # Return empty displays if no context\n",
535
- " return [[], [], [], \"No active learning goal\"]\n",
536
- " \n",
537
- " except Exception as e:\n",
538
- " logger.error(f\"Error updating context: {str(e)}\")\n",
539
- " return [[], [], [], \"Error updating context\"]\n",
540
- " \n",
541
- " async def process_message(msg, history):\n",
542
- " \"\"\"Process chat message.\"\"\"\n",
543
- " if not msg.strip():\n",
544
- " return history, \"\"\n",
545
- " \n",
546
- " response = await self.tutor.discuss_case(msg)\n",
547
- " history.append([msg, response])\n",
548
- " return history, \"\"\n",
549
- " \n",
550
- " def process_audio(audio):\n",
551
- " \"\"\"Convert audio to text.\"\"\"\n",
552
- " # Would integrate with speech-to-text here\n",
553
- " return \"Audio transcription would appear here\"\n",
554
- " \n",
555
- " async def generate_goals(self, state: Dict[str, Any]) -> List:\n",
556
- " \"\"\"Generate new goal suggestions.\n",
557
- " \n",
558
- " Args:\n",
559
- " state: Interface state\n",
560
- " \n",
561
- " Returns:\n",
562
- " list: Updated display data\n",
563
- " \"\"\"\n",
564
- " rotation = self.tutor.learning_context.rotation\n",
565
- " if rotation.specialty and rotation.setting:\n",
566
- " goals = await self.tutor.generate_smart_goals(\n",
567
- " rotation.specialty,\n",
568
- " rotation.setting\n",
569
- " )\n",
570
- " state[\"suggested_goals\"] = goals\n",
571
- " return self._update_displays(state)\n",
572
- " return [[], [], [], \"No active learning goal\"]\n",
573
- " \n",
574
- " async def add_user_goal(self, text: str, state: Dict[str, Any]) -> List:\n",
575
- " \"\"\"Add user-specified goal.\n",
576
- " \n",
577
- " Args:\n",
578
- " text: User's goal text\n",
579
- " state: Interface state\n",
580
- " \n",
581
- " Returns:\n",
582
- " list: Updated display data\n",
583
- " \"\"\"\n",
584
- " if not text.strip():\n",
585
- " return self._update_displays(state)\n",
586
- " \n",
587
- " rotation = self.tutor.learning_context.rotation\n",
588
- " if rotation.specialty and rotation.setting:\n",
589
- " goal = await self.tutor.generate_smart_goal(\n",
590
- " text,\n",
591
- " rotation.specialty,\n",
592
- " rotation.setting\n",
593
- " )\n",
594
- " if goal:\n",
595
- " state[\"suggested_goals\"].append(goal)\n",
596
- " return self._update_displays(state)\n",
597
- " return self._update_displays(state)\n",
598
- " \n",
599
- " def select_goal(self, evt: gr.SelectData, state: Dict[str, Any]) -> List:\n",
600
- " \"\"\"Set selected goal as active.\n",
601
- " \n",
602
- " Args:\n",
603
- " evt: Selection event data\n",
604
- " state: Interface state\n",
605
- " \n",
606
- " Returns:\n",
607
- " list: Updated display data\n",
608
- " \"\"\"\n",
609
- " if evt.index[0] < len(state[\"suggested_goals\"]):\n",
610
- " goal = state[\"suggested_goals\"][evt.index[0]]\n",
611
- " self.tutor.learning_context.add_smart_goal(goal)\n",
612
- " return self._update_displays(state)\n",
613
- " return self._update_displays(state)\n",
614
- " \n",
615
- " def end_discussion(self, state: Dict[str, Any]) -> List:\n",
616
- " \"\"\"End current discussion.\n",
617
- " \n",
618
- " Args:\n",
619
- " state: Interface state\n",
620
- " \n",
621
- " Returns:\n",
622
- " list: Updated display data\n",
623
- " \"\"\"\n",
624
- " if self.tutor.learning_context.active_goal:\n",
625
- " self.tutor.learning_context.complete_active_goal()\n",
626
- " \n",
627
- " self.tutor.end_discussion()\n",
628
- " state[\"discussion_active\"] = False\n",
629
- " state[\"discussion_start\"] = None\n",
630
- " \n",
631
- " return self._update_displays(state) \n",
632
- " \n",
633
- " def clear_discussion():\n",
634
- " \"\"\"Clear discussion history.\"\"\"\n",
635
- " self.tutor.clear_discussion()\n",
636
- " return [], \"\"\n",
637
- " \n",
638
- " # Wire up events\n",
639
- " specialty.change(\n",
640
- " update_context,\n",
641
- " inputs=[specialty, setting, state], # Add state to inputs\n",
642
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
643
- " )\n",
644
- " \n",
645
- " setting.change(\n",
646
- " update_context,\n",
647
- " inputs=[specialty, setting, state], # Add state to inputs\n",
648
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
649
- " )\n",
650
- " \n",
651
- " message.submit(\n",
652
- " process_message,\n",
653
- " inputs=[message, chatbot],\n",
654
- " outputs=[chatbot, message]\n",
655
- " )\n",
656
- " \n",
657
- " audio_input.stream(\n",
658
- " process_audio,\n",
659
- " inputs=[audio_input],\n",
660
- " outputs=[message]\n",
661
- " )\n",
662
- " \n",
663
- " clear.click(\n",
664
- " clear_discussion,\n",
665
- " outputs=[chatbot, message]\n",
666
- " )\n",
667
- " \n",
668
- " end.click(\n",
669
- " end_discussion,\n",
670
- " inputs=[state],\n",
671
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
672
- " )\n",
673
- " \n",
674
- " generate.click(\n",
675
- " generate_goals,\n",
676
- " inputs=[state],\n",
677
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
678
- " )\n",
679
- " \n",
680
- " new_goal.submit(\n",
681
- " add_user_goal,\n",
682
- " inputs=[new_goal, state],\n",
683
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
684
- " )\n",
685
- " \n",
686
- " goals_list.select(\n",
687
- " select_goal,\n",
688
- " inputs=[state],\n",
689
- " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
690
- " )\n",
691
- " \n",
692
- " return interface\n",
693
- " \n",
694
- " def _update_displays(self, state: Dict) -> List:\n",
695
- " \"\"\"Update all display components.\"\"\"\n",
696
- " context = self.tutor.learning_context\n",
697
- " \n",
698
- " # Update goals list\n",
699
- " goals_data = []\n",
700
- " for goal in state[\"suggested_goals\"]:\n",
701
- " status = \"Active\" if (\n",
702
- " context.active_goal and \n",
703
- " context.active_goal.id == goal.id\n",
704
- " ) else \"Available\"\n",
705
- " \n",
706
- " goals_data.append([\n",
707
- " goal.smart_version,\n",
708
- " goal.category.value,\n",
709
- " status\n",
710
- " ])\n",
711
- " \n",
712
- " # Update progress display\n",
713
- " summary = context.get_category_summary()\n",
714
- " progress_data = [\n",
715
- " [cat, data[\"completed\"], data[\"total\"]]\n",
716
- " for cat, data in summary.items()\n",
717
- " ]\n",
718
- " \n",
719
- " # Update recent completions\n",
720
- " recent_data = []\n",
721
- " for cat, data in summary.items():\n",
722
- " for goal in data[\"recent\"]:\n",
723
- " recent_data.append([\n",
724
- " goal[\"smart_version\"],\n",
725
- " cat,\n",
726
- " goal[\"completed_at\"]\n",
727
- " ])\n",
728
- " \n",
729
- " # Update active goal display\n",
730
- " goal_text = (\n",
731
- " f\"Current Goal: {context.active_goal.smart_version}\"\n",
732
- " if context.active_goal else\n",
733
- " \"No active learning goal\"\n",
734
- " )\n",
735
- " \n",
736
- " return [\n",
737
- " goals_data,\n",
738
- " progress_data,\n",
739
- " recent_data,\n",
740
- " goal_text\n",
741
- " ]\n",
742
- " def _update_discussion_status(self, state: Dict[str, Any]) -> str:\n",
743
- " \"\"\"Update discussion status display based on state.\n",
744
- " \n",
745
- " Args:\n",
746
- " state: Current interface state\n",
747
- " \n",
748
- " Returns:\n",
749
- " str: Status message to display\n",
750
- " \"\"\"\n",
751
- " try:\n",
752
- " if not state.get(\"discussion_active\"):\n",
753
- " return \"Start a new case discussion\"\n",
754
- " \n",
755
- " start = datetime.fromisoformat(state[\"discussion_start\"])\n",
756
- " duration = datetime.now() - start\n",
757
- " minutes = int(duration.total_seconds() / 60)\n",
758
- " \n",
759
- " return f\"Active discussion ({minutes} minutes)\"\n",
760
- " \n",
761
- " except Exception as e:\n",
762
- " logger.error(f\"Error updating status: {str(e)}\")\n",
763
- " return \"Discussion status unknown\""
764
  ]
765
  },
766
  {
 
178
  " Features:\n",
179
  " - Context selection\n",
180
  " - Goal management\n",
181
+ " - Case discussion with streaming\n",
182
  " - Progress tracking\n",
183
  " \"\"\"\n",
184
  " \n",
 
187
  " context_path: Optional[Path] = None,\n",
188
  " model: str = \"anthropic/claude-3.5-sonnet\"\n",
189
  " ):\n",
190
+ " \"\"\"Initialize interface.\"\"\"\n",
 
 
 
 
 
 
191
  " self.tutor = ClinicalTutor(context_path, model)\n",
192
  " \n",
193
  " # Available options\n",
 
202
  " self.settings = [\"Clinic\", \"Wards\", \"ED\"]\n",
203
  " \n",
204
  " logger.info(\"Learning interface initialized\")\n",
205
+ " \n",
206
  " async def process_chat(\n",
207
  " self,\n",
208
  " message: str,\n",
 
246
  " history = []\n",
247
  " history.append([message, \"I apologize, but I encountered an error. Please try again.\"])\n",
248
  " yield history, \"\", state\n",
249
+ "\n",
250
+ " def _update_discussion_status(self, state: Dict[str, Any]) -> str:\n",
251
+ " \"\"\"Update discussion status display.\"\"\"\n",
252
+ " try:\n",
253
+ " if not state.get(\"discussion_active\"):\n",
254
+ " return \"Start a new case discussion\"\n",
255
+ " \n",
256
+ " start = datetime.fromisoformat(state[\"discussion_start\"])\n",
257
+ " duration = datetime.now() - start\n",
258
+ " minutes = int(duration.total_seconds() / 60)\n",
259
+ " \n",
260
+ " return f\"Active discussion ({minutes} minutes)\"\n",
261
+ " \n",
262
+ " except Exception as e:\n",
263
+ " logger.error(f\"Error updating status: {str(e)}\")\n",
264
+ " return \"Discussion status unknown\"\n",
265
+ "\n",
266
+ " async def generate_goals(self, state: Dict[str, Any]) -> List:\n",
267
+ " \"\"\"Generate new goal suggestions.\"\"\"\n",
268
+ " rotation = self.tutor.learning_context.rotation\n",
269
+ " if rotation.specialty and rotation.setting:\n",
270
+ " goals = await self.tutor.generate_smart_goals(\n",
271
+ " rotation.specialty,\n",
272
+ " rotation.setting\n",
273
+ " )\n",
274
+ " state[\"suggested_goals\"] = goals\n",
275
+ " return self._update_displays(state)\n",
276
+ " return [[], [], [], \"No active learning goal\"]\n",
277
+ "\n",
278
+ " async def add_user_goal(self, text: str, state: Dict[str, Any]) -> List:\n",
279
+ " \"\"\"Add user-specified goal.\"\"\"\n",
280
+ " if not text.strip():\n",
281
+ " return self._update_displays(state)\n",
282
+ " \n",
283
+ " rotation = self.tutor.learning_context.rotation\n",
284
+ " if rotation.specialty and rotation.setting:\n",
285
+ " goal = await self.tutor.generate_smart_goal(\n",
286
+ " text,\n",
287
+ " rotation.specialty,\n",
288
+ " rotation.setting\n",
289
+ " )\n",
290
+ " if goal:\n",
291
+ " if \"suggested_goals\" not in state:\n",
292
+ " state[\"suggested_goals\"] = []\n",
293
+ " state[\"suggested_goals\"].append(goal)\n",
294
+ " return self._update_displays(state)\n",
295
+ " return self._update_displays(state)\n",
296
+ "\n",
297
+ " def select_goal(self, evt: gr.SelectData, state: Dict[str, Any]) -> List:\n",
298
+ " \"\"\"Set selected goal as active.\"\"\"\n",
299
+ " if \"suggested_goals\" in state and evt.index[0] < len(state[\"suggested_goals\"]):\n",
300
+ " goal = state[\"suggested_goals\"][evt.index[0]]\n",
301
+ " self.tutor.learning_context.add_smart_goal(goal)\n",
302
+ " return self._update_displays(state)\n",
303
+ " return self._update_displays(state)\n",
304
+ "\n",
305
+ " def end_discussion(self, state: Dict[str, Any]) -> List:\n",
306
+ " \"\"\"End current discussion.\"\"\"\n",
307
+ " self.tutor.end_discussion()\n",
308
+ " state[\"discussion_active\"] = False\n",
309
+ " state[\"discussion_start\"] = None\n",
310
+ " state[\"last_message\"] = None\n",
311
+ " return self._update_displays(state)\n",
312
+ "\n",
313
+ " def _update_displays(self, state: Dict[str, Any]) -> List:\n",
314
+ " \"\"\"Update all display components.\"\"\"\n",
315
+ " context = self.tutor.learning_context\n",
316
+ " \n",
317
+ " # Update goals list\n",
318
+ " goals_data = []\n",
319
+ " for goal in state.get(\"suggested_goals\", []):\n",
320
+ " status = \"Active\" if (\n",
321
+ " context.active_goal and \n",
322
+ " context.active_goal.id == goal.id\n",
323
+ " ) else \"Available\"\n",
324
+ " \n",
325
+ " goals_data.append([\n",
326
+ " goal.smart_version,\n",
327
+ " goal.category.value,\n",
328
+ " status\n",
329
+ " ])\n",
330
+ " \n",
331
+ " # Update progress display\n",
332
+ " summary = context.get_category_summary()\n",
333
+ " progress_data = [\n",
334
+ " [cat, data[\"completed\"], data[\"total\"]]\n",
335
+ " for cat, data in summary.items()\n",
336
+ " ]\n",
337
+ " \n",
338
+ " # Update recent completions\n",
339
+ " recent_data = []\n",
340
+ " for cat, data in summary.items():\n",
341
+ " for goal in data[\"recent\"]:\n",
342
+ " recent_data.append([\n",
343
+ " goal[\"smart_version\"],\n",
344
+ " cat,\n",
345
+ " goal[\"completed_at\"]\n",
346
+ " ])\n",
347
+ " \n",
348
+ " # Update active goal display\n",
349
+ " goal_text = (\n",
350
+ " f\"Current Goal: {context.active_goal.smart_version}\"\n",
351
+ " if context.active_goal else\n",
352
+ " \"No active learning goal\"\n",
353
+ " )\n",
354
+ " \n",
355
+ " return [goals_data, progress_data, recent_data, goal_text]\n",
356
+ "\n",
357
  " def create_interface(self) -> gr.Blocks:\n",
358
+ " \"\"\"Create streaming-enabled interface.\"\"\"\n",
359
  " with gr.Blocks(title=\"Clinical Learning Assistant\", css=create_css()) as interface:\n",
360
  " # State management\n",
361
  " state = gr.State({\n",
 
364
  " \"discussion_start\": None,\n",
365
  " \"last_message\": None\n",
366
  " })\n",
367
+ "\n",
368
  " # Header\n",
369
  " gr.Markdown(\"# Clinical Learning Assistant\")\n",
370
+ "\n",
371
  " with gr.Row():\n",
372
  " # Context selection\n",
373
  " specialty = gr.Dropdown(\n",
 
380
  " label=\"Setting\",\n",
381
  " value=self.tutor.learning_context.rotation.setting or None\n",
382
  " )\n",
383
+ "\n",
384
  " # Main content\n",
385
  " with gr.Row():\n",
386
  " # Left: Discussion interface\n",
387
  " with gr.Column(scale=2):\n",
388
  " # Active goal display\n",
389
  " goal_display = gr.Markdown(value=\"No active learning goal\")\n",
390
+ "\n",
391
  " # Discussion status\n",
392
  " discussion_status = gr.Markdown(\n",
393
  " value=\"Start a new case discussion\",\n",
394
  " elem_classes=[\"discussion-status\"]\n",
395
  " )\n",
396
+ "\n",
397
  " # Chat interface\n",
398
  " chatbot = gr.Chatbot(\n",
399
  " height=400,\n",
 
401
  " type=\"messages\", # Use newer message format\n",
402
  " elem_classes=[\"chat-window\"]\n",
403
  " )\n",
404
+ "\n",
405
  " with gr.Row():\n",
406
  " msg = gr.Textbox(\n",
407
  " label=\"Present your case or ask questions\",\n",
 
413
  " ),\n",
414
  " lines=4\n",
415
  " )\n",
416
+ "\n",
417
  " audio_input = gr.Audio(\n",
418
  " sources=[\"microphone\"],\n",
419
  " type=\"numpy\",\n",
420
  " label=\"Or speak your case\",\n",
421
  " streaming=True\n",
422
  " )\n",
423
+ "\n",
424
  " with gr.Row():\n",
425
  " clear = gr.Button(\"Clear Discussion\")\n",
426
  " end = gr.Button(\n",
427
  " \"End Discussion & Review\",\n",
428
  " variant=\"primary\"\n",
429
  " )\n",
430
+ "\n",
431
  " # Right: Progress & Goals\n",
432
  " with gr.Column(scale=1):\n",
433
  " with gr.Tab(\"Learning Goals\"):\n",
 
439
  " placeholder=\"What do you want to get better at?\"\n",
440
  " )\n",
441
  " add_goal = gr.Button(\"Add Goal\")\n",
442
+ "\n",
443
  " # Goals list\n",
444
  " goals_list = gr.DataFrame(\n",
445
  " headers=[\"Goal\", \"Category\", \"Status\"],\n",
 
448
  " interactive=True,\n",
449
  " wrap=True\n",
450
  " )\n",
451
+ "\n",
452
  " with gr.Tab(\"Progress\"):\n",
453
  " # Category progress\n",
454
  " progress_display = gr.DataFrame(\n",
 
456
  " value=[],\n",
457
  " label=\"Progress by Category\"\n",
458
  " )\n",
459
+ "\n",
460
  " # Recent completions\n",
461
  " recent_display = gr.DataFrame(\n",
462
  " headers=[\"Goal\", \"Category\", \"Completed\"],\n",
463
  " value=[],\n",
464
  " label=\"Recently Completed\"\n",
465
  " )\n",
466
+ "\n",
467
  " # Event handlers\n",
468
  " async def update_context(spec: str, set: str, state: Dict) -> List:\n",
469
  " \"\"\"Update rotation context.\"\"\"\n",
 
474
  " state[\"suggested_goals\"] = goals\n",
475
  " return self._update_displays(state)\n",
476
  " return [[], [], [], \"No active learning goal\"]\n",
477
+ "\n",
478
  " def process_audio(audio: np.ndarray) -> str:\n",
479
  " \"\"\"Convert audio to text.\"\"\"\n",
480
  " if audio is None:\n",
481
  " return \"\"\n",
 
482
  " return \"Audio transcription would appear here\"\n",
483
+ "\n",
484
  " def clear_discussion() -> Tuple[List, str, Dict]:\n",
485
  " \"\"\"Clear chat history.\"\"\"\n",
486
  " return [], \"\", {\n",
 
489
  " \"discussion_start\": None,\n",
490
  " \"last_message\": None\n",
491
  " }\n",
492
+ "\n",
493
  " # Wire up events\n",
494
  " specialty.change(\n",
495
  " update_context,\n",
496
  " inputs=[specialty, setting, state],\n",
497
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
498
  " )\n",
499
+ "\n",
500
  " setting.change(\n",
501
  " update_context,\n",
502
  " inputs=[specialty, setting, state],\n",
503
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
504
  " )\n",
505
+ "\n",
506
  " # Chat events with streaming\n",
507
  " msg.submit(\n",
508
  " self.process_chat,\n",
 
514
  " inputs=[state],\n",
515
  " outputs=[discussion_status]\n",
516
  " )\n",
517
+ "\n",
518
  " # Voice input handling\n",
519
  " audio_input.stream(\n",
520
  " process_audio,\n",
 
525
  " inputs=[msg, chatbot, state],\n",
526
  " outputs=[chatbot, msg, state]\n",
527
  " )\n",
528
+ "\n",
529
  " # Button handlers\n",
530
  " clear.click(\n",
531
  " clear_discussion,\n",
 
534
  " lambda: \"Start a new case discussion\",\n",
535
  " outputs=[discussion_status]\n",
536
  " )\n",
537
+ "\n",
538
  " end.click(\n",
539
  " self.end_discussion,\n",
540
  " inputs=[state],\n",
541
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
542
  " )\n",
543
+ "\n",
544
  " generate.click(\n",
545
  " self.generate_goals,\n",
546
  " inputs=[state],\n",
547
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
548
  " )\n",
549
+ "\n",
550
  " # Goal management\n",
551
  " add_goal.click(\n",
552
  " self.add_user_goal,\n",
553
  " inputs=[new_goal, state],\n",
554
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
555
  " )\n",
556
+ "\n",
557
  " goals_list.select(\n",
558
  " self.select_goal,\n",
559
  " inputs=[state],\n",
560
  " outputs=[goals_list, progress_display, recent_display, goal_display]\n",
561
  " )\n",
562
+ "\n",
563
+ " return interface"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  ]
565
  },
566
  {
wardbuddy/_modidx.py CHANGED
@@ -64,10 +64,18 @@ d = { 'settings': { 'branch': 'main',
64
  'wardbuddy/learning_interface.py'),
65
  'wardbuddy.learning_interface.LearningInterface._update_displays': ( 'learning_interface.html#learninginterface._update_displays',
66
  'wardbuddy/learning_interface.py'),
 
 
67
  'wardbuddy.learning_interface.LearningInterface.create_interface': ( 'learning_interface.html#learninginterface.create_interface',
68
  'wardbuddy/learning_interface.py'),
 
 
 
 
69
  'wardbuddy.learning_interface.LearningInterface.process_chat': ( 'learning_interface.html#learninginterface.process_chat',
70
  'wardbuddy/learning_interface.py'),
 
 
71
  'wardbuddy.learning_interface.create_css': ( 'learning_interface.html#create_css',
72
  'wardbuddy/learning_interface.py')},
73
  'wardbuddy.main': { 'wardbuddy.main.launch': ('main.html#launch', 'wardbuddy/main.py'),
 
64
  'wardbuddy/learning_interface.py'),
65
  'wardbuddy.learning_interface.LearningInterface._update_displays': ( 'learning_interface.html#learninginterface._update_displays',
66
  'wardbuddy/learning_interface.py'),
67
+ 'wardbuddy.learning_interface.LearningInterface.add_user_goal': ( 'learning_interface.html#learninginterface.add_user_goal',
68
+ 'wardbuddy/learning_interface.py'),
69
  'wardbuddy.learning_interface.LearningInterface.create_interface': ( 'learning_interface.html#learninginterface.create_interface',
70
  'wardbuddy/learning_interface.py'),
71
+ 'wardbuddy.learning_interface.LearningInterface.end_discussion': ( 'learning_interface.html#learninginterface.end_discussion',
72
+ 'wardbuddy/learning_interface.py'),
73
+ 'wardbuddy.learning_interface.LearningInterface.generate_goals': ( 'learning_interface.html#learninginterface.generate_goals',
74
+ 'wardbuddy/learning_interface.py'),
75
  'wardbuddy.learning_interface.LearningInterface.process_chat': ( 'learning_interface.html#learninginterface.process_chat',
76
  'wardbuddy/learning_interface.py'),
77
+ 'wardbuddy.learning_interface.LearningInterface.select_goal': ( 'learning_interface.html#learninginterface.select_goal',
78
+ 'wardbuddy/learning_interface.py'),
79
  'wardbuddy.learning_interface.create_css': ( 'learning_interface.html#create_css',
80
  'wardbuddy/learning_interface.py')},
81
  'wardbuddy.main': { 'wardbuddy.main.launch': ('main.html#launch', 'wardbuddy/main.py'),
wardbuddy/learning_interface.py CHANGED
@@ -89,7 +89,7 @@ class LearningInterface:
89
  Features:
90
  - Context selection
91
  - Goal management
92
- - Case discussion
93
  - Progress tracking
94
  """
95
 
@@ -98,13 +98,7 @@ class LearningInterface:
98
  context_path: Optional[Path] = None,
99
  model: str = "anthropic/claude-3.5-sonnet"
100
  ):
101
- """
102
- Initialize interface.
103
-
104
- Args:
105
- context_path: Path for context persistence
106
- model: OpenRouter model identifier
107
- """
108
  self.tutor = ClinicalTutor(context_path, model)
109
 
110
  # Available options
@@ -119,7 +113,7 @@ class LearningInterface:
119
  self.settings = ["Clinic", "Wards", "ED"]
120
 
121
  logger.info("Learning interface initialized")
122
-
123
  async def process_chat(
124
  self,
125
  message: str,
@@ -163,9 +157,116 @@ class LearningInterface:
163
  history = []
164
  history.append([message, "I apologize, but I encountered an error. Please try again."])
165
  yield history, "", state
166
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  def create_interface(self) -> gr.Blocks:
168
- """Create Gradio interface with streaming support."""
169
  with gr.Blocks(title="Clinical Learning Assistant", css=create_css()) as interface:
170
  # State management
171
  state = gr.State({
@@ -174,10 +275,10 @@ class LearningInterface:
174
  "discussion_start": None,
175
  "last_message": None
176
  })
177
-
178
  # Header
179
  gr.Markdown("# Clinical Learning Assistant")
180
-
181
  with gr.Row():
182
  # Context selection
183
  specialty = gr.Dropdown(
@@ -190,20 +291,20 @@ class LearningInterface:
190
  label="Setting",
191
  value=self.tutor.learning_context.rotation.setting or None
192
  )
193
-
194
  # Main content
195
  with gr.Row():
196
  # Left: Discussion interface
197
  with gr.Column(scale=2):
198
  # Active goal display
199
  goal_display = gr.Markdown(value="No active learning goal")
200
-
201
  # Discussion status
202
  discussion_status = gr.Markdown(
203
  value="Start a new case discussion",
204
  elem_classes=["discussion-status"]
205
  )
206
-
207
  # Chat interface
208
  chatbot = gr.Chatbot(
209
  height=400,
@@ -211,7 +312,7 @@ class LearningInterface:
211
  type="messages", # Use newer message format
212
  elem_classes=["chat-window"]
213
  )
214
-
215
  with gr.Row():
216
  msg = gr.Textbox(
217
  label="Present your case or ask questions",
@@ -223,21 +324,21 @@ class LearningInterface:
223
  ),
224
  lines=4
225
  )
226
-
227
  audio_input = gr.Audio(
228
  sources=["microphone"],
229
  type="numpy",
230
  label="Or speak your case",
231
  streaming=True
232
  )
233
-
234
  with gr.Row():
235
  clear = gr.Button("Clear Discussion")
236
  end = gr.Button(
237
  "End Discussion & Review",
238
  variant="primary"
239
  )
240
-
241
  # Right: Progress & Goals
242
  with gr.Column(scale=1):
243
  with gr.Tab("Learning Goals"):
@@ -249,7 +350,7 @@ class LearningInterface:
249
  placeholder="What do you want to get better at?"
250
  )
251
  add_goal = gr.Button("Add Goal")
252
-
253
  # Goals list
254
  goals_list = gr.DataFrame(
255
  headers=["Goal", "Category", "Status"],
@@ -258,7 +359,7 @@ class LearningInterface:
258
  interactive=True,
259
  wrap=True
260
  )
261
-
262
  with gr.Tab("Progress"):
263
  # Category progress
264
  progress_display = gr.DataFrame(
@@ -266,14 +367,14 @@ class LearningInterface:
266
  value=[],
267
  label="Progress by Category"
268
  )
269
-
270
  # Recent completions
271
  recent_display = gr.DataFrame(
272
  headers=["Goal", "Category", "Completed"],
273
  value=[],
274
  label="Recently Completed"
275
  )
276
-
277
  # Event handlers
278
  async def update_context(spec: str, set: str, state: Dict) -> List:
279
  """Update rotation context."""
@@ -284,14 +385,13 @@ class LearningInterface:
284
  state["suggested_goals"] = goals
285
  return self._update_displays(state)
286
  return [[], [], [], "No active learning goal"]
287
-
288
  def process_audio(audio: np.ndarray) -> str:
289
  """Convert audio to text."""
290
  if audio is None:
291
  return ""
292
- # Would integrate with speech-to-text here
293
  return "Audio transcription would appear here"
294
-
295
  def clear_discussion() -> Tuple[List, str, Dict]:
296
  """Clear chat history."""
297
  return [], "", {
@@ -300,20 +400,20 @@ class LearningInterface:
300
  "discussion_start": None,
301
  "last_message": None
302
  }
303
-
304
  # Wire up events
305
  specialty.change(
306
  update_context,
307
  inputs=[specialty, setting, state],
308
  outputs=[goals_list, progress_display, recent_display, goal_display]
309
  )
310
-
311
  setting.change(
312
  update_context,
313
  inputs=[specialty, setting, state],
314
  outputs=[goals_list, progress_display, recent_display, goal_display]
315
  )
316
-
317
  # Chat events with streaming
318
  msg.submit(
319
  self.process_chat,
@@ -325,7 +425,7 @@ class LearningInterface:
325
  inputs=[state],
326
  outputs=[discussion_status]
327
  )
328
-
329
  # Voice input handling
330
  audio_input.stream(
331
  process_audio,
@@ -336,7 +436,7 @@ class LearningInterface:
336
  inputs=[msg, chatbot, state],
337
  outputs=[chatbot, msg, state]
338
  )
339
-
340
  # Button handlers
341
  clear.click(
342
  clear_discussion,
@@ -345,330 +445,30 @@ class LearningInterface:
345
  lambda: "Start a new case discussion",
346
  outputs=[discussion_status]
347
  )
348
-
349
  end.click(
350
  self.end_discussion,
351
  inputs=[state],
352
  outputs=[goals_list, progress_display, recent_display, goal_display]
353
  )
354
-
355
  generate.click(
356
  self.generate_goals,
357
  inputs=[state],
358
  outputs=[goals_list, progress_display, recent_display, goal_display]
359
  )
360
-
361
  # Goal management
362
  add_goal.click(
363
  self.add_user_goal,
364
  inputs=[new_goal, state],
365
  outputs=[goals_list, progress_display, recent_display, goal_display]
366
  )
367
-
368
  goals_list.select(
369
  self.select_goal,
370
  inputs=[state],
371
  outputs=[goals_list, progress_display, recent_display, goal_display]
372
  )
373
-
374
- return interface
375
-
376
- # Event handlers
377
- async def update_context(self, spec: str, set: str, state: Dict) -> List:
378
- """Update rotation context.
379
-
380
- Args:
381
- spec: Specialty
382
- set: Setting
383
- state: Interface state
384
-
385
- Returns:
386
- list: Updated display data
387
- """
388
- try:
389
- if spec and set:
390
- self.tutor.learning_context.update_rotation(spec, set)
391
-
392
- # Initialize goals list if needed
393
- if not state.get("suggested_goals"):
394
- goals = await self.tutor.generate_smart_goals(spec, set)
395
- state["suggested_goals"] = goals
396
-
397
- # Return all display updates
398
- goals_data = []
399
- context = self.tutor.learning_context
400
-
401
- # Update goals list
402
- for goal in state.get("suggested_goals", []):
403
- status = "Active" if (
404
- context.active_goal and
405
- context.active_goal.id == goal.id
406
- ) else "Available"
407
-
408
- goals_data.append([
409
- goal.smart_version,
410
- goal.category.value,
411
- status
412
- ])
413
-
414
- # Update progress display
415
- summary = context.get_category_summary()
416
- progress_data = [
417
- [cat, data["completed"], data["total"]]
418
- for cat, data in summary.items()
419
- ]
420
-
421
- # Update recent completions
422
- recent_data = []
423
- for cat, data in summary.items():
424
- for goal in data["recent"]:
425
- recent_data.append([
426
- goal["smart_version"],
427
- cat,
428
- goal["completed_at"]
429
- ])
430
-
431
- # Update active goal display
432
- goal_text = (
433
- f"Current Goal: {context.active_goal.smart_version}"
434
- if context.active_goal else
435
- "No active learning goal"
436
- )
437
-
438
- return [
439
- goals_data,
440
- progress_data,
441
- recent_data,
442
- goal_text
443
- ]
444
-
445
- # Return empty displays if no context
446
- return [[], [], [], "No active learning goal"]
447
-
448
- except Exception as e:
449
- logger.error(f"Error updating context: {str(e)}")
450
- return [[], [], [], "Error updating context"]
451
-
452
- async def process_message(msg, history):
453
- """Process chat message."""
454
- if not msg.strip():
455
- return history, ""
456
-
457
- response = await self.tutor.discuss_case(msg)
458
- history.append([msg, response])
459
- return history, ""
460
-
461
- def process_audio(audio):
462
- """Convert audio to text."""
463
- # Would integrate with speech-to-text here
464
- return "Audio transcription would appear here"
465
-
466
- async def generate_goals(self, state: Dict[str, Any]) -> List:
467
- """Generate new goal suggestions.
468
-
469
- Args:
470
- state: Interface state
471
-
472
- Returns:
473
- list: Updated display data
474
- """
475
- rotation = self.tutor.learning_context.rotation
476
- if rotation.specialty and rotation.setting:
477
- goals = await self.tutor.generate_smart_goals(
478
- rotation.specialty,
479
- rotation.setting
480
- )
481
- state["suggested_goals"] = goals
482
- return self._update_displays(state)
483
- return [[], [], [], "No active learning goal"]
484
-
485
- async def add_user_goal(self, text: str, state: Dict[str, Any]) -> List:
486
- """Add user-specified goal.
487
-
488
- Args:
489
- text: User's goal text
490
- state: Interface state
491
-
492
- Returns:
493
- list: Updated display data
494
- """
495
- if not text.strip():
496
- return self._update_displays(state)
497
-
498
- rotation = self.tutor.learning_context.rotation
499
- if rotation.specialty and rotation.setting:
500
- goal = await self.tutor.generate_smart_goal(
501
- text,
502
- rotation.specialty,
503
- rotation.setting
504
- )
505
- if goal:
506
- state["suggested_goals"].append(goal)
507
- return self._update_displays(state)
508
- return self._update_displays(state)
509
-
510
- def select_goal(self, evt: gr.SelectData, state: Dict[str, Any]) -> List:
511
- """Set selected goal as active.
512
-
513
- Args:
514
- evt: Selection event data
515
- state: Interface state
516
-
517
- Returns:
518
- list: Updated display data
519
- """
520
- if evt.index[0] < len(state["suggested_goals"]):
521
- goal = state["suggested_goals"][evt.index[0]]
522
- self.tutor.learning_context.add_smart_goal(goal)
523
- return self._update_displays(state)
524
- return self._update_displays(state)
525
-
526
- def end_discussion(self, state: Dict[str, Any]) -> List:
527
- """End current discussion.
528
-
529
- Args:
530
- state: Interface state
531
-
532
- Returns:
533
- list: Updated display data
534
- """
535
- if self.tutor.learning_context.active_goal:
536
- self.tutor.learning_context.complete_active_goal()
537
-
538
- self.tutor.end_discussion()
539
- state["discussion_active"] = False
540
- state["discussion_start"] = None
541
-
542
- return self._update_displays(state)
543
-
544
- def clear_discussion():
545
- """Clear discussion history."""
546
- self.tutor.clear_discussion()
547
- return [], ""
548
-
549
- # Wire up events
550
- specialty.change(
551
- update_context,
552
- inputs=[specialty, setting, state], # Add state to inputs
553
- outputs=[goals_list, progress_display, recent_display, goal_display]
554
- )
555
-
556
- setting.change(
557
- update_context,
558
- inputs=[specialty, setting, state], # Add state to inputs
559
- outputs=[goals_list, progress_display, recent_display, goal_display]
560
- )
561
-
562
- message.submit(
563
- process_message,
564
- inputs=[message, chatbot],
565
- outputs=[chatbot, message]
566
- )
567
-
568
- audio_input.stream(
569
- process_audio,
570
- inputs=[audio_input],
571
- outputs=[message]
572
- )
573
-
574
- clear.click(
575
- clear_discussion,
576
- outputs=[chatbot, message]
577
- )
578
-
579
- end.click(
580
- end_discussion,
581
- inputs=[state],
582
- outputs=[goals_list, progress_display, recent_display, goal_display]
583
- )
584
-
585
- generate.click(
586
- generate_goals,
587
- inputs=[state],
588
- outputs=[goals_list, progress_display, recent_display, goal_display]
589
- )
590
-
591
- new_goal.submit(
592
- add_user_goal,
593
- inputs=[new_goal, state],
594
- outputs=[goals_list, progress_display, recent_display, goal_display]
595
- )
596
-
597
- goals_list.select(
598
- select_goal,
599
- inputs=[state],
600
- outputs=[goals_list, progress_display, recent_display, goal_display]
601
- )
602
-
603
  return interface
604
-
605
- def _update_displays(self, state: Dict) -> List:
606
- """Update all display components."""
607
- context = self.tutor.learning_context
608
-
609
- # Update goals list
610
- goals_data = []
611
- for goal in state["suggested_goals"]:
612
- status = "Active" if (
613
- context.active_goal and
614
- context.active_goal.id == goal.id
615
- ) else "Available"
616
-
617
- goals_data.append([
618
- goal.smart_version,
619
- goal.category.value,
620
- status
621
- ])
622
-
623
- # Update progress display
624
- summary = context.get_category_summary()
625
- progress_data = [
626
- [cat, data["completed"], data["total"]]
627
- for cat, data in summary.items()
628
- ]
629
-
630
- # Update recent completions
631
- recent_data = []
632
- for cat, data in summary.items():
633
- for goal in data["recent"]:
634
- recent_data.append([
635
- goal["smart_version"],
636
- cat,
637
- goal["completed_at"]
638
- ])
639
-
640
- # Update active goal display
641
- goal_text = (
642
- f"Current Goal: {context.active_goal.smart_version}"
643
- if context.active_goal else
644
- "No active learning goal"
645
- )
646
-
647
- return [
648
- goals_data,
649
- progress_data,
650
- recent_data,
651
- goal_text
652
- ]
653
- def _update_discussion_status(self, state: Dict[str, Any]) -> str:
654
- """Update discussion status display based on state.
655
-
656
- Args:
657
- state: Current interface state
658
-
659
- Returns:
660
- str: Status message to display
661
- """
662
- try:
663
- if not state.get("discussion_active"):
664
- return "Start a new case discussion"
665
-
666
- start = datetime.fromisoformat(state["discussion_start"])
667
- duration = datetime.now() - start
668
- minutes = int(duration.total_seconds() / 60)
669
-
670
- return f"Active discussion ({minutes} minutes)"
671
-
672
- except Exception as e:
673
- logger.error(f"Error updating status: {str(e)}")
674
- return "Discussion status unknown"
 
89
  Features:
90
  - Context selection
91
  - Goal management
92
+ - Case discussion with streaming
93
  - Progress tracking
94
  """
95
 
 
98
  context_path: Optional[Path] = None,
99
  model: str = "anthropic/claude-3.5-sonnet"
100
  ):
101
+ """Initialize interface."""
 
 
 
 
 
 
102
  self.tutor = ClinicalTutor(context_path, model)
103
 
104
  # Available options
 
113
  self.settings = ["Clinic", "Wards", "ED"]
114
 
115
  logger.info("Learning interface initialized")
116
+
117
  async def process_chat(
118
  self,
119
  message: str,
 
157
  history = []
158
  history.append([message, "I apologize, but I encountered an error. Please try again."])
159
  yield history, "", state
160
+
161
+ def _update_discussion_status(self, state: Dict[str, Any]) -> str:
162
+ """Update discussion status display."""
163
+ try:
164
+ if not state.get("discussion_active"):
165
+ return "Start a new case discussion"
166
+
167
+ start = datetime.fromisoformat(state["discussion_start"])
168
+ duration = datetime.now() - start
169
+ minutes = int(duration.total_seconds() / 60)
170
+
171
+ return f"Active discussion ({minutes} minutes)"
172
+
173
+ except Exception as e:
174
+ logger.error(f"Error updating status: {str(e)}")
175
+ return "Discussion status unknown"
176
+
177
+ async def generate_goals(self, state: Dict[str, Any]) -> List:
178
+ """Generate new goal suggestions."""
179
+ rotation = self.tutor.learning_context.rotation
180
+ if rotation.specialty and rotation.setting:
181
+ goals = await self.tutor.generate_smart_goals(
182
+ rotation.specialty,
183
+ rotation.setting
184
+ )
185
+ state["suggested_goals"] = goals
186
+ return self._update_displays(state)
187
+ return [[], [], [], "No active learning goal"]
188
+
189
+ async def add_user_goal(self, text: str, state: Dict[str, Any]) -> List:
190
+ """Add user-specified goal."""
191
+ if not text.strip():
192
+ return self._update_displays(state)
193
+
194
+ rotation = self.tutor.learning_context.rotation
195
+ if rotation.specialty and rotation.setting:
196
+ goal = await self.tutor.generate_smart_goal(
197
+ text,
198
+ rotation.specialty,
199
+ rotation.setting
200
+ )
201
+ if goal:
202
+ if "suggested_goals" not in state:
203
+ state["suggested_goals"] = []
204
+ state["suggested_goals"].append(goal)
205
+ return self._update_displays(state)
206
+ return self._update_displays(state)
207
+
208
+ def select_goal(self, evt: gr.SelectData, state: Dict[str, Any]) -> List:
209
+ """Set selected goal as active."""
210
+ if "suggested_goals" in state and evt.index[0] < len(state["suggested_goals"]):
211
+ goal = state["suggested_goals"][evt.index[0]]
212
+ self.tutor.learning_context.add_smart_goal(goal)
213
+ return self._update_displays(state)
214
+ return self._update_displays(state)
215
+
216
+ def end_discussion(self, state: Dict[str, Any]) -> List:
217
+ """End current discussion."""
218
+ self.tutor.end_discussion()
219
+ state["discussion_active"] = False
220
+ state["discussion_start"] = None
221
+ state["last_message"] = None
222
+ return self._update_displays(state)
223
+
224
+ def _update_displays(self, state: Dict[str, Any]) -> List:
225
+ """Update all display components."""
226
+ context = self.tutor.learning_context
227
+
228
+ # Update goals list
229
+ goals_data = []
230
+ for goal in state.get("suggested_goals", []):
231
+ status = "Active" if (
232
+ context.active_goal and
233
+ context.active_goal.id == goal.id
234
+ ) else "Available"
235
+
236
+ goals_data.append([
237
+ goal.smart_version,
238
+ goal.category.value,
239
+ status
240
+ ])
241
+
242
+ # Update progress display
243
+ summary = context.get_category_summary()
244
+ progress_data = [
245
+ [cat, data["completed"], data["total"]]
246
+ for cat, data in summary.items()
247
+ ]
248
+
249
+ # Update recent completions
250
+ recent_data = []
251
+ for cat, data in summary.items():
252
+ for goal in data["recent"]:
253
+ recent_data.append([
254
+ goal["smart_version"],
255
+ cat,
256
+ goal["completed_at"]
257
+ ])
258
+
259
+ # Update active goal display
260
+ goal_text = (
261
+ f"Current Goal: {context.active_goal.smart_version}"
262
+ if context.active_goal else
263
+ "No active learning goal"
264
+ )
265
+
266
+ return [goals_data, progress_data, recent_data, goal_text]
267
+
268
  def create_interface(self) -> gr.Blocks:
269
+ """Create streaming-enabled interface."""
270
  with gr.Blocks(title="Clinical Learning Assistant", css=create_css()) as interface:
271
  # State management
272
  state = gr.State({
 
275
  "discussion_start": None,
276
  "last_message": None
277
  })
278
+
279
  # Header
280
  gr.Markdown("# Clinical Learning Assistant")
281
+
282
  with gr.Row():
283
  # Context selection
284
  specialty = gr.Dropdown(
 
291
  label="Setting",
292
  value=self.tutor.learning_context.rotation.setting or None
293
  )
294
+
295
  # Main content
296
  with gr.Row():
297
  # Left: Discussion interface
298
  with gr.Column(scale=2):
299
  # Active goal display
300
  goal_display = gr.Markdown(value="No active learning goal")
301
+
302
  # Discussion status
303
  discussion_status = gr.Markdown(
304
  value="Start a new case discussion",
305
  elem_classes=["discussion-status"]
306
  )
307
+
308
  # Chat interface
309
  chatbot = gr.Chatbot(
310
  height=400,
 
312
  type="messages", # Use newer message format
313
  elem_classes=["chat-window"]
314
  )
315
+
316
  with gr.Row():
317
  msg = gr.Textbox(
318
  label="Present your case or ask questions",
 
324
  ),
325
  lines=4
326
  )
327
+
328
  audio_input = gr.Audio(
329
  sources=["microphone"],
330
  type="numpy",
331
  label="Or speak your case",
332
  streaming=True
333
  )
334
+
335
  with gr.Row():
336
  clear = gr.Button("Clear Discussion")
337
  end = gr.Button(
338
  "End Discussion & Review",
339
  variant="primary"
340
  )
341
+
342
  # Right: Progress & Goals
343
  with gr.Column(scale=1):
344
  with gr.Tab("Learning Goals"):
 
350
  placeholder="What do you want to get better at?"
351
  )
352
  add_goal = gr.Button("Add Goal")
353
+
354
  # Goals list
355
  goals_list = gr.DataFrame(
356
  headers=["Goal", "Category", "Status"],
 
359
  interactive=True,
360
  wrap=True
361
  )
362
+
363
  with gr.Tab("Progress"):
364
  # Category progress
365
  progress_display = gr.DataFrame(
 
367
  value=[],
368
  label="Progress by Category"
369
  )
370
+
371
  # Recent completions
372
  recent_display = gr.DataFrame(
373
  headers=["Goal", "Category", "Completed"],
374
  value=[],
375
  label="Recently Completed"
376
  )
377
+
378
  # Event handlers
379
  async def update_context(spec: str, set: str, state: Dict) -> List:
380
  """Update rotation context."""
 
385
  state["suggested_goals"] = goals
386
  return self._update_displays(state)
387
  return [[], [], [], "No active learning goal"]
388
+
389
  def process_audio(audio: np.ndarray) -> str:
390
  """Convert audio to text."""
391
  if audio is None:
392
  return ""
 
393
  return "Audio transcription would appear here"
394
+
395
  def clear_discussion() -> Tuple[List, str, Dict]:
396
  """Clear chat history."""
397
  return [], "", {
 
400
  "discussion_start": None,
401
  "last_message": None
402
  }
403
+
404
  # Wire up events
405
  specialty.change(
406
  update_context,
407
  inputs=[specialty, setting, state],
408
  outputs=[goals_list, progress_display, recent_display, goal_display]
409
  )
410
+
411
  setting.change(
412
  update_context,
413
  inputs=[specialty, setting, state],
414
  outputs=[goals_list, progress_display, recent_display, goal_display]
415
  )
416
+
417
  # Chat events with streaming
418
  msg.submit(
419
  self.process_chat,
 
425
  inputs=[state],
426
  outputs=[discussion_status]
427
  )
428
+
429
  # Voice input handling
430
  audio_input.stream(
431
  process_audio,
 
436
  inputs=[msg, chatbot, state],
437
  outputs=[chatbot, msg, state]
438
  )
439
+
440
  # Button handlers
441
  clear.click(
442
  clear_discussion,
 
445
  lambda: "Start a new case discussion",
446
  outputs=[discussion_status]
447
  )
448
+
449
  end.click(
450
  self.end_discussion,
451
  inputs=[state],
452
  outputs=[goals_list, progress_display, recent_display, goal_display]
453
  )
454
+
455
  generate.click(
456
  self.generate_goals,
457
  inputs=[state],
458
  outputs=[goals_list, progress_display, recent_display, goal_display]
459
  )
460
+
461
  # Goal management
462
  add_goal.click(
463
  self.add_user_goal,
464
  inputs=[new_goal, state],
465
  outputs=[goals_list, progress_display, recent_display, goal_display]
466
  )
467
+
468
  goals_list.select(
469
  self.select_goal,
470
  inputs=[state],
471
  outputs=[goals_list, progress_display, recent_display, goal_display]
472
  )
473
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  return interface