inspect-ai 0.3.57__py3-none-any.whl → 0.3.59__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 (161) hide show
  1. inspect_ai/__init__.py +2 -1
  2. inspect_ai/_cli/common.py +7 -3
  3. inspect_ai/_cli/eval.py +17 -2
  4. inspect_ai/_cli/trace.py +21 -2
  5. inspect_ai/_display/core/active.py +4 -3
  6. inspect_ai/_display/core/config.py +3 -3
  7. inspect_ai/_display/core/panel.py +7 -3
  8. inspect_ai/_display/plain/__init__.py +0 -0
  9. inspect_ai/_display/plain/display.py +203 -0
  10. inspect_ai/_display/rich/display.py +4 -9
  11. inspect_ai/_display/textual/app.py +4 -1
  12. inspect_ai/_display/textual/widgets/port_mappings.py +110 -0
  13. inspect_ai/_display/textual/widgets/samples.py +119 -16
  14. inspect_ai/_display/textual/widgets/sandbox.py +37 -0
  15. inspect_ai/_eval/eval.py +32 -20
  16. inspect_ai/_eval/evalset.py +7 -5
  17. inspect_ai/_eval/score.py +1 -0
  18. inspect_ai/_eval/task/__init__.py +2 -2
  19. inspect_ai/_eval/task/images.py +40 -25
  20. inspect_ai/_eval/task/results.py +50 -22
  21. inspect_ai/_eval/task/run.py +180 -124
  22. inspect_ai/_eval/task/sandbox.py +10 -5
  23. inspect_ai/_eval/task/task.py +140 -25
  24. inspect_ai/_util/constants.py +2 -0
  25. inspect_ai/_util/content.py +23 -1
  26. inspect_ai/_util/images.py +20 -17
  27. inspect_ai/_util/kvstore.py +73 -0
  28. inspect_ai/_util/notgiven.py +18 -0
  29. inspect_ai/_util/port_names.py +61 -0
  30. inspect_ai/_util/text.py +23 -0
  31. inspect_ai/_util/thread.py +5 -0
  32. inspect_ai/_view/www/App.css +31 -1
  33. inspect_ai/_view/www/dist/assets/index.css +31 -1
  34. inspect_ai/_view/www/dist/assets/index.js +25375 -1846
  35. inspect_ai/_view/www/log-schema.json +129 -15
  36. inspect_ai/_view/www/package.json +2 -0
  37. inspect_ai/_view/www/src/App.mjs +8 -10
  38. inspect_ai/_view/www/src/Types.mjs +0 -1
  39. inspect_ai/_view/www/src/components/ChatView.mjs +133 -43
  40. inspect_ai/_view/www/src/components/ExpandablePanel.mjs +0 -4
  41. inspect_ai/_view/www/src/components/LargeModal.mjs +19 -20
  42. inspect_ai/_view/www/src/components/MessageBand.mjs +2 -2
  43. inspect_ai/_view/www/src/components/MessageContent.mjs +43 -1
  44. inspect_ai/_view/www/src/components/TabSet.mjs +3 -1
  45. inspect_ai/_view/www/src/components/VirtualList.mjs +266 -84
  46. inspect_ai/_view/www/src/index.js +75 -2
  47. inspect_ai/_view/www/src/navbar/Navbar.mjs +3 -0
  48. inspect_ai/_view/www/src/navbar/SecondaryBar.mjs +18 -9
  49. inspect_ai/_view/www/src/samples/SampleDialog.mjs +5 -1
  50. inspect_ai/_view/www/src/samples/SampleDisplay.mjs +23 -15
  51. inspect_ai/_view/www/src/samples/SampleList.mjs +18 -48
  52. inspect_ai/_view/www/src/samples/SampleTranscript.mjs +8 -3
  53. inspect_ai/_view/www/src/samples/SamplesDescriptor.mjs +29 -13
  54. inspect_ai/_view/www/src/samples/SamplesTab.mjs +4 -1
  55. inspect_ai/_view/www/src/samples/SamplesTools.mjs +8 -8
  56. inspect_ai/_view/www/src/samples/tools/SampleFilter.mjs +712 -89
  57. inspect_ai/_view/www/src/samples/tools/filters.mjs +260 -87
  58. inspect_ai/_view/www/src/samples/transcript/ErrorEventView.mjs +24 -2
  59. inspect_ai/_view/www/src/samples/transcript/EventPanel.mjs +29 -24
  60. inspect_ai/_view/www/src/samples/transcript/EventRow.mjs +1 -1
  61. inspect_ai/_view/www/src/samples/transcript/InfoEventView.mjs +24 -2
  62. inspect_ai/_view/www/src/samples/transcript/InputEventView.mjs +24 -2
  63. inspect_ai/_view/www/src/samples/transcript/ModelEventView.mjs +31 -10
  64. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.mjs +24 -2
  65. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.mjs +23 -2
  66. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.mjs +24 -2
  67. inspect_ai/_view/www/src/samples/transcript/StepEventView.mjs +33 -3
  68. inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.mjs +25 -2
  69. inspect_ai/_view/www/src/samples/transcript/ToolEventView.mjs +25 -2
  70. inspect_ai/_view/www/src/samples/transcript/TranscriptView.mjs +193 -11
  71. inspect_ai/_view/www/src/samples/transcript/Types.mjs +10 -0
  72. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.mjs +26 -2
  73. inspect_ai/_view/www/src/types/log.d.ts +62 -27
  74. inspect_ai/_view/www/src/utils/Format.mjs +10 -3
  75. inspect_ai/_view/www/src/utils/Json.mjs +12 -6
  76. inspect_ai/_view/www/src/workspace/WorkSpace.mjs +10 -4
  77. inspect_ai/_view/www/vite.config.js +7 -0
  78. inspect_ai/_view/www/yarn.lock +116 -0
  79. inspect_ai/approval/_human/__init__.py +0 -0
  80. inspect_ai/approval/_human/util.py +2 -2
  81. inspect_ai/approval/_policy.py +12 -6
  82. inspect_ai/dataset/_sources/csv.py +2 -1
  83. inspect_ai/dataset/_sources/json.py +2 -1
  84. inspect_ai/dataset/_sources/util.py +15 -7
  85. inspect_ai/log/_condense.py +11 -1
  86. inspect_ai/log/_log.py +3 -6
  87. inspect_ai/log/_recorders/eval.py +19 -8
  88. inspect_ai/log/_samples.py +26 -5
  89. inspect_ai/log/_transcript.py +32 -2
  90. inspect_ai/model/__init__.py +10 -2
  91. inspect_ai/model/_call_tools.py +59 -12
  92. inspect_ai/model/_chat_message.py +2 -4
  93. inspect_ai/model/_conversation.py +61 -0
  94. inspect_ai/model/_generate_config.py +10 -4
  95. inspect_ai/model/_model.py +117 -18
  96. inspect_ai/model/_model_output.py +7 -2
  97. inspect_ai/model/_providers/anthropic.py +109 -51
  98. inspect_ai/model/_providers/azureai.py +26 -24
  99. inspect_ai/model/_providers/bedrock.py +43 -44
  100. inspect_ai/model/_providers/google.py +121 -58
  101. inspect_ai/model/_providers/groq.py +7 -5
  102. inspect_ai/model/_providers/hf.py +11 -6
  103. inspect_ai/model/_providers/mistral.py +17 -20
  104. inspect_ai/model/_providers/openai.py +32 -21
  105. inspect_ai/model/_providers/openai_o1.py +9 -8
  106. inspect_ai/model/_providers/providers.py +1 -1
  107. inspect_ai/model/_providers/together.py +8 -8
  108. inspect_ai/model/_providers/vertex.py +18 -8
  109. inspect_ai/scorer/__init__.py +13 -2
  110. inspect_ai/scorer/_metrics/__init__.py +2 -2
  111. inspect_ai/scorer/_metrics/std.py +3 -3
  112. inspect_ai/scorer/_reducer/reducer.py +1 -1
  113. inspect_ai/scorer/_scorer.py +2 -2
  114. inspect_ai/solver/__init__.py +2 -5
  115. inspect_ai/solver/_prompt.py +35 -5
  116. inspect_ai/solver/_task_state.py +80 -38
  117. inspect_ai/tool/__init__.py +11 -1
  118. inspect_ai/tool/_tool.py +21 -3
  119. inspect_ai/tool/_tool_call.py +10 -0
  120. inspect_ai/tool/_tool_def.py +16 -5
  121. inspect_ai/tool/_tool_with.py +21 -4
  122. inspect_ai/tool/beta/__init__.py +5 -0
  123. inspect_ai/tool/beta/_computer/__init__.py +3 -0
  124. inspect_ai/tool/beta/_computer/_common.py +133 -0
  125. inspect_ai/tool/beta/_computer/_computer.py +155 -0
  126. inspect_ai/tool/beta/_computer/_computer_split.py +198 -0
  127. inspect_ai/tool/beta/_computer/_resources/Dockerfile +100 -0
  128. inspect_ai/tool/beta/_computer/_resources/README.md +30 -0
  129. inspect_ai/tool/beta/_computer/_resources/entrypoint/entrypoint.sh +18 -0
  130. inspect_ai/tool/beta/_computer/_resources/entrypoint/novnc_startup.sh +20 -0
  131. inspect_ai/tool/beta/_computer/_resources/entrypoint/x11vnc_startup.sh +48 -0
  132. inspect_ai/tool/beta/_computer/_resources/entrypoint/xfce_startup.sh +13 -0
  133. inspect_ai/tool/beta/_computer/_resources/entrypoint/xvfb_startup.sh +48 -0
  134. inspect_ai/tool/beta/_computer/_resources/image_home_dir/Desktop/Firefox Web Browser.desktop +10 -0
  135. inspect_ai/tool/beta/_computer/_resources/image_home_dir/Desktop/Visual Studio Code.desktop +10 -0
  136. inspect_ai/tool/beta/_computer/_resources/image_home_dir/Desktop/XPaint.desktop +10 -0
  137. inspect_ai/tool/beta/_computer/_resources/tool/__init__.py +0 -0
  138. inspect_ai/tool/beta/_computer/_resources/tool/_logger.py +22 -0
  139. inspect_ai/tool/beta/_computer/_resources/tool/_run.py +42 -0
  140. inspect_ai/tool/beta/_computer/_resources/tool/_tool_result.py +33 -0
  141. inspect_ai/tool/beta/_computer/_resources/tool/_x11_client.py +262 -0
  142. inspect_ai/tool/beta/_computer/_resources/tool/computer_tool.py +85 -0
  143. inspect_ai/tool/beta/_computer/_resources/tool/requirements.txt +0 -0
  144. inspect_ai/util/__init__.py +2 -3
  145. inspect_ai/util/{_trace.py → _conversation.py} +3 -17
  146. inspect_ai/util/_display.py +14 -4
  147. inspect_ai/util/_limit.py +26 -0
  148. inspect_ai/util/_sandbox/context.py +12 -13
  149. inspect_ai/util/_sandbox/docker/compose.py +24 -11
  150. inspect_ai/util/_sandbox/docker/docker.py +84 -14
  151. inspect_ai/util/_sandbox/docker/internal.py +3 -1
  152. inspect_ai/util/_sandbox/environment.py +27 -1
  153. inspect_ai/util/_sandbox/local.py +1 -0
  154. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/METADATA +2 -2
  155. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/RECORD +159 -128
  156. inspect_ai/_view/www/src/samples/transcript/TranscriptState.mjs +0 -70
  157. inspect_ai/model/_trace.py +0 -48
  158. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/LICENSE +0 -0
  159. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/WHEEL +0 -0
  160. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/entry_points.txt +0 -0
  161. {inspect_ai-0.3.57.dist-info → inspect_ai-0.3.59.dist-info}/top_level.txt +0 -0
@@ -210,6 +210,12 @@
210
210
  },
211
211
  {
212
212
  "$ref": "#/$defs/ContentImage"
213
+ },
214
+ {
215
+ "$ref": "#/$defs/ContentAudio"
216
+ },
217
+ {
218
+ "$ref": "#/$defs/ContentVideo"
213
219
  }
214
220
  ]
215
221
  },
@@ -281,6 +287,12 @@
281
287
  },
282
288
  {
283
289
  "$ref": "#/$defs/ContentImage"
290
+ },
291
+ {
292
+ "$ref": "#/$defs/ContentAudio"
293
+ },
294
+ {
295
+ "$ref": "#/$defs/ContentVideo"
284
296
  }
285
297
  ]
286
298
  },
@@ -336,6 +348,12 @@
336
348
  },
337
349
  {
338
350
  "$ref": "#/$defs/ContentImage"
351
+ },
352
+ {
353
+ "$ref": "#/$defs/ContentAudio"
354
+ },
355
+ {
356
+ "$ref": "#/$defs/ContentVideo"
339
357
  }
340
358
  ]
341
359
  },
@@ -429,6 +447,12 @@
429
447
  },
430
448
  {
431
449
  "$ref": "#/$defs/ContentImage"
450
+ },
451
+ {
452
+ "$ref": "#/$defs/ContentAudio"
453
+ },
454
+ {
455
+ "$ref": "#/$defs/ContentVideo"
432
456
  }
433
457
  ]
434
458
  },
@@ -482,6 +506,36 @@
482
506
  "type": "object",
483
507
  "additionalProperties": false
484
508
  },
509
+ "ContentAudio": {
510
+ "properties": {
511
+ "type": {
512
+ "const": "audio",
513
+ "default": "audio",
514
+ "title": "Type",
515
+ "type": "string"
516
+ },
517
+ "audio": {
518
+ "title": "Audio",
519
+ "type": "string"
520
+ },
521
+ "format": {
522
+ "enum": [
523
+ "wav",
524
+ "mp3"
525
+ ],
526
+ "title": "Format",
527
+ "type": "string"
528
+ }
529
+ },
530
+ "required": [
531
+ "type",
532
+ "audio",
533
+ "format"
534
+ ],
535
+ "title": "ContentAudio",
536
+ "type": "object",
537
+ "additionalProperties": false
538
+ },
485
539
  "ContentImage": {
486
540
  "properties": {
487
541
  "type": {
@@ -535,6 +589,37 @@
535
589
  "type": "object",
536
590
  "additionalProperties": false
537
591
  },
592
+ "ContentVideo": {
593
+ "properties": {
594
+ "type": {
595
+ "const": "video",
596
+ "default": "video",
597
+ "title": "Type",
598
+ "type": "string"
599
+ },
600
+ "video": {
601
+ "title": "Video",
602
+ "type": "string"
603
+ },
604
+ "format": {
605
+ "enum": [
606
+ "mp4",
607
+ "mpeg",
608
+ "mov"
609
+ ],
610
+ "title": "Format",
611
+ "type": "string"
612
+ }
613
+ },
614
+ "required": [
615
+ "type",
616
+ "video",
617
+ "format"
618
+ ],
619
+ "title": "ContentVideo",
620
+ "type": "object",
621
+ "additionalProperties": false
622
+ },
538
623
  "ErrorEvent": {
539
624
  "description": "Event with sample error.",
540
625
  "properties": {
@@ -657,18 +742,6 @@
657
742
  "default": null,
658
743
  "title": "Epochs Reducer"
659
744
  },
660
- "trace": {
661
- "anyOf": [
662
- {
663
- "type": "boolean"
664
- },
665
- {
666
- "type": "null"
667
- }
668
- ],
669
- "default": null,
670
- "title": "Trace"
671
- },
672
745
  "approval": {
673
746
  "anyOf": [
674
747
  {
@@ -847,7 +920,6 @@
847
920
  "sample_id",
848
921
  "epochs",
849
922
  "epochs_reducer",
850
- "trace",
851
923
  "approval",
852
924
  "fail_on_error",
853
925
  "message_limit",
@@ -1065,6 +1137,7 @@
1065
1137
  "logprobs": null,
1066
1138
  "top_logprobs": null,
1067
1139
  "parallel_tool_calls": null,
1140
+ "internal_tools": null,
1068
1141
  "max_tool_output": null,
1069
1142
  "cache_prompt": null,
1070
1143
  "reasoning_effort": null
@@ -1444,7 +1517,8 @@
1444
1517
  "time",
1445
1518
  "message",
1446
1519
  "token",
1447
- "operator"
1520
+ "operator",
1521
+ "custom"
1448
1522
  ],
1449
1523
  "title": "Type",
1450
1524
  "type": "string"
@@ -2118,6 +2192,18 @@
2118
2192
  "default": null,
2119
2193
  "title": "Parallel Tool Calls"
2120
2194
  },
2195
+ "internal_tools": {
2196
+ "anyOf": [
2197
+ {
2198
+ "type": "boolean"
2199
+ },
2200
+ {
2201
+ "type": "null"
2202
+ }
2203
+ ],
2204
+ "default": null,
2205
+ "title": "Internal Tools"
2206
+ },
2121
2207
  "max_tool_output": {
2122
2208
  "anyOf": [
2123
2209
  {
@@ -2186,6 +2272,7 @@
2186
2272
  "logprobs",
2187
2273
  "top_logprobs",
2188
2274
  "parallel_tool_calls",
2275
+ "internal_tools",
2189
2276
  "max_tool_output",
2190
2277
  "cache_prompt",
2191
2278
  "reasoning_effort"
@@ -2609,6 +2696,18 @@
2609
2696
  "output": {
2610
2697
  "$ref": "#/$defs/ModelOutput"
2611
2698
  },
2699
+ "error": {
2700
+ "anyOf": [
2701
+ {
2702
+ "type": "string"
2703
+ },
2704
+ {
2705
+ "type": "null"
2706
+ }
2707
+ ],
2708
+ "default": null,
2709
+ "title": "Error"
2710
+ },
2612
2711
  "cache": {
2613
2712
  "anyOf": [
2614
2713
  {
@@ -2647,6 +2746,7 @@
2647
2746
  "tool_choice",
2648
2747
  "config",
2649
2748
  "output",
2749
+ "error",
2650
2750
  "cache",
2651
2751
  "call"
2652
2752
  ],
@@ -2994,7 +3094,8 @@
2994
3094
  "message",
2995
3095
  "time",
2996
3096
  "token",
2997
- "operator"
3097
+ "operator",
3098
+ "custom"
2998
3099
  ],
2999
3100
  "title": "Type",
3000
3101
  "type": "string"
@@ -3721,6 +3822,12 @@
3721
3822
  {
3722
3823
  "$ref": "#/$defs/ContentImage"
3723
3824
  },
3825
+ {
3826
+ "$ref": "#/$defs/ContentAudio"
3827
+ },
3828
+ {
3829
+ "$ref": "#/$defs/ContentVideo"
3830
+ },
3724
3831
  {
3725
3832
  "items": {
3726
3833
  "anyOf": [
@@ -3729,6 +3836,12 @@
3729
3836
  },
3730
3837
  {
3731
3838
  "$ref": "#/$defs/ContentImage"
3839
+ },
3840
+ {
3841
+ "$ref": "#/$defs/ContentAudio"
3842
+ },
3843
+ {
3844
+ "$ref": "#/$defs/ContentVideo"
3732
3845
  }
3733
3846
  ]
3734
3847
  },
@@ -4123,6 +4236,7 @@
4123
4236
  "best_of": null,
4124
4237
  "cache_prompt": null,
4125
4238
  "frequency_penalty": null,
4239
+ "internal_tools": null,
4126
4240
  "logit_bias": null,
4127
4241
  "logprobs": null,
4128
4242
  "max_connections": null,
@@ -30,8 +30,10 @@
30
30
  "bootstrap": "^5.3.3",
31
31
  "bootstrap-icons": "^1.11.3",
32
32
  "clipboard": "^2.0.11",
33
+ "codemirror": "^6.0.1",
33
34
  "fast-json-patch": "^3.1.1",
34
35
  "fflate": "^0.8.2",
36
+ "filtrex": "^3.1.0",
35
37
  "htm": "^3.1.1",
36
38
  "json": "^11.0.0",
37
39
  "json5": "^2.2.3",
@@ -38,7 +38,7 @@ import {
38
38
  } from "./samples/SamplesDescriptor.mjs";
39
39
  import { byEpoch, bySample, sortSamples } from "./samples/tools/SortFilter.mjs";
40
40
  import { resolveAttachments } from "./utils/attachments.mjs";
41
- import { filterFnForType } from "./samples/tools/filters.mjs";
41
+ import { filterSamples } from "./samples/tools/filters.mjs";
42
42
 
43
43
  import {
44
44
  kEvalWorkspaceTabId,
@@ -308,21 +308,19 @@ export function App({
308
308
 
309
309
  useEffect(() => {
310
310
  const samples = selectedLog?.contents?.sampleSummaries || [];
311
- const filtered = samples.filter((sample) => {
311
+ const { result: prefiltered } = filterSamples(
312
+ evalDescriptor,
313
+ samples,
314
+ filter?.value,
315
+ );
316
+ const filtered = prefiltered.filter((sample) => {
312
317
  // Filter by epoch if specified
313
318
  if (epoch && epoch !== "all") {
314
319
  if (epoch !== sample.epoch + "") {
315
320
  return false;
316
321
  }
317
322
  }
318
-
319
- // Apply the filter
320
- const filterFn = filterFnForType(filter);
321
- if (filterFn && filter.value) {
322
- return filterFn(samplesDescriptor, sample, filter.value);
323
- } else {
324
- return true;
325
- }
323
+ return true;
326
324
  });
327
325
 
328
326
  // Sort the samples
@@ -25,7 +25,6 @@
25
25
  /**
26
26
  * @typedef {Object} ScoreFilter
27
27
  * @property {string} [value]
28
- * @property {string} [type]
29
28
  */
30
29
 
31
30
  /**
@@ -7,6 +7,54 @@ import { MessageContent } from "./MessageContent.mjs";
7
7
  import { ExpandablePanel } from "./ExpandablePanel.mjs";
8
8
  import { FontSize, TextStyle } from "../appearance/Fonts.mjs";
9
9
  import { resolveToolInput, ToolCallView } from "./Tools.mjs";
10
+ import { VirtualList } from "./VirtualList.mjs";
11
+
12
+ /**
13
+ * Renders the ChatViewVirtualList component.
14
+ *
15
+ * @param {Object} props - The properties passed to the component.
16
+ * @param {string} props.id - The ID for the chat view.
17
+ * @param {import("../types/log").Messages} props.messages - The array of chat messages.
18
+ * @param {"compact" | "complete"} [props.toolCallStyle] - Whether to show tool calls
19
+ * @param {Object} [props.style] - Inline styles for the chat view.
20
+ * @param {boolean} props.indented - Whether the chatview has indented messages
21
+ * @param {boolean} [props.numbered] - Whether the chatview is numbered
22
+ * @param {import("htm/preact").MutableRef<HTMLElement>} props.scrollRef - The scrollable parent element
23
+ * @returns {import("preact").JSX.Element} The component.
24
+ */
25
+ export const ChatViewVirtualList = ({
26
+ id,
27
+ messages,
28
+ toolCallStyle,
29
+ style,
30
+ indented,
31
+ numbered = true,
32
+ scrollRef,
33
+ }) => {
34
+ const collapsedMessages = resolveMessages(messages);
35
+
36
+ const renderRow = (item, index) => {
37
+ const number =
38
+ collapsedMessages.length > 1 && numbered ? index + 1 : undefined;
39
+ return html`<${ChatMessageRow}
40
+ id=${id}
41
+ number=${number}
42
+ resolvedMessage=${item}
43
+ indented=${indented}
44
+ toolCallStyle=${toolCallStyle}
45
+ />`;
46
+ };
47
+
48
+ const result = html`<${VirtualList}
49
+ data=${collapsedMessages}
50
+ tabIndex="0"
51
+ renderRow=${renderRow}
52
+ scrollRef=${scrollRef}
53
+ style=${{ width: "100%", marginTop: "1em", ...style }}
54
+ />`;
55
+
56
+ return result;
57
+ };
10
58
 
11
59
  /**
12
60
  * Renders the ChatView component.
@@ -28,6 +76,90 @@ export const ChatView = ({
28
76
  indented,
29
77
  numbered = true,
30
78
  }) => {
79
+ const collapsedMessages = resolveMessages(messages);
80
+ const result = html` <div style=${style}>
81
+ ${collapsedMessages.map((msg, index) => {
82
+ const number =
83
+ collapsedMessages.length > 1 && numbered ? index + 1 : undefined;
84
+ return html`<${ChatMessageRow}
85
+ id=${id}
86
+ number=${number}
87
+ resolvedMessage=${msg}
88
+ indented=${indented}
89
+ toolCallStyle=${toolCallStyle}
90
+ />`;
91
+ })}
92
+ </div>`;
93
+ return result;
94
+ };
95
+
96
+ /**
97
+ * Renders the ChatMessage component.
98
+ *
99
+ * @param {Object} props - The properties passed to the component.
100
+ * @param {string} props.id - The ID for the chat view.
101
+ * @param {number} [props.number] - The message number
102
+ * @param {ResolvedMessage} props.resolvedMessage - The array of chat messages.
103
+ * @param {"compact" | "complete"} [props.toolCallStyle] - Whether to show tool calls
104
+ * @param {boolean} props.indented - Whether the chatview has indented messages
105
+ * @returns {import("preact").JSX.Element} The component.
106
+ */
107
+ export const ChatMessageRow = ({
108
+ id,
109
+ number,
110
+ resolvedMessage,
111
+ toolCallStyle,
112
+ indented,
113
+ }) => {
114
+ if (number) {
115
+ return html` <div
116
+ style=${{
117
+ display: "grid",
118
+ gridTemplateColumns: "max-content auto",
119
+ columnGap: "0.4em",
120
+ }}
121
+ >
122
+ <div
123
+ style=${{
124
+ fontSize: FontSize.smaller,
125
+ ...TextStyle.secondary,
126
+ marginTop: "0.1em",
127
+ }}
128
+ >
129
+ ${number}
130
+ </div>
131
+ <${ChatMessage}
132
+ id=${`${id}-chat-messages`}
133
+ message=${resolvedMessage.message}
134
+ toolMessages=${resolvedMessage.toolMessages}
135
+ indented=${indented}
136
+ toolCallStyle=${toolCallStyle}
137
+ />
138
+ </div>`;
139
+ } else {
140
+ return html`<${ChatMessage}
141
+ id=${`${id}-chat-messages`}
142
+ message=${resolvedMessage.message}
143
+ toolMessages=${resolvedMessage.toolMessages}
144
+ indented=${indented}
145
+ toolCallStyle=${toolCallStyle}
146
+ />`;
147
+ }
148
+ };
149
+
150
+ /**
151
+ * @typedef {Object} ResolvedMessage
152
+ * @property {import("../types/log").ChatMessageAssistant | import("../types/log").ChatMessageSystem | import("../types/log").ChatMessageUser} message - The main chat message.
153
+ * @property {import("../types/log").ChatMessageTool[]} [toolMessages] - Optional array of tool-related messages.
154
+ */
155
+
156
+ /**
157
+ * Renders the ChatView component.
158
+ *
159
+ * @param {import("../types/log").Messages} messages - The array of chat messages.
160
+ * @returns {ResolvedMessage[]} The component.
161
+ */
162
+ export const resolveMessages = (messages) => {
31
163
  // Filter tool messages into a sidelist that the chat stream
32
164
  // can use to lookup the tool responses
33
165
 
@@ -88,49 +220,7 @@ export const ChatView = ({
88
220
  if (systemMessage && systemMessage.content.length > 0) {
89
221
  collapsedMessages.unshift({ message: systemMessage });
90
222
  }
91
-
92
- const result = html`
93
- <div style=${style}>
94
- ${collapsedMessages.map((msg, index) => {
95
- if (collapsedMessages.length > 1 && numbered) {
96
- return html` <div
97
- style=${{
98
- display: "grid",
99
- gridTemplateColumns: "max-content auto",
100
- columnGap: "0.4em",
101
- }}
102
- >
103
- <div
104
- style=${{
105
- fontSize: FontSize.smaller,
106
- ...TextStyle.secondary,
107
- marginTop: "0.1em",
108
- }}
109
- >
110
- ${index + 1}
111
- </div>
112
- <${ChatMessage}
113
- id=${`${id}-chat-messages`}
114
- message=${msg.message}
115
- toolMessages=${msg.toolMessages}
116
- indented=${indented}
117
- toolCallStyle=${toolCallStyle}
118
- />
119
- </div>`;
120
- } else {
121
- return html` <${ChatMessage}
122
- id=${`${id}-chat-messages`}
123
- message=${msg.message}
124
- toolMessages=${msg.toolMessages}
125
- indented=${indented}
126
- toolCallStyle=${toolCallStyle}
127
- />`;
128
- }
129
- })}
130
- </div>
131
- `;
132
-
133
- return result;
223
+ return collapsedMessages;
134
224
  };
135
225
 
136
226
  /**
@@ -70,10 +70,6 @@ export const ExpandablePanel = ({
70
70
  contentsStyle.border = "solid var(--bs-light-border-subtle) 1px";
71
71
  }
72
72
 
73
- if (!showToggle) {
74
- contentsStyle.marginBottom = "1em";
75
- }
76
-
77
73
  return html`<div
78
74
  class="expandable-panel"
79
75
  ref=${contentsRef}
@@ -5,25 +5,24 @@ import { FontSize } from "../appearance/Fonts.mjs";
5
5
  import { ProgressBar } from "./ProgressBar.mjs";
6
6
  import { MessageBand } from "./MessageBand.mjs";
7
7
 
8
- export const LargeModal = (props) => {
9
- const {
10
- id,
11
- title,
12
- detail,
13
- detailTools,
14
- footer,
15
- onkeyup,
16
- visible,
17
- onHide,
18
- showProgress,
19
- children,
20
- initialScrollPositionRef,
21
- setInitialScrollPosition,
22
- warning,
23
- warningHidden,
24
- setWarningHidden,
25
- } = props;
26
-
8
+ export const LargeModal = ({
9
+ id,
10
+ title,
11
+ detail,
12
+ detailTools,
13
+ footer,
14
+ onkeyup,
15
+ visible,
16
+ onHide,
17
+ showProgress,
18
+ children,
19
+ initialScrollPositionRef,
20
+ setInitialScrollPosition,
21
+ warning,
22
+ warningHidden,
23
+ setWarningHidden,
24
+ scrollRef,
25
+ }) => {
27
26
  // The footer
28
27
  const modalFooter = footer
29
28
  ? html`<div class="modal-footer">${footer}</div>`
@@ -31,7 +30,7 @@ export const LargeModal = (props) => {
31
30
 
32
31
  // Support restoring the scroll position
33
32
  // but only do this for the first time that the children are set
34
- const scrollRef = useRef(/** @type {HTMLElement|null} */ (null));
33
+ scrollRef = scrollRef || useRef(/** @type {HTMLElement|null} */ (null));
35
34
  useEffect(() => {
36
35
  if (scrollRef.current) {
37
36
  setTimeout(() => {
@@ -8,7 +8,7 @@ export const MessageBand = ({ message, hidden, setHidden, type }) => {
8
8
  const bgColor =
9
9
  type === "info" ? "var(--bs-light)" : "var(--bs-" + type + "-bg-subtle)";
10
10
  const color =
11
- "var(--bs-" + (type === "info" ? "secondary" : type) + "-text-emphasis)";
11
+ type === "info" ? undefined : "var(--bs-" + type + "-text-emphasis)";
12
12
 
13
13
  return html`
14
14
  <div
@@ -32,7 +32,7 @@ export const MessageBand = ({ message, hidden, setHidden, type }) => {
32
32
  fontSize: FontSize["title-secondary"],
33
33
  margin: "0",
34
34
  padding: "0",
35
- color: "var(--bs-" + type + "-text-emphasis)",
35
+ color: color,
36
36
  height: FontSize["title-secondary"],
37
37
  lineHeight: FontSize["title-secondary"],
38
38
  }}
@@ -7,7 +7,7 @@ import { ToolOutput } from "./Tools.mjs";
7
7
  * Supports rendering strings, images, and tools using specific renderers.
8
8
  *
9
9
  * @param {Object} props - The props object.
10
- * @param {string|string[]| (import("../types/log").ContentText | import("../types/log").ContentImage | import("../Types.mjs").ContentTool)[]} props.contents - The content or array of contents to render.
10
+ * @param {string|string[]| (import("../types/log").ContentText | import("../types/log").ContentImage | import("../types/log").ContentAudio | import("../types/log").ContentVideo | import("../Types.mjs").ContentTool)[]} props.contents - The content or array of contents to render.
11
11
  * @returns {import("preact").JSX.Element | import("preact").JSX.Element[]} The component.
12
12
  */
13
13
  export const MessageContent = ({ contents }) => {
@@ -61,9 +61,51 @@ const messageRenderers = {
61
61
  }
62
62
  },
63
63
  },
64
+ audio: {
65
+ render: (content) => {
66
+ return html` <audio controls>
67
+ <source
68
+ src=${content.audio}
69
+ type=${mimeTypeForFormat(content.format)}
70
+ />
71
+ </audio>`;
72
+ },
73
+ },
74
+ video: {
75
+ render: (content) => {
76
+ return html` <video width="500" height="375" controls>
77
+ <source
78
+ src=${content.video}
79
+ type=${mimeTypeForFormat(content.format)}
80
+ />
81
+ </video>`;
82
+ },
83
+ },
64
84
  tool: {
65
85
  render: (content) => {
66
86
  return html`<${ToolOutput} output=${content.content} />`;
67
87
  },
68
88
  },
69
89
  };
90
+
91
+ /**
92
+ * Renders message content based on its type.
93
+ * Supports rendering strings, images, and tools using specific renderers.
94
+ *
95
+ * @param {import("../types/log").Format | import("../types/log").Format1 } format - The format
96
+ * @returns {string} - The mime type.
97
+ */
98
+ const mimeTypeForFormat = (format) => {
99
+ switch (format) {
100
+ case "mov":
101
+ return "video/quicktime";
102
+ case "wav":
103
+ return "audio/wav";
104
+ case "mp3":
105
+ return "audio/mpeg";
106
+ case "mp4":
107
+ return "video/mp4";
108
+ case "mpeg":
109
+ return "video/mpeg";
110
+ }
111
+ };
@@ -38,13 +38,15 @@ export const TabPanel = ({
38
38
  selected,
39
39
  style,
40
40
  scrollable,
41
+ scrollRef,
41
42
  classes,
42
43
  scrollPosition,
43
44
  setScrollPosition,
44
45
  children,
45
46
  }) => {
46
47
  const tabContentsId = computeTabContentsId(id, index);
47
- const tabContentsRef = useRef(/** @type {HTMLElement|null} */ (null));
48
+ const tabContentsRef =
49
+ scrollRef || useRef(/** @type {HTMLElement|null} */ (null));
48
50
  useEffect(() => {
49
51
  setTimeout(() => {
50
52
  if (