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
@@ -1,24 +1,31 @@
1
1
  import { clsx } from "clsx";
2
2
 
3
- import { FC } from "react";
3
+ import { FC, useCallback } from "react";
4
4
  import { ApplicationIcons } from "../appearance/icons";
5
+ import { useMessageVisibility } from "../state/hooks";
5
6
  import "./MessageBand.css";
6
7
 
7
8
  interface MessageBandProps {
9
+ id: string;
8
10
  message: string;
9
- hidden: boolean;
10
- setHidden: (hidden: boolean) => void;
11
+ scope?: "sample" | "eval";
11
12
  type: "info" | "warning" | "error";
12
13
  }
13
14
 
14
15
  export const MessageBand: FC<MessageBandProps> = ({
16
+ id,
15
17
  message,
16
- hidden,
17
- setHidden,
18
18
  type,
19
+ scope = "eval",
19
20
  }) => {
20
21
  const className: string[] = [type];
21
- if (hidden) {
22
+
23
+ const [visible, setVisible] = useMessageVisibility(id, scope);
24
+ const handleClick = useCallback(() => {
25
+ setVisible(false);
26
+ }, [setVisible]);
27
+
28
+ if (!visible) {
22
29
  className.push("hidden");
23
30
  }
24
31
 
@@ -29,9 +36,7 @@ export const MessageBand: FC<MessageBandProps> = ({
29
36
  <button
30
37
  className={clsx("btn", "message-band-btn", type)}
31
38
  title="Close"
32
- onClick={() => {
33
- setHidden(true);
34
- }}
39
+ onClick={handleClick}
35
40
  >
36
41
  <i className={ApplicationIcons.close}></i>
37
42
  </button>
@@ -1,14 +1,14 @@
1
1
  import { Popover } from "bootstrap";
2
- import React, { useEffect, useRef } from "react";
2
+ import { FC, ReactNode, useEffect, useRef } from "react";
3
3
  import "./MorePopover.css";
4
4
 
5
5
  interface MorePopoverProps {
6
6
  title: string;
7
7
  customClass?: string;
8
- children: React.ReactNode;
8
+ children: ReactNode;
9
9
  }
10
10
 
11
- export const MorePopover: React.FC<MorePopoverProps> = ({
11
+ export const MorePopover: FC<MorePopoverProps> = ({
12
12
  title,
13
13
  customClass,
14
14
  children,
@@ -1,5 +1,6 @@
1
1
  import { clsx } from "clsx";
2
- import { FC, ReactElement, ReactNode, useState } from "react";
2
+ import { FC, MouseEvent, ReactElement, ReactNode, useCallback } from "react";
3
+ import { useProperty } from "../state/hooks";
3
4
  import styles from "./NavPills.module.css";
4
5
 
5
6
  interface NavPillChildProps {
@@ -8,13 +9,16 @@ interface NavPillChildProps {
8
9
  }
9
10
 
10
11
  interface NavPillsProps {
12
+ id: string;
11
13
  children?: ReactElement<NavPillChildProps>[];
12
14
  }
13
15
 
14
- export const NavPills: FC<NavPillsProps> = ({ children }) => {
15
- const [activeItem, setActiveItem] = useState(
16
- children ? children[0].props["title"] : null,
17
- );
16
+ export const NavPills: FC<NavPillsProps> = ({ id, children }) => {
17
+ const defaultNav = children ? children[0].props["title"] : "";
18
+ const [activeItem, setActiveItem] = useProperty(id, "active", {
19
+ defaultValue: defaultNav,
20
+ });
21
+
18
22
  if (!activeItem || !children) {
19
23
  return undefined;
20
24
  }
@@ -77,6 +81,15 @@ const NavPill: FC<NavPillProps> = ({
77
81
  children,
78
82
  }) => {
79
83
  const active = activeItem === title;
84
+ const handleClick = useCallback(
85
+ (e: MouseEvent<HTMLButtonElement>) => {
86
+ const target = (e.currentTarget as HTMLButtonElement).dataset.target;
87
+ if (target) {
88
+ setActiveItem(target);
89
+ }
90
+ },
91
+ [setActiveItem],
92
+ );
80
93
 
81
94
  return (
82
95
  <li className={"nav-item"}>
@@ -90,9 +103,8 @@ const NavPill: FC<NavPillProps> = ({
90
103
  active ? "active " : "",
91
104
  styles.pill,
92
105
  )}
93
- onClick={() => {
94
- setActiveItem(title);
95
- }}
106
+ data-target={title}
107
+ onClick={handleClick}
96
108
  >
97
109
  {title}
98
110
  </button>
@@ -0,0 +1,12 @@
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
+ }
@@ -0,0 +1,20 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+
4
+ import { ApplicationIcons } from "../appearance/icons";
5
+ import styles from "./NoContentsPanel.module.css";
6
+
7
+ interface NoContentsPanelProps {
8
+ text: string;
9
+ }
10
+
11
+ export const NoContentsPanel: FC<NoContentsPanelProps> = ({ text }) => {
12
+ return (
13
+ <div className={clsx(styles.panel)}>
14
+ <div className={clsx(styles.container, "text-size-smaller")}>
15
+ <i className={ApplicationIcons.noSamples} />
16
+ <div>{text}</div>
17
+ </div>
18
+ </div>
19
+ );
20
+ };
@@ -6,25 +6,26 @@
6
6
  height: 0;
7
7
  overflow-y: visible;
8
8
  overflow-x: visible;
9
- z-index: 1001;
10
9
  text-align: center;
11
10
  }
12
11
 
13
12
  .container {
14
13
  width: 100%;
15
- height: 2px;
14
+ height: 1px;
16
15
  background-color: transparent;
17
- position: relative;
16
+ position: sticky;
18
17
  overflow: hidden;
18
+ z-index: 1200;
19
19
  }
20
20
 
21
21
  .animate {
22
22
  width: 5%;
23
- height: 2px;
23
+ height: 1px;
24
24
  animation: leftToRight 2s linear infinite;
25
25
  background-color: #3b82f6;
26
26
  position: absolute;
27
27
  left: 0;
28
+ top: 0;
28
29
  }
29
30
 
30
31
  @keyframes leftToRight {
@@ -1,3 +1,4 @@
1
+ import clsx from "clsx";
1
2
  import { FC } from "react";
2
3
  import styles from "./ProgressBar.module.css";
3
4
 
@@ -7,9 +8,9 @@ interface ProgressBarProps {
7
8
 
8
9
  export const ProgressBar: FC<ProgressBarProps> = ({ animating }) => {
9
10
  return (
10
- <div className={styles.wrapper}>
11
+ <div className={clsx(styles.wrapper)}>
11
12
  <div
12
- className={styles.container}
13
+ className={clsx(styles.container)}
13
14
  role="progressbar"
14
15
  aria-label="Basic example"
15
16
  aria-valuenow={25}
@@ -0,0 +1,81 @@
1
+ /* PulsingDots.module.css */
2
+ .container {
3
+ display: inline-flex;
4
+ flex-direction: column;
5
+ align-items: center;
6
+ }
7
+
8
+ .dotsContainer {
9
+ padding-top: 8px;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ }
14
+
15
+ .small .dotsContainer {
16
+ column-gap: 3px;
17
+ }
18
+
19
+ .medium .dotsContainer {
20
+ column-gap: 5px;
21
+ padding-bottom: 2px;
22
+ }
23
+
24
+ .large .dotsContainer {
25
+ column-gap: 6px;
26
+ padding-bottom: 3px;
27
+ padding-left: 2px;
28
+ }
29
+
30
+ .dot {
31
+ border-radius: 50%;
32
+ display: inline-block;
33
+ animation: pulse 1.5s ease-in-out infinite;
34
+ }
35
+
36
+ .subtle {
37
+ background-color: var(--bs-primary-bg-subtle);
38
+ }
39
+
40
+ .primary {
41
+ background-color: var(--bs-primary);
42
+ }
43
+
44
+ .small .dot {
45
+ width: 4px;
46
+ height: 4px;
47
+ }
48
+
49
+ .medium .dot {
50
+ width: 8px;
51
+ height: 8px;
52
+ }
53
+
54
+ .large .dot {
55
+ width: 12px;
56
+ height: 12px;
57
+ }
58
+
59
+ .visuallyHidden {
60
+ position: absolute;
61
+ width: 1px;
62
+ height: 1px;
63
+ padding: 0;
64
+ margin: -1px;
65
+ overflow: hidden;
66
+ clip: rect(0, 0, 0, 0);
67
+ white-space: nowrap;
68
+ border-width: 0;
69
+ }
70
+
71
+ @keyframes pulse {
72
+ 0%,
73
+ 100% {
74
+ transform: scale(1);
75
+ opacity: 0.3;
76
+ }
77
+ 50% {
78
+ transform: scale(1.2);
79
+ opacity: 1;
80
+ }
81
+ }
@@ -0,0 +1,45 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+ import styles from "./PulsingDots.module.css";
4
+
5
+ interface PulsingDotsProps {
6
+ text?: string;
7
+ dotsCount?: number;
8
+ subtle?: boolean;
9
+ size?: "small" | "medium" | "large";
10
+ }
11
+
12
+ export const PulsingDots: FC<PulsingDotsProps> = ({
13
+ text = "Loading...",
14
+ dotsCount = 3,
15
+ subtle = true,
16
+ size = "small",
17
+ }) => {
18
+ return (
19
+ <div
20
+ className={clsx(
21
+ styles.container,
22
+ size === "small"
23
+ ? styles.small
24
+ : size === "medium"
25
+ ? styles.medium
26
+ : styles.large,
27
+ )}
28
+ role="status"
29
+ >
30
+ <div className={styles.dotsContainer}>
31
+ {[...Array(dotsCount)].map((_, index) => (
32
+ <div
33
+ key={index}
34
+ className={clsx(
35
+ styles.dot,
36
+ subtle ? styles.subtle : styles.primary,
37
+ )}
38
+ style={{ animationDelay: `${index * 0.15}s` }}
39
+ />
40
+ ))}
41
+ </div>
42
+ <span className={styles.visuallyHidden}>{text}</span>
43
+ </div>
44
+ );
45
+ };
@@ -9,11 +9,9 @@ import {
9
9
  ReactElement,
10
10
  ReactNode,
11
11
  RefObject,
12
- UIEvent,
13
- useCallback,
14
- useEffect,
15
12
  useRef,
16
13
  } from "react";
14
+ import { useStatefulScrollPosition } from "../state/scrolling";
17
15
  import moduleStyles from "./TabSet.module.css";
18
16
 
19
17
  interface TabSetProps {
@@ -36,8 +34,6 @@ interface TabPanelProps {
36
34
  scrollable?: boolean;
37
35
  scrollRef?: RefObject<HTMLDivElement | null>;
38
36
  className?: string | string[];
39
- scrollPosition?: number;
40
- setScrollPosition?: (position: number) => void;
41
37
  children?: ReactNode;
42
38
  title: string;
43
39
  icon?: string;
@@ -107,7 +103,7 @@ const Tab: FC<{
107
103
  role="tab"
108
104
  aria-controls={tabContentsId}
109
105
  aria-selected={isActive}
110
- onClick={(e) => tab.props.onSelected(e)}
106
+ onClick={tab.props.onSelected}
111
107
  >
112
108
  {tab.props.icon && (
113
109
  <i className={clsx(tab.props.icon, moduleStyles.tabIcon)} />
@@ -139,42 +135,14 @@ export const TabPanel: FC<TabPanelProps> = ({
139
135
  scrollable = true,
140
136
  scrollRef,
141
137
  className,
142
- scrollPosition,
143
- setScrollPosition,
144
138
  children,
145
139
  }) => {
146
140
  const tabContentsId = computeTabContentsId(id);
147
141
  const panelRef = useRef<HTMLDivElement>(null);
148
142
  const tabContentsRef = scrollRef || panelRef;
149
143
 
150
- useEffect(() => {
151
- if (!selected || scrollPosition === undefined || !tabContentsRef.current)
152
- return;
153
-
154
- const observer = new MutationObserver(() => {
155
- if (tabContentsRef.current) {
156
- tabContentsRef.current.scrollTop = scrollPosition;
157
- }
158
- observer.disconnect(); // Stop observing after first content load
159
- });
160
-
161
- observer.observe(tabContentsRef.current, {
162
- childList: true,
163
- subtree: true,
164
- });
165
-
166
- return () => observer.disconnect();
167
- }, []);
168
-
169
- // Handle scrolling
170
- const onScroll = useCallback(
171
- (e: UIEvent<HTMLDivElement>) => {
172
- if (setScrollPosition) {
173
- setScrollPosition(e.currentTarget.scrollTop);
174
- }
175
- },
176
- [setScrollPosition],
177
- );
144
+ // Attach a scroll listener to this ref to track scrolling
145
+ useStatefulScrollPosition(tabContentsRef, tabContentsId, 1000, scrollable);
178
146
 
179
147
  return (
180
148
  <div
@@ -188,7 +156,6 @@ export const TabPanel: FC<TabPanelProps> = ({
188
156
  scrollable && moduleStyles.scrollable,
189
157
  )}
190
158
  style={style}
191
- onScroll={onScroll}
192
159
  >
193
160
  {children}
194
161
  </div>
@@ -1,14 +1,13 @@
1
- import React, { ReactNode } from "react";
1
+ import { ButtonHTMLAttributes, forwardRef, ReactNode } from "react";
2
2
  import "./ToolButton.css";
3
3
 
4
- interface ToolButtonProps
5
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ interface ToolButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
6
5
  label: string | ReactNode;
7
6
  classes?: string;
8
7
  icon?: string;
9
8
  }
10
9
 
11
- export const ToolButton = React.forwardRef<HTMLButtonElement, ToolButtonProps>(
10
+ export const ToolButton = forwardRef<HTMLButtonElement, ToolButtonProps>(
12
11
  ({ label, classes = "", icon, className, ...rest }, ref) => {
13
12
  // Combine class names, ensuring default classes are applied first
14
13
  const combinedClasses =
@@ -1,21 +1,29 @@
1
1
  import { createRoot } from "react-dom/client";
2
- import { App } from "./App";
3
2
  import api from "./api/index";
4
- import { ApplicationState, Capabilities } from "./types";
5
- import { throttle } from "./utils/sync";
3
+ import { Capabilities } from "./api/types";
4
+ import { App } from "./App";
5
+ import { AppErrorBoundary } from "./AppErrorBoundary";
6
+ import { initializeStore } from "./state/store";
7
+ import storage from "./storage";
6
8
  import { getVscodeApi } from "./utils/vscode";
7
9
 
8
- // Read any state from the page itself
10
+ // Resolve the api
11
+ const applicationApi = api;
12
+ const applicationStorage = storage;
13
+
14
+ // Application capabilities
9
15
  const vscode = getVscodeApi();
10
- let initialState = undefined;
11
16
  let capabilities: Capabilities = {
12
17
  downloadFiles: true,
13
18
  webWorkers: true,
19
+ streamSamples: !!applicationApi.get_log_pending_samples,
20
+ streamSampleData: !!applicationApi.get_log_sample_data,
21
+ nativeFind: !vscode,
14
22
  };
15
- if (vscode) {
16
- initialState = filterState(vscode.getState() as ApplicationState);
17
23
 
18
- // Determine the capabilities
24
+ // Initial state / storage
25
+ if (vscode) {
26
+ // Adjust capabilities
19
27
  const extensionVersionEl = document.querySelector(
20
28
  'meta[name="inspect-extension:version"]',
21
29
  );
@@ -24,10 +32,15 @@ if (vscode) {
24
32
  : undefined;
25
33
 
26
34
  if (!extensionVersion) {
27
- capabilities = { downloadFiles: false, webWorkers: false };
35
+ capabilities.downloadFiles = false;
36
+ capabilities.webWorkers = false;
28
37
  }
29
38
  }
30
39
 
40
+ // Inititialize the application store
41
+ initializeStore(applicationApi, capabilities, applicationStorage);
42
+
43
+ // Find the root element and render into it
31
44
  const containerId = "app";
32
45
  const container = document.getElementById(containerId);
33
46
  if (!container) {
@@ -37,91 +50,10 @@ if (!container) {
37
50
  );
38
51
  }
39
52
 
53
+ // Render into the root
40
54
  const root = createRoot(container as HTMLElement);
41
55
  root.render(
42
- <App
43
- api={api}
44
- applicationState={initialState}
45
- saveApplicationState={throttle((state) => {
46
- const vscode = getVscodeApi();
47
- if (vscode) {
48
- vscode.setState(filterState(state));
49
- }
50
- }, 1000)}
51
- capabilities={capabilities}
52
- pollForLogs={false}
53
- />,
56
+ <AppErrorBoundary>
57
+ <App api={applicationApi} />
58
+ </AppErrorBoundary>,
54
59
  );
55
-
56
- function filterState(state: ApplicationState) {
57
- if (!state) {
58
- return state;
59
- }
60
-
61
- // When saving state, we can't store vast amounts of data (like a large sample)
62
- const filters = [filterLargeSample, filterLargeSelectedLog];
63
- return filters.reduce(
64
- (filteredState, filter) => filter(filteredState),
65
- state,
66
- );
67
- }
68
-
69
- // Filters the selected Sample if it is large
70
- function filterLargeSample(state: ApplicationState) {
71
- if (!state || !state.selectedSample) {
72
- return state;
73
- }
74
-
75
- const estimatedTotalSize = estimateSize(state.selectedSample.messages);
76
- if (estimatedTotalSize > 400000) {
77
- const { selectedSample, ...filteredState } = state;
78
- return filteredState;
79
- } else {
80
- return state;
81
- }
82
- }
83
-
84
- // Filters the selectedlog if it is too large
85
- function filterLargeSelectedLog(state: ApplicationState) {
86
- if (!state || !state.selectedLog?.contents) {
87
- return state;
88
- }
89
-
90
- const estimatedSize = estimateSize(
91
- state.selectedLog.contents.sampleSummaries,
92
- );
93
- if (estimatedSize > 400000) {
94
- const { selectedLog, ...filteredState } = state;
95
- return filteredState;
96
- } else {
97
- return state;
98
- }
99
- }
100
-
101
- function estimateSize(list: unknown[], frequency = 0.2) {
102
- if (!list || list.length === 0) {
103
- return 0;
104
- }
105
-
106
- // Total number of samples
107
- const sampleSize = Math.ceil(list.length * frequency);
108
-
109
- // Get a proper random sample without duplicates
110
- const messageIndices = new Set<number>();
111
- while (
112
- messageIndices.size < sampleSize &&
113
- messageIndices.size < list.length
114
- ) {
115
- const randomIndex = Math.floor(Math.random() * list.length);
116
- messageIndices.add(randomIndex);
117
- }
118
-
119
- // Calculate size from sampled messages
120
- const totalSize = Array.from(messageIndices).reduce((size, index) => {
121
- return size + JSON.stringify(list[index]).length;
122
- }, 0);
123
-
124
- // Estimate total size based on sample
125
- const estimatedTotalSize = (totalSize / sampleSize) * list.length;
126
- return estimatedTotalSize;
127
- }
@@ -17,6 +17,14 @@ interface SampleEntry {
17
17
  epoch: number;
18
18
  }
19
19
 
20
+ export class SampleNotFoundError extends Error {
21
+ constructor(message?: string) {
22
+ super(message || "Sample not found");
23
+ this.name = "SampleNotFoundError";
24
+
25
+ Object.setPrototypeOf(this, SampleNotFoundError.prototype);
26
+ }
27
+ }
20
28
  export interface RemoteLogFile {
21
29
  readHeader: () => Promise<EvalHeader>;
22
30
  readLogSummary: () => Promise<EvalSummary>;
@@ -101,7 +109,7 @@ export const openRemoteLogFile = async (
101
109
  if (remoteZipFile.centralDirectory.has(sampleFile)) {
102
110
  return (await readJSONFile(sampleFile, MAX_BYTES)) as EvalSample;
103
111
  } else {
104
- throw new Error(
112
+ throw new SampleNotFoundError(
105
113
  `Unable to read sample file ${sampleFile} - it is not present in the manifest.`,
106
114
  );
107
115
  }
@@ -67,7 +67,15 @@ export const openRemoteZipFile = async (
67
67
 
68
68
  // Check signature to make sure we found the EOCD record
69
69
  if (eocdrView.getUint32(0, true) !== 0x06054b50) {
70
- throw new Error("End of central directory record not found");
70
+ if (eocdrBuffer.length !== 22) {
71
+ // The range request seems like it was ignored because more bytes than
72
+ // were requested were returned.
73
+ throw new Error(
74
+ "Unexpected central directory size - does the HTTP server serving this file support HTTP range requests?",
75
+ );
76
+ } else {
77
+ throw new Error("End of central directory record not found");
78
+ }
71
79
  }
72
80
 
73
81
  let centralDirOffset = eocdrView.getUint32(16, true);
@@ -180,9 +188,27 @@ export const openRemoteZipFile = async (
180
188
  };
181
189
 
182
190
  export const fetchSize = async (url: string): Promise<number> => {
183
- const response = await fetch(`${url}`, { method: "HEAD" });
184
- const contentLength = Number(response.headers.get("Content-Length"));
185
- return contentLength;
191
+ // First try HEAD request to get Content-Length
192
+ const headResponse = await fetch(`${url}`, { method: "HEAD" });
193
+ const contentLength = headResponse.headers.get("Content-Length");
194
+
195
+ if (contentLength !== null) {
196
+ return Number(contentLength);
197
+ }
198
+
199
+ // If Content-Length is not present, use a GET with an 1 byte range request:
200
+ const getResponse = await fetch(`${url}`, {
201
+ method: "GET",
202
+ headers: { Range: "bytes=0-0" },
203
+ });
204
+ const contentRange = getResponse.headers.get("Content-Range");
205
+ if (contentRange !== null) {
206
+ const rangeMatch = contentRange.match(/bytes (\d+)-(\d+)\/(\d+)/);
207
+ if (rangeMatch !== null) {
208
+ return Number(rangeMatch[3]);
209
+ }
210
+ }
211
+ throw new Error("Could not determine content length");
186
212
  };
187
213
 
188
214
  /**