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
@@ -2,17 +2,19 @@ import clsx from "clsx";
2
2
  import {
3
3
  CSSProperties,
4
4
  FC,
5
+ memo,
5
6
  ReactNode,
6
7
  useCallback,
7
- useEffect,
8
8
  useRef,
9
9
  useState,
10
10
  } from "react";
11
11
  import { ApplicationIcons } from "../appearance/icons";
12
+ import { useCollapsedState } from "../state/hooks";
12
13
  import { useResizeObserver } from "../utils/dom";
13
14
  import "./ExpandablePanel.css";
14
15
 
15
16
  interface ExpandablePanelProps {
17
+ id: string;
16
18
  collapse: boolean;
17
19
  border?: boolean;
18
20
  lines?: number;
@@ -20,72 +22,64 @@ interface ExpandablePanelProps {
20
22
  className?: string | string[];
21
23
  }
22
24
 
23
- export const ExpandablePanel: FC<ExpandablePanelProps> = ({
24
- collapse,
25
- border,
26
- lines = 15,
27
- children,
28
- className,
29
- }) => {
30
- const [isCollapsed, setIsCollapsed] = useState(collapse);
31
- const [showToggle, setShowToggle] = useState(false);
32
- const lineHeightRef = useRef<number>(0);
25
+ export const ExpandablePanel: FC<ExpandablePanelProps> = memo(
26
+ ({ id, collapse, border, lines = 15, children, className }) => {
27
+ const [collapsed, setCollapsed] = useCollapsedState(id, collapse);
33
28
 
34
- useEffect(() => {
35
- setIsCollapsed(collapse);
36
- }, [collapse]);
29
+ const [showToggle, setShowToggle] = useState(false);
30
+ const lineHeightRef = useRef<number>(0);
37
31
 
38
- const checkOverflow = useCallback(
39
- (entry: ResizeObserverEntry) => {
40
- const element = entry.target as HTMLDivElement;
32
+ const checkOverflow = useCallback(
33
+ (entry: ResizeObserverEntry) => {
34
+ const element = entry.target as HTMLDivElement;
41
35
 
42
- // Calculate line height if we haven't yet
43
- if (!lineHeightRef.current) {
44
- const computedStyle = window.getComputedStyle(element);
45
- lineHeightRef.current = parseInt(computedStyle.lineHeight) || 16; // fallback to 16px if can't get line height
46
- }
36
+ // Calculate line height if we haven't yet
37
+ if (!lineHeightRef.current) {
38
+ const computedStyle = window.getComputedStyle(element);
39
+ lineHeightRef.current = parseInt(computedStyle.lineHeight) || 16; // fallback to 16px if can't get line height
40
+ }
47
41
 
48
- const maxCollapsedHeight = lines * lineHeightRef.current;
49
- const contentHeight = element.scrollHeight;
42
+ const maxCollapsedHeight = lines * lineHeightRef.current;
43
+ const contentHeight = element.scrollHeight;
50
44
 
51
- setShowToggle(contentHeight > maxCollapsedHeight);
52
- },
53
- [lines],
54
- );
45
+ setShowToggle(contentHeight > maxCollapsedHeight);
46
+ },
47
+ [lines],
48
+ );
49
+ const contentRef = useResizeObserver(checkOverflow);
55
50
 
56
- const contentRef = useResizeObserver(checkOverflow);
51
+ const baseStyles = {
52
+ overflow: "hidden",
53
+ ...(collapsed && {
54
+ maxHeight: `${lines}em`,
55
+ }),
56
+ };
57
57
 
58
- const baseStyles = {
59
- overflow: "hidden",
60
- ...(isCollapsed && {
61
- maxHeight: `${lines}em`,
62
- }),
63
- };
58
+ return (
59
+ <div className={clsx(className)}>
60
+ <div
61
+ style={baseStyles}
62
+ ref={contentRef}
63
+ className={clsx(
64
+ "expandable-panel",
65
+ collapsed ? "expandable-collapsed" : undefined,
66
+ border ? "expandable-bordered" : undefined,
67
+ )}
68
+ >
69
+ {children}
70
+ </div>
64
71
 
65
- return (
66
- <div className={clsx(className)}>
67
- <div
68
- style={baseStyles}
69
- ref={contentRef}
70
- className={clsx(
71
- "expandable-panel",
72
- isCollapsed ? "expandable-collapsed" : undefined,
73
- border ? "expandable-bordered" : undefined,
72
+ {showToggle && (
73
+ <MoreToggle
74
+ collapsed={collapsed}
75
+ setCollapsed={setCollapsed}
76
+ border={!border}
77
+ />
74
78
  )}
75
- >
76
- {children}
77
79
  </div>
78
-
79
- {showToggle && (
80
- <MoreToggle
81
- collapsed={isCollapsed}
82
- setCollapsed={setIsCollapsed}
83
- border={!border}
84
- />
85
- )}
86
- </div>
87
- );
88
- };
80
+ );
81
+ },
82
+ );
89
83
 
90
84
  interface MoreToggleProps {
91
85
  collapsed: boolean;
@@ -105,13 +99,14 @@ const MoreToggle: FC<MoreToggleProps> = ({
105
99
  ? ApplicationIcons["expand-down"]
106
100
  : ApplicationIcons.collapse.up;
107
101
 
102
+ const handleClick = useCallback(() => {
103
+ setCollapsed(!collapsed);
104
+ }, [setCollapsed, collapsed]);
105
+
108
106
  return (
109
107
  <div className={`more-toggle ${border ? "bordered" : ""}`} style={style}>
110
108
  <div className="more-toggle-container">
111
- <button
112
- className="btn more-toggle-button"
113
- onClick={() => setCollapsed(!collapsed)}
114
- >
109
+ <button className="btn more-toggle-button" onClick={handleClick}>
115
110
  <i className={icon} />
116
111
  {text}
117
112
  </button>
@@ -1,13 +1,13 @@
1
1
  import { FC, KeyboardEvent, useCallback, useEffect, useRef } from "react";
2
2
  import { ApplicationIcons } from "../appearance/icons";
3
+ import { useStore } from "../state/store";
3
4
  import "./FindBand.css";
4
5
 
5
- interface FindBandProps {
6
- hideBand: () => void;
7
- }
6
+ interface FindBandProps {}
8
7
 
9
- export const FindBand: FC<FindBandProps> = ({ hideBand }) => {
8
+ export const FindBand: FC<FindBandProps> = () => {
10
9
  const searchBoxRef = useRef<HTMLInputElement>(null);
10
+ const storeHideFind = useStore((state) => state.appActions.hideFind);
11
11
 
12
12
  useEffect(() => {
13
13
  setTimeout(() => {
@@ -84,14 +84,22 @@ export const FindBand: FC<FindBandProps> = ({ hideBand }) => {
84
84
  const handleKeyDown = useCallback(
85
85
  (e: KeyboardEvent<HTMLInputElement>) => {
86
86
  if (e.key === "Escape") {
87
- hideBand();
87
+ storeHideFind();
88
88
  } else if (e.key === "Enter") {
89
89
  handleSearch(false);
90
90
  }
91
91
  },
92
- [hideBand, handleSearch],
92
+ [storeHideFind, handleSearch],
93
93
  );
94
94
 
95
+ const showSearch = useCallback(() => {
96
+ handleSearch(true);
97
+ }, [handleSearch]);
98
+
99
+ const hideSearch = useCallback(() => {
100
+ handleSearch(false);
101
+ }, [handleSearch]);
102
+
95
103
  return (
96
104
  <div className="findBand">
97
105
  <input
@@ -105,7 +113,7 @@ export const FindBand: FC<FindBandProps> = ({ hideBand }) => {
105
113
  type="button"
106
114
  title="Previous match"
107
115
  className="btn next"
108
- onClick={() => handleSearch(true)}
116
+ onClick={showSearch}
109
117
  >
110
118
  <i className={ApplicationIcons.arrows.up} />
111
119
  </button>
@@ -113,7 +121,7 @@ export const FindBand: FC<FindBandProps> = ({ hideBand }) => {
113
121
  type="button"
114
122
  title="Next match"
115
123
  className="btn prev"
116
- onClick={() => handleSearch(false)}
124
+ onClick={hideSearch}
117
125
  >
118
126
  <i className={ApplicationIcons.arrows.down} />
119
127
  </button>
@@ -121,7 +129,7 @@ export const FindBand: FC<FindBandProps> = ({ hideBand }) => {
121
129
  type="button"
122
130
  title="Close"
123
131
  className="btn close"
124
- onClick={hideBand}
132
+ onClick={storeHideFind}
125
133
  >
126
134
  <i className={ApplicationIcons.close} />
127
135
  </button>
@@ -133,7 +133,7 @@ export const HumanBaselineView: FC<HumanBaselineViewProps> = ({
133
133
  />
134
134
  </div>
135
135
  <div className={"asciinema-body"}>
136
- <LightboxCarousel slides={player_fns} />
136
+ <LightboxCarousel id="ascii-cinema" slides={player_fns} />
137
137
  </div>
138
138
  </div>
139
139
  </div>
@@ -1,20 +1,18 @@
1
1
  import clsx from "clsx";
2
- import { highlightElement } from "prismjs";
3
- import React, { useEffect, useMemo, useRef } from "react";
2
+ import { CSSProperties, FC, useMemo } from "react";
3
+ import { usePrismHighlight } from "../state/hooks";
4
4
  import "./JsonPanel.css";
5
5
 
6
- const kPrismRenderMaxSize = 250000;
7
-
8
6
  interface JSONPanelProps {
9
7
  id?: string;
10
8
  data?: unknown;
11
9
  json?: string;
12
10
  simple?: boolean;
13
- style?: React.CSSProperties;
11
+ style?: CSSProperties;
14
12
  className?: string | string[];
15
13
  }
16
14
 
17
- export const JSONPanel: React.FC<JSONPanelProps> = ({
15
+ export const JSONPanel: FC<JSONPanelProps> = ({
18
16
  id,
19
17
  json,
20
18
  data,
@@ -22,30 +20,22 @@ export const JSONPanel: React.FC<JSONPanelProps> = ({
22
20
  style,
23
21
  className,
24
22
  }) => {
25
- const codeRef = useRef<HTMLElement>(null);
26
23
  const sourceCode = useMemo(() => {
27
24
  return json || JSON.stringify(resolveBase64(data), undefined, 2);
28
25
  }, [json, data]);
29
-
30
- useEffect(() => {
31
- if (sourceCode.length < kPrismRenderMaxSize && codeRef.current) {
32
- highlightElement(codeRef.current);
33
- }
34
- }, [sourceCode]);
26
+ const prismParentRef = usePrismHighlight(sourceCode);
35
27
 
36
28
  return (
37
- <pre
38
- className={clsx("json-panel", simple ? "simple" : "", className)}
39
- style={style}
40
- >
41
- <code
42
- id={id}
43
- ref={codeRef}
44
- className={clsx("source-code", "language-javascript")}
29
+ <div ref={prismParentRef}>
30
+ <pre
31
+ className={clsx("json-panel", simple ? "simple" : "", className)}
32
+ style={style}
45
33
  >
46
- {sourceCode}
47
- </code>
48
- </pre>
34
+ <code id={id} className={clsx("source-code", "language-javascript")}>
35
+ {sourceCode}
36
+ </code>
37
+ </pre>
38
+ </div>
49
39
  );
50
40
  };
51
41
 
@@ -1,15 +1,7 @@
1
1
  import clsx from "clsx";
2
2
  import { ProgressBar } from "./ProgressBar";
3
3
 
4
- import {
5
- FC,
6
- ReactNode,
7
- RefObject,
8
- UIEvent,
9
- useCallback,
10
- useEffect,
11
- useRef,
12
- } from "react";
4
+ import { FC, ReactNode, RefObject, useRef } from "react";
13
5
  import styles from "./LargeModal.module.css";
14
6
 
15
7
  export interface ModalTool {
@@ -35,8 +27,6 @@ interface LargeModalProps {
35
27
  onkeyup: (e: any) => void;
36
28
  onHide: () => void;
37
29
  scrollRef: RefObject<HTMLDivElement | null>;
38
- initialScrollPositionRef: RefObject<number>;
39
- setInitialScrollPosition: (position: number) => void;
40
30
  children: ReactNode;
41
31
  }
42
32
 
@@ -51,8 +41,6 @@ export const LargeModal: FC<LargeModalProps> = ({
51
41
  visible,
52
42
  onHide,
53
43
  showProgress,
54
- initialScrollPositionRef,
55
- setInitialScrollPosition,
56
44
  scrollRef,
57
45
  }) => {
58
46
  // The footer
@@ -67,27 +55,6 @@ export const LargeModal: FC<LargeModalProps> = ({
67
55
  const modalRef = useRef(null);
68
56
  scrollRef = scrollRef || modalRef;
69
57
 
70
- useEffect(() => {
71
- if (scrollRef.current) {
72
- setTimeout(() => {
73
- if (
74
- scrollRef.current &&
75
- initialScrollPositionRef.current &&
76
- scrollRef.current.scrollTop !== initialScrollPositionRef?.current
77
- ) {
78
- scrollRef.current.scrollTop = initialScrollPositionRef.current;
79
- }
80
- }, 0);
81
- }
82
- }, []);
83
-
84
- const onScroll = useCallback(
85
- (e: UIEvent<HTMLDivElement>) => {
86
- setInitialScrollPosition(e.currentTarget.scrollTop);
87
- },
88
- [setInitialScrollPosition],
89
- );
90
-
91
58
  return (
92
59
  <div
93
60
  id={id}
@@ -149,7 +116,7 @@ export const LargeModal: FC<LargeModalProps> = ({
149
116
  </button>
150
117
  </div>
151
118
  <ProgressBar animating={showProgress} />
152
- <div className={"modal-body"} ref={scrollRef} onScroll={onScroll}>
119
+ <div className={"modal-body"} ref={scrollRef}>
153
120
  {children}
154
121
  </div>
155
122
  {modalFooter}
@@ -1,6 +1,7 @@
1
1
  import clsx from "clsx";
2
- import { FC, ReactNode, useCallback, useEffect, useState } from "react";
2
+ import { FC, MouseEvent, ReactNode, useCallback, useEffect } from "react";
3
3
  import { ApplicationIcons } from "../appearance/icons";
4
+ import { useProperty } from "../state/hooks";
4
5
  import styles from "./LightboxCarousel.module.css";
5
6
 
6
7
  interface Slide {
@@ -9,16 +10,25 @@ interface Slide {
9
10
  }
10
11
 
11
12
  interface LightboxCarouselProps {
13
+ id: string;
12
14
  slides: Slide[];
13
15
  }
14
16
 
15
17
  /**
16
18
  * LightboxCarousel component provides a carousel with lightbox functionality.
17
19
  */
18
- export const LightboxCarousel: FC<LightboxCarouselProps> = ({ slides }) => {
19
- const [isOpen, setIsOpen] = useState(false);
20
- const [showOverlay, setShowOverlay] = useState(false);
21
- const [currentIndex, setCurrentIndex] = useState(0);
20
+ export const LightboxCarousel: FC<LightboxCarouselProps> = ({ id, slides }) => {
21
+ const [isOpen, setIsOpen] = useProperty(id, "isOpen", {
22
+ defaultValue: false,
23
+ });
24
+
25
+ const [currentIndex, setCurrentIndex] = useProperty(id, "currentIndex", {
26
+ defaultValue: 0,
27
+ });
28
+
29
+ const [showOverlay, setShowOverlay] = useProperty(id, "showOverlay", {
30
+ defaultValue: false,
31
+ });
22
32
 
23
33
  const openLightbox = useCallback(
24
34
  (index: number) => {
@@ -28,7 +38,7 @@ export const LightboxCarousel: FC<LightboxCarouselProps> = ({ slides }) => {
28
38
  // Slight delay before setting isOpen so the fade-in starts from opacity: 0
29
39
  setTimeout(() => setIsOpen(true), 10);
30
40
  },
31
- [setCurrentIndex, setShowOverlay],
41
+ [setIsOpen],
32
42
  );
33
43
 
34
44
  const closeLightbox = useCallback(() => {
@@ -46,13 +56,11 @@ export const LightboxCarousel: FC<LightboxCarouselProps> = ({ slides }) => {
46
56
  }, [isOpen, showOverlay, setShowOverlay]);
47
57
 
48
58
  const showNext = useCallback(() => {
49
- setCurrentIndex((prev) => {
50
- return (prev + 1) % slides.length;
51
- });
59
+ setCurrentIndex(currentIndex + 1);
52
60
  }, [slides, setCurrentIndex]);
53
61
 
54
62
  const showPrev = useCallback(() => {
55
- setCurrentIndex((prev) => (prev - 1 + slides.length) % slides.length);
63
+ setCurrentIndex((currentIndex - 1 + slides.length) % slides.length);
56
64
  }, [slides, setCurrentIndex]);
57
65
 
58
66
  // Keyboard Navigation
@@ -73,6 +81,13 @@ export const LightboxCarousel: FC<LightboxCarouselProps> = ({ slides }) => {
73
81
  return () => window.removeEventListener("keyup", handleKeyUp);
74
82
  }, [isOpen, showNext, showPrev]);
75
83
 
84
+ const handleThumbClick = useCallback(
85
+ (e: MouseEvent<HTMLDivElement>) => {
86
+ const index = Number((e.currentTarget as HTMLDivElement).dataset.index);
87
+ openLightbox(index);
88
+ },
89
+ [openLightbox],
90
+ );
76
91
  return (
77
92
  <div className={clsx("lightbox-carousel-container")}>
78
93
  <div className={clsx(styles.carouselThumbs)}>
@@ -80,8 +95,9 @@ export const LightboxCarousel: FC<LightboxCarouselProps> = ({ slides }) => {
80
95
  return (
81
96
  <div
82
97
  key={index}
98
+ data-index={index}
83
99
  className={clsx(styles.carouselThumb)}
84
- onClick={() => openLightbox(index)}
100
+ onClick={handleThumbClick}
85
101
  >
86
102
  <div>{slide.label}</div>
87
103
  <div>
@@ -0,0 +1,11 @@
1
+ .progressContainer {
2
+ width: 100%;
3
+ display: flex;
4
+ align-content: flex-start;
5
+ justify-content: flex-start;
6
+ margin-left: 2.5em;
7
+ }
8
+
9
+ .progressText {
10
+ margin-left: 0.4em;
11
+ }
@@ -0,0 +1,177 @@
1
+ import clsx from "clsx";
2
+ import {
3
+ ReactNode,
4
+ RefObject,
5
+ useCallback,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
11
+ import { usePrevious, useProperty } from "../state/hooks";
12
+ import { useRafThrottle, useVirtuosoState } from "../state/scrolling";
13
+ import { PulsingDots } from "./PulsingDots";
14
+
15
+ import styles from "./LiveVirtualList.module.css";
16
+
17
+ interface LiveVirtualListProps<T> {
18
+ id: string;
19
+ className?: string | string[];
20
+
21
+ // The scroll ref to use for the virtual list
22
+ scrollRef?: RefObject<HTMLDivElement | null>;
23
+
24
+ // The data and rendering function for the data
25
+ data: T[];
26
+ renderRow: (index: number, item: T) => ReactNode;
27
+
28
+ // Whether the virtual list is live (controls its follow
29
+ // behavior)
30
+ live?: boolean;
31
+
32
+ // The progress message to show (if any)
33
+ // no message show if progress isn't provided
34
+ showProgress?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Renders the Transcript component.
39
+ */
40
+ export const LiveVirtualList = <T,>({
41
+ id,
42
+ className,
43
+ data,
44
+ renderRow,
45
+ scrollRef,
46
+ live,
47
+ showProgress,
48
+ }: LiveVirtualListProps<T>) => {
49
+ // The list handle and list state management
50
+ const listHandle = useRef<VirtuosoHandle>(null);
51
+ const { getRestoreState, isScrolling } = useVirtuosoState(
52
+ listHandle,
53
+ `live-virtual-list-${id}`,
54
+ );
55
+
56
+ // Track whether we're following output
57
+ const [followOutput, setFollowOutput] = useProperty<boolean | null>(
58
+ id,
59
+ "follow",
60
+ {
61
+ defaultValue: null,
62
+ },
63
+ );
64
+ const isAutoScrollingRef = useRef(false);
65
+
66
+ // Only we first load set the defaul value for following
67
+ // based upon whether or not the transcript is 'live'
68
+ useEffect(() => {
69
+ if (followOutput === null) {
70
+ setFollowOutput(!!live);
71
+ }
72
+ }, []);
73
+
74
+ // Track whether we were previously running so we can
75
+ // decide whether to pop up to the top
76
+ const prevLive = usePrevious(live);
77
+ useEffect(() => {
78
+ // When we finish running, if we are following output
79
+ // then scroll up to the top
80
+ if (!live && prevLive && followOutput && scrollRef?.current) {
81
+ setFollowOutput(false);
82
+ setTimeout(() => {
83
+ if (scrollRef.current) {
84
+ scrollRef.current.scrollTo({ top: 0, behavior: "instant" });
85
+ }
86
+ }, 100);
87
+ }
88
+ }, [live, followOutput]);
89
+
90
+ const handleScroll = useRafThrottle(() => {
91
+ // Skip processing if auto-scrolling is in progress
92
+ if (isAutoScrollingRef.current) return;
93
+
94
+ // If we're not running, don't mess with auto scrolling
95
+ if (!live) return;
96
+
97
+ // Make the bottom start following
98
+ if (scrollRef?.current && listHandle.current) {
99
+ const parent = scrollRef.current;
100
+ const isAtBottom =
101
+ parent.scrollHeight - parent.scrollTop <= parent.clientHeight + 30;
102
+
103
+ if (isAtBottom && !followOutput) {
104
+ setFollowOutput(true);
105
+ } else if (!isAtBottom && followOutput) {
106
+ setFollowOutput(false);
107
+ }
108
+ }
109
+ }, [setFollowOutput, followOutput, live]);
110
+
111
+ const heightChanged = useCallback(
112
+ (height: number) => {
113
+ requestAnimationFrame(() => {
114
+ if (followOutput && live && scrollRef?.current) {
115
+ isAutoScrollingRef.current = true;
116
+ listHandle.current?.scrollTo({ top: height });
117
+ requestAnimationFrame(() => {
118
+ isAutoScrollingRef.current = false;
119
+ });
120
+ }
121
+ });
122
+ },
123
+ [scrollRef, followOutput, live],
124
+ );
125
+
126
+ useEffect(() => {
127
+ // Force a re-render after initial mount
128
+ // This is here only because in VScode, for some reason,
129
+ // when this transcript is restored, the height isn't computed
130
+ // properly on the first render. This basically gives it a second
131
+ // change to compute the height and lay itself out.
132
+ const timer = setTimeout(() => {
133
+ forceUpdate();
134
+ }, 0);
135
+ return () => clearTimeout(timer);
136
+ }, []);
137
+
138
+ const [, forceRender] = useState({});
139
+ const forceUpdate = useCallback(() => forceRender({}), []);
140
+
141
+ const Footer = () => {
142
+ return showProgress ? (
143
+ <div className={clsx(styles.progressContainer)}>
144
+ <PulsingDots subtle={false} size="medium" />
145
+ </div>
146
+ ) : undefined;
147
+ };
148
+
149
+ useEffect(() => {
150
+ // Listen to scroll events
151
+ const parent = scrollRef?.current;
152
+ if (parent) {
153
+ parent.addEventListener("scroll", handleScroll);
154
+ return () => parent.removeEventListener("scroll", handleScroll);
155
+ }
156
+ }, [scrollRef, handleScroll]);
157
+
158
+ return (
159
+ <Virtuoso
160
+ ref={listHandle}
161
+ customScrollParent={scrollRef?.current ? scrollRef.current : undefined}
162
+ style={{ height: "100%", width: "100%" }}
163
+ data={data}
164
+ defaultItemHeight={250}
165
+ itemContent={renderRow}
166
+ increaseViewportBy={{ top: 1000, bottom: 1000 }}
167
+ overscan={{ main: 2, reverse: 2 }}
168
+ className={clsx("transcript", className)}
169
+ isScrolling={isScrolling}
170
+ restoreStateFrom={getRestoreState()}
171
+ totalListHeightChanged={heightChanged}
172
+ components={{
173
+ Footer,
174
+ }}
175
+ />
176
+ );
177
+ };
@@ -1,15 +1,15 @@
1
1
  import clsx from "clsx";
2
2
  import markdownit from "markdown-it";
3
- import React from "react";
3
+ import { CSSProperties, forwardRef } from "react";
4
4
  import "./MarkdownDiv.css";
5
5
 
6
6
  interface MarkdownDivProps {
7
7
  markdown: string;
8
- style?: React.CSSProperties;
8
+ style?: CSSProperties;
9
9
  className?: string | string[];
10
10
  }
11
11
 
12
- export const MarkdownDiv = React.forwardRef<HTMLDivElement, MarkdownDivProps>(
12
+ export const MarkdownDiv = forwardRef<HTMLDivElement, MarkdownDivProps>(
13
13
  ({ markdown, style, className }, ref) => {
14
14
  // Escape all tags
15
15
  const escaped = markdown ? escape(markdown) : "";