inspect-ai 0.3.81__py3-none-any.whl → 0.3.82__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 (179) hide show
  1. inspect_ai/_cli/eval.py +35 -2
  2. inspect_ai/_cli/util.py +44 -1
  3. inspect_ai/_display/core/config.py +1 -1
  4. inspect_ai/_display/core/display.py +13 -4
  5. inspect_ai/_display/core/results.py +1 -1
  6. inspect_ai/_display/textual/widgets/task_detail.py +5 -4
  7. inspect_ai/_eval/eval.py +38 -1
  8. inspect_ai/_eval/evalset.py +5 -0
  9. inspect_ai/_eval/run.py +5 -2
  10. inspect_ai/_eval/task/log.py +53 -6
  11. inspect_ai/_eval/task/run.py +51 -10
  12. inspect_ai/_util/constants.py +2 -0
  13. inspect_ai/_util/file.py +17 -1
  14. inspect_ai/_util/json.py +36 -1
  15. inspect_ai/_view/server.py +113 -1
  16. inspect_ai/_view/www/App.css +1 -1
  17. inspect_ai/_view/www/dist/assets/index.css +518 -296
  18. inspect_ai/_view/www/dist/assets/index.js +38803 -36307
  19. inspect_ai/_view/www/eslint.config.mjs +1 -1
  20. inspect_ai/_view/www/log-schema.json +13 -0
  21. inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
  22. inspect_ai/_view/www/package.json +8 -2
  23. inspect_ai/_view/www/src/App.tsx +151 -855
  24. inspect_ai/_view/www/src/api/api-browser.ts +176 -5
  25. inspect_ai/_view/www/src/api/api-vscode.ts +75 -1
  26. inspect_ai/_view/www/src/api/client-api.ts +66 -10
  27. inspect_ai/_view/www/src/api/jsonrpc.ts +2 -0
  28. inspect_ai/_view/www/src/api/types.ts +107 -2
  29. inspect_ai/_view/www/src/appearance/icons.ts +1 -0
  30. inspect_ai/_view/www/src/components/AsciinemaPlayer.tsx +3 -3
  31. inspect_ai/_view/www/src/components/DownloadPanel.tsx +2 -2
  32. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +56 -61
  33. inspect_ai/_view/www/src/components/FindBand.tsx +17 -9
  34. inspect_ai/_view/www/src/components/HumanBaselineView.tsx +1 -1
  35. inspect_ai/_view/www/src/components/JsonPanel.tsx +14 -24
  36. inspect_ai/_view/www/src/components/LargeModal.tsx +2 -35
  37. inspect_ai/_view/www/src/components/LightboxCarousel.tsx +27 -11
  38. inspect_ai/_view/www/src/components/LiveVirtualList.module.css +11 -0
  39. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +177 -0
  40. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +3 -3
  41. inspect_ai/_view/www/src/components/MessageBand.tsx +14 -9
  42. inspect_ai/_view/www/src/components/MorePopOver.tsx +3 -3
  43. inspect_ai/_view/www/src/components/NavPills.tsx +20 -8
  44. inspect_ai/_view/www/src/components/NoContentsPanel.module.css +12 -0
  45. inspect_ai/_view/www/src/components/NoContentsPanel.tsx +20 -0
  46. inspect_ai/_view/www/src/components/ProgressBar.module.css +5 -4
  47. inspect_ai/_view/www/src/components/ProgressBar.tsx +3 -2
  48. inspect_ai/_view/www/src/components/PulsingDots.module.css +81 -0
  49. inspect_ai/_view/www/src/components/PulsingDots.tsx +45 -0
  50. inspect_ai/_view/www/src/components/TabSet.tsx +4 -37
  51. inspect_ai/_view/www/src/components/ToolButton.tsx +3 -4
  52. inspect_ai/_view/www/src/index.tsx +26 -94
  53. inspect_ai/_view/www/src/logfile/remoteLogFile.ts +9 -1
  54. inspect_ai/_view/www/src/logfile/remoteZipFile.ts +30 -4
  55. inspect_ai/_view/www/src/metadata/RenderedContent.tsx +4 -6
  56. inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +1 -1
  57. inspect_ai/_view/www/src/samples/InlineSampleDisplay.module.css +9 -1
  58. inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +67 -28
  59. inspect_ai/_view/www/src/samples/SampleDialog.tsx +51 -22
  60. inspect_ai/_view/www/src/samples/SampleDisplay.module.css +4 -0
  61. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +144 -90
  62. inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +4 -0
  63. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +82 -35
  64. inspect_ai/_view/www/src/samples/SamplesTools.tsx +23 -30
  65. inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +2 -1
  66. inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +1 -1
  67. inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +45 -53
  68. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +4 -1
  69. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +3 -0
  70. inspect_ai/_view/www/src/samples/chat/messages.ts +34 -0
  71. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.module.css +3 -0
  72. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +10 -1
  73. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +22 -46
  74. inspect_ai/_view/www/src/samples/descriptor/samplesDescriptor.tsx +25 -17
  75. inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.tsx +2 -1
  76. inspect_ai/_view/www/src/samples/descriptor/types.ts +6 -5
  77. inspect_ai/_view/www/src/samples/list/SampleFooter.module.css +21 -3
  78. inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +20 -1
  79. inspect_ai/_view/www/src/samples/list/SampleList.tsx +105 -85
  80. inspect_ai/_view/www/src/samples/list/SampleRow.module.css +6 -0
  81. inspect_ai/_view/www/src/samples/list/SampleRow.tsx +27 -14
  82. inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +29 -18
  83. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +28 -28
  84. inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +19 -9
  85. inspect_ai/_view/www/src/samples/sampleDataAdapter.ts +33 -0
  86. inspect_ai/_view/www/src/samples/sampleLimit.ts +2 -2
  87. inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +7 -9
  88. inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +7 -11
  89. inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +0 -13
  90. inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +0 -13
  91. inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +0 -13
  92. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +4 -0
  93. inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +10 -24
  94. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +0 -13
  95. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +4 -22
  96. inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +15 -24
  97. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +0 -13
  98. inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +6 -28
  99. inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +24 -34
  100. inspect_ai/_view/www/src/samples/transcript/ToolEventView.module.css +4 -0
  101. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +8 -13
  102. inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +197 -338
  103. inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.module.css +16 -0
  104. inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.tsx +44 -0
  105. inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +7 -4
  106. inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +52 -58
  107. inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.module.css +23 -0
  108. inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.tsx +27 -0
  109. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +30 -1
  110. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +102 -72
  111. inspect_ai/_view/www/src/scoring/utils.ts +87 -0
  112. inspect_ai/_view/www/src/state/appSlice.ts +244 -0
  113. inspect_ai/_view/www/src/state/hooks.ts +397 -0
  114. inspect_ai/_view/www/src/state/logPolling.ts +196 -0
  115. inspect_ai/_view/www/src/state/logSlice.ts +214 -0
  116. inspect_ai/_view/www/src/state/logsPolling.ts +118 -0
  117. inspect_ai/_view/www/src/state/logsSlice.ts +181 -0
  118. inspect_ai/_view/www/src/state/samplePolling.ts +311 -0
  119. inspect_ai/_view/www/src/state/sampleSlice.ts +127 -0
  120. inspect_ai/_view/www/src/state/sampleUtils.ts +21 -0
  121. inspect_ai/_view/www/src/state/scrolling.ts +206 -0
  122. inspect_ai/_view/www/src/state/store.ts +168 -0
  123. inspect_ai/_view/www/src/state/store_filter.ts +84 -0
  124. inspect_ai/_view/www/src/state/utils.ts +23 -0
  125. inspect_ai/_view/www/src/storage/index.ts +26 -0
  126. inspect_ai/_view/www/src/types/log.d.ts +2 -0
  127. inspect_ai/_view/www/src/types.ts +94 -32
  128. inspect_ai/_view/www/src/utils/attachments.ts +58 -23
  129. inspect_ai/_view/www/src/utils/logger.ts +52 -0
  130. inspect_ai/_view/www/src/utils/polling.ts +100 -0
  131. inspect_ai/_view/www/src/utils/react.ts +30 -0
  132. inspect_ai/_view/www/src/utils/vscode.ts +1 -1
  133. inspect_ai/_view/www/src/workspace/WorkSpace.tsx +181 -216
  134. inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +11 -53
  135. inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +8 -18
  136. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.module.css +1 -0
  137. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +40 -22
  138. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.module.css +0 -1
  139. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +98 -39
  140. inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.module.css +32 -0
  141. inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.tsx +32 -0
  142. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +11 -13
  143. inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +6 -2
  144. inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +4 -4
  145. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +28 -13
  146. inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +5 -10
  147. inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +4 -4
  148. inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.module.css +22 -0
  149. inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.tsx +19 -0
  150. inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +110 -115
  151. inspect_ai/_view/www/src/workspace/tabs/grouping.ts +37 -5
  152. inspect_ai/_view/www/src/workspace/tabs/types.ts +4 -0
  153. inspect_ai/_view/www/src/workspace/types.ts +4 -3
  154. inspect_ai/_view/www/src/workspace/utils.ts +4 -4
  155. inspect_ai/_view/www/vite.config.js +6 -0
  156. inspect_ai/_view/www/yarn.lock +370 -354
  157. inspect_ai/log/_condense.py +26 -0
  158. inspect_ai/log/_log.py +6 -3
  159. inspect_ai/log/_recorders/buffer/__init__.py +14 -0
  160. inspect_ai/log/_recorders/buffer/buffer.py +30 -0
  161. inspect_ai/log/_recorders/buffer/database.py +685 -0
  162. inspect_ai/log/_recorders/buffer/filestore.py +259 -0
  163. inspect_ai/log/_recorders/buffer/types.py +84 -0
  164. inspect_ai/log/_recorders/eval.py +2 -11
  165. inspect_ai/log/_recorders/types.py +30 -0
  166. inspect_ai/log/_transcript.py +27 -1
  167. inspect_ai/model/_call_tools.py +1 -0
  168. inspect_ai/model/_generate_config.py +2 -2
  169. inspect_ai/model/_model.py +1 -0
  170. inspect_ai/tool/_tool_support_helpers.py +4 -4
  171. inspect_ai/tool/_tools/_web_browser/_web_browser.py +3 -1
  172. inspect_ai/util/_subtask.py +1 -0
  173. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/METADATA +1 -1
  174. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/RECORD +178 -138
  175. inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +0 -22
  176. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/WHEEL +0 -0
  177. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/entry_points.txt +0 -0
  178. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/licenses/LICENSE +0 -0
  179. {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  import clsx from "clsx";
2
2
  import { FC } from "react";
3
- import { SampleSummary } from "../../api/types";
4
3
  import { ExpandablePanel } from "../../components/ExpandablePanel";
5
4
  import { LabeledValue } from "../../components/LabeledValue";
6
5
  import { EvalDescriptor } from "../../samples/descriptor/types";
7
6
  import { scoreFilterItems } from "../../samples/sample-tools/filters";
7
+ import { useEvalDescriptor } from "../../state/hooks";
8
8
  import {
9
9
  EvalDataset,
10
10
  EvalPlan,
@@ -20,9 +20,8 @@ interface SecondaryBarProps {
20
20
  evalPlan?: EvalPlan;
21
21
  evalResults?: EvalResults;
22
22
  evalStats?: EvalStats;
23
- evalDescriptor?: EvalDescriptor;
24
- samples?: SampleSummary[];
25
23
  status?: string;
24
+ sampleCount?: number;
26
25
  }
27
26
 
28
27
  /**
@@ -33,10 +32,10 @@ export const SecondaryBar: FC<SecondaryBarProps> = ({
33
32
  evalPlan,
34
33
  evalResults,
35
34
  evalStats,
36
- samples,
37
- evalDescriptor,
38
35
  status,
36
+ sampleCount,
39
37
  }) => {
38
+ const evalDescriptor = useEvalDescriptor();
40
39
  if (!evalSpec || status !== "success") {
41
40
  return null;
42
41
  }
@@ -56,11 +55,11 @@ export const SecondaryBar: FC<SecondaryBarProps> = ({
56
55
  <LabeledValue
57
56
  key="sb-dataset"
58
57
  label="Dataset"
59
- className={(styles.staticCol, "text-size-small")}
58
+ className={clsx(styles.staticCol, "text-size-small")}
60
59
  >
61
60
  <DatasetSummary
62
61
  dataset={evalSpec.dataset}
63
- samples={samples}
62
+ sampleCount={sampleCount}
64
63
  epochs={epochs}
65
64
  />
66
65
  </LabeledValue>
@@ -122,6 +121,7 @@ export const SecondaryBar: FC<SecondaryBarProps> = ({
122
121
 
123
122
  return (
124
123
  <ExpandablePanel
124
+ id={"secondary-nav-bar"}
125
125
  className={clsx(styles.container, "text-size-small")}
126
126
  collapse={true}
127
127
  lines={4}
@@ -146,16 +146,16 @@ export const SecondaryBar: FC<SecondaryBarProps> = ({
146
146
 
147
147
  interface DatasetSummaryProps {
148
148
  dataset?: EvalDataset;
149
- samples?: SampleSummary[];
150
149
  epochs: number;
150
+ sampleCount?: number;
151
151
  }
152
152
 
153
153
  /**
154
154
  * A component that displays the dataset
155
155
  */
156
156
  const DatasetSummary: FC<DatasetSummaryProps> = ({
157
+ sampleCount,
157
158
  dataset,
158
- samples,
159
159
  epochs,
160
160
  }) => {
161
161
  if (!dataset) {
@@ -164,15 +164,13 @@ const DatasetSummary: FC<DatasetSummaryProps> = ({
164
164
 
165
165
  return (
166
166
  <div>
167
- {samples?.length
168
- ? formatDataset(samples.length, epochs, dataset.name)
169
- : ""}
167
+ {sampleCount ? formatDataset(sampleCount, epochs, dataset.name) : ""}
170
168
  </div>
171
169
  );
172
170
  };
173
171
 
174
172
  interface ScoreSummaryProps {
175
- evalDescriptor?: EvalDescriptor;
173
+ evalDescriptor?: EvalDescriptor | null;
176
174
  }
177
175
 
178
176
  /**
@@ -37,13 +37,17 @@ export const RunningPanel: FC<StatusProps> = ({ sampleCount }) => {
37
37
  );
38
38
  };
39
39
 
40
- interface StatusPanelProps {
40
+ export interface StatusPanelProps {
41
41
  icon: string;
42
42
  status: string;
43
43
  sampleCount: number;
44
44
  }
45
45
 
46
- const StatusPanel: FC<StatusPanelProps> = ({ icon, status, sampleCount }) => {
46
+ export const StatusPanel: FC<StatusPanelProps> = ({
47
+ icon,
48
+ status,
49
+ sampleCount,
50
+ }) => {
47
51
  return (
48
52
  <div className={styles.statusPanel}>
49
53
  <i className={clsx(icon, styles.statusIcon)} style={{}} />
@@ -1,16 +1,16 @@
1
1
  import clsx from "clsx";
2
2
  import { FC } from "react";
3
+ import { useStore } from "../../state/store";
3
4
  import styles from "./LogDirectoryTitleView.module.css";
4
5
 
5
6
  interface LogDirectoryTitleViewProps {
6
7
  log_dir?: string;
7
- offcanvas: boolean;
8
8
  }
9
9
 
10
10
  export const LogDirectoryTitleView: FC<LogDirectoryTitleViewProps> = ({
11
11
  log_dir,
12
- offcanvas,
13
12
  }) => {
13
+ const offCanvas = useStore((state) => state.app.offcanvas);
14
14
  if (log_dir) {
15
15
  const displayDir = prettyDir(log_dir);
16
16
  return (
@@ -28,14 +28,14 @@ export const LogDirectoryTitleView: FC<LogDirectoryTitleViewProps> = ({
28
28
  title={displayDir}
29
29
  className={clsx("text-size-base", styles.dirname)}
30
30
  >
31
- {offcanvas ? displayDir : ""}
31
+ {offCanvas ? displayDir : ""}
32
32
  </span>
33
33
  </div>
34
34
  );
35
35
  } else {
36
36
  return (
37
37
  <span className={clsx("text-size-title")}>
38
- {offcanvas ? "Log History" : ""}
38
+ {offCanvas ? "Log History" : ""}
39
39
  </span>
40
40
  );
41
41
  }
@@ -1,9 +1,11 @@
1
1
  import clsx from "clsx";
2
- import { FC } from "react";
2
+ import { FC, MouseEvent, useCallback, useRef } from "react";
3
3
  import { Fragment } from "react/jsx-runtime";
4
4
  import { EvalLogHeader, LogFiles } from "../../api/types";
5
5
  import { ApplicationIcons } from "../../appearance/icons";
6
6
  import { ProgressBar } from "../../components/ProgressBar";
7
+ import { useStatefulScrollPosition } from "../../state/scrolling";
8
+ import { useStore } from "../../state/store";
7
9
  import { LogDirectoryTitleView } from "./LogDirectoryTitleView";
8
10
  import styles from "./Sidebar.module.css";
9
11
  import { SidebarLogEntry } from "./SidebarLogEntry";
@@ -11,8 +13,6 @@ import { SidebarLogEntry } from "./SidebarLogEntry";
11
13
  interface SidebarProps {
12
14
  logs: LogFiles;
13
15
  logHeaders: Record<string, EvalLogHeader>;
14
- offcanvas: boolean;
15
- setOffcanvas: (offcanvas: boolean) => void;
16
16
  loading: boolean;
17
17
  selectedIndex: number;
18
18
  onSelectedIndexChanged: (index: number) => void;
@@ -21,29 +21,40 @@ interface SidebarProps {
21
21
  export const Sidebar: FC<SidebarProps> = ({
22
22
  logs,
23
23
  logHeaders,
24
- offcanvas,
25
- setOffcanvas,
26
24
  loading,
27
25
  selectedIndex,
28
26
  onSelectedIndexChanged,
29
27
  }) => {
30
- const handleToggle = () => {
31
- setOffcanvas(!offcanvas);
32
- };
28
+ const setOffCanvas = useStore((state) => state.appActions.setOffcanvas);
29
+ const offCanvas = useStore((state) => state.app.offcanvas);
30
+ const handleToggle = useCallback(() => {
31
+ setOffCanvas(!offCanvas);
32
+ }, [offCanvas, setOffCanvas]);
33
+
34
+ const sidebarContentsRef = useRef(null);
35
+ useStatefulScrollPosition(sidebarContentsRef, "sidebar-contents", 1000);
36
+
37
+ const handleClick = useCallback(
38
+ (e: MouseEvent<HTMLLIElement>) => {
39
+ const index = Number((e.currentTarget as HTMLLIElement).dataset.index);
40
+ onSelectedIndexChanged(index);
41
+ },
42
+ [onSelectedIndexChanged],
43
+ );
33
44
 
34
45
  return (
35
46
  <Fragment>
36
47
  {/* Optional backdrop for small screens, appears only when offcanvas is open */}
37
- {offcanvas && <div className={styles.backdrop} onClick={handleToggle} />}
48
+ {offCanvas && <div className={styles.backdrop} onClick={handleToggle} />}
38
49
 
39
50
  <div
40
51
  className={clsx(
41
52
  styles.sidebar,
42
- offcanvas ? styles.sidebarOpen : styles.sidebarClosed,
53
+ offCanvas ? styles.sidebarOpen : styles.sidebarClosed,
43
54
  )}
44
55
  >
45
56
  <div className={styles.header}>
46
- <LogDirectoryTitleView log_dir={logs.log_dir} offcanvas={offcanvas} />
57
+ <LogDirectoryTitleView log_dir={logs.log_dir} />
47
58
  <button
48
59
  onClick={handleToggle}
49
60
  className={clsx("btn", styles.toggle)}
@@ -58,7 +69,10 @@ export const Sidebar: FC<SidebarProps> = ({
58
69
  <ProgressBar animating={loading} />
59
70
  </div>
60
71
 
61
- <ul className={clsx("list-group", styles.list)}>
72
+ <ul
73
+ ref={sidebarContentsRef}
74
+ className={clsx("list-group", styles.list)}
75
+ >
62
76
  {logs.files.map((file, index) => {
63
77
  const logHeader = logHeaders[file.name];
64
78
  return (
@@ -70,7 +84,8 @@ export const Sidebar: FC<SidebarProps> = ({
70
84
  styles.item,
71
85
  selectedIndex === index ? styles.active : undefined,
72
86
  )}
73
- onClick={() => onSelectedIndexChanged(index)}
87
+ data-index={index}
88
+ onClick={handleClick}
74
89
  >
75
90
  <SidebarLogEntry
76
91
  logHeader={logHeader}
@@ -1,4 +1,4 @@
1
- import { FC, useEffect, useState } from "react";
1
+ import { FC } from "react";
2
2
  import { SampleSummary } from "../../api/types";
3
3
  import { MessageBand } from "../../components/MessageBand";
4
4
  import { PlanCard } from "../../plan/PlanCard";
@@ -20,6 +20,7 @@ interface PlanTabProps {
20
20
  samples?: SampleSummary[];
21
21
  evalStatus?: "started" | "error" | "cancelled" | "success";
22
22
  evalError?: EvalError;
23
+ sampleCount?: number;
23
24
  }
24
25
 
25
26
  export const InfoTab: FC<PlanTabProps> = ({
@@ -27,17 +28,12 @@ export const InfoTab: FC<PlanTabProps> = ({
27
28
  evalPlan,
28
29
  evalResults,
29
30
  evalStats,
30
- samples,
31
31
  evalStatus,
32
32
  evalError,
33
+ sampleCount,
33
34
  }) => {
34
- const [hidden, setHidden] = useState(false);
35
- useEffect(() => {
36
- setHidden(false);
37
- }, [evalSpec, evalPlan, evalResults, evalStats, samples]);
38
-
39
35
  const showWarning =
40
- (!samples || samples.length === 0) &&
36
+ sampleCount === 0 &&
41
37
  evalStatus === "success" &&
42
38
  evalSpec?.dataset.samples &&
43
39
  evalSpec.dataset.samples > 0;
@@ -46,9 +42,8 @@ export const InfoTab: FC<PlanTabProps> = ({
46
42
  <div style={{ width: "100%" }}>
47
43
  {showWarning ? (
48
44
  <MessageBand
45
+ id="sample-too-large"
49
46
  message="Unable to display samples (this evaluation log may be too large)."
50
- hidden={hidden}
51
- setHidden={setHidden}
52
47
  type="warning"
53
48
  />
54
49
  ) : (
@@ -1,16 +1,15 @@
1
1
  import { filename } from "../../utils/path";
2
2
 
3
3
  import { FC } from "react";
4
- import { Capabilities } from "../../api/types";
5
4
  import { DownloadPanel } from "../../components/DownloadPanel";
6
5
  import { JSONPanel } from "../../components/JsonPanel";
6
+ import { useStore } from "../../state/store";
7
7
  import styles from "./JsonTab.module.css";
8
8
 
9
9
  const kJsonMaxSize = 10000000;
10
10
 
11
11
  interface JsonTabProps {
12
12
  logFile?: string;
13
- capabilities: Capabilities;
14
13
  selected: boolean;
15
14
  json: string;
16
15
  }
@@ -18,8 +17,9 @@ interface JsonTabProps {
18
17
  /**
19
18
  * Renders JSON tab
20
19
  */
21
- export const JsonTab: FC<JsonTabProps> = ({ logFile, capabilities, json }) => {
22
- if (logFile && json.length > kJsonMaxSize && capabilities.downloadFiles) {
20
+ export const JsonTab: FC<JsonTabProps> = ({ logFile, json }) => {
21
+ const downloadFiles = useStore((state) => state.capabilities.downloadFiles);
22
+ if (logFile && json.length > kJsonMaxSize && downloadFiles) {
23
23
  // This JSON file is so large we can't really productively render it
24
24
  // we should instead just provide a DL link
25
25
  const file = `${filename(logFile)}.json`;
@@ -0,0 +1,22 @@
1
+ .panel {
2
+ width: 100%;
3
+ display: flex;
4
+ justify-content: center;
5
+ }
6
+
7
+ .container {
8
+ margin-top: 3em;
9
+ display: grid;
10
+ grid-template-columns: max-content max-content;
11
+ column-gap: 0.3em;
12
+ }
13
+
14
+ .spinner {
15
+ border-width: 1px;
16
+ height: 15px;
17
+ width: 15px;
18
+ }
19
+
20
+ .text {
21
+ margin-top: -2px;
22
+ }
@@ -0,0 +1,19 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+
4
+ import styles from "./RunningNoSamples.module.css";
5
+
6
+ interface RunningNoSamplesProps {}
7
+
8
+ export const RunningNoSamples: FC<RunningNoSamplesProps> = () => {
9
+ return (
10
+ <div className={clsx(styles.panel)}>
11
+ <div className={clsx(styles.container, "text-size-smaller")}>
12
+ <div className={clsx(styles.spinner, "spinner-border")} role="status">
13
+ <span className={clsx("visually-hidden")}>starting...</span>
14
+ </div>
15
+ <div className={clsx(styles.text)}>starting....</div>
16
+ </div>
17
+ </div>
18
+ );
19
+ };
@@ -1,135 +1,136 @@
1
1
  import {
2
2
  FC,
3
3
  Fragment,
4
- RefObject,
5
4
  useCallback,
6
5
  useEffect,
6
+ useMemo,
7
7
  useRef,
8
8
  useState,
9
9
  } from "react";
10
10
  import { VirtuosoHandle } from "react-virtuoso";
11
- import { SampleSummary } from "../../api/types.ts";
12
- import { EmptyPanel } from "../../components/EmptyPanel.tsx";
13
- import { InlineSampleDisplay } from "../../samples/InlineSampleDisplay";
14
- import { SampleDialog } from "../../samples/SampleDialog";
15
- import { SamplesDescriptor } from "../../samples/descriptor/samplesDescriptor.tsx";
16
- import { SampleList } from "../../samples/list/SampleList";
17
- import { SampleMode, ScoreFilter } from "../../types.ts";
18
- import { EvalSample } from "../../types/log";
11
+ import { NoContentsPanel } from "../../components/NoContentsPanel.tsx";
12
+ import { InlineSampleDisplay } from "../../samples/InlineSampleDisplay.tsx";
13
+ import { SampleDialog } from "../../samples/SampleDialog.tsx";
14
+ import { SampleList } from "../../samples/list/SampleList.tsx";
15
+ import {
16
+ useFilteredSamples,
17
+ useGroupBy,
18
+ useGroupByOrder,
19
+ useSampleDescriptor,
20
+ useScore,
21
+ useTotalSampleCount,
22
+ } from "../../state/hooks.ts";
23
+ import { useStore } from "../../state/store.ts";
24
+ import { RunningNoSamples } from "./RunningNoSamples.tsx";
19
25
  import { getSampleProcessor } from "./grouping.ts";
20
26
  import { ListItem } from "./types.ts";
21
27
 
22
28
  interface SamplesTabProps {
23
- // Optional props
24
- sample?: EvalSample;
25
- samples?: SampleSummary[];
26
- sampleDescriptor?: SamplesDescriptor;
27
- sampleError?: Error;
28
-
29
29
  // Required props
30
- sampleMode: SampleMode;
31
- groupBy: "epoch" | "sample" | "none";
32
- groupByOrder: "asc" | "desc";
33
- sampleStatus: string;
34
- selectedSampleIndex: number;
35
- setSelectedSampleIndex: (index: number) => void;
36
- showingSampleDialog: boolean;
37
- setShowingSampleDialog: (showing: boolean) => void;
38
- selectedSampleTab?: string;
39
- setSelectedSampleTab: (tab: string) => void;
40
- epoch: string;
41
- filter: ScoreFilter;
42
- sampleScrollPositionRef: RefObject<number>;
43
- setSampleScrollPosition: (position: number) => void;
44
- sampleTabScrollRef: RefObject<HTMLDivElement | null>;
30
+ running: boolean;
45
31
  }
46
32
 
47
- export const SamplesTab: FC<SamplesTabProps> = ({
48
- sample,
49
- samples,
50
- sampleMode,
51
- groupBy,
52
- groupByOrder,
53
- sampleDescriptor,
54
- sampleStatus,
55
- sampleError,
56
- selectedSampleIndex,
57
- setSelectedSampleIndex,
58
- showingSampleDialog,
59
- setShowingSampleDialog,
60
- selectedSampleTab,
61
- setSelectedSampleTab,
62
- sampleScrollPositionRef,
63
- setSampleScrollPosition,
64
- sampleTabScrollRef,
65
- }) => {
33
+ export const SamplesTab: FC<SamplesTabProps> = ({ running }) => {
34
+ const selectSample = useStore((state) => state.logActions.selectSample);
35
+ const selectedSampleIndex = useStore(
36
+ (state) => state.log.selectedSampleIndex,
37
+ );
38
+
39
+ const sampleSummaries = useFilteredSamples();
40
+ const selectedLogSummary = useStore((state) => state.log.selectedLogSummary);
41
+ const totalSampleCount = useTotalSampleCount();
42
+
43
+ const samplesDescriptor = useSampleDescriptor();
44
+ const groupBy = useGroupBy();
45
+ const groupByOrder = useGroupByOrder();
46
+ const currentScore = useScore();
47
+
48
+ const selectedSample = useStore((state) => state.sample.selectedSample);
49
+
66
50
  const [items, setItems] = useState<ListItem[]>([]);
67
51
  const [sampleItems, setSampleItems] = useState<ListItem[]>([]);
68
52
 
69
53
  const sampleListHandle = useRef<VirtuosoHandle | null>(null);
70
54
  const sampleDialogRef = useRef<HTMLDivElement>(null);
71
55
 
56
+ const selectedSampleTab = useStore((state) => state.app.tabs.sample);
57
+ const setSelectedSampleTab = useStore(
58
+ (state) => state.appActions.setSampleTab,
59
+ );
60
+ const showingSampleDialog = useStore((state) => state.app.dialogs.sample);
61
+ const setShowingSampleDialog = useStore(
62
+ (state) => state.appActions.setShowingSampleDialog,
63
+ );
64
+
72
65
  // Shows the sample dialog
73
66
  const showSample = useCallback(
74
67
  (index: number) => {
75
- setSelectedSampleIndex(index);
68
+ selectSample(index);
76
69
  setShowingSampleDialog(true);
77
70
  },
78
- [setSelectedSampleIndex, setShowingSampleDialog],
71
+ [selectSample, setShowingSampleDialog],
79
72
  );
80
73
 
74
+ // Keep the selected item scrolled into view
75
+ useEffect(() => {
76
+ setTimeout(() => {
77
+ if (sampleListHandle.current) {
78
+ sampleListHandle.current.scrollIntoView({ index: selectedSampleIndex });
79
+ }
80
+ }, 0);
81
+ }, [selectedSampleIndex]);
82
+
83
+ // Focus the dialog when it is shown
81
84
  useEffect(() => {
82
85
  if (showingSampleDialog) {
83
86
  setTimeout(() => {
84
87
  sampleDialogRef.current?.focus();
85
88
  }, 0);
86
- } else {
87
- setTimeout(() => {
88
- if (sampleListHandle.current) {
89
- sampleListHandle.current.scrollToIndex(0);
90
- }
91
- }, 0);
92
89
  }
93
90
  }, [showingSampleDialog]);
94
91
 
92
+ const sampleProcessor = useMemo(() => {
93
+ if (!samplesDescriptor) return undefined;
94
+
95
+ return getSampleProcessor(
96
+ sampleSummaries || [],
97
+ selectedLogSummary?.eval?.config?.epochs || 1,
98
+ groupBy,
99
+ groupByOrder,
100
+ samplesDescriptor,
101
+ currentScore,
102
+ );
103
+ }, [
104
+ samplesDescriptor,
105
+ sampleSummaries,
106
+ selectedLogSummary?.eval?.config?.epochs,
107
+ groupBy,
108
+ groupByOrder,
109
+ currentScore,
110
+ ]);
111
+
95
112
  useEffect(() => {
96
- const sampleProcessor = sampleDescriptor
97
- ? getSampleProcessor(
98
- samples || [],
99
- groupBy,
100
- groupByOrder,
101
- sampleDescriptor,
102
- )
103
- : undefined;
104
-
105
- // Process the samples into the proper data structure
106
- const items = samples?.flatMap((sample, index) => {
113
+ const resolvedSamples = sampleSummaries?.flatMap((sample, index) => {
107
114
  const results: ListItem[] = [];
108
- const previousSample = index !== 0 ? samples[index - 1] : undefined;
115
+ const previousSample =
116
+ index !== 0 ? sampleSummaries[index - 1] : undefined;
109
117
  const items = sampleProcessor
110
118
  ? sampleProcessor(sample, index, previousSample)
111
119
  : [];
120
+
112
121
  results.push(...items);
113
122
  return results;
114
123
  });
115
124
 
116
- setItems(items || []);
125
+ setItems(resolvedSamples || []);
117
126
  setSampleItems(
118
- items
119
- ? items.filter((item) => {
127
+ resolvedSamples
128
+ ? resolvedSamples.filter((item) => {
120
129
  return item.type === "sample";
121
130
  })
122
131
  : [],
123
132
  );
124
- }, [samples, groupBy, groupByOrder, sampleDescriptor]);
125
-
126
- const nextSampleIndex = useCallback(() => {
127
- if (selectedSampleIndex < sampleItems.length - 1) {
128
- return selectedSampleIndex + 1;
129
- } else {
130
- return -1;
131
- }
132
- }, [selectedSampleIndex, sampleItems.length]);
133
+ }, [sampleSummaries, sampleProcessor]);
133
134
 
134
135
  const previousSampleIndex = useCallback(() => {
135
136
  return selectedSampleIndex > 0 ? selectedSampleIndex - 1 : -1;
@@ -137,68 +138,62 @@ export const SamplesTab: FC<SamplesTabProps> = ({
137
138
 
138
139
  // Manage the next / previous state the selected sample
139
140
  const nextSample = useCallback(() => {
140
- const next = nextSampleIndex();
141
- if (sampleStatus !== "loading" && next > -1) {
142
- setSelectedSampleIndex(next);
141
+ const next = Math.min(selectedSampleIndex + 1, sampleItems.length - 1);
142
+ if (next > -1) {
143
+ selectSample(next);
143
144
  }
144
- }, [nextSampleIndex, sampleStatus, setSelectedSampleIndex]);
145
+ }, [selectedSampleIndex, sampleItems, selectSample]);
145
146
 
146
147
  const previousSample = useCallback(() => {
147
148
  const prev = previousSampleIndex();
148
- if (sampleStatus !== "loading" && prev > -1) {
149
- setSelectedSampleIndex(prev);
149
+ if (prev > -1) {
150
+ selectSample(prev);
150
151
  }
151
- }, [previousSampleIndex, sampleStatus, setSelectedSampleIndex]);
152
+ }, [previousSampleIndex, selectSample]);
152
153
 
153
154
  const title =
154
155
  selectedSampleIndex > -1 && sampleItems.length > selectedSampleIndex
155
156
  ? sampleItems[selectedSampleIndex].label
156
157
  : "";
157
158
 
158
- if (!sampleDescriptor) {
159
- return <EmptyPanel />;
159
+ if (totalSampleCount === 0) {
160
+ if (running) {
161
+ return <RunningNoSamples />;
162
+ } else {
163
+ return <NoContentsPanel text="No samples" />;
164
+ }
160
165
  } else {
161
166
  return (
162
167
  <Fragment>
163
- {sampleDescriptor && sampleMode === "single" ? (
168
+ {samplesDescriptor && totalSampleCount === 1 ? (
164
169
  <InlineSampleDisplay
165
170
  id="sample-display"
166
- sample={sample}
167
- sampleStatus={sampleStatus}
168
- sampleError={sampleError}
169
- sampleDescriptor={sampleDescriptor}
170
171
  selectedTab={selectedSampleTab}
171
172
  setSelectedTab={setSelectedSampleTab}
172
- scrollRef={sampleTabScrollRef}
173
173
  />
174
174
  ) : undefined}
175
- {sampleDescriptor && sampleMode === "many" ? (
175
+ {samplesDescriptor && totalSampleCount > 1 ? (
176
176
  <SampleList
177
177
  listHandle={sampleListHandle}
178
178
  items={items}
179
- sampleDescriptor={sampleDescriptor}
180
- selectedIndex={selectedSampleIndex}
179
+ running={running}
181
180
  nextSample={nextSample}
182
181
  prevSample={previousSample}
183
182
  showSample={showSample}
184
183
  />
185
184
  ) : undefined}
186
- <SampleDialog
187
- id={String(sample?.id || "")}
188
- title={title}
189
- sample={sample}
190
- sampleStatus={sampleStatus}
191
- sampleError={sampleError}
192
- sampleDescriptor={sampleDescriptor}
193
- showingSampleDialog={showingSampleDialog}
194
- setShowingSampleDialog={setShowingSampleDialog}
195
- selectedTab={selectedSampleTab}
196
- setSelectedTab={setSelectedSampleTab}
197
- nextSample={nextSample}
198
- prevSample={previousSample}
199
- sampleScrollPositionRef={sampleScrollPositionRef}
200
- setSampleScrollPosition={setSampleScrollPosition}
201
- />
185
+ {showingSampleDialog ? (
186
+ <SampleDialog
187
+ id={String(selectedSample?.id || "")}
188
+ title={title}
189
+ showingSampleDialog={showingSampleDialog}
190
+ setShowingSampleDialog={setShowingSampleDialog}
191
+ selectedTab={selectedSampleTab}
192
+ setSelectedTab={setSelectedSampleTab}
193
+ nextSample={nextSample}
194
+ prevSample={previousSample}
195
+ />
196
+ ) : undefined}
202
197
  </Fragment>
203
198
  );
204
199
  }