inspect-ai 0.3.96__py3-none-any.whl → 0.3.98__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 (136) hide show
  1. inspect_ai/_eval/eval.py +10 -2
  2. inspect_ai/_eval/run.py +6 -1
  3. inspect_ai/_eval/task/util.py +32 -3
  4. inspect_ai/_util/registry.py +7 -0
  5. inspect_ai/_util/timer.py +13 -0
  6. inspect_ai/_view/www/dist/assets/index.css +275 -195
  7. inspect_ai/_view/www/dist/assets/index.js +8568 -7376
  8. inspect_ai/_view/www/src/app/App.css +1 -0
  9. inspect_ai/_view/www/src/app/App.tsx +27 -10
  10. inspect_ai/_view/www/src/app/appearance/icons.ts +5 -0
  11. inspect_ai/_view/www/src/app/content/RecordTree.module.css +22 -0
  12. inspect_ai/_view/www/src/app/content/RecordTree.tsx +370 -0
  13. inspect_ai/_view/www/src/app/content/RenderedContent.module.css +5 -0
  14. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +32 -19
  15. inspect_ai/_view/www/src/app/content/record_processors/store.ts +101 -0
  16. inspect_ai/_view/www/src/app/content/record_processors/types.ts +3 -0
  17. inspect_ai/_view/www/src/app/content/types.ts +5 -0
  18. inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -0
  19. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +35 -28
  20. inspect_ai/_view/www/src/app/log-view/LogViewLayout.tsx +1 -8
  21. inspect_ai/_view/www/src/app/log-view/navbar/PrimaryBar.tsx +2 -4
  22. inspect_ai/_view/www/src/app/log-view/navbar/ResultsPanel.tsx +13 -3
  23. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.module.css +15 -0
  24. inspect_ai/_view/www/src/app/log-view/navbar/ScoreGrid.tsx +14 -10
  25. inspect_ai/_view/www/src/app/log-view/tabs/InfoTab.tsx +9 -3
  26. inspect_ai/_view/www/src/app/log-view/tabs/JsonTab.tsx +1 -3
  27. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +8 -2
  28. inspect_ai/_view/www/src/app/log-view/types.ts +1 -0
  29. inspect_ai/_view/www/src/app/plan/ModelCard.module.css +7 -0
  30. inspect_ai/_view/www/src/app/plan/ModelCard.tsx +5 -2
  31. inspect_ai/_view/www/src/app/plan/PlanCard.tsx +13 -8
  32. inspect_ai/_view/www/src/app/routing/navigationHooks.ts +63 -8
  33. inspect_ai/_view/www/src/app/routing/url.ts +45 -0
  34. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +2 -1
  35. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.tsx +15 -8
  36. inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +3 -0
  37. inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +16 -5
  38. inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +9 -1
  39. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +68 -31
  40. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.module.css +12 -7
  41. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +17 -5
  42. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.module.css +9 -0
  43. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +48 -18
  44. inspect_ai/_view/www/src/app/samples/chat/ChatView.tsx +0 -1
  45. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.module.css +4 -0
  46. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +41 -1
  47. inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -0
  48. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.module.css +0 -3
  49. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -1
  50. inspect_ai/_view/www/src/app/samples/chat/tools/ToolInput.module.css +1 -1
  51. inspect_ai/_view/www/src/app/samples/chat/tools/ToolOutput.module.css +1 -1
  52. inspect_ai/_view/www/src/app/samples/descriptor/score/NumericScoreDescriptor.tsx +5 -1
  53. inspect_ai/_view/www/src/app/samples/descriptor/score/PassFailScoreDescriptor.tsx +11 -6
  54. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +7 -0
  55. inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +5 -18
  56. inspect_ai/_view/www/src/app/samples/sample-tools/SortFilter.tsx +1 -1
  57. inspect_ai/_view/www/src/app/samples/scores/SampleScoresGrid.tsx +18 -5
  58. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.module.css +0 -6
  59. inspect_ai/_view/www/src/app/samples/scores/SampleScoresView.tsx +4 -1
  60. inspect_ai/_view/www/src/app/samples/transcript/ApprovalEventView.tsx +4 -2
  61. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +6 -4
  62. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.module.css +1 -1
  63. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +13 -6
  64. inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +6 -4
  65. inspect_ai/_view/www/src/app/samples/transcript/LoggerEventView.tsx +4 -2
  66. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +11 -8
  67. inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +14 -8
  68. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +13 -8
  69. inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +25 -16
  70. inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +7 -5
  71. inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +11 -28
  72. inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +12 -20
  73. inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +12 -31
  74. inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +25 -29
  75. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +297 -0
  76. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +0 -8
  77. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +43 -25
  78. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +43 -0
  79. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +109 -43
  80. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +19 -8
  81. inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +128 -60
  82. inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +14 -4
  83. inspect_ai/_view/www/src/app/samples/transcript/types.ts +6 -4
  84. inspect_ai/_view/www/src/app/types.ts +12 -1
  85. inspect_ai/_view/www/src/components/Card.css +6 -3
  86. inspect_ai/_view/www/src/components/Card.tsx +15 -2
  87. inspect_ai/_view/www/src/components/CopyButton.tsx +4 -6
  88. inspect_ai/_view/www/src/components/ExpandablePanel.module.css +20 -14
  89. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +17 -22
  90. inspect_ai/_view/www/src/components/LargeModal.tsx +5 -1
  91. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +25 -1
  92. inspect_ai/_view/www/src/components/MarkdownDiv.css +4 -0
  93. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +2 -2
  94. inspect_ai/_view/www/src/components/TabSet.module.css +6 -1
  95. inspect_ai/_view/www/src/components/TabSet.tsx +8 -2
  96. inspect_ai/_view/www/src/state/hooks.ts +83 -13
  97. inspect_ai/_view/www/src/state/logPolling.ts +2 -2
  98. inspect_ai/_view/www/src/state/logSlice.ts +1 -2
  99. inspect_ai/_view/www/src/state/logsSlice.ts +9 -9
  100. inspect_ai/_view/www/src/state/samplePolling.ts +1 -1
  101. inspect_ai/_view/www/src/state/sampleSlice.ts +134 -7
  102. inspect_ai/_view/www/src/state/scoring.ts +1 -1
  103. inspect_ai/_view/www/src/state/scrolling.ts +39 -6
  104. inspect_ai/_view/www/src/state/store.ts +5 -0
  105. inspect_ai/_view/www/src/state/store_filter.ts +47 -44
  106. inspect_ai/_view/www/src/utils/debugging.ts +95 -0
  107. inspect_ai/_view/www/src/utils/format.ts +2 -2
  108. inspect_ai/_view/www/src/utils/json.ts +29 -0
  109. inspect_ai/agent/__init__.py +2 -1
  110. inspect_ai/agent/_agent.py +12 -0
  111. inspect_ai/agent/_react.py +184 -48
  112. inspect_ai/agent/_types.py +14 -1
  113. inspect_ai/analysis/beta/__init__.py +0 -2
  114. inspect_ai/analysis/beta/_dataframe/columns.py +11 -16
  115. inspect_ai/analysis/beta/_dataframe/evals/table.py +65 -40
  116. inspect_ai/analysis/beta/_dataframe/events/table.py +24 -36
  117. inspect_ai/analysis/beta/_dataframe/messages/table.py +24 -15
  118. inspect_ai/analysis/beta/_dataframe/progress.py +35 -5
  119. inspect_ai/analysis/beta/_dataframe/record.py +13 -9
  120. inspect_ai/analysis/beta/_dataframe/samples/columns.py +1 -1
  121. inspect_ai/analysis/beta/_dataframe/samples/table.py +156 -46
  122. inspect_ai/analysis/beta/_dataframe/util.py +14 -12
  123. inspect_ai/dataset/_dataset.py +0 -1
  124. inspect_ai/model/_call_tools.py +1 -1
  125. inspect_ai/model/_providers/anthropic.py +18 -5
  126. inspect_ai/model/_providers/azureai.py +7 -2
  127. inspect_ai/model/_providers/google.py +6 -0
  128. inspect_ai/model/_providers/util/llama31.py +3 -3
  129. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/METADATA +2 -2
  130. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/RECORD +134 -129
  131. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/WHEEL +1 -1
  132. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.module.css +0 -48
  133. inspect_ai/_view/www/src/app/samples/transcript/TranscriptView.tsx +0 -276
  134. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/entry_points.txt +0 -0
  135. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/licenses/LICENSE +0 -0
  136. {inspect_ai-0.3.96.dist-info → inspect_ai-0.3.98.dist-info}/top_level.txt +0 -0
@@ -5,23 +5,33 @@ import {
5
5
  ReactElement,
6
6
  ReactNode,
7
7
  useCallback,
8
+ useState,
8
9
  } from "react";
9
10
  import { ApplicationIcons } from "../../../appearance/icons";
10
11
  import { EventNavs } from "./EventNavs";
11
12
 
12
- import { useProperty } from "../../../../state/hooks";
13
+ import { useParams } from "react-router-dom";
14
+ import { CopyButton } from "../../../../components/CopyButton";
15
+ import { useCollapseSampleEvent, useProperty } from "../../../../state/hooks";
16
+ import {
17
+ sampleEventUrl,
18
+ supportsLinking,
19
+ toFullUrl,
20
+ } from "../../../routing/url";
13
21
  import styles from "./EventPanel.module.css";
14
22
 
15
23
  interface EventPanelProps {
16
24
  id: string;
25
+ depth: number;
17
26
  className?: string | string[];
18
27
  title?: string;
19
28
  subTitle?: string;
20
29
  text?: string;
21
30
  icon?: string;
22
- collapse?: boolean;
23
31
  children?: ReactNode | ReactNode[];
24
- running?: boolean;
32
+ childIds?: string[];
33
+ collapsibleContent?: boolean;
34
+ collapseControl?: "top" | "bottom";
25
35
  }
26
36
 
27
37
  interface ChildProps {
@@ -33,19 +43,33 @@ interface ChildProps {
33
43
  */
34
44
  export const EventPanel: FC<EventPanelProps> = ({
35
45
  id,
46
+ depth,
36
47
  className,
37
48
  title,
38
49
  subTitle,
39
50
  text,
40
51
  icon,
41
- collapse,
42
52
  children,
53
+ childIds,
54
+ collapsibleContent,
55
+ collapseControl = "top",
43
56
  }) => {
44
- const [isCollapsed, setCollapsed] = useProperty(id, "collapsed", {
45
- defaultValue: !!collapse,
46
- });
57
+ const [collapsed, setCollapsed] = useCollapseSampleEvent(id);
58
+ const isCollapsible = (childIds || []).length > 0 || collapsibleContent;
59
+ const useBottomDongle = isCollapsible && collapseControl === "bottom";
47
60
 
48
- const hasCollapse = collapse !== undefined;
61
+ // Get all URL parameters at component level
62
+ const { logPath, sampleId, epoch } = useParams<{
63
+ logPath?: string;
64
+ tabId?: string;
65
+ sampleId?: string;
66
+ epoch?: string;
67
+ }>();
68
+
69
+ const url =
70
+ logPath && supportsLinking()
71
+ ? toFullUrl(sampleEventUrl(id, logPath, sampleId, epoch))
72
+ : undefined;
49
73
 
50
74
  const pillId = (index: number) => {
51
75
  return `${id}-nav-pill-${index}`;
@@ -66,7 +90,7 @@ export const EventPanel: FC<EventPanelProps> = ({
66
90
  const gridColumns = [];
67
91
 
68
92
  // chevron
69
- if (hasCollapse) {
93
+ if (isCollapsible && !useBottomDongle) {
70
94
  gridColumns.push("minmax(0, max-content)");
71
95
  }
72
96
 
@@ -77,31 +101,39 @@ export const EventPanel: FC<EventPanelProps> = ({
77
101
 
78
102
  // title
79
103
  gridColumns.push("minmax(0, max-content)");
104
+ // id
105
+ if (url) {
106
+ gridColumns.push("minmax(0, max-content)");
107
+ }
80
108
  gridColumns.push("auto");
81
109
  gridColumns.push("minmax(0, max-content)");
82
110
  gridColumns.push("minmax(0, max-content)");
83
111
 
84
112
  const toggleCollapse = useCallback(() => {
85
- setCollapsed(!isCollapsed);
86
- }, [setCollapsed, isCollapsed]);
113
+ setCollapsed(!collapsed);
114
+ }, [setCollapsed, collapsed, childIds]);
115
+
116
+ const [mouseOver, setMouseOver] = useState(false);
87
117
 
88
118
  const titleEl =
89
119
  title || icon || filteredArrChildren.length > 1 ? (
90
120
  <div
91
121
  title={subTitle}
92
- className={clsx("text-size-small")}
122
+ className={clsx("text-size-small", mouseOver ? styles.hover : "")}
93
123
  style={{
94
124
  display: "grid",
95
125
  gridTemplateColumns: gridColumns.join(" "),
96
126
  columnGap: "0.3em",
97
- cursor: hasCollapse ? "pointer" : undefined,
127
+ cursor: isCollapsible && !useBottomDongle ? "pointer" : undefined,
98
128
  }}
129
+ onMouseEnter={() => setMouseOver(true)}
130
+ onMouseLeave={() => setMouseOver(false)}
99
131
  >
100
- {hasCollapse ? (
132
+ {isCollapsible && !useBottomDongle ? (
101
133
  <i
102
134
  onClick={toggleCollapse}
103
135
  className={
104
- isCollapsed
136
+ collapsed
105
137
  ? ApplicationIcons.chevron.right
106
138
  : ApplicationIcons.chevron.down
107
139
  }
@@ -126,17 +158,26 @@ export const EventPanel: FC<EventPanelProps> = ({
126
158
  >
127
159
  {title}
128
160
  </div>
161
+ {url ? (
162
+ <CopyButton
163
+ value={url}
164
+ icon={ApplicationIcons.link}
165
+ className={clsx(styles.copyLink)}
166
+ />
167
+ ) : (
168
+ ""
169
+ )}
129
170
  <div onClick={toggleCollapse}></div>
130
171
  <div
131
172
  className={clsx("text-style-secondary", styles.label)}
132
173
  onClick={toggleCollapse}
133
174
  >
134
- {isCollapsed ? text : ""}
175
+ {collapsed ? text : ""}
135
176
  </div>
136
177
  <div className={styles.navs}>
137
- {(!hasCollapse || !isCollapsed) &&
138
- filteredArrChildren &&
139
- filteredArrChildren.length > 1 ? (
178
+ {isCollapsible && collapsibleContent && collapsed ? (
179
+ ""
180
+ ) : filteredArrChildren && filteredArrChildren.length > 1 ? (
140
181
  <EventNavs
141
182
  navs={filteredArrChildren.map((child, index) => {
142
183
  const defaultTitle = `Tab ${index}`;
@@ -163,33 +204,58 @@ export const EventPanel: FC<EventPanelProps> = ({
163
204
  );
164
205
 
165
206
  const card = (
166
- <>
167
- <div id={id} className={clsx(className, styles.card)}>
168
- {titleEl}
207
+ <div
208
+ id={id}
209
+ className={clsx(
210
+ className,
211
+ styles.card,
212
+ depth === 0 ? styles.root : undefined,
213
+ )}
214
+ >
215
+ {titleEl}
216
+ <div
217
+ className={clsx(
218
+ "tab-content",
219
+ styles.cardContent,
220
+ isCollapsible && collapsed && collapsibleContent
221
+ ? styles.hidden
222
+ : undefined,
223
+ )}
224
+ >
225
+ {filteredArrChildren?.map((child, index) => {
226
+ const id = pillId(index);
227
+ const isSelected = id === selectedNav;
228
+
229
+ return (
230
+ <div
231
+ key={`children-${id}-${index}`}
232
+ id={id}
233
+ className={clsx("tab-pane", "show", isSelected ? "active" : "")}
234
+ >
235
+ {child}
236
+ </div>
237
+ );
238
+ })}
239
+ </div>
240
+
241
+ {isCollapsible && useBottomDongle ? (
169
242
  <div
170
- className={clsx(
171
- "tab-content",
172
- styles.cardContent,
173
- hasCollapse && isCollapsed ? styles.hidden : undefined,
174
- )}
243
+ className={clsx(styles.bottomDongle, "text-size-smallest")}
244
+ onClick={toggleCollapse}
175
245
  >
176
- {filteredArrChildren?.map((child, index) => {
177
- const id = pillId(index);
178
- const isSelected = id === selectedNav;
179
-
180
- return (
181
- <div
182
- key={`children-${id}-${index}`}
183
- id={id}
184
- className={clsx("tab-pane", "show", isSelected ? "active" : "")}
185
- >
186
- {child}
187
- </div>
188
- );
189
- })}
246
+ <i
247
+ className={clsx(
248
+ collapsed
249
+ ? ApplicationIcons.chevron.right
250
+ : ApplicationIcons.chevron.down,
251
+ styles.dongleIcon,
252
+ )}
253
+ />
254
+ transcript ({childIds?.length}{" "}
255
+ {childIds?.length === 1 ? "event" : "events"})
190
256
  </div>
191
- </div>
192
- </>
257
+ ) : undefined}
258
+ </div>
193
259
  );
194
260
  return card;
195
261
  };
@@ -16,12 +16,13 @@ import {
16
16
  StoreSpecificRenderableTypes,
17
17
  } from "./StateEventRenderers";
18
18
 
19
- import { FC, useMemo } from "react";
19
+ import { FC, useEffect, useMemo } from "react";
20
+ import { useStore } from "../../../../state/store";
21
+ import { EventNode } from "../types";
20
22
  import styles from "./StateEventView.module.css";
21
23
 
22
24
  interface StateEventViewProps {
23
- id: string;
24
- event: StateEvent | StoreEvent;
25
+ eventNode: EventNode<StateEvent | StoreEvent>;
25
26
  isStore?: boolean;
26
27
  className?: string | string[];
27
28
  }
@@ -30,11 +31,12 @@ interface StateEventViewProps {
30
31
  * Renders the StateEventView component.
31
32
  */
32
33
  export const StateEventView: FC<StateEventViewProps> = ({
33
- id,
34
- event,
35
- isStore = false,
34
+ eventNode,
36
35
  className,
37
36
  }) => {
37
+ const event = eventNode.event;
38
+ const id = eventNode.id;
39
+
38
40
  const summary = useMemo(() => {
39
41
  return summarizeChanges(event.changes);
40
42
  }, [event.changes]);
@@ -56,19 +58,28 @@ export const StateEventView: FC<StateEventViewProps> = ({
56
58
  // and as a result may be decorated with additional properties, etc..., resulting in DOM elements
57
59
  // appearing attached to state.
58
60
  const changePreview = useMemo(() => {
61
+ const isStore = eventNode.event.event === "store";
59
62
  return generatePreview(event.changes, structuredClone(after), isStore);
60
- }, [event.changes, after, isStore]);
63
+ }, [event.changes, after]);
61
64
  // Compute the title
62
65
  const title = event.event === "state" ? "State Updated" : "Store Updated";
63
66
 
67
+ const collapseEvent = useStore((state) => state.sampleActions.collapseEvent);
68
+ useEffect(() => {
69
+ if (changePreview === undefined) {
70
+ collapseEvent(id, true);
71
+ }
72
+ }, [changePreview, collapseEvent]);
73
+
64
74
  return (
65
75
  <EventPanel
66
76
  id={id}
77
+ depth={eventNode.depth}
67
78
  title={title}
68
79
  className={className}
69
80
  subTitle={formatDateTime(new Date(event.timestamp))}
70
81
  text={!changePreview ? summary : undefined}
71
- collapse={changePreview === undefined ? true : undefined}
82
+ collapsibleContent={true}
72
83
  >
73
84
  {changePreview ? (
74
85
  <div data-name="Summary" className={clsx(styles.summary)}>
@@ -2,9 +2,18 @@ import { Events } from "../../../../@types/log";
2
2
  import { EventNode, EventType } from "../types";
3
3
  import {
4
4
  ACTION_BEGIN,
5
- ET_SPAN_BEGIN,
6
- ET_SPAN_END,
7
- ET_STEP,
5
+ SPAN_BEGIN,
6
+ SPAN_END,
7
+ STATE,
8
+ STEP,
9
+ STORE,
10
+ SUBTASK,
11
+ TOOL,
12
+ TYPE_AGENT,
13
+ TYPE_HANDOFF,
14
+ TYPE_SOLVER,
15
+ TYPE_SUBTASK,
16
+ TYPE_TOOL,
8
17
  hasSpans,
9
18
  } from "./utils";
10
19
 
@@ -17,19 +26,33 @@ type TreeifyFunction = (
17
26
 
18
27
  export function treeifyEvents(events: Events, depth: number): EventNode[] {
19
28
  const useSpans = hasSpans(events);
20
- const treeFn = useSpans ? treeifyFnSpan : treeifyFnStep;
29
+ const pathIndices: number[] = [];
21
30
 
22
31
  const rootNodes: EventNode[] = [];
23
32
  const stack: EventNode[] = [];
24
33
 
25
34
  const addNode = (event: EventType): EventNode => {
26
- const node = new EventNode(event, stack.length + depth);
35
+ const currentDepth = stack.length;
36
+
37
+ // Track sibling count for the parent node
38
+ if (pathIndices.length <= currentDepth) {
39
+ pathIndices.push(0);
40
+ } else {
41
+ pathIndices[currentDepth]++;
42
+ // Reset deeper levels if coming back up the stack
43
+ pathIndices.length = currentDepth + 1;
44
+ }
45
+
46
+ // Create a new node
47
+ const idPath = pathIndices.slice(0, currentDepth + 1).join(".");
48
+ const node = new EventNode(idPath, event, currentDepth + depth);
27
49
  if (stack.length > 0) {
28
50
  const parentNode = stack[stack.length - 1];
29
51
  parentNode.children.push(node);
30
52
  } else {
31
53
  rootNodes.push(node);
32
54
  }
55
+
33
56
  return node;
34
57
  };
35
58
 
@@ -38,13 +61,12 @@ export function treeifyEvents(events: Events, depth: number): EventNode[] {
38
61
  };
39
62
 
40
63
  const popStack = (): void => {
41
- if (stack.length > 0) {
42
- stack.pop();
43
- }
64
+ stack.pop();
65
+ pathIndices.pop();
44
66
  };
45
67
 
46
68
  events.forEach((event) => {
47
- treeFn(event, addNode, pushStack, popStack);
69
+ treeifyFn(event, addNode, pushStack, popStack);
48
70
  });
49
71
 
50
72
  if (useSpans) {
@@ -54,14 +76,14 @@ export function treeifyEvents(events: Events, depth: number): EventNode[] {
54
76
  }
55
77
  }
56
78
 
57
- const treeifyFnStep: TreeifyFunction = (
79
+ const treeifyFn: TreeifyFunction = (
58
80
  event: EventType,
59
81
  addNode: (event: EventType) => EventNode,
60
82
  pushStack: (node: EventNode) => void,
61
83
  popStack: () => void,
62
84
  ): void => {
63
85
  switch (event.event) {
64
- case ET_STEP:
86
+ case STEP:
65
87
  if (event.action === ACTION_BEGIN) {
66
88
  // Starting a new step
67
89
  const node = addNode(event);
@@ -71,40 +93,55 @@ const treeifyFnStep: TreeifyFunction = (
71
93
  popStack();
72
94
  }
73
95
  break;
74
- case ET_SPAN_BEGIN: {
75
- // These shoudn't be here, but throw away
76
- break;
77
- }
78
- case ET_SPAN_END: {
79
- // These shoudn't be here, but throw away
80
- break;
81
- }
82
- default:
83
- // An event
84
- addNode(event);
85
- break;
86
- }
87
- };
88
-
89
- const treeifyFnSpan: TreeifyFunction = (
90
- event: EventType,
91
- addNode: (event: EventType) => EventNode,
92
- pushStack: (node: EventNode) => void,
93
- popStack: () => void,
94
- ): void => {
95
- switch (event.event) {
96
- case ET_STEP:
97
- // strip steps
98
- break;
99
- case ET_SPAN_BEGIN: {
96
+ case SPAN_BEGIN: {
100
97
  const node = addNode(event);
101
98
  pushStack(node);
102
99
  break;
103
100
  }
104
- case ET_SPAN_END: {
101
+ case SPAN_END: {
105
102
  popStack();
106
103
  break;
107
104
  }
105
+ case TOOL:
106
+ {
107
+ const node = addNode(event);
108
+
109
+ // In the span world, the first child will be a span of type tool
110
+ if (
111
+ event.events.length > 0 &&
112
+ (event.events[0].event !== SPAN_BEGIN ||
113
+ event.events[0].type !== TYPE_TOOL)
114
+ ) {
115
+ // Expand the children
116
+ pushStack(node);
117
+ for (const child of event.events) {
118
+ treeifyFn(child, addNode, pushStack, popStack);
119
+ }
120
+ popStack();
121
+ }
122
+ }
123
+
124
+ break;
125
+ case SUBTASK:
126
+ {
127
+ const node = addNode(event);
128
+
129
+ // In the span world, the first child will be a span of type tool
130
+ if (
131
+ event.events.length > 0 &&
132
+ (event.events[0].event !== SPAN_BEGIN ||
133
+ event.events[0].type !== TYPE_SUBTASK)
134
+ ) {
135
+ // Expand the children
136
+ pushStack(node);
137
+ for (const child of event.events) {
138
+ treeifyFn(child, addNode, pushStack, popStack);
139
+ }
140
+ popStack();
141
+ }
142
+ }
143
+
144
+ break;
108
145
  default:
109
146
  // An event
110
147
  addNode(event);
@@ -122,50 +159,50 @@ const treeNodeTransformers: TreeNodeTransformer[] = [
122
159
  {
123
160
  name: "unwrap_tools",
124
161
  matches: (node) =>
125
- node.event.event === "span_begin" && node.event.type === "tool",
126
- process: (node) => elevateChildNode(node, "tool") || node,
162
+ node.event.event === SPAN_BEGIN && node.event.type === TYPE_TOOL,
163
+ process: (node) => elevateChildNode(node, TYPE_TOOL) || node,
127
164
  },
128
165
  {
129
166
  name: "unwrap_subtasks",
130
167
  matches: (node) =>
131
- node.event.event === "span_begin" && node.event.type === "subtask",
132
- process: (node) => elevateChildNode(node, "subtask") || node,
168
+ node.event.event === SPAN_BEGIN && node.event.type === TYPE_SUBTASK,
169
+ process: (node) => elevateChildNode(node, TYPE_SUBTASK) || node,
133
170
  },
134
171
  {
135
172
  name: "unwrap_agent_solver",
136
173
  matches: (node) =>
137
- node.event.event === "span_begin" &&
138
- node.event["type"] === "solver" &&
174
+ node.event.event === SPAN_BEGIN &&
175
+ node.event["type"] === TYPE_SOLVER &&
139
176
  node.children.length === 2 &&
140
- node.children[0].event.event === "span_begin" &&
141
- node.children[0].event.type === "agent" &&
142
- node.children[1].event.event === "state",
177
+ node.children[0].event.event === SPAN_BEGIN &&
178
+ node.children[0].event.type === TYPE_AGENT &&
179
+ node.children[1].event.event === STATE,
143
180
 
144
181
  process: (node) => skipFirstChildNode(node),
145
182
  },
146
183
  {
147
184
  name: "unwrap_agent_solver w/store",
148
185
  matches: (node) =>
149
- node.event.event === "span_begin" &&
150
- node.event["type"] === "solver" &&
186
+ node.event.event === SPAN_BEGIN &&
187
+ node.event["type"] === TYPE_SOLVER &&
151
188
  node.children.length === 3 &&
152
- node.children[0].event.event === "span_begin" &&
153
- node.children[0].event.type === "agent" &&
154
- node.children[1].event.event === "state" &&
155
- node.children[2].event.event === "store",
189
+ node.children[0].event.event === SPAN_BEGIN &&
190
+ node.children[0].event.type === TYPE_AGENT &&
191
+ node.children[1].event.event === STATE &&
192
+ node.children[2].event.event === STORE,
156
193
  process: (node) => skipFirstChildNode(node),
157
194
  },
158
195
  {
159
196
  name: "unwrap_handoff",
160
197
  matches: (node) =>
161
- node.event.event === "span_begin" &&
162
- node.event["type"] === "handoff" &&
198
+ node.event.event === SPAN_BEGIN &&
199
+ node.event["type"] === TYPE_HANDOFF &&
163
200
  node.children.length === 2 &&
164
- node.children[0].event.event === "tool" &&
165
- node.children[1].event.event === "store" &&
201
+ node.children[0].event.event === TOOL &&
202
+ node.children[1].event.event === STORE &&
166
203
  node.children[0].children.length === 2 &&
167
- node.children[0].children[0].event.event === "span_begin" &&
168
- node.children[0].children[0].event.type === "agent",
204
+ node.children[0].children[0].event.event === SPAN_BEGIN &&
205
+ node.children[0].children[0].event.type === TYPE_AGENT,
169
206
  process: (node) => skipThisNode(node),
170
207
  },
171
208
  ];
@@ -217,7 +254,7 @@ const elevateChildNode = (
217
254
 
218
255
  // Process the remaining children
219
256
  targetNode.depth = node.depth;
220
- targetNode.children = reduceDepth(remainingChildren);
257
+ targetNode.children = setDepth(remainingChildren, node.depth + 1);
221
258
 
222
259
  // No need to update the event itself (events have been deprecated
223
260
  // and more importantly we drive children / transcripts using the tree structure itself
@@ -249,3 +286,34 @@ const reduceDepth = (nodes: EventNode[], depth: number = 1): EventNode[] => {
249
286
  return node;
250
287
  });
251
288
  };
289
+
290
+ const setDepth = (nodes: EventNode[], depth: number): EventNode[] => {
291
+ return nodes.map((node) => {
292
+ if (node.children.length > 0) {
293
+ node.children = setDepth(node.children, depth + 1);
294
+ }
295
+ node.depth = depth;
296
+ return node;
297
+ });
298
+ };
299
+
300
+ /**
301
+ * Flatten the tree structure into a flat array of EventNode objects
302
+ * Each node in the result will have its children set properly
303
+ * @param events - The events to flatten
304
+ * @param depth - The current depth in the tree
305
+ * @returns An array of EventNode objects
306
+ */
307
+ export const flatTree = (
308
+ eventNodes: EventNode[],
309
+ collapsed: Record<string, true> | null,
310
+ ): EventNode[] => {
311
+ const result: EventNode[] = [];
312
+ for (const node of eventNodes) {
313
+ result.push(node);
314
+ if (collapsed === null || collapsed[node.id] !== true) {
315
+ result.push(...flatTree(node.children, collapsed));
316
+ }
317
+ }
318
+ return result;
319
+ };
@@ -1,11 +1,21 @@
1
1
  import { Events } from "../../../../@types/log";
2
2
 
3
- export const ET_STEP = "step";
3
+ export const STEP = "step";
4
4
  export const ACTION_BEGIN = "begin";
5
5
 
6
- export const ET_SPAN_BEGIN = "span_begin";
7
- export const ET_SPAN_END = "span_end";
6
+ export const SPAN_BEGIN = "span_begin";
7
+ export const SPAN_END = "span_end";
8
+ export const TOOL = "tool";
9
+ export const SUBTASK = "subtask";
10
+ export const STORE = "store";
11
+ export const STATE = "state";
12
+
13
+ export const TYPE_TOOL = "tool";
14
+ export const TYPE_SUBTASK = "subtask";
15
+ export const TYPE_SOLVER = "solver";
16
+ export const TYPE_AGENT = "agent";
17
+ export const TYPE_HANDOFF = "handoff";
8
18
 
9
19
  export const hasSpans = (events: Events): boolean => {
10
- return events.some((event) => event.event === ET_SPAN_BEGIN);
20
+ return events.some((event) => event.event === SPAN_BEGIN);
11
21
  };
@@ -45,12 +45,14 @@ export type EventType =
45
45
  | SpanBeginEvent
46
46
  | SpanEndEvent;
47
47
 
48
- export class EventNode {
49
- event: EventType;
50
- children: EventNode[] = [];
48
+ export class EventNode<T extends EventType = EventType> {
49
+ id: string;
50
+ event: T;
51
+ children: EventNode<EventType>[] = [];
51
52
  depth: number;
52
53
 
53
- constructor(event: EventType, depth: number) {
54
+ constructor(id: string, event: T, depth: number) {
55
+ this.id = id;
54
56
  this.event = event;
55
57
  this.depth = depth;
56
58
  }
@@ -58,6 +58,7 @@ export interface LogsState {
58
58
  logHeaders: Record<string, EvalLogHeader>;
59
59
  headersLoading: boolean;
60
60
  selectedLogIndex: number;
61
+ selectedLogFile?: string;
61
62
  }
62
63
 
63
64
  export interface LogState {
@@ -78,13 +79,23 @@ export interface LogState {
78
79
 
79
80
  export type SampleStatus = "ok" | "loading" | "streaming" | "error";
80
81
 
82
+ export type SampleIdentifier = {
83
+ id: string | number;
84
+ epoch: number;
85
+ };
86
+
81
87
  export interface SampleState {
82
- selectedSample: EvalSample | undefined;
88
+ sample_identifier: SampleIdentifier | undefined;
89
+ sampleInState: boolean;
90
+ selectedSampleObject?: EvalSample;
83
91
  sampleStatus: SampleStatus;
84
92
  sampleError: Error | undefined;
93
+ sampleNeedsReload: number;
85
94
 
86
95
  // Events and attachments
87
96
  runningEvents: Event[];
97
+ collapsedEvents: Record<string, true> | null;
98
+ collapsedIdBuckets: Record<string, Record<string, boolean>>;
88
99
  }
89
100
 
90
101
  export type Event =