inspect-ai 0.3.82__py3-none-any.whl → 0.3.83__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. inspect_ai/__init__.py +2 -1
  2. inspect_ai/_display/textual/app.py +14 -3
  3. inspect_ai/_display/textual/display.py +4 -0
  4. inspect_ai/_display/textual/widgets/samples.py +9 -3
  5. inspect_ai/_display/textual/widgets/task_detail.py +3 -4
  6. inspect_ai/_display/textual/widgets/tasks.py +17 -1
  7. inspect_ai/_display/textual/widgets/vscode.py +44 -0
  8. inspect_ai/_eval/eval.py +36 -24
  9. inspect_ai/_eval/evalset.py +17 -18
  10. inspect_ai/_eval/loader.py +34 -11
  11. inspect_ai/_eval/run.py +8 -13
  12. inspect_ai/_eval/score.py +13 -3
  13. inspect_ai/_eval/task/generate.py +8 -9
  14. inspect_ai/_eval/task/log.py +2 -0
  15. inspect_ai/_eval/task/task.py +23 -9
  16. inspect_ai/_util/file.py +13 -0
  17. inspect_ai/_util/json.py +2 -1
  18. inspect_ai/_util/registry.py +1 -0
  19. inspect_ai/_util/vscode.py +37 -0
  20. inspect_ai/_view/www/App.css +6 -0
  21. inspect_ai/_view/www/dist/assets/index.css +304 -128
  22. inspect_ai/_view/www/dist/assets/index.js +47495 -27519
  23. inspect_ai/_view/www/log-schema.json +124 -31
  24. inspect_ai/_view/www/package.json +3 -0
  25. inspect_ai/_view/www/src/App.tsx +12 -0
  26. inspect_ai/_view/www/src/appearance/icons.ts +1 -0
  27. inspect_ai/_view/www/src/components/Card.tsx +6 -4
  28. inspect_ai/_view/www/src/components/LinkButton.module.css +16 -0
  29. inspect_ai/_view/www/src/components/LinkButton.tsx +33 -0
  30. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +1 -1
  31. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +113 -23
  32. inspect_ai/_view/www/src/components/Modal.module.css +38 -0
  33. inspect_ai/_view/www/src/components/Modal.tsx +77 -0
  34. inspect_ai/_view/www/src/plan/DetailStep.module.css +4 -0
  35. inspect_ai/_view/www/src/plan/DetailStep.tsx +6 -3
  36. inspect_ai/_view/www/src/plan/SolverDetailView.module.css +2 -1
  37. inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +7 -0
  38. inspect_ai/_view/www/src/samples/SampleDialog.tsx +7 -0
  39. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +11 -34
  40. inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +6 -0
  41. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +2 -2
  42. inspect_ai/_view/www/src/samples/SamplesTools.tsx +12 -0
  43. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +2 -0
  44. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -0
  45. inspect_ai/_view/www/src/samples/chat/messages.ts +3 -1
  46. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +1 -0
  47. inspect_ai/_view/www/src/samples/descriptor/samplesDescriptor.tsx +9 -3
  48. inspect_ai/_view/www/src/samples/descriptor/score/BooleanScoreDescriptor.module.css +3 -3
  49. inspect_ai/_view/www/src/samples/descriptor/score/BooleanScoreDescriptor.tsx +1 -1
  50. inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.module.css +4 -4
  51. inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.tsx +10 -11
  52. inspect_ai/_view/www/src/samples/list/SampleFooter.module.css +2 -1
  53. inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +7 -1
  54. inspect_ai/_view/www/src/samples/list/SampleList.tsx +25 -8
  55. inspect_ai/_view/www/src/samples/list/SampleRow.tsx +1 -1
  56. inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +11 -22
  57. inspect_ai/_view/www/src/samples/scores/SampleScoresGrid.module.css +38 -0
  58. inspect_ai/_view/www/src/samples/scores/SampleScoresGrid.tsx +118 -0
  59. inspect_ai/_view/www/src/samples/scores/{SampleScoreView.module.css → SampleScoresView.module.css} +10 -1
  60. inspect_ai/_view/www/src/samples/scores/SampleScoresView.tsx +78 -0
  61. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +3 -3
  62. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +25 -4
  63. inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +29 -2
  64. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +0 -1
  65. inspect_ai/_view/www/src/state/hooks.ts +5 -3
  66. inspect_ai/_view/www/src/state/logPolling.ts +5 -1
  67. inspect_ai/_view/www/src/state/logSlice.ts +10 -0
  68. inspect_ai/_view/www/src/state/samplePolling.ts +4 -1
  69. inspect_ai/_view/www/src/state/sampleSlice.ts +13 -0
  70. inspect_ai/_view/www/src/types/log.d.ts +34 -26
  71. inspect_ai/_view/www/src/types/markdown-it-katex.d.ts +21 -0
  72. inspect_ai/_view/www/src/utils/json-worker.ts +79 -12
  73. inspect_ai/_view/www/src/workspace/WorkSpace.tsx +18 -16
  74. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.module.css +16 -0
  75. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +68 -71
  76. inspect_ai/_view/www/src/workspace/navbar/ScoreGrid.module.css +35 -0
  77. inspect_ai/_view/www/src/workspace/navbar/ScoreGrid.tsx +117 -0
  78. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +1 -1
  79. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.module.css +3 -2
  80. inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +18 -0
  81. inspect_ai/_view/www/yarn.lock +94 -1
  82. inspect_ai/agent/__init__.py +36 -0
  83. inspect_ai/agent/_agent.py +268 -0
  84. inspect_ai/agent/_as_solver.py +72 -0
  85. inspect_ai/agent/_as_tool.py +122 -0
  86. inspect_ai/{solver → agent}/_bridge/bridge.py +23 -37
  87. inspect_ai/{solver → agent}/_bridge/patch.py +9 -8
  88. inspect_ai/agent/_filter.py +46 -0
  89. inspect_ai/agent/_handoff.py +93 -0
  90. inspect_ai/{solver/_human_agent → agent/_human}/agent.py +11 -12
  91. inspect_ai/{solver/_human_agent → agent/_human}/commands/__init__.py +2 -3
  92. inspect_ai/{solver/_human_agent → agent/_human}/commands/clock.py +3 -1
  93. inspect_ai/{solver/_human_agent → agent/_human}/commands/score.py +5 -5
  94. inspect_ai/{solver/_human_agent → agent/_human}/install.py +6 -3
  95. inspect_ai/{solver/_human_agent → agent/_human}/service.py +7 -3
  96. inspect_ai/{solver/_human_agent → agent/_human}/state.py +5 -5
  97. inspect_ai/agent/_react.py +241 -0
  98. inspect_ai/agent/_run.py +36 -0
  99. inspect_ai/agent/_types.py +81 -0
  100. inspect_ai/log/_log.py +11 -2
  101. inspect_ai/log/_transcript.py +13 -9
  102. inspect_ai/model/__init__.py +7 -1
  103. inspect_ai/model/_call_tools.py +256 -52
  104. inspect_ai/model/_chat_message.py +7 -4
  105. inspect_ai/model/_conversation.py +13 -62
  106. inspect_ai/model/_display.py +85 -0
  107. inspect_ai/model/_model.py +113 -14
  108. inspect_ai/model/_model_output.py +14 -9
  109. inspect_ai/model/_openai.py +16 -4
  110. inspect_ai/model/_openai_computer_use.py +162 -0
  111. inspect_ai/model/_openai_responses.py +319 -165
  112. inspect_ai/model/_providers/anthropic.py +20 -21
  113. inspect_ai/model/_providers/azureai.py +24 -13
  114. inspect_ai/model/_providers/bedrock.py +1 -7
  115. inspect_ai/model/_providers/cloudflare.py +3 -3
  116. inspect_ai/model/_providers/goodfire.py +2 -6
  117. inspect_ai/model/_providers/google.py +11 -10
  118. inspect_ai/model/_providers/groq.py +6 -3
  119. inspect_ai/model/_providers/hf.py +7 -3
  120. inspect_ai/model/_providers/mistral.py +7 -10
  121. inspect_ai/model/_providers/openai.py +47 -17
  122. inspect_ai/model/_providers/openai_o1.py +11 -4
  123. inspect_ai/model/_providers/openai_responses.py +12 -14
  124. inspect_ai/model/_providers/providers.py +2 -2
  125. inspect_ai/model/_providers/together.py +12 -2
  126. inspect_ai/model/_providers/util/chatapi.py +7 -2
  127. inspect_ai/model/_providers/util/hf_handler.py +4 -2
  128. inspect_ai/model/_providers/util/llama31.py +4 -2
  129. inspect_ai/model/_providers/vertex.py +11 -9
  130. inspect_ai/model/_providers/vllm.py +4 -4
  131. inspect_ai/scorer/__init__.py +2 -0
  132. inspect_ai/scorer/_metrics/__init__.py +2 -0
  133. inspect_ai/scorer/_metrics/grouped.py +84 -0
  134. inspect_ai/scorer/_score.py +26 -6
  135. inspect_ai/solver/__init__.py +2 -2
  136. inspect_ai/solver/_basic_agent.py +22 -9
  137. inspect_ai/solver/_bridge.py +31 -0
  138. inspect_ai/solver/_chain.py +20 -12
  139. inspect_ai/solver/_fork.py +5 -1
  140. inspect_ai/solver/_human_agent.py +52 -0
  141. inspect_ai/solver/_prompt.py +3 -1
  142. inspect_ai/solver/_run.py +59 -0
  143. inspect_ai/solver/_solver.py +14 -4
  144. inspect_ai/solver/_task_state.py +5 -3
  145. inspect_ai/tool/_tool_call.py +15 -8
  146. inspect_ai/tool/_tool_def.py +17 -12
  147. inspect_ai/tool/_tool_support_helpers.py +2 -2
  148. inspect_ai/tool/_tool_with.py +14 -11
  149. inspect_ai/tool/_tools/_bash_session.py +11 -2
  150. inspect_ai/tool/_tools/_computer/_common.py +18 -2
  151. inspect_ai/tool/_tools/_computer/_computer.py +18 -2
  152. inspect_ai/tool/_tools/_computer/_resources/tool/_constants.py +2 -0
  153. inspect_ai/tool/_tools/_computer/_resources/tool/_x11_client.py +17 -0
  154. inspect_ai/tool/_tools/_think.py +1 -1
  155. inspect_ai/tool/_tools/_web_browser/_web_browser.py +100 -61
  156. inspect_ai/util/__init__.py +2 -0
  157. inspect_ai/util/_anyio.py +27 -0
  158. inspect_ai/util/_sandbox/__init__.py +2 -1
  159. inspect_ai/util/_sandbox/context.py +32 -7
  160. inspect_ai/util/_sandbox/docker/cleanup.py +4 -0
  161. inspect_ai/util/_sandbox/docker/compose.py +2 -2
  162. inspect_ai/util/_sandbox/docker/docker.py +12 -1
  163. inspect_ai/util/_store_model.py +30 -7
  164. inspect_ai/util/_subprocess.py +13 -3
  165. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/METADATA +1 -1
  166. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/RECORD +179 -153
  167. inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +0 -167
  168. /inspect_ai/{solver → agent}/_bridge/__init__.py +0 -0
  169. /inspect_ai/{solver/_human_agent → agent/_human}/__init__.py +0 -0
  170. /inspect_ai/{solver/_human_agent → agent/_human}/commands/command.py +0 -0
  171. /inspect_ai/{solver/_human_agent → agent/_human}/commands/instructions.py +0 -0
  172. /inspect_ai/{solver/_human_agent → agent/_human}/commands/note.py +0 -0
  173. /inspect_ai/{solver/_human_agent → agent/_human}/commands/status.py +0 -0
  174. /inspect_ai/{solver/_human_agent → agent/_human}/commands/submit.py +0 -0
  175. /inspect_ai/{solver/_human_agent → agent/_human}/panel.py +0 -0
  176. /inspect_ai/{solver/_human_agent → agent/_human}/view.py +0 -0
  177. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/WHEEL +0 -0
  178. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/entry_points.txt +0 -0
  179. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/licenses/LICENSE +0 -0
  180. {inspect_ai-0.3.82.dist-info → inspect_ai-0.3.83.dist-info}/top_level.txt +0 -0
@@ -257,6 +257,17 @@
257
257
  "default": null,
258
258
  "title": "Source"
259
259
  },
260
+ "internal": {
261
+ "anyOf": [
262
+ {
263
+ "$ref": "#/$defs/JsonValue"
264
+ },
265
+ {
266
+ "type": "null"
267
+ }
268
+ ],
269
+ "default": null
270
+ },
260
271
  "role": {
261
272
  "const": "assistant",
262
273
  "default": "assistant",
@@ -277,14 +288,28 @@
277
288
  ],
278
289
  "default": null,
279
290
  "title": "Tool Calls"
291
+ },
292
+ "model": {
293
+ "anyOf": [
294
+ {
295
+ "type": "string"
296
+ },
297
+ {
298
+ "type": "null"
299
+ }
300
+ ],
301
+ "default": null,
302
+ "title": "Model"
280
303
  }
281
304
  },
282
305
  "required": [
283
306
  "id",
284
307
  "content",
285
308
  "source",
309
+ "internal",
286
310
  "role",
287
- "tool_calls"
311
+ "tool_calls",
312
+ "model"
288
313
  ],
289
314
  "title": "ChatMessageAssistant",
290
315
  "type": "object",
@@ -351,6 +376,17 @@
351
376
  "default": null,
352
377
  "title": "Source"
353
378
  },
379
+ "internal": {
380
+ "anyOf": [
381
+ {
382
+ "$ref": "#/$defs/JsonValue"
383
+ },
384
+ {
385
+ "type": "null"
386
+ }
387
+ ],
388
+ "default": null
389
+ },
354
390
  "role": {
355
391
  "const": "system",
356
392
  "default": "system",
@@ -362,6 +398,7 @@
362
398
  "id",
363
399
  "content",
364
400
  "source",
401
+ "internal",
365
402
  "role"
366
403
  ],
367
404
  "title": "ChatMessageSystem",
@@ -429,6 +466,17 @@
429
466
  "default": null,
430
467
  "title": "Source"
431
468
  },
469
+ "internal": {
470
+ "anyOf": [
471
+ {
472
+ "$ref": "#/$defs/JsonValue"
473
+ },
474
+ {
475
+ "type": "null"
476
+ }
477
+ ],
478
+ "default": null
479
+ },
432
480
  "role": {
433
481
  "const": "tool",
434
482
  "default": "tool",
@@ -459,18 +507,6 @@
459
507
  "default": null,
460
508
  "title": "Function"
461
509
  },
462
- "internal_name": {
463
- "anyOf": [
464
- {
465
- "type": "string"
466
- },
467
- {
468
- "type": "null"
469
- }
470
- ],
471
- "default": null,
472
- "title": "Internal Name"
473
- },
474
510
  "error": {
475
511
  "anyOf": [
476
512
  {
@@ -487,10 +523,10 @@
487
523
  "id",
488
524
  "content",
489
525
  "source",
526
+ "internal",
490
527
  "role",
491
528
  "tool_call_id",
492
529
  "function",
493
- "internal_name",
494
530
  "error"
495
531
  ],
496
532
  "title": "ChatMessageTool",
@@ -558,6 +594,17 @@
558
594
  "default": null,
559
595
  "title": "Source"
560
596
  },
597
+ "internal": {
598
+ "anyOf": [
599
+ {
600
+ "$ref": "#/$defs/JsonValue"
601
+ },
602
+ {
603
+ "type": "null"
604
+ }
605
+ ],
606
+ "default": null
607
+ },
561
608
  "role": {
562
609
  "const": "user",
563
610
  "default": "user",
@@ -584,6 +631,7 @@
584
631
  "id",
585
632
  "content",
586
633
  "source",
634
+ "internal",
587
635
  "role",
588
636
  "tool_call_id"
589
637
  ],
@@ -708,11 +756,24 @@
708
756
  "text": {
709
757
  "title": "Text",
710
758
  "type": "string"
759
+ },
760
+ "refusal": {
761
+ "anyOf": [
762
+ {
763
+ "type": "boolean"
764
+ },
765
+ {
766
+ "type": "null"
767
+ }
768
+ ],
769
+ "default": null,
770
+ "title": "Refusal"
711
771
  }
712
772
  },
713
773
  "required": [
714
774
  "type",
715
- "text"
775
+ "text",
776
+ "refusal"
716
777
  ],
717
778
  "title": "ContentText",
718
779
  "type": "object",
@@ -2112,6 +2173,18 @@
2112
2173
  "default": null,
2113
2174
  "title": "Task File"
2114
2175
  },
2176
+ "task_registry_name": {
2177
+ "anyOf": [
2178
+ {
2179
+ "type": "string"
2180
+ },
2181
+ {
2182
+ "type": "null"
2183
+ }
2184
+ ],
2185
+ "default": null,
2186
+ "title": "Task Registry Name"
2187
+ },
2115
2188
  "task_attribs": {
2116
2189
  "title": "Task Attribs",
2117
2190
  "type": "object"
@@ -2273,6 +2346,7 @@
2273
2346
  "task_id",
2274
2347
  "task_version",
2275
2348
  "task_file",
2349
+ "task_registry_name",
2276
2350
  "task_attribs",
2277
2351
  "task_args",
2278
2352
  "solver",
@@ -4488,21 +4562,16 @@
4488
4562
  "title": "Arguments",
4489
4563
  "type": "object"
4490
4564
  },
4491
- "type": {
4492
- "title": "Type",
4493
- "type": "string"
4494
- },
4495
- "internal_name": {
4565
+ "internal": {
4496
4566
  "anyOf": [
4497
4567
  {
4498
- "type": "string"
4568
+ "$ref": "#/$defs/JsonValue"
4499
4569
  },
4500
4570
  {
4501
4571
  "type": "null"
4502
4572
  }
4503
4573
  ],
4504
- "default": null,
4505
- "title": "Internal Name"
4574
+ "default": null
4506
4575
  },
4507
4576
  "parse_error": {
4508
4577
  "anyOf": [
@@ -4532,8 +4601,7 @@
4532
4601
  "id",
4533
4602
  "function",
4534
4603
  "arguments",
4535
- "type",
4536
- "internal_name",
4604
+ "internal",
4537
4605
  "parse_error",
4538
4606
  "view"
4539
4607
  ],
@@ -4693,17 +4761,16 @@
4693
4761
  "title": "Arguments",
4694
4762
  "type": "object"
4695
4763
  },
4696
- "internal_name": {
4764
+ "internal": {
4697
4765
  "anyOf": [
4698
4766
  {
4699
- "type": "string"
4767
+ "$ref": "#/$defs/JsonValue"
4700
4768
  },
4701
4769
  {
4702
4770
  "type": "null"
4703
4771
  }
4704
4772
  ],
4705
- "default": null,
4706
- "title": "Internal Name"
4773
+ "default": null
4707
4774
  },
4708
4775
  "view": {
4709
4776
  "anyOf": [
@@ -4880,6 +4947,30 @@
4880
4947
  ],
4881
4948
  "default": null,
4882
4949
  "title": "Working Time"
4950
+ },
4951
+ "agent": {
4952
+ "anyOf": [
4953
+ {
4954
+ "type": "string"
4955
+ },
4956
+ {
4957
+ "type": "null"
4958
+ }
4959
+ ],
4960
+ "default": null,
4961
+ "title": "Agent"
4962
+ },
4963
+ "failed": {
4964
+ "anyOf": [
4965
+ {
4966
+ "type": "boolean"
4967
+ },
4968
+ {
4969
+ "type": "null"
4970
+ }
4971
+ ],
4972
+ "default": null,
4973
+ "title": "Failed"
4883
4974
  }
4884
4975
  },
4885
4976
  "required": [
@@ -4891,14 +4982,16 @@
4891
4982
  "id",
4892
4983
  "function",
4893
4984
  "arguments",
4894
- "internal_name",
4985
+ "internal",
4895
4986
  "view",
4896
4987
  "result",
4897
4988
  "truncated",
4898
4989
  "error",
4899
4990
  "events",
4900
4991
  "completed",
4901
- "working_time"
4992
+ "working_time",
4993
+ "agent",
4994
+ "failed"
4902
4995
  ],
4903
4996
  "title": "ToolEvent",
4904
4997
  "type": "object",
@@ -37,6 +37,7 @@
37
37
  "eslint": "9.x",
38
38
  "eslint-plugin-react-hooks": "^5.1.0",
39
39
  "globals": "^15.6.0",
40
+ "json-schema-to-typescript": "^15.0.4",
40
41
  "prettier": "^3.3.3",
41
42
  "typescript": "^5.7.3",
42
43
  "typescript-eslint": "^8.25.0",
@@ -64,7 +65,9 @@
64
65
  "json": "^11.0.0",
65
66
  "json5": "^2.2.3",
66
67
  "jsondiffpatch": "^0.6.0",
68
+ "katex": "^0.16.21",
67
69
  "markdown-it": "^14.1.0",
70
+ "markdown-it-katex": "^2.0.3",
68
71
  "murmurhash": "^2.0.1",
69
72
  "postcss-url": "^10.1.3",
70
73
  "prismjs": "^1.30.0",
@@ -72,6 +72,7 @@ export const App: FC<AppProps> = ({ api }) => {
72
72
  );
73
73
  const resetFiltering = useStore((state) => state.logActions.resetFiltering);
74
74
  const loadLog = useStore((state) => state.logActions.loadLog);
75
+ const pollLog = useStore((state) => state.logActions.pollLog);
75
76
  const refreshLog = useStore((state) => state.logActions.refreshLog);
76
77
  const selectSample = useStore((state) => state.logActions.selectSample);
77
78
 
@@ -102,6 +103,17 @@ export const App: FC<AppProps> = ({ api }) => {
102
103
  loadSpecificLog();
103
104
  }, [selectedLogFile, loadedLogFile, loadLog, setAppStatus]);
104
105
 
106
+ useEffect(() => {
107
+ // If the component re-mounts and there is a running load loaded
108
+ // start up polling
109
+ const doPoll = async () => {
110
+ await pollLog();
111
+ };
112
+ if (selectedLogSummary?.status === "started") {
113
+ doPoll();
114
+ }
115
+ }, []);
116
+
105
117
  useEffect(() => {
106
118
  if (logs.log_dir && logs.files.length === 0) {
107
119
  setAppStatus({
@@ -66,6 +66,7 @@ export const ApplicationIcons = {
66
66
  menu: "bi bi-list",
67
67
  messages: "bi bi-chat-right-text",
68
68
  metadata: "bi bi-table",
69
+ metrics: "bi bi-clipboard-data",
69
70
  model: "bi bi-grid-3x3-gap",
70
71
  "toggle-right": "bi bi-chevron-right",
71
72
  more: "bi bi-zoom-in",
@@ -14,11 +14,13 @@ interface CardHeaderProps {
14
14
  interface CardBodyProps {
15
15
  id?: string;
16
16
  children?: ReactNode;
17
+ className?: string | string[];
17
18
  }
18
19
 
19
20
  interface CardProps {
20
21
  id?: string;
21
22
  children?: ReactNode;
23
+ className?: string | string[];
22
24
  }
23
25
 
24
26
  interface CardCollapsingHeaderProps {
@@ -51,17 +53,17 @@ export const CardHeader: FC<CardHeaderProps> = ({
51
53
  );
52
54
  };
53
55
 
54
- export const CardBody: FC<CardBodyProps> = ({ id, children }) => {
56
+ export const CardBody: FC<CardBodyProps> = ({ id, children, className }) => {
55
57
  return (
56
- <div className={"card-body"} id={id || ""}>
58
+ <div className={clsx("card-body", className)} id={id || ""}>
57
59
  {children}
58
60
  </div>
59
61
  );
60
62
  };
61
63
 
62
- export const Card: FC<CardProps> = ({ id, children }) => {
64
+ export const Card: FC<CardProps> = ({ id, children, className }) => {
63
65
  return (
64
- <div className={"card"} id={id}>
66
+ <div className={clsx("card", className)} id={id}>
65
67
  {children}
66
68
  </div>
67
69
  );
@@ -0,0 +1,16 @@
1
+ .button {
2
+ display: flex;
3
+ border: none;
4
+ flex-direction: row;
5
+ background-color: unset;
6
+ color: var(--bs-link-color);
7
+ }
8
+
9
+ .button:hover {
10
+ color: var(--bs-link-hover-color);
11
+ cursor: pointer;
12
+ }
13
+
14
+ .label {
15
+ margin-left: 0.4em;
16
+ }
@@ -0,0 +1,33 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+ import styles from "./LinkButton.module.css";
4
+
5
+ interface LinkButtonProps {
6
+ id?: string;
7
+ text?: string;
8
+ icon?: string;
9
+ onClick: () => void;
10
+ className?: string | string[];
11
+ }
12
+
13
+ /**
14
+ * LightboxCarousel component provides a carousel with lightbox functionality.
15
+ */
16
+ export const LinkButton: FC<LinkButtonProps> = ({
17
+ id,
18
+ text,
19
+ icon,
20
+ className,
21
+ onClick,
22
+ }) => {
23
+ return (
24
+ <button
25
+ id={id}
26
+ onClick={onClick}
27
+ className={clsx(className, styles.button, "text-size-smaller")}
28
+ >
29
+ {icon ? <i className={clsx(icon)}></i> : undefined}
30
+ {text ? <div className={clsx(styles.label)}>{text}</div> : undefined}
31
+ </button>
32
+ );
33
+ };
@@ -63,7 +63,7 @@ export const LiveVirtualList = <T,>({
63
63
  );
64
64
  const isAutoScrollingRef = useRef(false);
65
65
 
66
- // Only we first load set the defaul value for following
66
+ // Only we first load set the default value for following
67
67
  // based upon whether or not the transcript is 'live'
68
68
  useEffect(() => {
69
69
  if (followOutput === null) {
@@ -1,5 +1,7 @@
1
1
  import clsx from "clsx";
2
+ import "katex/dist/katex.min.css";
2
3
  import markdownit from "markdown-it";
4
+ import markdownitKatex from "markdown-it-katex";
3
5
  import { CSSProperties, forwardRef } from "react";
4
6
  import "./MarkdownDiv.css";
5
7
 
@@ -11,21 +13,34 @@ interface MarkdownDivProps {
11
13
 
12
14
  export const MarkdownDiv = forwardRef<HTMLDivElement, MarkdownDivProps>(
13
15
  ({ markdown, style, className }, ref) => {
16
+ // Protect backslashes in LaTeX expressions
17
+ const protectedContent = protectBackslashesInLatex(markdown);
18
+
14
19
  // Escape all tags
15
- const escaped = markdown ? escape(markdown) : "";
20
+ const escaped = escapeHtmlCharacters(protectedContent);
16
21
 
17
22
  // Pre-render any text that isn't handled by markdown
18
23
  const preRendered = preRenderText(escaped);
19
24
 
20
25
  const protectedText = protectMarkdown(preRendered);
21
26
 
22
- let renderedHtml = protectedText;
27
+ // Restore backslashes for LaTeX processing
28
+ const preparedForMarkdown = restoreBackslashesForLatex(protectedText);
29
+
30
+ let renderedHtml = preparedForMarkdown;
23
31
  try {
24
32
  const md = markdownit({
25
33
  breaks: true,
26
34
  html: true,
27
35
  });
28
- renderedHtml = md.render(protectedText);
36
+
37
+ // Add KaTeX support
38
+ md.use(markdownitKatex, {
39
+ throwOnError: false,
40
+ errorColor: "#cc0000",
41
+ });
42
+
43
+ renderedHtml = md.render(preparedForMarkdown);
29
44
  } catch (ex) {
30
45
  console.log("Unable to markdown render content");
31
46
  console.error(ex);
@@ -53,7 +68,95 @@ export const MarkdownDiv = forwardRef<HTMLDivElement, MarkdownDivProps>(
53
68
  const kLetterListPattern = /^([a-zA-Z][).]\s.*?)$/gm;
54
69
  const kCommonmarkReferenceLinkPattern = /\[([^\]]*)\]: (?!http)(.*)/g;
55
70
 
71
+ const protectBackslashesInLatex = (content: string): string => {
72
+ if (!content) return content;
73
+
74
+ try {
75
+ // Match inline math: $...$
76
+ const inlineRegex = /\$(.*?)\$/g;
77
+
78
+ // Match block math: $$...$$
79
+ const blockRegex = /\$\$([\s\S]*?)\$\$/g;
80
+
81
+ // Replace backslashes in LaTeX blocks with a placeholder
82
+ let result = content.replace(inlineRegex, (_match, latex) => {
83
+ const protectedTex = latex.replace(/\\/g, "___LATEX_BACKSLASH___");
84
+ return `$${protectedTex}$`;
85
+ });
86
+
87
+ result = result.replace(blockRegex, (_match, latex) => {
88
+ const protectedTex = latex.replace(/\\/g, "___LATEX_BACKSLASH___");
89
+ return `$$${protectedTex}$$`;
90
+ });
91
+
92
+ return result;
93
+ } catch (error) {
94
+ console.error("Error protecting LaTeX backslashes:", error);
95
+ return content;
96
+ }
97
+ };
98
+
99
+ const restoreBackslashesForLatex = (content: string): string => {
100
+ if (!content) return content;
101
+
102
+ try {
103
+ // First restore backslashes
104
+ let result = content.replace(/___LATEX_BACKSLASH___/g, "\\");
105
+
106
+ // Then fix dots notation for better KaTeX compatibility
107
+ // This replaces \dots with \ldots which has better support
108
+ result = fixDotsNotation(result);
109
+
110
+ return result;
111
+ } catch (error) {
112
+ console.error("Error restoring LaTeX backslashes:", error);
113
+ return content; // Return input content if something goes wrong
114
+ }
115
+ };
116
+
117
+ // Fixes dots notation in LaTeX by replacing \dots with \ldots for better compatibility
118
+ const fixDotsNotation = (content: string): string => {
119
+ if (!content) return content;
120
+
121
+ try {
122
+ // Handle both inline and block math
123
+ // First, fix inline math expressions ($...$)
124
+ let result = content.replace(/(\$[^$]*?)\\dots([^$]*?\$)/g, "$1\\ldots$2");
125
+
126
+ // Then, fix block math expressions ($...$)
127
+ result = result.replace(/(\$\$[^$]*?)\\dots([^$]*?\$\$)/g, "$1\\ldots$2");
128
+
129
+ return result;
130
+ } catch (error) {
131
+ console.error("Error fixing dots notation:", error);
132
+ return content;
133
+ }
134
+ };
135
+
136
+ const escapeHtmlCharacters = (content: string): string => {
137
+ if (!content) return content;
138
+
139
+ return content.replace(/[<>&'"]/g, (c: string): string => {
140
+ switch (c) {
141
+ case "<":
142
+ return "&lt;";
143
+ case ">":
144
+ return "&gt;";
145
+ case "&":
146
+ return "&amp;";
147
+ case "'":
148
+ return "&apos;";
149
+ case '"':
150
+ return "&quot;";
151
+ default:
152
+ throw new Error("Matched a value that isn't replaceable");
153
+ }
154
+ });
155
+ };
156
+
56
157
  const preRenderText = (txt: string): string => {
158
+ if (!txt) return txt;
159
+
57
160
  // eslint-disable-next-line no-misleading-character-class
58
161
  txt = txt.replace(/^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/, "");
59
162
 
@@ -66,6 +169,8 @@ const preRenderText = (txt: string): string => {
66
169
  };
67
170
 
68
171
  const protectMarkdown = (txt: string): string => {
172
+ if (!txt) return txt;
173
+
69
174
  // Special handling for commonmark like reference links which might
70
175
  // look like:
71
176
  // [alias]: http://www.google.com
@@ -74,36 +179,21 @@ const protectMarkdown = (txt: string): string => {
74
179
  // Also fools this
75
180
  return txt.replaceAll(
76
181
  kCommonmarkReferenceLinkPattern,
77
- "(open:767A125E)$1(close:767A125E) $2 ",
182
+ "(open:767A125E)$1(close:767A125E) $2 ",
78
183
  );
79
184
  };
80
185
 
81
186
  const unprotectMarkdown = (txt: string): string => {
187
+ if (!txt) return txt;
188
+
82
189
  txt = txt.replaceAll("(open:767A125E)", "[");
83
190
  txt = txt.replaceAll("(close:767A125E)", "]");
84
191
  return txt;
85
192
  };
86
193
 
87
- const escape = (content: string): string => {
88
- return content.replace(/[<>&'"]/g, (c: string): string => {
89
- switch (c) {
90
- case "<":
91
- return "&lt;";
92
- case ">":
93
- return "&gt;";
94
- case "&":
95
- return "&amp;";
96
- case "'":
97
- return "&apos;";
98
- case '"':
99
- return "&quot;";
100
- default:
101
- throw new Error("Matched a value that isn't replaceable");
102
- }
103
- });
104
- };
105
-
106
194
  function unescapeCodeHtmlEntities(str: string): string {
195
+ if (!str) return str;
196
+
107
197
  const htmlEntities: Record<string, string> = {
108
198
  "&lt;": "<",
109
199
  "&gt;": ">",
@@ -0,0 +1,38 @@
1
+ .modal {
2
+ margin-left: auto;
3
+ margin-right: auto;
4
+ display: flex;
5
+ flex: 1 1 auto;
6
+ justify-content: center;
7
+ max-width: 90vw;
8
+ margin-top: 3rem;
9
+ border-radius: var(--bs-border-radius);
10
+ position: relative;
11
+ z-index: 1050;
12
+ }
13
+
14
+ .header {
15
+ height: 2em;
16
+ }
17
+
18
+ .modalTitle {
19
+ font-weight: 600;
20
+ }
21
+
22
+ .btnClose {
23
+ height: 0.8em;
24
+ width: 0.8em;
25
+ }
26
+
27
+ /* Backdrop styling for the glass effect */
28
+ .backdrop {
29
+ position: fixed;
30
+ top: 0;
31
+ left: 0;
32
+ width: 100%;
33
+ height: 100%;
34
+ background-color: var(--inspect-glass-color);
35
+ opacity: var(--inspect-glass-opacity);
36
+
37
+ z-index: 1040;
38
+ }