inspect-ai 0.3.69__py3-none-any.whl → 0.3.71__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 (242) hide show
  1. inspect_ai/_cli/eval.py +27 -9
  2. inspect_ai/_display/core/display.py +2 -0
  3. inspect_ai/_display/core/footer.py +13 -3
  4. inspect_ai/_display/plain/display.py +6 -2
  5. inspect_ai/_display/rich/display.py +19 -6
  6. inspect_ai/_display/textual/app.py +9 -3
  7. inspect_ai/_display/textual/display.py +4 -0
  8. inspect_ai/_display/textual/widgets/samples.py +4 -10
  9. inspect_ai/_display/textual/widgets/transcript.py +35 -18
  10. inspect_ai/_eval/eval.py +14 -2
  11. inspect_ai/_eval/evalset.py +6 -1
  12. inspect_ai/_eval/run.py +6 -0
  13. inspect_ai/_eval/task/run.py +49 -23
  14. inspect_ai/_eval/task/task.py +26 -3
  15. inspect_ai/_util/content.py +20 -1
  16. inspect_ai/_util/interrupt.py +6 -0
  17. inspect_ai/_util/logger.py +19 -0
  18. inspect_ai/_util/rich.py +7 -8
  19. inspect_ai/_util/text.py +13 -0
  20. inspect_ai/_util/transcript.py +20 -6
  21. inspect_ai/_util/working.py +50 -0
  22. inspect_ai/_view/www/App.css +6 -0
  23. inspect_ai/_view/www/dist/assets/index.css +171 -99
  24. inspect_ai/_view/www/dist/assets/index.js +5972 -2770
  25. inspect_ai/_view/www/eslint.config.mjs +24 -1
  26. inspect_ai/_view/www/log-schema.json +619 -21
  27. inspect_ai/_view/www/package.json +8 -3
  28. inspect_ai/_view/www/src/App.tsx +2 -2
  29. inspect_ai/_view/www/src/appearance/icons.ts +3 -1
  30. inspect_ai/_view/www/src/components/AnsiDisplay.tsx +4 -3
  31. inspect_ai/_view/www/src/components/Card.tsx +9 -8
  32. inspect_ai/_view/www/src/components/DownloadButton.tsx +2 -1
  33. inspect_ai/_view/www/src/components/EmptyPanel.tsx +2 -2
  34. inspect_ai/_view/www/src/components/ErrorPanel.tsx +4 -3
  35. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +13 -5
  36. inspect_ai/_view/www/src/components/FindBand.tsx +3 -3
  37. inspect_ai/_view/www/src/components/HumanBaselineView.tsx +3 -3
  38. inspect_ai/_view/www/src/components/LabeledValue.tsx +5 -4
  39. inspect_ai/_view/www/src/components/LargeModal.tsx +18 -13
  40. inspect_ai/_view/www/src/components/{LightboxCarousel.css → LightboxCarousel.module.css} +22 -18
  41. inspect_ai/_view/www/src/components/LightboxCarousel.tsx +36 -27
  42. inspect_ai/_view/www/src/components/MessageBand.tsx +2 -1
  43. inspect_ai/_view/www/src/components/NavPills.tsx +9 -8
  44. inspect_ai/_view/www/src/components/ProgressBar.tsx +2 -1
  45. inspect_ai/_view/www/src/components/TabSet.tsx +21 -15
  46. inspect_ai/_view/www/src/index.tsx +2 -2
  47. inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +11 -9
  48. inspect_ai/_view/www/src/metadata/MetaDataView.tsx +3 -2
  49. inspect_ai/_view/www/src/metadata/MetadataGrid.module.css +1 -0
  50. inspect_ai/_view/www/src/metadata/RenderedContent.tsx +16 -1
  51. inspect_ai/_view/www/src/plan/DatasetDetailView.tsx +3 -2
  52. inspect_ai/_view/www/src/plan/DetailStep.tsx +2 -1
  53. inspect_ai/_view/www/src/plan/PlanCard.tsx +2 -5
  54. inspect_ai/_view/www/src/plan/PlanDetailView.tsx +6 -9
  55. inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +2 -1
  56. inspect_ai/_view/www/src/plan/SolverDetailView.tsx +3 -3
  57. inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +2 -2
  58. inspect_ai/_view/www/src/samples/SampleDialog.tsx +3 -3
  59. inspect_ai/_view/www/src/samples/SampleDisplay.module.css +9 -1
  60. inspect_ai/_view/www/src/samples/SampleDisplay.tsx +30 -3
  61. inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +4 -0
  62. inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +25 -4
  63. inspect_ai/_view/www/src/samples/SamplesTools.tsx +2 -1
  64. inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +3 -19
  65. inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +2 -1
  66. inspect_ai/_view/www/src/samples/chat/ChatMessageRow.tsx +2 -1
  67. inspect_ai/_view/www/src/samples/chat/ChatView.tsx +2 -1
  68. inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +22 -7
  69. inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +35 -6
  70. inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -2
  71. inspect_ai/_view/www/src/samples/chat/messages.ts +15 -2
  72. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +13 -4
  73. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.module.css +2 -2
  74. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +18 -19
  75. inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.module.css +1 -1
  76. inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +4 -3
  77. inspect_ai/_view/www/src/samples/chat/tools/ToolTitle.tsx +2 -2
  78. inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +2 -3
  79. inspect_ai/_view/www/src/samples/error/SampleErrorView.tsx +3 -2
  80. inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +2 -1
  81. inspect_ai/_view/www/src/samples/list/SampleHeader.tsx +2 -1
  82. inspect_ai/_view/www/src/samples/list/SampleList.tsx +57 -45
  83. inspect_ai/_view/www/src/samples/list/SampleRow.tsx +2 -1
  84. inspect_ai/_view/www/src/samples/list/SampleSeparator.tsx +2 -1
  85. inspect_ai/_view/www/src/samples/sample-tools/EpochFilter.tsx +2 -2
  86. inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +4 -3
  87. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +2 -5
  88. inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +2 -2
  89. inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +2 -1
  90. inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +2 -2
  91. inspect_ai/_view/www/src/samples/transcript/ApprovalEventView.tsx +2 -1
  92. inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +2 -1
  93. inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +2 -1
  94. inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +2 -1
  95. inspect_ai/_view/www/src/samples/transcript/LoggerEventView.module.css +4 -0
  96. inspect_ai/_view/www/src/samples/transcript/LoggerEventView.tsx +12 -2
  97. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +1 -1
  98. inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +25 -28
  99. inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +2 -1
  100. inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +9 -4
  101. inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +2 -2
  102. inspect_ai/_view/www/src/samples/transcript/SandboxEventView.module.css +32 -0
  103. inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +153 -0
  104. inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +2 -2
  105. inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +12 -5
  106. inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +18 -14
  107. inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -5
  108. inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +53 -16
  109. inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +2 -1
  110. inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +2 -1
  111. inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +6 -3
  112. inspect_ai/_view/www/src/samples/transcript/event/EventRow.tsx +3 -2
  113. inspect_ai/_view/www/src/samples/transcript/event/EventSection.tsx +2 -2
  114. inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.module.css +28 -0
  115. inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.tsx +115 -0
  116. inspect_ai/_view/www/src/samples/transcript/event/utils.ts +29 -0
  117. inspect_ai/_view/www/src/samples/transcript/state/StateDiffView.tsx +2 -1
  118. inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +3 -3
  119. inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +11 -8
  120. inspect_ai/_view/www/src/samples/transcript/types.ts +3 -1
  121. inspect_ai/_view/www/src/types/log.d.ts +312 -137
  122. inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +6 -10
  123. inspect_ai/_view/www/src/usage/ModelUsagePanel.module.css +4 -0
  124. inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +32 -9
  125. inspect_ai/_view/www/src/usage/TokenTable.tsx +4 -6
  126. inspect_ai/_view/www/src/usage/UsageCard.tsx +2 -1
  127. inspect_ai/_view/www/src/utils/format.ts +8 -5
  128. inspect_ai/_view/www/src/utils/json.ts +24 -0
  129. inspect_ai/_view/www/src/workspace/WorkSpace.tsx +6 -5
  130. inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +18 -8
  131. inspect_ai/_view/www/src/workspace/error/TaskErrorPanel.tsx +2 -1
  132. inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +2 -1
  133. inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +3 -3
  134. inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +4 -3
  135. inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +5 -4
  136. inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +5 -8
  137. inspect_ai/_view/www/src/workspace/sidebar/EvalStatus.tsx +5 -4
  138. inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +2 -1
  139. inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +2 -1
  140. inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -2
  141. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +2 -1
  142. inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +2 -2
  143. inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +2 -2
  144. inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +2 -5
  145. inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +12 -11
  146. inspect_ai/_view/www/yarn.lock +241 -5
  147. inspect_ai/log/__init__.py +2 -0
  148. inspect_ai/log/_condense.py +4 -0
  149. inspect_ai/log/_log.py +72 -12
  150. inspect_ai/log/_recorders/eval.py +6 -1
  151. inspect_ai/log/_samples.py +5 -1
  152. inspect_ai/log/_transcript.py +89 -2
  153. inspect_ai/model/__init__.py +2 -0
  154. inspect_ai/model/_call_tools.py +8 -1
  155. inspect_ai/model/_chat_message.py +22 -7
  156. inspect_ai/model/_conversation.py +11 -9
  157. inspect_ai/model/_generate_config.py +25 -4
  158. inspect_ai/model/_model.py +164 -72
  159. inspect_ai/model/_model_call.py +10 -3
  160. inspect_ai/model/_model_output.py +3 -0
  161. inspect_ai/model/_openai.py +106 -40
  162. inspect_ai/model/_providers/anthropic.py +145 -26
  163. inspect_ai/model/_providers/bedrock.py +7 -0
  164. inspect_ai/model/_providers/cloudflare.py +20 -7
  165. inspect_ai/model/_providers/google.py +29 -8
  166. inspect_ai/model/_providers/groq.py +66 -27
  167. inspect_ai/model/_providers/hf.py +6 -0
  168. inspect_ai/model/_providers/mistral.py +78 -51
  169. inspect_ai/model/_providers/openai.py +66 -4
  170. inspect_ai/model/_providers/openai_o1.py +10 -0
  171. inspect_ai/model/_providers/providers.py +2 -2
  172. inspect_ai/model/_providers/util/tracker.py +92 -0
  173. inspect_ai/model/_providers/vllm.py +13 -5
  174. inspect_ai/model/_reasoning.py +15 -2
  175. inspect_ai/scorer/_model.py +23 -19
  176. inspect_ai/solver/_basic_agent.py +1 -3
  177. inspect_ai/solver/_bridge/patch.py +0 -2
  178. inspect_ai/solver/_human_agent/agent.py +14 -10
  179. inspect_ai/solver/_human_agent/commands/__init__.py +7 -3
  180. inspect_ai/solver/_human_agent/commands/submit.py +76 -30
  181. inspect_ai/solver/_limit.py +4 -4
  182. inspect_ai/solver/_plan.py +0 -3
  183. inspect_ai/solver/_task_state.py +7 -0
  184. inspect_ai/tool/__init__.py +2 -0
  185. inspect_ai/tool/_tool.py +3 -1
  186. inspect_ai/tool/_tools/_computer/_resources/tool/_run.py +1 -1
  187. inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +8 -0
  188. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +24 -0
  189. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +25 -0
  190. inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +5 -6
  191. inspect_ai/tool/_tools/_web_browser/_resources/README.md +10 -11
  192. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +71 -0
  193. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +323 -0
  194. inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +5 -0
  195. inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +279 -0
  196. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +9 -0
  197. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +293 -0
  198. inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +94 -0
  199. inspect_ai/tool/_tools/_web_browser/_resources/constants.py +2 -0
  200. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +2 -0
  201. inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +50 -0
  202. inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +31 -359
  203. inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +280 -0
  204. inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +65 -0
  205. inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +64 -0
  206. inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +146 -0
  207. inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +64 -0
  208. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +180 -0
  209. inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +15 -9
  210. inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +15 -0
  211. inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +44 -0
  212. inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +39 -0
  213. inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +198 -48
  214. inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +26 -25
  215. inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +178 -39
  216. inspect_ai/tool/_tools/_web_browser/_web_browser.py +38 -19
  217. inspect_ai/tool/_tools/_web_search.py +3 -3
  218. inspect_ai/util/__init__.py +2 -1
  219. inspect_ai/util/_concurrency.py +14 -8
  220. inspect_ai/util/_display.py +12 -0
  221. inspect_ai/util/_sandbox/context.py +15 -0
  222. inspect_ai/util/_sandbox/docker/docker.py +7 -5
  223. inspect_ai/util/_sandbox/environment.py +32 -1
  224. inspect_ai/util/_sandbox/events.py +183 -0
  225. inspect_ai/util/_sandbox/local.py +3 -3
  226. inspect_ai/util/_sandbox/self_check.py +131 -43
  227. inspect_ai/util/_subtask.py +11 -0
  228. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/METADATA +3 -3
  229. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/RECORD +233 -211
  230. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/WHEEL +1 -1
  231. inspect_ai/_view/www/src/components/VirtualList.module.css +0 -19
  232. inspect_ai/_view/www/src/components/VirtualList.tsx +0 -292
  233. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_node.py +0 -312
  234. inspect_ai/tool/_tools/_web_browser/_resources/dm_env_servicer.py +0 -275
  235. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.png +0 -0
  236. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_node.py +0 -176
  237. inspect_ai/tool/_tools/_web_browser/_resources/test_dm_env_servicer.py +0 -135
  238. inspect_ai/tool/_tools/_web_browser/_resources/test_web_environment.py +0 -71
  239. inspect_ai/tool/_tools/_web_browser/_resources/web_environment.py +0 -184
  240. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/LICENSE +0 -0
  241. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/entry_points.txt +0 -0
  242. {inspect_ai-0.3.69.dist-info → inspect_ai-0.3.71.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,19 +0,0 @@
1
- .container {
2
- position: relative;
3
- width: 100%;
4
- min-height: 100%;
5
- overflow: visible;
6
- }
7
-
8
- .container.hidden {
9
- overflow: hidden;
10
- }
11
-
12
- .content {
13
- position: absolute;
14
- top: 0;
15
- left: 0;
16
- height: 100%;
17
- width: 100%;
18
- overflow: visible;
19
- }
@@ -1,292 +0,0 @@
1
- import clsx from "clsx";
2
- import React, {
3
- forwardRef,
4
- useEffect,
5
- useImperativeHandle,
6
- useMemo,
7
- useRef,
8
- useState,
9
- } from "react";
10
- import styles from "./VirtualList.module.css";
11
-
12
- export interface VirtualListRef {
13
- focus: () => void;
14
- scrollToIndex: (index: number, direction?: "up" | "down") => void;
15
- }
16
-
17
- interface VirtualListProps<T> {
18
- data: T[];
19
- renderRow: (item: T, index: number) => React.ReactNode;
20
- overscanCount?: number;
21
- initialEstimatedRowHeight?: number;
22
- sync?: boolean;
23
- scrollRef?: React.RefObject<HTMLElement | null>;
24
- className?: string;
25
- style?: React.CSSProperties;
26
- tabIndex?: number;
27
- onKeyDown?: (event: React.KeyboardEvent<HTMLDivElement>) => void;
28
- }
29
-
30
- interface ListMetrics {
31
- rowHeights: Map<number, number>;
32
- totalHeight: number;
33
- estimatedRowHeight: number;
34
- }
35
-
36
- export const VirtualList = forwardRef(function VirtualList<T>(
37
- {
38
- data,
39
- renderRow,
40
- overscanCount = 15,
41
- initialEstimatedRowHeight = 50,
42
- sync = false,
43
- scrollRef,
44
- onKeyDown,
45
- ...props
46
- }: VirtualListProps<T>,
47
- ref: React.Ref<VirtualListRef>,
48
- ) {
49
- const [height, setHeight] = useState(0);
50
- const [offset, setOffset] = useState(0);
51
- const [listMetrics, setListMetrics] = useState<ListMetrics>({
52
- rowHeights: new Map(),
53
- totalHeight: data.length * initialEstimatedRowHeight,
54
- estimatedRowHeight: initialEstimatedRowHeight,
55
- });
56
-
57
- const baseRef = useRef<HTMLDivElement>(null);
58
- const containerRef = useRef<HTMLDivElement>(null);
59
- const rowRefs = useRef<Map<number, HTMLElement>>(new Map());
60
-
61
- const getRowHeight = (index: number): number => {
62
- return listMetrics.rowHeights.get(index) || listMetrics.estimatedRowHeight;
63
- };
64
-
65
- // Calculate new estimated height based on measured rows
66
- const calculateEstimatedHeight = (heights: Map<number, number>): number => {
67
- if (heights.size === 0) return listMetrics.estimatedRowHeight;
68
-
69
- // Calculate average of measured heights
70
- let sum = 0;
71
- heights.forEach((height) => {
72
- sum += height;
73
- });
74
-
75
- // Use exponential moving average to smooth transitions
76
- const alpha = 0.2; // Smoothing factor
77
- const newEstimate = sum / heights.size;
78
- return Math.round(
79
- alpha * newEstimate + (1 - alpha) * listMetrics.estimatedRowHeight,
80
- );
81
- };
82
-
83
- const rowPositions = useMemo(() => {
84
- let currentPosition = 0;
85
- const positions = new Map<number, number>();
86
-
87
- for (let i = 0; i < data.length; i++) {
88
- positions.set(i, currentPosition);
89
- currentPosition += getRowHeight(i);
90
- }
91
-
92
- return positions;
93
- }, [listMetrics.rowHeights, listMetrics.estimatedRowHeight, data.length]);
94
-
95
- // Measure rendered rows and update heights if needed
96
- const measureRows = () => {
97
- let updates: [number, number][] = [];
98
-
99
- rowRefs.current.forEach((element, index) => {
100
- if (element) {
101
- const measuredHeight = element.offsetHeight;
102
- if (
103
- measuredHeight &&
104
- measuredHeight !== listMetrics.rowHeights.get(index)
105
- ) {
106
- updates.push([index, measuredHeight]);
107
- }
108
- }
109
- });
110
-
111
- if (updates.length === 0) return;
112
-
113
- const newHeights = new Map(listMetrics.rowHeights);
114
- updates.forEach(([index, height]) => {
115
- newHeights.set(index, height);
116
- });
117
-
118
- // Calculate new estimated height
119
- const newEstimatedHeight = calculateEstimatedHeight(newHeights);
120
-
121
- let newTotalHeight = 0;
122
- for (let i = 0; i < data.length; i++) {
123
- newTotalHeight += newHeights.get(i) || newEstimatedHeight;
124
- }
125
-
126
- setListMetrics({
127
- rowHeights: newHeights,
128
- totalHeight: newTotalHeight,
129
- estimatedRowHeight: newEstimatedHeight,
130
- });
131
- };
132
-
133
- useImperativeHandle(
134
- ref,
135
- () => ({
136
- focus: () => {
137
- baseRef.current?.focus();
138
- },
139
- scrollToIndex: (index: number, direction?: "up" | "down") => {
140
- const scrollElement = scrollRef?.current || baseRef.current;
141
- if (!scrollElement || index < 0 || index >= data.length) return;
142
-
143
- const currentScrollTop = scrollElement.scrollTop;
144
- const viewportHeight = scrollElement.offsetHeight;
145
-
146
- const rowTop = rowPositions.get(index) || 0;
147
- const rowHeight = getRowHeight(index);
148
- const rowBottom = rowTop + rowHeight;
149
-
150
- const isVisible =
151
- rowTop >= currentScrollTop &&
152
- rowBottom <= currentScrollTop + viewportHeight;
153
- if (isVisible) return;
154
-
155
- let newScrollTop: number;
156
- if (direction === "up") {
157
- newScrollTop = rowTop;
158
- } else {
159
- newScrollTop = rowBottom - viewportHeight;
160
- }
161
-
162
- newScrollTop = Math.max(
163
- 0,
164
- Math.min(newScrollTop, listMetrics.totalHeight - viewportHeight),
165
- );
166
- scrollElement.scrollTop = newScrollTop;
167
- },
168
- }),
169
- [rowPositions, data.length],
170
- );
171
-
172
- const resize = () => {
173
- const scrollElement = scrollRef?.current || baseRef.current;
174
- if (scrollElement && height !== scrollElement.offsetHeight) {
175
- setHeight(scrollElement.offsetHeight);
176
- }
177
- };
178
-
179
- const handleScroll = throttle(() => {
180
- const scrollElement = scrollRef?.current || baseRef.current;
181
- if (scrollElement) {
182
- setOffset(scrollElement.scrollTop);
183
- }
184
- if (sync) {
185
- setOffset((prev) => prev);
186
- }
187
- }, 100);
188
-
189
- useEffect(() => {
190
- resize();
191
- const scrollElement = scrollRef?.current || baseRef.current;
192
-
193
- if (scrollElement) {
194
- scrollElement.addEventListener("scroll", handleScroll);
195
- window.addEventListener("resize", resize);
196
-
197
- return () => {
198
- scrollElement.removeEventListener("scroll", handleScroll);
199
- window.removeEventListener("resize", resize);
200
- };
201
- }
202
- }, [scrollRef?.current]);
203
-
204
- useEffect(() => {
205
- measureRows();
206
- });
207
-
208
- const findRowAtOffset = (targetOffset: number): number => {
209
- if (targetOffset <= 0) return 0;
210
- if (targetOffset >= listMetrics.totalHeight) return data.length - 1;
211
-
212
- let low = 0;
213
- let high = data.length - 1;
214
- let lastValid = 0;
215
-
216
- while (low <= high) {
217
- const mid = Math.floor((low + high) / 2);
218
- const rowStart = rowPositions.get(mid) || 0;
219
-
220
- if (rowStart <= targetOffset) {
221
- lastValid = mid;
222
- low = mid + 1;
223
- } else {
224
- high = mid - 1;
225
- }
226
- }
227
- return lastValid;
228
- };
229
-
230
- const firstVisibleIdx = findRowAtOffset(offset);
231
- const lastVisibleIdx = findRowAtOffset(offset + height);
232
-
233
- const start = Math.max(0, firstVisibleIdx - overscanCount);
234
- const end = Math.min(data.length, lastVisibleIdx + overscanCount);
235
-
236
- const renderedRows = useMemo(() => {
237
- const selection = data.slice(start, end);
238
- return selection.map((item, index) => {
239
- const actualIndex = start + index;
240
- return (
241
- <div
242
- key={`list-item-${actualIndex}`}
243
- ref={(el) => {
244
- if (el) {
245
- rowRefs.current.set(actualIndex, el);
246
- } else {
247
- rowRefs.current.delete(actualIndex);
248
- }
249
- }}
250
- >
251
- {renderRow(item, actualIndex)}
252
- </div>
253
- );
254
- });
255
- }, [data, start, end, renderRow]);
256
-
257
- const top = rowPositions.get(start) || 0;
258
- const scrollProps = scrollRef ? {} : { onScroll: handleScroll };
259
-
260
- return (
261
- <div ref={baseRef} {...props} {...scrollProps} onKeyDown={onKeyDown}>
262
- <div
263
- className={clsx(
264
- styles.container,
265
- !scrollRef?.current ? styles.hidden : undefined,
266
- )}
267
- style={{ height: `${listMetrics.totalHeight}px` }}
268
- >
269
- <div
270
- className={styles.content}
271
- style={{ transform: `translateY(${top}px)` }}
272
- ref={containerRef}
273
- >
274
- {renderedRows}
275
- </div>
276
- </div>
277
- </div>
278
- );
279
- }) as <T>(
280
- props: VirtualListProps<T> & { ref?: React.Ref<VirtualListRef> },
281
- ) => React.ReactElement;
282
-
283
- const throttle = (func: (...args: any[]) => void, limit: number) => {
284
- let inThrottle: boolean;
285
- return function (this: any, ...args: any[]) {
286
- if (!inThrottle) {
287
- func.apply(this, args);
288
- inThrottle = true;
289
- setTimeout(() => (inThrottle = false), limit);
290
- }
291
- };
292
- };
@@ -1,312 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- # Properities to ignore when printing out the accessibility tree.
6
- _IGNORED_ACTREE_PROPERTIES = (
7
- "focusable",
8
- "readonly",
9
- "level",
10
- "settable",
11
- "multiline",
12
- "invalid",
13
- )
14
-
15
- _IGNORED_AT_ROLES = (
16
- "generic",
17
- "list",
18
- "strong",
19
- "paragraph",
20
- "banner",
21
- "navigation",
22
- "Section",
23
- "LabelText",
24
- "Legend",
25
- "listitem",
26
- )
27
-
28
- # Typically we use the bounding rect provided by the DOM in order to track
29
- # if an element is visible or not. However, sometimes it can be useful to
30
- # instead take the union of the elements bounds and all its children's bounds.
31
- # This process is a little slower, and is probably not needed, but can be
32
- # enabled using this flag.
33
- USE_UNION_BOUNDS = False
34
-
35
- # The maximum length of the description 'tag' for each element.
36
- # Tags longer than this will be truncated with an '...' appended.
37
- MAX_NODE_TEXT_LENGTH = 120
38
-
39
- # Used for debugging, include the elements bounding rect during output.
40
- _INCLUDE_BOUNDS_IN_OUTPUT = False
41
-
42
-
43
- class NodeBounds:
44
- """Class to hold bounding rect for notes."""
45
-
46
- def __init__(self, x: int, y: int, width: int, height: int):
47
- self.x = int(x)
48
- self.y = int(y)
49
- self.width = int(width)
50
- self.height = int(height)
51
-
52
- @classmethod
53
- def from_lrtb(cls, left: int, right: int, top: int, bottom: int) -> NodeBounds:
54
- return cls(left, top, right - left, bottom - top)
55
-
56
- @property
57
- def left(self) -> int:
58
- return self.x
59
-
60
- @property
61
- def top(self) -> int:
62
- return self.y
63
-
64
- @property
65
- def right(self) -> int:
66
- return self.x + self.width
67
-
68
- @property
69
- def bottom(self) -> int:
70
- return self.y + self.height
71
-
72
- @property
73
- def center_x(self) -> int:
74
- return self.x + self.width // 2
75
-
76
- @property
77
- def center_y(self) -> int:
78
- return self.y + self.height // 2
79
-
80
- @property
81
- def area(self) -> int:
82
- return self.width * self.height
83
-
84
- def __str__(self):
85
- return f"({self.left}, {self.top}, {self.width}, {self.height})"
86
-
87
- def union(self, other: NodeBounds) -> NodeBounds:
88
- """Return the (convex) union of two bounds."""
89
- # Special cases when one or the other is an empty bound.
90
- if self.area == 0:
91
- return other
92
- if other.area == 0:
93
- return self
94
-
95
- return self.from_lrtb(
96
- min(self.left, other.left),
97
- max(self.right, other.right),
98
- min(self.top, other.top),
99
- max(self.bottom, other.bottom),
100
- )
101
-
102
- def is_inside(self, x: int, y: int) -> bool:
103
- """Returns if given point is inside the bounds or not."""
104
- return x >= self.left and y >= self.top and x < self.right and y < self.bottom
105
-
106
- def overlaps(self, other: NodeBounds) -> bool:
107
- """Returns if the two bounds intersect."""
108
- return (
109
- other.left < self.right
110
- and other.right > self.left
111
- and other.top < self.bottom
112
- and other.bottom > self.top
113
- )
114
-
115
-
116
- class AccessibilityNode:
117
- """A class for an accessibility node.
118
-
119
- Accessibility properties are specified here:
120
- https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/#type-AXNode
121
- """
122
-
123
- def __init__(self, node: dict[str, Any], parent=None, bounds=None):
124
- self._node = node
125
- self._parent: AccessibilityNode | None = parent
126
- self.bounds = bounds or NodeBounds(0, 0, 0, 0)
127
-
128
- def __getitem__(self, key: str) -> Any:
129
- """Access the underlying node fields."""
130
- return self._node.get(key)
131
-
132
- def __setitem__(self, key: str, value: Any):
133
- """Set the underlying node fields."""
134
- self._node[key] = value
135
-
136
- def __str__(self):
137
- if _INCLUDE_BOUNDS_IN_OUTPUT:
138
- bounds_string = " " + str(
139
- self.get_union_bounds() if USE_UNION_BOUNDS else self.bounds
140
- )
141
- else:
142
- bounds_string = ""
143
-
144
- node_input = self.current_input
145
- if node_input:
146
- input_string = f' Current input: "{node_input}"'
147
- else:
148
- input_string = ""
149
-
150
- # Without bounds we can't click on the element, and therefore the ID
151
- # can not be used. Still useful to show these elements though (e.g.)
152
- # to let user know about options.
153
- id_string = "*" if self.bounds.area == 0 else self.node_id
154
-
155
- url_string = ""
156
- if self.role == "image" and self._node.get("src", ""):
157
- url_string = " " + self._node["src"]
158
-
159
- return (
160
- f'[{id_string}] {self.role} "{self.short_name}"'
161
- + url_string
162
- + input_string
163
- + self.property_string()
164
- + bounds_string
165
- )
166
-
167
- @property
168
- def node_id(self) -> str:
169
- return self._node["nodeId"]
170
-
171
- @property
172
- def value(self) -> str:
173
- value_field = self._node.get("value", "{}")
174
- if isinstance(value_field, dict):
175
- return value_field.get("value", "")
176
- else:
177
- return ""
178
-
179
- @property
180
- def parent(self) -> Any | None:
181
- """Returns the first visible parent."""
182
- parent = self._parent
183
- while parent and parent.is_ignored:
184
- parent = parent._parent
185
- return parent
186
-
187
- @property
188
- def role(self) -> str:
189
- if "role" in self._node:
190
- return self._node["role"].get("value", "")
191
- else:
192
- return ""
193
-
194
- @property
195
- def name(self) -> str:
196
- if "name" in self._node:
197
- return self._node["name"].get("value", "")
198
- else:
199
- return ""
200
-
201
- @property
202
- def short_name(self) -> str:
203
- short_name = self.name[:MAX_NODE_TEXT_LENGTH]
204
- if short_name != self.name:
205
- short_name = short_name[: MAX_NODE_TEXT_LENGTH - 3] + "..."
206
- return short_name
207
-
208
- @property
209
- def is_ignored(self) -> bool:
210
- return (
211
- self._node["ignored"]
212
- or self.role in _IGNORED_AT_ROLES
213
- or not self.is_visible
214
- or (not self.name and self.role != "textbox")
215
- or (self.parent and self.parent.name == self.name)
216
- )
217
-
218
- @property
219
- def children(self) -> list[Any]:
220
- return self._node.get("children", [])
221
-
222
- @property
223
- def is_visible(self) -> bool:
224
- return self._node.get("is_visible", True)
225
-
226
- def is_selected(self) -> bool:
227
- return self._node.get("selected", False)
228
-
229
- @property
230
- def is_editable(self) -> bool:
231
- for node_property in self.properties:
232
- if node_property["name"] == "editable":
233
- return True
234
- return False
235
-
236
- @property
237
- def is_expanded(self) -> bool:
238
- for node_property in self.properties:
239
- if node_property["name"] == "expanded":
240
- return bool(node_property["value"].get("value"))
241
- return False
242
-
243
- @property
244
- def properties(self) -> list[Any]:
245
- return self._node.get("properties", [])
246
-
247
- @property
248
- def dom_id(self) -> str:
249
- """Returns the backend DOM id associated with this node."""
250
- return self._node.get("backendDOMNodeId", -1)
251
-
252
- @property
253
- def current_input(self) -> str:
254
- # For some reason text edit boxes get 'input' but comboboxs get 'values'
255
- if self.role == "combobox":
256
- value = self.value
257
- else:
258
- value = self.get_property("input")
259
- return value
260
-
261
- def get_union_bounds(self) -> list[int]:
262
- """Returns the union of the bounds for this element all children."""
263
- bounds = self.bounds
264
- for child in self.children:
265
- child_union_bounds = child.get_union_bounds()
266
- bounds = bounds.union(child_union_bounds)
267
- return bounds
268
-
269
- def link_children(self, node_lookup: dict[str, Any]) -> None:
270
- """Creates links to children nodes.
271
-
272
- Nodes are referenced by id, but we want to add references to the
273
- AccessibilityNode. We also want add a reference to the parent node.
274
-
275
- To perform this task we need access to the complete list of nodes, and
276
- therefore it must be done on a second pass after initializing the nodes.
277
-
278
- Args:
279
- node_lookup: A dictionary mapping from node_id to node instances.
280
- """
281
- self._node["children"] = []
282
- for idx in self._node.get("childIds", []):
283
- self._node["children"].append(node_lookup[idx])
284
- node_lookup[idx]._parent = self
285
-
286
- def get_property(self, property_name: str) -> Any:
287
- """Query the property value of the underlying accessibility tree node."""
288
- return self._node.get(property_name)
289
-
290
- def to_string(self, indent: int = 0, include_children: bool = True) -> str:
291
- """Returns the string representation of the node."""
292
- result = ""
293
- if not self.is_ignored:
294
- result = " " * indent + str(self) + "\n"
295
- indent += 1
296
- if include_children:
297
- children_strings = [child.to_string(indent) for child in self.children]
298
- result = result + "".join([s for s in children_strings if s])
299
- return result
300
-
301
- def property_string(self) -> str:
302
- properties = []
303
- for node_property in self.properties:
304
- try:
305
- if node_property["name"] in _IGNORED_ACTREE_PROPERTIES:
306
- continue
307
- properties.append(
308
- f"{node_property['name']}: {node_property['value'].get('value')}"
309
- )
310
- except KeyError:
311
- pass
312
- return " [" + ", ".join(properties) + "]" if properties else ""