inspect-ai 0.3.96__py3-none-any.whl → 0.3.97__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 (133) hide show
  1. inspect_ai/_eval/eval.py +10 -2
  2. inspect_ai/_eval/task/util.py +32 -3
  3. inspect_ai/_util/registry.py +7 -0
  4. inspect_ai/_util/timer.py +13 -0
  5. inspect_ai/_view/www/dist/assets/index.css +275 -195
  6. inspect_ai/_view/www/dist/assets/index.js +8568 -7376
  7. inspect_ai/_view/www/src/app/App.css +1 -0
  8. inspect_ai/_view/www/src/app/App.tsx +27 -10
  9. inspect_ai/_view/www/src/app/appearance/icons.ts +5 -0
  10. inspect_ai/_view/www/src/app/content/RecordTree.module.css +22 -0
  11. inspect_ai/_view/www/src/app/content/RecordTree.tsx +370 -0
  12. inspect_ai/_view/www/src/app/content/RenderedContent.module.css +5 -0
  13. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +32 -19
  14. inspect_ai/_view/www/src/app/content/record_processors/store.ts +101 -0
  15. inspect_ai/_view/www/src/app/content/record_processors/types.ts +3 -0
  16. inspect_ai/_view/www/src/app/content/types.ts +5 -0
  17. inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -0
  18. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +35 -28
  19. inspect_ai/_view/www/src/app/log-view/LogViewLayout.tsx +1 -8
  20. inspect_ai/_view/www/src/app/log-view/navbar/PrimaryBar.tsx +2 -4
  21. inspect_ai/_view/www/src/app/log-view/navbar/ResultsPanel.tsx +13 -3
  22. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.module.css +15 -0
  23. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.tsx +14 -10
  24. inspect_ai/_view/www/src/app/log-view/tabs/InfoTab.tsx +9 -3
  25. inspect_ai/_view/www/src/app/log-view/tabs/JsonTab.tsx +1 -3
  26. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +8 -2
  27. inspect_ai/_view/www/src/app/log-view/types.ts +1 -0
  28. inspect_ai/_view/www/src/app/plan/ModelCard.module.css +7 -0
  29. inspect_ai/_view/www/src/app/plan/ModelCard.tsx +5 -2
  30. inspect_ai/_view/www/src/app/plan/PlanCard.tsx +13 -8
  31. inspect_ai/_view/www/src/app/routing/navigationHooks.ts +63 -8
  32. inspect_ai/_view/www/src/app/routing/url.ts +45 -0
  33. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +2 -1
  34. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.tsx +15 -8
  35. inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +3 -0
  36. inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +16 -5
  37. inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +9 -1
  38. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +68 -31
  39. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.module.css +12 -7
  40. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +17 -5
  41. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.module.css +9 -0
  42. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +48 -18
  43. inspect_ai/_view/www/src/app/samples/chat/ChatView.tsx +0 -1
  44. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.module.css +4 -0
  45. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +41 -1
  46. inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -0
  47. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.module.css +0 -3
  48. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -1
  49. inspect_ai/_view/www/src/app/samples/chat/tools/ToolInput.module.css +1 -1
  50. inspect_ai/_view/www/src/app/samples/chat/tools/ToolOutput.module.css +1 -1
  51. inspect_ai/_view/www/src/app/samples/descriptor/score/NumericScoreDescriptor.tsx +5 -1
  52. inspect_ai/_view/www/src/app/samples/descriptor/score/PassFailScoreDescriptor.tsx +11 -6
  53. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +7 -0
  54. inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +5 -18
  55. inspect_ai/_view/www/src/app/samples/sample-tools/SortFilter.tsx +1 -1
  56. inspect_ai/_view/www/src/app/samples/scores/SampleScoresGrid.tsx +18 -5
  57. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.module.css +0 -6
  58. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.tsx +4 -1
  59. inspect_ai/_view/www/src/app/samples/transcript/ApprovalEventView.tsx +4 -2
  60. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +6 -4
  61. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.module.css +1 -1
  62. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +13 -6
  63. inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +6 -4
  64. inspect_ai/_view/www/src/app/samples/transcript/LoggerEventView.tsx +4 -2
  65. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +11 -8
  66. inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +14 -8
  67. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +13 -8
  68. inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +25 -16
  69. inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +7 -5
  70. inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +11 -28
  71. inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +12 -20
  72. inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +12 -31
  73. inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +25 -29
  74. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +297 -0
  75. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +0 -8
  76. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +43 -25
  77. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +43 -0
  78. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +109 -43
  79. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +19 -8
  80. inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +128 -60
  81. inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +14 -4
  82. inspect_ai/_view/www/src/app/samples/transcript/types.ts +6 -4
  83. inspect_ai/_view/www/src/app/types.ts +12 -1
  84. inspect_ai/_view/www/src/components/Card.css +6 -3
  85. inspect_ai/_view/www/src/components/Card.tsx +15 -2
  86. inspect_ai/_view/www/src/components/CopyButton.tsx +4 -6
  87. inspect_ai/_view/www/src/components/ExpandablePanel.module.css +20 -14
  88. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +17 -22
  89. inspect_ai/_view/www/src/components/LargeModal.tsx +5 -1
  90. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +25 -1
  91. inspect_ai/_view/www/src/components/MarkdownDiv.css +4 -0
  92. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +2 -2
  93. inspect_ai/_view/www/src/components/TabSet.module.css +6 -1
  94. inspect_ai/_view/www/src/components/TabSet.tsx +8 -2
  95. inspect_ai/_view/www/src/state/hooks.ts +83 -13
  96. inspect_ai/_view/www/src/state/logPolling.ts +2 -2
  97. inspect_ai/_view/www/src/state/logSlice.ts +1 -2
  98. inspect_ai/_view/www/src/state/logsSlice.ts +9 -9
  99. inspect_ai/_view/www/src/state/samplePolling.ts +1 -1
  100. inspect_ai/_view/www/src/state/sampleSlice.ts +134 -7
  101. inspect_ai/_view/www/src/state/scoring.ts +1 -1
  102. inspect_ai/_view/www/src/state/scrolling.ts +39 -6
  103. inspect_ai/_view/www/src/state/store.ts +5 -0
  104. inspect_ai/_view/www/src/state/store_filter.ts +47 -44
  105. inspect_ai/_view/www/src/utils/debugging.ts +95 -0
  106. inspect_ai/_view/www/src/utils/format.ts +2 -2
  107. inspect_ai/_view/www/src/utils/json.ts +29 -0
  108. inspect_ai/agent/__init__.py +2 -1
  109. inspect_ai/agent/_agent.py +12 -0
  110. inspect_ai/agent/_react.py +184 -48
  111. inspect_ai/agent/_types.py +14 -1
  112. inspect_ai/analysis/beta/__init__.py +0 -2
  113. inspect_ai/analysis/beta/_dataframe/columns.py +11 -16
  114. inspect_ai/analysis/beta/_dataframe/evals/table.py +65 -40
  115. inspect_ai/analysis/beta/_dataframe/events/table.py +24 -36
  116. inspect_ai/analysis/beta/_dataframe/messages/table.py +24 -15
  117. inspect_ai/analysis/beta/_dataframe/progress.py +35 -5
  118. inspect_ai/analysis/beta/_dataframe/record.py +13 -9
  119. inspect_ai/analysis/beta/_dataframe/samples/columns.py +1 -1
  120. inspect_ai/analysis/beta/_dataframe/samples/table.py +156 -46
  121. inspect_ai/analysis/beta/_dataframe/util.py +14 -12
  122. inspect_ai/model/_call_tools.py +1 -1
  123. inspect_ai/model/_providers/anthropic.py +18 -5
  124. inspect_ai/model/_providers/azureai.py +7 -2
  125. inspect_ai/model/_providers/util/llama31.py +3 -3
  126. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/METADATA +1 -1
  127. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/RECORD +131 -126
  128. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/WHEEL +1 -1
  129. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.module.css +0 -48
  130. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.tsx +0 -276
  131. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/entry_points.txt +0 -0
  132. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/licenses/LICENSE +0 -0
  133. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.97.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@
5
5
  padding: 0.5em 0.5em 0.5em 0.5em;
6
6
  font-size: var(--inspect-font-size-small);
7
7
  font-weight: 600;
8
+ border-bottom: solid 1px var(--bs-light-border-subtle);
8
9
  }
9
10
 
10
11
  .card-header-icon {
@@ -13,9 +14,7 @@
13
14
 
14
15
  .card-body {
15
16
  background-color: var(--bs-body-bg);
16
- border: solid 1px var(--bs-light-border-subtle);
17
- border-radius: var(--bs-border-radius);
18
- margin: 0 8px 8px 8px;
17
+
19
18
  padding: 0.5em;
20
19
  }
21
20
 
@@ -57,3 +56,7 @@
57
56
  padding: 0 0.5em 0.1em 0.5em;
58
57
  font-size: var(--inspect-font-size-smaller);
59
58
  }
59
+
60
+ .card-body.card-no-padding {
61
+ padding: 0;
62
+ }
@@ -15,6 +15,7 @@ interface CardBodyProps {
15
15
  id?: string;
16
16
  children?: ReactNode;
17
17
  className?: string | string[];
18
+ padded?: boolean;
18
19
  }
19
20
 
20
21
  interface CardProps {
@@ -53,9 +54,21 @@ export const CardHeader: FC<CardHeaderProps> = ({
53
54
  );
54
55
  };
55
56
 
56
- export const CardBody: FC<CardBodyProps> = ({ id, children, className }) => {
57
+ export const CardBody: FC<CardBodyProps> = ({
58
+ id,
59
+ children,
60
+ className,
61
+ padded = true,
62
+ }) => {
57
63
  return (
58
- <div className={clsx("card-body", className)} id={id || ""}>
64
+ <div
65
+ className={clsx(
66
+ "card-body",
67
+ className,
68
+ !padded ? "card-no-padding" : undefined,
69
+ )}
70
+ id={id || ""}
71
+ >
59
72
  {children}
60
73
  </div>
61
74
  );
@@ -4,6 +4,7 @@ import { ApplicationIcons } from "../app/appearance/icons";
4
4
  import styles from "./CopyButton.module.css";
5
5
 
6
6
  interface CopyButtonProps {
7
+ icon?: string;
7
8
  value: string;
8
9
  onCopySuccess?: () => void;
9
10
  onCopyError?: (error: Error) => void;
@@ -12,6 +13,7 @@ interface CopyButtonProps {
12
13
  }
13
14
 
14
15
  export const CopyButton = ({
16
+ icon = ApplicationIcons.copy,
15
17
  value,
16
18
  onCopySuccess,
17
19
  onCopyError,
@@ -40,17 +42,13 @@ export const CopyButton = ({
40
42
  return (
41
43
  <button
42
44
  type="button"
43
- className={clsx(styles.copyButton, className)}
45
+ className={clsx("copy-button", styles.copyButton, className)}
44
46
  onClick={handleClick}
45
47
  aria-label={ariaLabel}
46
48
  disabled={isCopied}
47
49
  >
48
50
  <i
49
- className={
50
- isCopied
51
- ? `${ApplicationIcons.confirm} primary`
52
- : ApplicationIcons.copy
53
- }
51
+ className={isCopied ? `${ApplicationIcons.confirm} primary` : icon}
54
52
  aria-hidden="true"
55
53
  />
56
54
  </button>
@@ -1,5 +1,10 @@
1
+ .expandablePanel {
2
+ position: relative;
3
+ }
4
+
1
5
  .expandableBordered {
2
6
  border: solid var(--bs-light-border-subtle) 1px;
7
+ padding: 0.5em;
3
8
  }
4
9
 
5
10
  .expandableTogglable {
@@ -17,27 +22,28 @@
17
22
  .moreToggle {
18
23
  display: flex;
19
24
  margin-top: 0;
20
- position: relative;
21
- height: 18px;
25
+ position: absolute;
26
+ bottom: 0.25em;
27
+ right: 0.25em;
28
+ height: 20px;
29
+ background-color: var(--bs-body-bg);
30
+ border-radius: 5px;
31
+ border: solid var(--bs-light-border-subtle) 1px;
32
+ color: var(--bs-link-color);
22
33
  }
23
34
 
24
35
  .moreToggle.bordered {
25
36
  border-top: solid var(--bs-light-border-subtle) 1px;
26
37
  }
27
38
 
28
- .moreToggleContainer {
29
- position: absolute;
30
- top: -1px;
31
- right: 0;
32
- display: inline-block;
33
- border: solid var(--bs-light-border-subtle) 1px;
34
- border-top: none;
35
- margin-left: auto;
36
- margin-right: 0;
37
- }
38
-
39
39
  .moreToggleButton {
40
40
  font-size: var(--inspect-font-size-smaller);
41
41
  border: none;
42
- padding: 0.1rem 0.5rem;
42
+ padding: 0rem 0.5rem;
43
+ }
44
+
45
+ .separator {
46
+ height: 1px;
47
+ background-color: var(--bs-light-border-subtle);
48
+ margin-top: -1px;
43
49
  }
@@ -8,7 +8,6 @@ import {
8
8
  useRef,
9
9
  useState,
10
10
  } from "react";
11
- import { ApplicationIcons } from "../app/appearance/icons";
12
11
  import { useCollapsedState } from "../state/hooks";
13
12
  import { useResizeObserver } from "../utils/dom";
14
13
  import styles from "./ExpandablePanel.module.css";
@@ -64,18 +63,21 @@ export const ExpandablePanel: FC<ExpandablePanelProps> = memo(
64
63
  styles.expandablePanel,
65
64
  collapsed ? styles.expandableCollapsed : undefined,
66
65
  border ? styles.expandableBordered : undefined,
66
+ showToggle ? styles.padBottom : undefined,
67
67
  )}
68
68
  >
69
69
  {children}
70
+ {showToggle && (
71
+ <>
72
+ <MoreToggle
73
+ collapsed={collapsed}
74
+ setCollapsed={setCollapsed}
75
+ border={!border}
76
+ />
77
+ </>
78
+ )}
70
79
  </div>
71
-
72
- {showToggle && (
73
- <MoreToggle
74
- collapsed={collapsed}
75
- setCollapsed={setCollapsed}
76
- border={!border}
77
- />
78
- )}
80
+ {showToggle && <div className={clsx(styles.separator)}></div>}
79
81
  </div>
80
82
  );
81
83
  },
@@ -95,10 +97,6 @@ const MoreToggle: FC<MoreToggleProps> = ({
95
97
  style,
96
98
  }) => {
97
99
  const text = collapsed ? "more" : "less";
98
- const icon = collapsed
99
- ? ApplicationIcons["expand-down"]
100
- : ApplicationIcons.collapse.up;
101
-
102
100
  const handleClick = useCallback(() => {
103
101
  setCollapsed(!collapsed);
104
102
  }, [setCollapsed, collapsed]);
@@ -108,15 +106,12 @@ const MoreToggle: FC<MoreToggleProps> = ({
108
106
  className={clsx(styles.moreToggle, border ? styles.bordered : undefined)}
109
107
  style={style}
110
108
  >
111
- <div className={clsx(styles.moreToggleContainer)}>
112
- <button
113
- className={clsx("btn", styles.moreToggleButton, "text-size-smallest")}
114
- onClick={handleClick}
115
- >
116
- <i className={clsx(icon, styles.icon)} />
117
- {text}
118
- </button>
119
- </div>
109
+ <button
110
+ className={clsx("btn", styles.moreToggleButton, "text-size-smallest")}
111
+ onClick={handleClick}
112
+ >
113
+ {text}...
114
+ </button>
120
115
  </div>
121
116
  );
122
117
  };
@@ -28,6 +28,9 @@ interface LargeModalProps {
28
28
  onHide: () => void;
29
29
  scrollRef: RefObject<HTMLDivElement | null>;
30
30
  children: ReactNode;
31
+ classNames?: {
32
+ body?: string | string[];
33
+ };
31
34
  }
32
35
 
33
36
  export const LargeModal: FC<LargeModalProps> = ({
@@ -42,6 +45,7 @@ export const LargeModal: FC<LargeModalProps> = ({
42
45
  onHide,
43
46
  showProgress,
44
47
  scrollRef,
48
+ classNames,
45
49
  }) => {
46
50
  // The footer
47
51
  const modalFooter = footer ? (
@@ -127,7 +131,7 @@ export const LargeModal: FC<LargeModalProps> = ({
127
131
  </button>
128
132
  </div>
129
133
  <ProgressBar animating={showProgress} />
130
- <div className={"modal-body"} ref={scrollRef}>
134
+ <div className={clsx("modal-body", classNames?.body)} ref={scrollRef}>
131
135
  {children}
132
136
  </div>
133
137
  {modalFooter}
@@ -7,7 +7,7 @@ import {
7
7
  useRef,
8
8
  useState,
9
9
  } from "react";
10
- import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
10
+ import { Components, Virtuoso, VirtuosoHandle } from "react-virtuoso";
11
11
  import { usePrevious, useProperty } from "../state/hooks";
12
12
  import { useRafThrottle, useVirtuosoState } from "../state/scrolling";
13
13
  import { PulsingDots } from "./PulsingDots";
@@ -32,6 +32,11 @@ interface LiveVirtualListProps<T> {
32
32
  // The progress message to show (if any)
33
33
  // no message show if progress isn't provided
34
34
  showProgress?: boolean;
35
+
36
+ // The initial index to scroll to when loading
37
+ initialTopMostItemIndex?: number;
38
+
39
+ components?: Components<T>;
35
40
  }
36
41
 
37
42
  /**
@@ -45,6 +50,8 @@ export const LiveVirtualList = <T,>({
45
50
  scrollRef,
46
51
  live,
47
52
  showProgress,
53
+ initialTopMostItemIndex,
54
+ components,
48
55
  }: LiveVirtualListProps<T>) => {
49
56
  // The list handle and list state management
50
57
  const listHandle = useRef<VirtuosoHandle>(null);
@@ -155,6 +162,22 @@ export const LiveVirtualList = <T,>({
155
162
  }
156
163
  }, [scrollRef, handleScroll]);
157
164
 
165
+ // Scroll to index when component mounts or targetIndex changes
166
+ useEffect(() => {
167
+ if (initialTopMostItemIndex !== undefined && listHandle.current) {
168
+ // If there is an initial index, scroll to it after a short delay
169
+ const timer = setTimeout(() => {
170
+ listHandle.current?.scrollToIndex({
171
+ index: initialTopMostItemIndex,
172
+ align: "start",
173
+ behavior: "auto",
174
+ });
175
+ }, 50);
176
+
177
+ return () => clearTimeout(timer);
178
+ }
179
+ }, [initialTopMostItemIndex]);
180
+
158
181
  return (
159
182
  <Virtuoso
160
183
  ref={listHandle}
@@ -171,6 +194,7 @@ export const LiveVirtualList = <T,>({
171
194
  totalListHeightChanged={heightChanged}
172
195
  components={{
173
196
  Footer,
197
+ ...components,
174
198
  }}
175
199
  />
176
200
  );
@@ -1,3 +1,7 @@
1
1
  .markdown-ordered-list-item {
2
2
  margin-bottom: 0.2em;
3
3
  }
4
+
5
+ .markdown-content p:last-child {
6
+ margin-bottom: 0;
7
+ }
@@ -59,13 +59,13 @@ export const MarkdownDiv = forwardRef<HTMLDivElement, MarkdownDivProps>(
59
59
  ref={ref}
60
60
  dangerouslySetInnerHTML={markup}
61
61
  style={style}
62
- className={clsx(className, "markdown-content")}
62
+ className={clsx(className, "markdown-content", "text-size-base")}
63
63
  />
64
64
  );
65
65
  },
66
66
  );
67
67
 
68
- const kLetterListPattern = /^([a-zA-Z][).]\s.*?)$/gm;
68
+ const kLetterListPattern = /^([a-zA-Z0-9][).]\s.*?)$/gm;
69
69
  const kCommonmarkReferenceLinkPattern = /\[([^\]]*)\]: (?!http)(.*)/g;
70
70
 
71
71
  const protectBackslashesInLatex = (content: string): string => {
@@ -1,5 +1,5 @@
1
1
  .tabs {
2
- align-items: space-between;
2
+ align-items: center;
3
3
  }
4
4
 
5
5
  .tabContents {
@@ -38,3 +38,8 @@
38
38
  flex-wrap: wrap;
39
39
  row-gap: 0.3rem;
40
40
  }
41
+
42
+ .tabStyle {
43
+ padding-left: 0.7em;
44
+ padding-right: 0.7em;
45
+ }
@@ -56,7 +56,13 @@ export const TabSet: FC<TabSetProps> = ({
56
56
  <Fragment>
57
57
  <ul
58
58
  id={id}
59
- className={clsx("nav", `nav-${type}`, className, moduleStyles.tabs)}
59
+ className={clsx(
60
+ "nav",
61
+ `nav-${type}`,
62
+ type === "tabs" ? moduleStyles.tabStyle : undefined,
63
+ className,
64
+ moduleStyles.tabs,
65
+ )}
60
66
  role="tablist"
61
67
  aria-orientation="horizontal"
62
68
  >
@@ -157,7 +163,7 @@ export const TabPanel: FC<TabPanelProps> = ({
157
163
  )}
158
164
  style={style}
159
165
  >
160
- {children}
166
+ {selected ? children : null}
161
167
  </div>
162
168
  );
163
169
  };
@@ -226,25 +226,38 @@ export const useSelectedSampleSummary = (): SampleSummary | undefined => {
226
226
  export const useSampleData = () => {
227
227
  const sampleStatus = useStore((state) => state.sample.sampleStatus);
228
228
  const sampleError = useStore((state) => state.sample.sampleError);
229
- const selectedSample = useStore((state) => state.sample.selectedSample);
229
+ const getSelectedSample = useStore(
230
+ (state) => state.sampleActions.getSelectedSample,
231
+ );
232
+ const selectedSampleIdentifier = useStore(
233
+ (state) => state.sample.sample_identifier,
234
+ );
235
+ const sampleNeedsReload = useStore((state) => state.sample.sampleNeedsReload);
230
236
  const runningEvents = useStore(
231
237
  (state) => state.sample.runningEvents,
232
238
  ) as Events;
233
239
  return useMemo(() => {
234
240
  return {
241
+ selectedSampleIdentifier,
235
242
  status: sampleStatus,
243
+ sampleNeedsReload,
236
244
  error: sampleError,
237
- sample: selectedSample,
245
+ getSelectedSample,
238
246
  running: runningEvents,
239
247
  };
240
- }, [sampleStatus, sampleError, selectedSample, runningEvents]);
248
+ }, [
249
+ sampleStatus,
250
+ sampleError,
251
+ getSelectedSample,
252
+ selectedSampleIdentifier,
253
+ sampleNeedsReload,
254
+ runningEvents,
255
+ ]);
241
256
  };
242
257
 
243
258
  export const useLogSelection = () => {
244
259
  const selectedSampleSummary = useSelectedSampleSummary();
245
- const selectedLogFile = useStore((state) =>
246
- state.logsActions.getSelectedLogFile(),
247
- );
260
+ const selectedLogFile = useStore((state) => state.logs.selectedLogFile);
248
261
  const loadedLog = useStore((state) => state.log.loadedLog);
249
262
 
250
263
  return useMemo(() => {
@@ -256,18 +269,68 @@ export const useLogSelection = () => {
256
269
  }, [selectedLogFile, selectedSampleSummary]);
257
270
  };
258
271
 
272
+ export const useCollapseSampleEvent = (
273
+ id: string,
274
+ ): [boolean, (collapsed: boolean) => void] => {
275
+ const collapsed = useStore((state) => state.sample.collapsedEvents);
276
+ const collapseEvent = useStore((state) => state.sampleActions.collapseEvent);
277
+
278
+ return useMemo(() => {
279
+ const isCollapsed = collapsed !== null && collapsed[id] === true;
280
+ const set = (value: boolean) => {
281
+ log.debug("Set collapsed", id, value);
282
+ collapseEvent(id, value);
283
+ };
284
+ return [isCollapsed, set];
285
+ }, [collapsed, collapseEvent, id]);
286
+ };
287
+
288
+ export const useCollapsibleIds = (
289
+ key: string,
290
+ ): [
291
+ Record<string, boolean>,
292
+ (id: string, value: boolean) => void,
293
+ () => void,
294
+ ] => {
295
+ const collapsedIds = useStore(
296
+ (state) => state.sample.collapsedIdBuckets[key],
297
+ );
298
+
299
+ const setCollapsed = useStore((state) => state.sampleActions.collapseId);
300
+ const collapseId = useCallback(
301
+ (id: string, value: boolean) => {
302
+ setCollapsed(key, id, value);
303
+ },
304
+ [setCollapsed],
305
+ );
306
+
307
+ const clearCollapsedIds = useStore(
308
+ (state) => state.sampleActions.clearCollapsedIds,
309
+ );
310
+ const clearIds = useCallback(() => {
311
+ clearCollapsedIds(key);
312
+ }, [clearCollapsedIds, key]);
313
+
314
+ return useMemo(() => {
315
+ return [collapsedIds, collapseId, clearIds];
316
+ }, [collapsedIds, collapseId, clearIds]);
317
+ };
318
+
259
319
  export const useCollapsedState = (
260
320
  id: string,
261
321
  defaultValue?: boolean,
322
+ scope?: string,
262
323
  ): [boolean, (value: boolean) => void] => {
324
+ const stateId = scope ? `${scope}-${id}` : id;
325
+
263
326
  const collapsed = useStore((state) =>
264
- state.appActions.getCollapsed(id, defaultValue),
327
+ state.appActions.getCollapsed(stateId, defaultValue),
265
328
  );
266
329
  const setCollapsed = useStore((state) => state.appActions.setCollapsed);
267
330
  return useMemo(() => {
268
331
  const set = (value: boolean) => {
269
- log.debug("Set collapsed", id, value);
270
- setCollapsed(id, value);
332
+ log.debug("Set collapsed", id, scope, value);
333
+ setCollapsed(stateId, value);
271
334
  };
272
335
  return [collapsed, set];
273
336
  }, [collapsed, setCollapsed]);
@@ -289,9 +352,7 @@ export const useMessageVisibility = (
289
352
  const isFirstRender = useRef(true);
290
353
 
291
354
  // Reset state if the eval changes, but not during initialization
292
- const selectedLogFile = useStore((state) =>
293
- state.logsActions.getSelectedLogFile(),
294
- );
355
+ const selectedLogFile = useStore((state) => state.logs.selectedLogFile);
295
356
  useEffect(() => {
296
357
  // Skip the first effect run
297
358
  if (isFirstRender.current) {
@@ -426,13 +487,22 @@ export const useSetSelectedLogIndex = () => {
426
487
  const clearSelectedLogSummary = useStore(
427
488
  (state) => state.logActions.clearSelectedLogSummary,
428
489
  );
490
+ const clearCollapsedEvents = useStore(
491
+ (state) => state.sampleActions.clearCollapsedEvents,
492
+ );
429
493
 
430
494
  return useCallback(
431
495
  (index: number) => {
496
+ clearCollapsedEvents();
432
497
  clearSelectedSample();
433
498
  clearSelectedLogSummary();
434
499
  setSelectedLogIndex(index);
435
500
  },
436
- [setSelectedLogIndex, clearSelectedLogSummary, clearSelectedSample],
501
+ [
502
+ setSelectedLogIndex,
503
+ clearSelectedLogSummary,
504
+ clearSelectedSample,
505
+ clearCollapsedEvents,
506
+ ],
437
507
  );
438
508
  };
@@ -26,7 +26,7 @@ export function createLogPolling(
26
26
 
27
27
  const state = get();
28
28
  const api = state.api;
29
- const selectedLogFile = state.logsActions.getSelectedLogFile();
29
+ const selectedLogFile = state.logs.selectedLogFile;
30
30
 
31
31
  if (!api || !selectedLogFile) {
32
32
  return false;
@@ -195,6 +195,6 @@ export function createLogPolling(
195
195
  cleanup,
196
196
  // Expose the refresh function so components can use it directly
197
197
  refreshLog: (clearPending = false) =>
198
- refreshLog(get().logsActions.getSelectedLogFile() || "", clearPending),
198
+ refreshLog(get().logs.selectedLogFile || "", clearPending),
199
199
  };
200
200
  }
@@ -169,7 +169,6 @@ export const createLogSlice = (
169
169
  try {
170
170
  const logContents = await api.get_log_summary(logFileName);
171
171
  state.logActions.setSelectedLogSummary(logContents);
172
- state.logActions.setEpoch;
173
172
 
174
173
  // Push the updated header information up
175
174
  const header = {
@@ -207,7 +206,7 @@ export const createLogSlice = (
207
206
  refreshLog: async () => {
208
207
  const state = get();
209
208
  const api = state.api;
210
- const selectedLogFile = state.logsActions.getSelectedLogFile();
209
+ const selectedLogFile = state.logs.selectedLogFile;
211
210
 
212
211
  if (!api || !selectedLogFile) {
213
212
  return;
@@ -26,9 +26,6 @@ export interface LogsSlice {
26
26
  refreshLogs: () => Promise<void>;
27
27
  selectLogFile: (logUrl: string) => Promise<void>;
28
28
  loadLogs: () => Promise<LogFiles>;
29
-
30
- // Computed values
31
- getSelectedLogFile: () => string | undefined;
32
29
  };
33
30
  }
34
31
 
@@ -37,6 +34,7 @@ const initialState: LogsState = {
37
34
  logHeaders: {},
38
35
  headersLoading: false,
39
36
  selectedLogIndex: -1,
37
+ selectedLogFile: undefined as string | undefined,
40
38
  };
41
39
 
42
40
  export const createLogsSlice = (
@@ -55,6 +53,10 @@ export const createLogsSlice = (
55
53
  setLogs: (logs: LogFiles) => {
56
54
  set((state) => {
57
55
  state.logs.logs = logs;
56
+ state.logs.selectedLogFile =
57
+ state.logs.selectedLogIndex > -1
58
+ ? logs.files[state.logs.selectedLogIndex]?.name
59
+ : undefined;
58
60
  });
59
61
 
60
62
  // If we have files in the logs, load the headers
@@ -79,6 +81,8 @@ export const createLogsSlice = (
79
81
  setSelectedLogIndex: (selectedLogIndex: number) => {
80
82
  set((state) => {
81
83
  state.logs.selectedLogIndex = selectedLogIndex;
84
+ const file = state.logs.logs.files[selectedLogIndex];
85
+ state.logs.selectedLogFile = file ? file.name : undefined;
82
86
  });
83
87
  },
84
88
  updateLogHeaders: (headers: Record<string, EvalLogHeader>) =>
@@ -94,6 +98,8 @@ export const createLogsSlice = (
94
98
 
95
99
  if (index > -1) {
96
100
  state.logsActions.setSelectedLogIndex(index);
101
+ state.logs.selectedLogFile =
102
+ state.logs.logs.files[index]?.name ?? undefined;
97
103
  }
98
104
  },
99
105
 
@@ -161,12 +167,6 @@ export const createLogsSlice = (
161
167
  );
162
168
  }
163
169
  },
164
-
165
- getSelectedLogFile: () => {
166
- const state = get();
167
- const file = state.logs.logs.files[state.logs.selectedLogIndex];
168
- return file !== undefined ? file.name : undefined;
169
- },
170
170
  },
171
171
  } as const;
172
172
 
@@ -140,7 +140,7 @@ export function createSamplePolling(
140
140
 
141
141
  // Update the store with the completed sample
142
142
  set((state) => {
143
- state.sample.selectedSample = migratedSample;
143
+ state.sampleActions.setSelectedSample(migratedSample);
144
144
  state.sampleActions.setSampleStatus("ok");
145
145
  state.sample.runningEvents = [];
146
146
  });