inspect-ai 0.3.80__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.80.dist-info → inspect_ai-0.3.82.dist-info}/METADATA +2 -2
  174. {inspect_ai-0.3.80.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.80.dist-info → inspect_ai-0.3.82.dist-info}/WHEEL +0 -0
  177. {inspect_ai-0.3.80.dist-info → inspect_ai-0.3.82.dist-info}/entry_points.txt +0 -0
  178. {inspect_ai-0.3.80.dist-info → inspect_ai-0.3.82.dist-info}/licenses/LICENSE +0 -0
  179. {inspect_ai-0.3.80.dist-info → inspect_ai-0.3.82.dist-info}/top_level.txt +0 -0
@@ -15,14 +15,14 @@ import {
15
15
  export interface SamplesDescriptor {
16
16
  evalDescriptor: EvalDescriptor;
17
17
  messageShape: MessageShape;
18
- selectedScoreDescriptor?: ScoreDescriptor;
19
18
  selectedScore: (sample: BasicSampleData) => SelectedScore | undefined;
20
- selectedScorerDescriptor: (sample: BasicSampleData) => ScorerDescriptor;
19
+ selectedScorerDescriptor: (
20
+ sample: BasicSampleData,
21
+ ) => ScorerDescriptor | undefined;
21
22
  }
22
23
 
23
24
  export const createEvalDescriptor = (
24
25
  scores: ScoreLabel[],
25
- epochs: number,
26
26
  samples?: SampleSummary[],
27
27
  ): EvalDescriptor | undefined => {
28
28
  if (!samples) {
@@ -57,10 +57,10 @@ export const createEvalDescriptor = (
57
57
 
58
58
  const scoreAnswer = (
59
59
  sample: BasicSampleData,
60
- scorer: string,
60
+ scorer: ScoreLabel,
61
61
  ): string | undefined => {
62
62
  if (sample && sample.scores) {
63
- const sampleScore = sample.scores[scorer];
63
+ const sampleScore = sample.scores[scorer.name];
64
64
  if (sampleScore && sampleScore.answer) {
65
65
  return sampleScore.answer;
66
66
  }
@@ -181,7 +181,7 @@ export const createEvalDescriptor = (
181
181
  return scoreExplanation(sample, scoreLabel.scorer) || "";
182
182
  },
183
183
  answer: () => {
184
- return scoreAnswer(sample, scoreLabel.scorer) || "";
184
+ return scoreAnswer(sample, scoreLabel) || "";
185
185
  },
186
186
  scores: () => {
187
187
  if (!sample || !sample.scores) {
@@ -252,8 +252,11 @@ export const createEvalDescriptor = (
252
252
 
253
253
  const score = (
254
254
  sample: BasicSampleData,
255
- scoreLabel: ScoreLabel,
256
- ): SelectedScore => {
255
+ scoreLabel?: ScoreLabel,
256
+ ): SelectedScore | undefined => {
257
+ if (!scoreLabel) {
258
+ return undefined;
259
+ }
257
260
  return {
258
261
  value: scoreValue(sample, scoreLabel),
259
262
  render: () => {
@@ -263,8 +266,6 @@ export const createEvalDescriptor = (
263
266
  };
264
267
 
265
268
  return {
266
- epochs,
267
- samples,
268
269
  scores,
269
270
  scorerDescriptor,
270
271
  scoreDescriptor,
@@ -274,14 +275,17 @@ export const createEvalDescriptor = (
274
275
  };
275
276
 
276
277
  export const createSamplesDescriptor = (
278
+ samples: SampleSummary[],
277
279
  evalDescriptor: EvalDescriptor,
278
- selectedScore: ScoreLabel,
280
+ selectedScore?: ScoreLabel,
279
281
  ): SamplesDescriptor | undefined => {
280
282
  // Find the total length of the value so we can compute an average
281
- const sizes = evalDescriptor.samples.reduce(
283
+ const sizes = samples.reduce(
282
284
  (previous, current) => {
283
285
  const text = inputString(current.input).join(" ");
284
- const score = evalDescriptor.score(current, selectedScore);
286
+ const score = selectedScore
287
+ ? evalDescriptor.score(current, selectedScore)
288
+ : undefined;
285
289
  const scoreValue = score?.value;
286
290
  const scoreText = scoreValue
287
291
  ? String(scoreValue)
@@ -296,7 +300,9 @@ export const createSamplesDescriptor = (
296
300
  previous[2] = Math.min(
297
301
  Math.max(
298
302
  previous[2],
299
- evalDescriptor.scoreAnswer(current, selectedScore?.name)?.length || 0,
303
+ selectedScore
304
+ ? evalDescriptor.scoreAnswer(current, selectedScore)?.length || 0
305
+ : 0,
300
306
  ),
301
307
  300,
302
308
  );
@@ -353,10 +359,12 @@ export const createSamplesDescriptor = (
353
359
  return {
354
360
  evalDescriptor,
355
361
  messageShape,
356
- selectedScoreDescriptor: evalDescriptor.scoreDescriptor(selectedScore),
357
- selectedScore: (sample) => evalDescriptor.score(sample, selectedScore),
362
+ selectedScore: (sample) =>
363
+ selectedScore ? evalDescriptor.score(sample, selectedScore) : undefined,
358
364
  selectedScorerDescriptor: (sample) =>
359
- evalDescriptor.scorerDescriptor(sample, selectedScore),
365
+ selectedScore
366
+ ? evalDescriptor.scorerDescriptor(sample, selectedScore)
367
+ : undefined,
360
368
  };
361
369
  };
362
370
 
@@ -52,13 +52,14 @@ export const objectScoreDescriptor = (values: Value2[]): ScoreDescriptor => {
52
52
  : String(value);
53
53
  scores.push(
54
54
  <div
55
+ key={`score-value-${index}`}
55
56
  className={clsx(
56
57
  styles.container,
57
58
  index + 1 < keys.length ? styles.padded : undefined,
58
59
  )}
59
60
  >
60
61
  <div className={clsx(styles.key, "text-size-smaller")}>{key}</div>
61
- <div className={clsx(styles.value, "text-size-title")}>
62
+ <div className={clsx(styles.value, "text-size-large")}>
62
63
  {formattedValue}
63
64
  </div>
64
65
  </div>,
@@ -1,11 +1,9 @@
1
1
  import { ReactNode } from "react";
2
- import { BasicSampleData, SampleSummary } from "../../api/types";
2
+ import { BasicSampleData } from "../../api/types";
3
3
  import { ScoreLabel } from "../../types";
4
4
  import { Value2 } from "../../types/log";
5
5
 
6
6
  export interface EvalDescriptor {
7
- epochs: number;
8
- samples: SampleSummary[];
9
7
  scores: ScoreLabel[];
10
8
  scoreDescriptor: (scoreLabel: ScoreLabel) => ScoreDescriptor;
11
9
  scorerDescriptor: (
@@ -14,9 +12,12 @@ export interface EvalDescriptor {
14
12
  ) => ScorerDescriptor;
15
13
  score: (
16
14
  sample: BasicSampleData,
17
- scoreLabel: ScoreLabel,
15
+ scoreLabel?: ScoreLabel,
18
16
  ) => SelectedScore | undefined;
19
- scoreAnswer: (sample: BasicSampleData, scorer: string) => string | undefined;
17
+ scoreAnswer: (
18
+ sample: BasicSampleData,
19
+ scorer: ScoreLabel,
20
+ ) => string | undefined;
20
21
  }
21
22
 
22
23
  export interface ScorerDescriptor {
@@ -2,8 +2,26 @@
2
2
  border-top: solid var(--bs-light-border-subtle) 1px;
3
3
  background: var(--bs-light-bg-subtle);
4
4
  display: grid;
5
- grid-template-columns: max-content;
6
- justify-content: end;
7
- align-content: end;
5
+ grid-template-columns: max-content max-content;
6
+ justify-content: space-between;
7
+
8
8
  padding: 0.2em 1em;
9
9
  }
10
+
11
+ .spinnerContainer {
12
+ display: grid;
13
+ grid-template-columns: max-content max-content;
14
+ column-gap: 0.3em;
15
+ padding-top: 0.2em;
16
+ }
17
+
18
+ .spinner {
19
+ height: 11px;
20
+ width: 11px;
21
+ color: var(--bs-secondary);
22
+ border-width: 1px;
23
+ }
24
+
25
+ .label {
26
+ margin-top: -4px;
27
+ }
@@ -1,14 +1,33 @@
1
1
  interface SampleFooterProps {
2
2
  sampleCount: number;
3
+ running: boolean;
3
4
  }
4
5
 
5
6
  import clsx from "clsx";
6
7
  import { FC } from "react";
7
8
  import styles from "./SampleFooter.module.css";
8
9
 
9
- export const SampleFooter: FC<SampleFooterProps> = ({ sampleCount }) => {
10
+ export const SampleFooter: FC<SampleFooterProps> = ({
11
+ sampleCount,
12
+ running,
13
+ }) => {
10
14
  return (
11
15
  <div className={clsx("text-size-smaller", styles.footer)}>
16
+ <div>
17
+ {running ? (
18
+ <div className={clsx(styles.spinnerContainer)}>
19
+ <div
20
+ className={clsx("spinner-border", styles.spinner)}
21
+ role="status"
22
+ >
23
+ <span className={clsx("visually-hidden")}>Running...</span>
24
+ </div>
25
+ <div className={clsx("text-style-secondary", styles.label)}>
26
+ running...
27
+ </div>
28
+ </div>
29
+ ) : undefined}
30
+ </div>
12
31
  <div>{sampleCount} Samples</div>
13
32
  </div>
14
33
  );
@@ -1,15 +1,14 @@
1
1
  import {
2
2
  FC,
3
3
  KeyboardEvent,
4
+ memo,
4
5
  RefObject,
5
6
  useCallback,
6
7
  useEffect,
7
8
  useMemo,
8
9
  useRef,
9
- useState,
10
10
  } from "react";
11
11
  import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
12
- import { EmptyPanel } from "../../components/EmptyPanel";
13
12
  import { MessageBand } from "../../components/MessageBand";
14
13
  import { formatNoDecimal } from "../../utils/format";
15
14
  import { ListItem } from "../../workspace/tabs/types";
@@ -18,6 +17,9 @@ import { SampleRow } from "./SampleRow";
18
17
  import { SampleSeparator } from "./SampleSeparator";
19
18
 
20
19
  import clsx from "clsx";
20
+ import { useProperty, useSampleDescriptor } from "../../state/hooks";
21
+ import { useVirtuosoState } from "../../state/scrolling";
22
+ import { useStore } from "../../state/store";
21
23
  import { SampleFooter } from "./SampleFooter";
22
24
  import { SampleHeader } from "./SampleHeader";
23
25
  import styles from "./SampleList.module.css";
@@ -27,8 +29,7 @@ const kSeparatorHeight = 24;
27
29
 
28
30
  interface SampleListProps {
29
31
  items: ListItem[];
30
- sampleDescriptor: SamplesDescriptor;
31
- selectedIndex: number;
32
+ running: boolean;
32
33
  nextSample: () => void;
33
34
  prevSample: () => void;
34
35
  showSample: (index: number) => void;
@@ -36,11 +37,10 @@ interface SampleListProps {
36
37
  listHandle: RefObject<VirtuosoHandle | null>;
37
38
  }
38
39
 
39
- export const SampleList: FC<SampleListProps> = (props) => {
40
+ export const SampleList: FC<SampleListProps> = memo((props) => {
40
41
  const {
41
42
  items,
42
- sampleDescriptor,
43
- selectedIndex,
43
+ running,
44
44
  nextSample,
45
45
  prevSample,
46
46
  showSample,
@@ -48,37 +48,50 @@ export const SampleList: FC<SampleListProps> = (props) => {
48
48
  listHandle,
49
49
  } = props;
50
50
 
51
- const [followOutput, setFollowOutput] = useState(false);
51
+ const { getRestoreState, isScrolling } = useVirtuosoState(
52
+ listHandle,
53
+ "sample-list",
54
+ );
52
55
 
53
- const [hidden, setHidden] = useState(false);
54
- useEffect(() => {
55
- setHidden(false);
56
- }, [items]);
56
+ const selectedSampleIndex = useStore(
57
+ (state) => state.log.selectedSampleIndex,
58
+ );
59
+ const samplesDescriptor = useSampleDescriptor();
60
+ const [followOutput, setFollowOutput] = useProperty("sample-list", "follow", {
61
+ defaultValue: false,
62
+ });
57
63
 
58
- // Keep a mapping of the indexes to items (skipping separators)
59
- const itemRowMapping = useMemo(() => {
60
- const rowIndexes: number[] = [];
61
- items.forEach((item, index) => {
62
- if (item.type === "sample") {
63
- rowIndexes.push(index);
64
- }
65
- });
66
- return rowIndexes;
67
- }, [items]);
64
+ // Track whether we were previously running so we can
65
+ // decide whether to pop up to the top
66
+ const prevRunningRef = useRef(running);
68
67
 
69
- const prevSelectedIndexRef = useRef<number>(null);
70
68
  useEffect(() => {
71
- const listEl = listHandle.current;
72
- if (listEl) {
73
- requestAnimationFrame(() => {
74
- setTimeout(() => {
75
- const actualRowIndex = itemRowMapping[selectedIndex];
76
- listEl.scrollToIndex(actualRowIndex);
77
- prevSelectedIndexRef.current = actualRowIndex;
78
- }, 10);
79
- });
69
+ // When we finish running, if we are following output
70
+ // then scroll up to the top
71
+ if (
72
+ !running &&
73
+ prevRunningRef.current &&
74
+ followOutput &&
75
+ listHandle.current
76
+ ) {
77
+ setFollowOutput(false);
78
+ setTimeout(() => {
79
+ if (listHandle.current) {
80
+ listHandle.current.scrollTo({ top: 0, behavior: "instant" });
81
+ }
82
+ }, 100);
80
83
  }
81
- }, [selectedIndex, listHandle, itemRowMapping]);
84
+ prevRunningRef.current = running;
85
+ }, [running, followOutput, listHandle]);
86
+
87
+ const handleAtBottomStateChange = useCallback(
88
+ (atBottom: boolean) => {
89
+ if (running) {
90
+ setFollowOutput(atBottom);
91
+ }
92
+ },
93
+ [running, setFollowOutput],
94
+ );
82
95
 
83
96
  const onkeydown = useCallback(
84
97
  (e: KeyboardEvent<HTMLDivElement>) => {
@@ -94,48 +107,51 @@ export const SampleList: FC<SampleListProps> = (props) => {
94
107
  e.stopPropagation();
95
108
  break;
96
109
  case "Enter":
97
- showSample(selectedIndex);
110
+ showSample(selectedSampleIndex);
98
111
  e.preventDefault();
99
112
  e.stopPropagation();
100
113
  break;
101
114
  }
102
115
  },
103
- [selectedIndex, nextSample, prevSample, showSample],
116
+ [selectedSampleIndex, nextSample, prevSample, showSample],
104
117
  );
105
118
 
106
- // If there are no samples, just display an empty state
107
- if (items.length === 0) {
108
- return <EmptyPanel>No Samples</EmptyPanel>;
109
- }
119
+ const gridColumnsTemplate = useMemo(() => {
120
+ return gridColumnsValue(samplesDescriptor);
121
+ }, [samplesDescriptor]);
110
122
 
111
- const renderRow = (item: ListItem) => {
112
- if (item.type === "sample") {
113
- return (
114
- <SampleRow
115
- id={`${item.number}`}
116
- index={item.index}
117
- sample={item.data}
118
- height={kSampleHeight}
119
- sampleDescriptor={sampleDescriptor}
120
- gridColumnsTemplate={gridColumnsValue(sampleDescriptor)}
121
- selected={selectedIndex === item.index}
122
- showSample={showSample}
123
- />
124
- );
125
- } else if (item.type === "separator") {
126
- return (
127
- <SampleSeparator
128
- id={`sample-group${item.number}`}
129
- title={item.data}
130
- height={kSeparatorHeight}
131
- />
132
- );
133
- } else {
134
- return null;
135
- }
136
- };
123
+ const renderRow = useCallback(
124
+ (_index: number, item: ListItem) => {
125
+ if (item.type === "sample") {
126
+ return (
127
+ <SampleRow
128
+ id={`${item.number}`}
129
+ index={item.index}
130
+ sample={item.data}
131
+ height={kSampleHeight}
132
+ answer={item.answer}
133
+ completed={item.completed}
134
+ scoreRendered={item.scoreRendered}
135
+ gridColumnsTemplate={gridColumnsTemplate}
136
+ showSample={showSample}
137
+ />
138
+ );
139
+ } else if (item.type === "separator") {
140
+ return (
141
+ <SampleSeparator
142
+ id={`sample-group${item.number}`}
143
+ title={item.data}
144
+ height={kSeparatorHeight}
145
+ />
146
+ );
147
+ } else {
148
+ return null;
149
+ }
150
+ },
151
+ [showSample],
152
+ );
137
153
 
138
- const { input, limit, answer, target } = gridColumns(sampleDescriptor);
154
+ const { input, limit, answer, target } = gridColumns(samplesDescriptor);
139
155
 
140
156
  const sampleCount = items?.reduce((prev, current) => {
141
157
  if (current.type === "sample") {
@@ -176,68 +192,72 @@ export const SampleList: FC<SampleListProps> = (props) => {
176
192
  <div className={styles.mainLayout}>
177
193
  {warningMessage ? (
178
194
  <MessageBand
195
+ id={"sample-warning-message"}
179
196
  message={warningMessage}
180
- hidden={hidden}
181
- setHidden={setHidden}
182
197
  type="info"
183
198
  />
184
199
  ) : undefined}
185
-
186
200
  <SampleHeader
187
201
  input={input !== "0"}
188
202
  target={target !== "0"}
189
203
  answer={answer !== "0"}
190
204
  limit={limit !== "0"}
191
- gridColumnsTemplate={gridColumnsValue(sampleDescriptor)}
205
+ gridColumnsTemplate={gridColumnsTemplate}
192
206
  />
193
207
  <Virtuoso
194
208
  ref={listHandle}
195
209
  style={{ height: "100%" }}
196
210
  data={items}
197
211
  defaultItemHeight={50}
198
- itemContent={(_index: number, data: ListItem) => {
199
- return renderRow(data);
200
- }}
212
+ itemContent={renderRow}
201
213
  followOutput={followOutput}
202
- atBottomStateChange={(atBottom: boolean) => {
203
- setFollowOutput(atBottom);
214
+ atBottomStateChange={handleAtBottomStateChange}
215
+ increaseViewportBy={{ top: 300, bottom: 300 }}
216
+ overscan={{
217
+ main: 10,
218
+ reverse: 10,
204
219
  }}
205
220
  className={clsx(className)}
206
221
  onKeyDown={onkeydown}
207
222
  skipAnimationFrameInResizeObserver={true}
223
+ isScrolling={isScrolling}
224
+ restoreStateFrom={getRestoreState()}
208
225
  />
209
- <SampleFooter sampleCount={sampleCount} />
226
+ <SampleFooter sampleCount={sampleCount} running={running} />
210
227
  </div>
211
228
  );
212
- };
229
+ });
213
230
 
214
- const gridColumnsValue = (sampleDescriptor: SamplesDescriptor) => {
231
+ const gridColumnsValue = (sampleDescriptor?: SamplesDescriptor) => {
215
232
  const { input, target, answer, limit, id, score } =
216
233
  gridColumns(sampleDescriptor);
217
234
  return `${id} ${input} ${target} ${answer} ${limit} ${score}`;
218
235
  };
219
236
 
220
- const gridColumns = (sampleDescriptor: SamplesDescriptor) => {
237
+ const gridColumns = (sampleDescriptor?: SamplesDescriptor) => {
221
238
  const input =
222
- sampleDescriptor?.messageShape.normalized.input > 0
239
+ sampleDescriptor && sampleDescriptor.messageShape.normalized.input > 0
223
240
  ? Math.max(0.15, sampleDescriptor.messageShape.normalized.input)
224
241
  : 0;
225
242
  const target =
226
- sampleDescriptor?.messageShape.normalized.target > 0
243
+ sampleDescriptor && sampleDescriptor.messageShape.normalized.target > 0
227
244
  ? Math.max(0.15, sampleDescriptor.messageShape.normalized.target)
228
245
  : 0;
229
246
  const answer =
230
- sampleDescriptor?.messageShape.normalized.answer > 0
247
+ sampleDescriptor && sampleDescriptor.messageShape.normalized.answer > 0
231
248
  ? Math.max(0.15, sampleDescriptor.messageShape.normalized.answer)
232
249
  : 0;
233
250
  const limit =
234
- sampleDescriptor?.messageShape.normalized.limit > 0
251
+ sampleDescriptor && sampleDescriptor.messageShape.normalized.limit > 0
235
252
  ? Math.max(0.15, sampleDescriptor.messageShape.normalized.limit)
236
253
  : 0;
237
- const id = Math.max(2, Math.min(10, sampleDescriptor?.messageShape.raw.id));
254
+ const id = Math.max(
255
+ 2,
256
+ Math.min(10, sampleDescriptor?.messageShape.raw.id || 0),
257
+ );
238
258
  const score = Math.max(
239
259
  3,
240
- Math.min(10, sampleDescriptor?.messageShape.raw.score),
260
+ Math.min(10, sampleDescriptor?.messageShape.raw.score || 0),
241
261
  );
242
262
 
243
263
  const frSize = (val: number) => {
@@ -31,3 +31,9 @@
31
31
  display: flex;
32
32
  justify-self: center;
33
33
  }
34
+
35
+ .spinner {
36
+ height: 1.4em;
37
+ width: 1.4em;
38
+ color: var(--bs-focus-ring-color);
39
+ }
@@ -1,9 +1,10 @@
1
1
  import clsx from "clsx";
2
- import { FC } from "react";
2
+ import { FC, ReactNode, useCallback } from "react";
3
3
  import { SampleSummary } from "../../api/types";
4
4
  import { MarkdownDiv } from "../../components/MarkdownDiv";
5
+ import { PulsingDots } from "../../components/PulsingDots";
6
+ import { useStore } from "../../state/store";
5
7
  import { arrayToString, inputString } from "../../utils/format";
6
- import { SamplesDescriptor } from "../descriptor/samplesDescriptor";
7
8
  import { SampleErrorView } from "../error/SampleErrorView";
8
9
  import styles from "./SampleRow.module.css";
9
10
 
@@ -11,10 +12,11 @@ interface SampleRowProps {
11
12
  id: string;
12
13
  index: number;
13
14
  sample: SampleSummary;
14
- sampleDescriptor: SamplesDescriptor;
15
+ answer: string;
16
+ completed: boolean;
17
+ scoreRendered: ReactNode;
15
18
  gridColumnsTemplate: string;
16
19
  height: number;
17
- selected: boolean;
18
20
  showSample: (index: number) => void;
19
21
  }
20
22
 
@@ -22,22 +24,33 @@ export const SampleRow: FC<SampleRowProps> = ({
22
24
  id,
23
25
  index,
24
26
  sample,
25
- sampleDescriptor,
27
+ answer,
28
+ completed,
29
+ scoreRendered,
26
30
  gridColumnsTemplate,
27
31
  height,
28
- selected,
29
32
  showSample,
30
33
  }) => {
34
+ const streamSampleData = useStore(
35
+ (state) => state.capabilities.streamSampleData,
36
+ );
37
+ const selectedSampleIndex = useStore(
38
+ (state) => state.log.selectedSampleIndex,
39
+ );
40
+ const handleClick = useCallback(() => {
41
+ if (completed || streamSampleData) {
42
+ showSample(index);
43
+ }
44
+ }, [index, showSample, completed]);
45
+
31
46
  return (
32
47
  <div
33
48
  id={`sample-${id}`}
34
- onClick={() => {
35
- showSample(index);
36
- }}
49
+ onClick={handleClick}
37
50
  className={clsx(
38
51
  styles.grid,
39
52
  "text-size-base",
40
- selected ? styles.selected : undefined,
53
+ selectedSampleIndex === index ? styles.selected : undefined,
41
54
  )}
42
55
  style={{
43
56
  height: `${height}px`,
@@ -67,9 +80,7 @@ export const SampleRow: FC<SampleRowProps> = ({
67
80
  <div className={clsx("sample-answer", "three-line-clamp", styles.cell)}>
68
81
  {sample ? (
69
82
  <MarkdownDiv
70
- markdown={sampleDescriptor
71
- ?.selectedScorerDescriptor(sample)
72
- .answer()}
83
+ markdown={answer || ""}
73
84
  className={clsx("no-last-para-padding", styles.noLeft)}
74
85
  />
75
86
  ) : (
@@ -90,8 +101,10 @@ export const SampleRow: FC<SampleRowProps> = ({
90
101
  <div className={clsx("text-size-small", styles.cell, styles.score)}>
91
102
  {sample.error ? (
92
103
  <SampleErrorView message={sample.error} />
104
+ ) : completed ? (
105
+ scoreRendered
93
106
  ) : (
94
- sampleDescriptor?.selectedScore(sample)?.render()
107
+ <PulsingDots />
95
108
  )}
96
109
  </div>
97
110
  </div>