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
@@ -1,5 +1,5 @@
1
1
  import { useCallback } from "react";
2
- import { useNavigate, useParams } from "react-router-dom";
2
+ import { useNavigate, useParams, useSearchParams } from "react-router-dom";
3
3
  import { useFilteredSamples } from "../../state/hooks";
4
4
  import { useStore } from "../../state/store";
5
5
  import { directoryRelativeUrl } from "../../utils/uri";
@@ -32,6 +32,56 @@ export const useLogNavigation = () => {
32
32
  };
33
33
  };
34
34
 
35
+ export const useSampleUrl = () => {
36
+ const { logPath, tabId, sampleTabId } = useParams<{
37
+ logPath?: string;
38
+ tabId?: string;
39
+ sampleTabId?: string;
40
+ }>();
41
+
42
+ const logDirectory = useStore((state) => state.logs.logs.log_dir);
43
+
44
+ const selectedLogFile = useStore((state) => state.logs.selectedLogFile);
45
+
46
+ // Helper function to resolve the log path for URLs
47
+ const resolveLogPath = useCallback(() => {
48
+ // If we have a logPath from URL params, use that
49
+ if (logPath) {
50
+ return logPath;
51
+ }
52
+
53
+ if (selectedLogFile) {
54
+ return directoryRelativeUrl(selectedLogFile, logDirectory);
55
+ }
56
+
57
+ return undefined;
58
+ }, [logPath, selectedLogFile, logDirectory]);
59
+
60
+ // Get a sample URL for a specific sample
61
+ const getSampleUrl = useCallback(
62
+ (
63
+ sampleId: string | number,
64
+ epoch: number,
65
+ specificSampleTabId?: string,
66
+ ) => {
67
+ const resolvedPath = resolveLogPath();
68
+ if (resolvedPath) {
69
+ const currentSampleTabId = specificSampleTabId || sampleTabId;
70
+ const url = sampleUrl(
71
+ resolvedPath,
72
+ sampleId,
73
+ epoch,
74
+ currentSampleTabId,
75
+ );
76
+ return url;
77
+ }
78
+ return undefined;
79
+ },
80
+ [resolveLogPath, tabId, sampleTabId],
81
+ );
82
+ return getSampleUrl;
83
+ };
84
+
35
85
  /**
36
86
  * Hook that provides sample navigation utilities with proper URL handling
37
87
  * for use across the application
@@ -50,9 +100,7 @@ export const useSampleNavigation = () => {
50
100
  }>();
51
101
 
52
102
  // Get the store access values directly in the hook
53
- const getSelectedLogFile = useStore(
54
- (state) => state.logsActions.getSelectedLogFile,
55
- );
103
+ const selectedLogFile = useStore((state) => state.logs.selectedLogFile);
56
104
 
57
105
  // Helper function to resolve the log path for URLs
58
106
  const resolveLogPath = useCallback(() => {
@@ -61,15 +109,12 @@ export const useSampleNavigation = () => {
61
109
  return logPath;
62
110
  }
63
111
 
64
- // Otherwise use the selected log file
65
- const selectedLogFile = getSelectedLogFile();
66
-
67
112
  if (selectedLogFile) {
68
113
  return directoryRelativeUrl(selectedLogFile, logDirectory);
69
114
  }
70
115
 
71
116
  return undefined;
72
- }, [logPath, getSelectedLogFile, logDirectory]);
117
+ }, [logPath, selectedLogFile, logDirectory]);
73
118
 
74
119
  // The samples
75
120
  const sampleSummaries = useFilteredSamples();
@@ -180,3 +225,13 @@ export const useSampleNavigation = () => {
180
225
  clearSampleUrl,
181
226
  };
182
227
  };
228
+
229
+ export const useSampleDetailNavigation = () => {
230
+ const [searchParams, _setSearchParams] = useSearchParams();
231
+ const message = searchParams.get("message");
232
+ const event = searchParams.get("event");
233
+ return {
234
+ message,
235
+ event,
236
+ };
237
+ };
@@ -1,3 +1,4 @@
1
+ import { kSampleMessagesTabId, kSampleTranscriptTabId } from "../../constants";
1
2
  import { directoryRelativeUrl } from "../../utils/uri";
2
3
 
3
4
  export const kLogRouteUrlPattern = "/logs/:logPath/:tabId?/:sampleTabId?";
@@ -29,6 +30,38 @@ export const sampleUrl = (
29
30
  }
30
31
  };
31
32
 
33
+ export const sampleEventUrl = (
34
+ eventId: string,
35
+ logPath: string,
36
+ sampleId?: string | number,
37
+ sampleEpoch?: string | number,
38
+ ) => {
39
+ const baseUrl = sampleUrl(
40
+ logPath,
41
+ sampleId,
42
+ sampleEpoch,
43
+ kSampleTranscriptTabId,
44
+ );
45
+
46
+ return `${baseUrl}?event=${eventId}`;
47
+ };
48
+
49
+ export const sampleMessageUrl = (
50
+ messageId: string,
51
+ logPath: string,
52
+ sampleId?: string | number,
53
+ sampleEpoch?: string | number,
54
+ ) => {
55
+ const baseUrl = sampleUrl(
56
+ logPath,
57
+ sampleId,
58
+ sampleEpoch,
59
+ kSampleMessagesTabId,
60
+ );
61
+
62
+ return `${baseUrl}?message=${messageId}`;
63
+ };
64
+
32
65
  export const logUrl = (log_file: string, log_dir?: string, tabId?: string) => {
33
66
  const pathSegment = directoryRelativeUrl(log_file, log_dir);
34
67
  return logUrlRaw(pathSegment, tabId);
@@ -41,3 +74,15 @@ export const logUrlRaw = (log_segment: string, tabId?: string) => {
41
74
  return `/logs/${encodeURIComponent(log_segment)}`;
42
75
  }
43
76
  };
77
+
78
+ export const supportsLinking = () => {
79
+ return (
80
+ location.hostname !== "localhost" &&
81
+ location.hostname !== "127.0.0.1" &&
82
+ location.protocol !== "vscode-webview:"
83
+ );
84
+ };
85
+
86
+ export const toFullUrl = (path: string) => {
87
+ return `${window.location.origin}${window.location.pathname}#${path}`;
88
+ };
@@ -5,7 +5,8 @@
5
5
  }
6
6
 
7
7
  .body {
8
- margin: 1em 1em 1em 1em;
8
+ margin-top: 1em;
9
+ margin-bottom: 1em;
9
10
  }
10
11
 
11
12
  .scroller {
@@ -31,30 +31,37 @@ export const InlineSampleDisplay: FC = () => {
31
31
  : true,
32
32
  );
33
33
  const prevLogFile = usePrevious<string | undefined>(logSelection.loadedLog);
34
+ const prevSampleNeedsReload = usePrevious<number>(
35
+ sampleData.sampleNeedsReload,
36
+ );
37
+
34
38
  useEffect(() => {
35
39
  if (logSelection.logFile && logSelection.sample) {
36
40
  const currentSampleCompleted =
37
- logSelection.sample?.completed !== undefined
41
+ logSelection.sample.completed !== undefined
38
42
  ? logSelection.sample.completed
39
43
  : true;
40
44
 
41
45
  if (
42
- (prevLogFile !== undefined && prevLogFile !== logSelection.loadedLog) ||
43
- sampleData.sample?.id !== logSelection.sample.id ||
44
- sampleData.sample?.epoch !== logSelection.sample.epoch ||
46
+ (prevLogFile !== undefined && prevLogFile !== logSelection.logFile) ||
47
+ sampleData.selectedSampleIdentifier?.id !== logSelection.sample.id ||
48
+ sampleData.selectedSampleIdentifier?.epoch !==
49
+ logSelection.sample.epoch ||
45
50
  (prevCompleted !== undefined &&
46
- currentSampleCompleted !== prevCompleted)
51
+ currentSampleCompleted !== prevCompleted) ||
52
+ prevSampleNeedsReload !== sampleData.sampleNeedsReload
47
53
  ) {
48
54
  loadSample(logSelection.logFile, logSelection.sample);
49
55
  }
50
56
  }
51
57
  }, [
52
- logSelection.loadedLog,
58
+ logSelection.logFile,
53
59
  logSelection.sample?.id,
54
60
  logSelection.sample?.epoch,
55
61
  logSelection.sample?.completed,
56
- sampleData.sample?.id,
57
- sampleData.sample?.epoch,
62
+ sampleData.selectedSampleIdentifier?.id,
63
+ sampleData.selectedSampleIdentifier?.epoch,
64
+ sampleData.sampleNeedsReload,
58
65
  ]);
59
66
 
60
67
  // Scroll ref
@@ -0,0 +1,3 @@
1
+ .modalBody {
2
+ padding: 1em 0;
3
+ }
@@ -9,6 +9,8 @@ import { useStore } from "../../state/store";
9
9
  import { useSampleNavigation } from "../routing/navigationHooks";
10
10
  import { SampleDisplay } from "./SampleDisplay";
11
11
 
12
+ import styles from "./SampleDialog.module.css";
13
+
12
14
  interface SampleDialogProps {
13
15
  id: string;
14
16
  title: string;
@@ -46,6 +48,9 @@ export const SampleDialog: FC<SampleDialogProps> = ({
46
48
  : true,
47
49
  );
48
50
  const prevLogFile = usePrevious<string | undefined>(logSelection.logFile);
51
+ const prevSampleNeedsReload = usePrevious<number>(
52
+ sampleData.sampleNeedsReload,
53
+ );
49
54
 
50
55
  useEffect(() => {
51
56
  if (logSelection.logFile && logSelection.sample) {
@@ -56,10 +61,12 @@ export const SampleDialog: FC<SampleDialogProps> = ({
56
61
 
57
62
  if (
58
63
  (prevLogFile !== undefined && prevLogFile !== logSelection.logFile) ||
59
- sampleData.sample?.id !== logSelection.sample.id ||
60
- sampleData.sample?.epoch !== logSelection.sample.epoch ||
64
+ sampleData.selectedSampleIdentifier?.id !== logSelection.sample.id ||
65
+ sampleData.selectedSampleIdentifier?.epoch !==
66
+ logSelection.sample.epoch ||
61
67
  (prevCompleted !== undefined &&
62
- currentSampleCompleted !== prevCompleted)
68
+ currentSampleCompleted !== prevCompleted) ||
69
+ prevSampleNeedsReload !== sampleData.sampleNeedsReload
63
70
  ) {
64
71
  loadSample(logSelection.logFile, logSelection.sample);
65
72
  }
@@ -69,8 +76,9 @@ export const SampleDialog: FC<SampleDialogProps> = ({
69
76
  logSelection.sample?.id,
70
77
  logSelection.sample?.epoch,
71
78
  logSelection.sample?.completed,
72
- sampleData.sample?.id,
73
- sampleData.sample?.epoch,
79
+ sampleData.selectedSampleIdentifier?.id,
80
+ sampleData.selectedSampleIdentifier?.epoch,
81
+ sampleData.sampleNeedsReload,
74
82
  ]);
75
83
 
76
84
  // Get sample navigation utilities
@@ -139,6 +147,9 @@ export const SampleDialog: FC<SampleDialogProps> = ({
139
147
  onkeyup={handleKeyUp}
140
148
  visible={showingSampleDialog}
141
149
  onHide={onHide}
150
+ classNames={{
151
+ body: styles.modalBody,
152
+ }}
142
153
  showProgress={
143
154
  sampleData.status === "loading" || sampleData.status === "streaming"
144
155
  }
@@ -41,5 +41,13 @@
41
41
  }
42
42
 
43
43
  .chat {
44
- padding-top: 1em;
44
+ padding: 1em;
45
+ }
46
+
47
+ .padded {
48
+ padding: 1em;
49
+ }
50
+
51
+ .transcriptContainer {
52
+ padding-bottom: 1em;
45
53
  }
@@ -1,5 +1,4 @@
1
1
  import { TabPanel, TabSet } from "../../components/TabSet";
2
- import { MetaDataView } from "../content/MetaDataView";
3
2
 
4
3
  import { escapeSelector } from "../../utils/html";
5
4
  import { isVscode } from "../../utils/vscode";
@@ -34,15 +33,23 @@ import {
34
33
  import { useFilteredSamples, useSampleData } from "../../state/hooks";
35
34
  import { useStore } from "../../state/store";
36
35
  import { formatTime } from "../../utils/format";
36
+ import { estimateSize } from "../../utils/json";
37
37
  import { printHeadingHtml, printHtml } from "../../utils/print";
38
- import { sampleUrl } from "../routing/url";
38
+ import { RecordTree } from "../content/RecordTree";
39
+ import { useSampleDetailNavigation } from "../routing/navigationHooks";
40
+ import {
41
+ sampleMessageUrl,
42
+ sampleUrl,
43
+ supportsLinking,
44
+ toFullUrl,
45
+ } from "../routing/url";
39
46
  import { ModelTokenTable } from "../usage/ModelTokenTable";
40
47
  import { ChatViewVirtualList } from "./chat/ChatViewVirtualList";
41
48
  import { messagesFromEvents } from "./chat/messages";
42
49
  import styles from "./SampleDisplay.module.css";
43
50
  import { SampleSummaryView } from "./SampleSummaryView";
44
51
  import { SampleScoresView } from "./scores/SampleScoresView";
45
- import { TranscriptVirtualList } from "./transcript/TranscriptView";
52
+ import { TranscriptVirtualList } from "./transcript/TranscriptVirtualList";
46
53
 
47
54
  interface SampleDisplayProps {
48
55
  id: string;
@@ -61,7 +68,10 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
61
68
  );
62
69
 
63
70
  const sampleData = useSampleData();
64
- const sample = sampleData.sample;
71
+ const sample = useMemo(() => {
72
+ return sampleData.getSelectedSample();
73
+ }, [sampleData.selectedSampleIdentifier, sampleData.getSelectedSample]);
74
+
65
75
  const runningSampleData = sampleData.running;
66
76
 
67
77
  // Selected tab handling
@@ -105,6 +115,12 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
105
115
  epoch?: string;
106
116
  }>();
107
117
 
118
+ const getMessageUrl = (id: string) => {
119
+ return id && urlLogPath && supportsLinking()
120
+ ? toFullUrl(sampleMessageUrl(id, urlLogPath, urlSampleId, urlEpoch))
121
+ : undefined;
122
+ };
123
+
108
124
  // Tab selection
109
125
  const onSelectedTab = useCallback(
110
126
  (e: MouseEvent<HTMLElement>) => {
@@ -129,7 +145,11 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
129
145
  ],
130
146
  );
131
147
 
132
- const sampleMetadatas = metadataViewsForSample(`${baseId}-${id}`, sample);
148
+ const sampleMetadatas = metadataViewsForSample(
149
+ `${baseId}-${id}`,
150
+ scrollRef,
151
+ sample,
152
+ );
133
153
 
134
154
  const tabsetId = `task-sample-details-tab-${id}`;
135
155
  const targetId = `${tabsetId}-content`;
@@ -153,6 +173,8 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
153
173
  // Is the sample running?
154
174
  const running = isRunning(sampleSummary, runningSampleData);
155
175
 
176
+ const sampleDetailNavigation = useSampleDetailNavigation();
177
+
156
178
  return (
157
179
  <Fragment>
158
180
  {sample || sampleSummary ? (
@@ -167,7 +189,7 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
167
189
  <TabPanel
168
190
  key={kSampleTranscriptTabId}
169
191
  id={kSampleTranscriptTabId}
170
- className="sample-tab"
192
+ className={clsx("sample-tab", styles.transcriptContainer)}
171
193
  title="Transcript"
172
194
  onSelected={onSelectedTab}
173
195
  selected={
@@ -180,6 +202,7 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
180
202
  key={`${baseId}-transcript-display-${id}`}
181
203
  id={`${baseId}-transcript-display-${id}`}
182
204
  events={sampleEvents || []}
205
+ initialEventId={sampleDetailNavigation.event}
183
206
  running={running}
184
207
  scrollRef={scrollRef}
185
208
  />
@@ -197,10 +220,12 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
197
220
  key={`${baseId}-chat-${id}`}
198
221
  id={`${baseId}-chat-${id}`}
199
222
  messages={sampleMessages}
223
+ initialMessageId={sampleDetailNavigation.message}
200
224
  indented={true}
201
225
  scrollRef={scrollRef}
202
226
  toolCallStyle="complete"
203
227
  running={running}
228
+ getMessageUrl={getMessageUrl}
204
229
  />
205
230
  </TabPanel>
206
231
  <TabPanel
@@ -211,7 +236,11 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
211
236
  onSelected={onSelectedTab}
212
237
  selected={effectiveSelectedTab === kSampleScoringTabId}
213
238
  >
214
- <SampleScoresView sample={sample} />
239
+ <SampleScoresView
240
+ sample={sample}
241
+ className={styles.padded}
242
+ scrollRef={scrollRef}
243
+ />
215
244
  </TabPanel>
216
245
  <TabPanel
217
246
  id={kSampleMetdataTabId}
@@ -220,8 +249,10 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
220
249
  onSelected={onSelectedTab}
221
250
  selected={effectiveSelectedTab === kSampleMetdataTabId}
222
251
  >
223
- {sampleMetadatas.length > 0 ? (
224
- <div className={clsx(styles.metadataPanel)}>{sampleMetadatas}</div>
252
+ {!sample || sampleMetadatas.length > 0 ? (
253
+ <div className={clsx(styles.padded, styles.fullWidth)}>
254
+ {sampleMetadatas}
255
+ </div>
225
256
  ) : (
226
257
  <NoContentsPanel text="No metadata" />
227
258
  )}
@@ -280,8 +311,8 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
280
311
  >
281
312
  {!sample ? (
282
313
  <NoContentsPanel text="JSON not available" />
283
- ) : sample.messages.length > 100 ? (
284
- <NoContentsPanel text="JSON too large too display" />
314
+ ) : estimateSize(sample.events) > 250000 ? (
315
+ <NoContentsPanel text="JSON too large to display" />
285
316
  ) : (
286
317
  <div className={clsx(styles.padded, styles.fullWidth)}>
287
318
  <JSONPanel
@@ -297,7 +328,11 @@ export const SampleDisplay: FC<SampleDisplayProps> = ({ id, scrollRef }) => {
297
328
  );
298
329
  };
299
330
 
300
- const metadataViewsForSample = (id: string, sample?: EvalSample) => {
331
+ const metadataViewsForSample = (
332
+ id: string,
333
+ scrollRef: RefObject<HTMLDivElement | null>,
334
+ sample?: EvalSample,
335
+ ) => {
301
336
  if (!sample) {
302
337
  return [];
303
338
  }
@@ -326,17 +361,16 @@ const metadataViewsForSample = (id: string, sample?: EvalSample) => {
326
361
  sampleMetadatas.push(
327
362
  <Card key={`sample-time-${id}`}>
328
363
  <CardHeader label="Time" />
329
- <CardBody>
330
- <div className={clsx(styles.timePanel, "text-size-smaller")}>
331
- <div className={clsx("text-style-label", "text-style-secondary")}>
332
- Working
333
- </div>
334
- <div>{formatTime(sample.working_time)}</div>
335
- <div className={clsx("text-style-label", "text-style-secondary")}>
336
- Total
337
- </div>
338
- <div>{formatTime(sample.total_time)}</div>
339
- </div>
364
+ <CardBody padded={false}>
365
+ <RecordTree
366
+ id={`task-sample-time-${id}`}
367
+ record={{
368
+ Working: formatTime(sample.working_time),
369
+ Total: formatTime(sample.total_time),
370
+ }}
371
+ className={clsx("tab-pane", styles.noTop)}
372
+ scrollRef={scrollRef}
373
+ />
340
374
  </CardBody>
341
375
  </Card>,
342
376
  );
@@ -346,11 +380,12 @@ const metadataViewsForSample = (id: string, sample?: EvalSample) => {
346
380
  sampleMetadatas.push(
347
381
  <Card key={`sample-metadata-${id}`}>
348
382
  <CardHeader label="Metadata" />
349
- <CardBody>
350
- <MetaDataView
351
- id="task-sample-metadata-${id}"
352
- entries={sample?.metadata as Record<string, unknown>}
383
+ <CardBody padded={false}>
384
+ <RecordTree
385
+ id={`task-sample-metadata-${id}`}
386
+ record={sample?.metadata as Record<string, unknown>}
353
387
  className={clsx("tab-pane", styles.noTop)}
388
+ scrollRef={scrollRef}
354
389
  />
355
390
  </CardBody>
356
391
  </Card>,
@@ -361,11 +396,13 @@ const metadataViewsForSample = (id: string, sample?: EvalSample) => {
361
396
  sampleMetadatas.push(
362
397
  <Card key={`sample-store-${id}`}>
363
398
  <CardHeader label="Store" />
364
- <CardBody>
365
- <MetaDataView
366
- id="task-sample-store-${id}"
367
- entries={sample?.store as Record<string, unknown>}
399
+ <CardBody padded={false}>
400
+ <RecordTree
401
+ id={`task-sample-store-${id}`}
402
+ record={sample?.store as Record<string, unknown>}
368
403
  className={clsx("tab-pane", styles.noTop)}
404
+ scrollRef={scrollRef}
405
+ processStore={true}
369
406
  />
370
407
  </CardBody>
371
408
  </Card>,
@@ -5,17 +5,13 @@
5
5
  white-space: normal;
6
6
  }
7
7
 
8
- .padded {
9
- padding-bottom: 1em;
10
- }
11
-
12
8
  .systemRole {
13
9
  opacity: 0.7;
14
10
  }
15
11
 
16
12
  .messageGrid {
17
13
  display: grid;
18
- grid-template-columns: max-content auto;
14
+ grid-template-columns: max-content max-content max-content;
19
15
  column-gap: 0.3em;
20
16
  font-weight: 500;
21
17
  margin-bottom: 0.3em;
@@ -27,6 +23,15 @@
27
23
  }
28
24
 
29
25
  .messageContents.indented {
30
- margin-left: 1.1rem;
31
- padding-bottom: 0.8rem;
26
+ margin-left: 0rem;
27
+ }
28
+
29
+ .copyLink {
30
+ opacity: 0;
31
+ padding-left: 0;
32
+ padding-right: 2em;
33
+ }
34
+
35
+ .copyLink:hover {
36
+ opacity: 1;
32
37
  }
@@ -6,10 +6,11 @@ import {
6
6
  ChatMessageTool,
7
7
  ChatMessageUser,
8
8
  } from "../../../@types/log";
9
+ import { CopyButton } from "../../../components/CopyButton";
9
10
  import ExpandablePanel from "../../../components/ExpandablePanel";
11
+ import { ApplicationIcons } from "../../appearance/icons";
10
12
  import styles from "./ChatMessage.module.css";
11
13
  import { MessageContents } from "./MessageContents";
12
- import { iconForMsg } from "./messages";
13
14
  import { ChatViewToolCallStyle } from "./types";
14
15
 
15
16
  interface ChatMessageProps {
@@ -18,7 +19,7 @@ interface ChatMessageProps {
18
19
  toolMessages: ChatMessageTool[];
19
20
  indented?: boolean;
20
21
  toolCallStyle: ChatViewToolCallStyle;
21
- padded?: boolean;
22
+ getMessageUrl?: (id: string) => string | undefined;
22
23
  }
23
24
 
24
25
  export const ChatMessage: FC<ChatMessageProps> = ({
@@ -27,8 +28,11 @@ export const ChatMessage: FC<ChatMessageProps> = ({
27
28
  toolMessages,
28
29
  indented,
29
30
  toolCallStyle,
30
- padded,
31
+ getMessageUrl,
31
32
  }) => {
33
+ const messageUrl =
34
+ message.id && getMessageUrl ? getMessageUrl(message.id) : undefined;
35
+
32
36
  const collapse = message.role === "system" || message.role === "user";
33
37
  return (
34
38
  <div
@@ -36,13 +40,21 @@ export const ChatMessage: FC<ChatMessageProps> = ({
36
40
  message.role,
37
41
  "text-size-base",
38
42
  styles.message,
39
- padded ? styles.padded : undefined,
40
43
  message.role === "system" ? styles.systemRole : undefined,
44
+ message.role === "user" ? styles.userRole : undefined,
41
45
  )}
42
46
  >
43
47
  <div className={clsx(styles.messageGrid, "text-style-label")}>
44
- <i className={iconForMsg(message)} />
45
48
  {message.role}
49
+ {messageUrl ? (
50
+ <CopyButton
51
+ icon={ApplicationIcons.link}
52
+ value={messageUrl}
53
+ className={clsx(styles.copyLink)}
54
+ />
55
+ ) : (
56
+ ""
57
+ )}
46
58
  </div>
47
59
  <div
48
60
  className={clsx(
@@ -7,3 +7,12 @@
7
7
  .number {
8
8
  margin-top: 0.1em;
9
9
  }
10
+
11
+ .user {
12
+ background-color: var(--bs-secondary-bg);
13
+ border-radius: var(--bs-border-radius);
14
+ }
15
+
16
+ .container {
17
+ padding: 0.4em 0.4em 0.4em 0.4em;
18
+ }