inspect-ai 0.3.99__py3-none-any.whl → 0.3.101__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 (138) hide show
  1. inspect_ai/_cli/eval.py +2 -1
  2. inspect_ai/_display/core/config.py +11 -5
  3. inspect_ai/_display/core/panel.py +66 -2
  4. inspect_ai/_display/core/textual.py +5 -2
  5. inspect_ai/_display/plain/display.py +1 -0
  6. inspect_ai/_display/rich/display.py +2 -2
  7. inspect_ai/_display/textual/widgets/transcript.py +37 -9
  8. inspect_ai/_eval/eval.py +13 -1
  9. inspect_ai/_eval/evalset.py +3 -2
  10. inspect_ai/_eval/run.py +2 -0
  11. inspect_ai/_eval/score.py +2 -4
  12. inspect_ai/_eval/task/log.py +3 -1
  13. inspect_ai/_eval/task/run.py +59 -81
  14. inspect_ai/_util/content.py +11 -6
  15. inspect_ai/_util/interrupt.py +2 -2
  16. inspect_ai/_util/text.py +7 -0
  17. inspect_ai/_util/working.py +8 -37
  18. inspect_ai/_view/__init__.py +0 -0
  19. inspect_ai/_view/schema.py +2 -1
  20. inspect_ai/_view/www/CLAUDE.md +15 -0
  21. inspect_ai/_view/www/dist/assets/index.css +307 -171
  22. inspect_ai/_view/www/dist/assets/index.js +24733 -21641
  23. inspect_ai/_view/www/log-schema.json +77 -3
  24. inspect_ai/_view/www/package.json +9 -5
  25. inspect_ai/_view/www/src/@types/log.d.ts +9 -0
  26. inspect_ai/_view/www/src/app/App.tsx +1 -15
  27. inspect_ai/_view/www/src/app/appearance/icons.ts +4 -1
  28. inspect_ai/_view/www/src/app/content/MetaDataGrid.tsx +24 -6
  29. inspect_ai/_view/www/src/app/content/MetadataGrid.module.css +0 -5
  30. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +220 -205
  31. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +2 -1
  32. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +5 -0
  33. inspect_ai/_view/www/src/app/log-view/tabs/grouping.ts +4 -4
  34. inspect_ai/_view/www/src/app/routing/navigationHooks.ts +22 -25
  35. inspect_ai/_view/www/src/app/routing/url.ts +84 -4
  36. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +0 -5
  37. inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +1 -1
  38. inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +7 -0
  39. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +24 -17
  40. inspect_ai/_view/www/src/app/samples/SampleSummaryView.module.css +1 -2
  41. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +8 -6
  42. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +0 -4
  43. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +3 -2
  44. inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +2 -0
  45. inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +2 -0
  46. inspect_ai/_view/www/src/app/samples/chat/messages.ts +1 -0
  47. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -0
  48. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +17 -5
  49. inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +1 -1
  50. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +1 -2
  51. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +1 -1
  52. inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +1 -2
  53. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.module.css +1 -1
  54. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
  55. inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +1 -1
  56. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +3 -2
  57. inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +4 -5
  58. inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +1 -1
  59. inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +1 -2
  60. inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +1 -3
  61. inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +1 -2
  62. inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +3 -4
  63. inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.module.css +42 -0
  64. inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.tsx +77 -0
  65. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +27 -71
  66. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +13 -3
  67. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +27 -2
  68. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +1 -0
  69. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +21 -22
  70. inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.module.css +45 -0
  71. inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.tsx +223 -0
  72. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.module.css +10 -0
  73. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +258 -0
  74. inspect_ai/_view/www/src/app/samples/transcript/outline/tree-visitors.ts +187 -0
  75. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventRenderers.tsx +8 -1
  76. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +3 -4
  77. inspect_ai/_view/www/src/app/samples/transcript/transform/hooks.ts +78 -0
  78. inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +340 -135
  79. inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +3 -0
  80. inspect_ai/_view/www/src/app/samples/transcript/types.ts +2 -0
  81. inspect_ai/_view/www/src/app/types.ts +5 -1
  82. inspect_ai/_view/www/src/client/api/api-browser.ts +2 -2
  83. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +6 -1
  84. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +1 -1
  85. inspect_ai/_view/www/src/components/PopOver.tsx +422 -0
  86. inspect_ai/_view/www/src/components/PulsingDots.module.css +9 -9
  87. inspect_ai/_view/www/src/components/PulsingDots.tsx +4 -1
  88. inspect_ai/_view/www/src/components/StickyScroll.tsx +183 -0
  89. inspect_ai/_view/www/src/components/TabSet.tsx +4 -0
  90. inspect_ai/_view/www/src/state/hooks.ts +52 -2
  91. inspect_ai/_view/www/src/state/logSlice.ts +4 -3
  92. inspect_ai/_view/www/src/state/samplePolling.ts +8 -0
  93. inspect_ai/_view/www/src/state/sampleSlice.ts +53 -9
  94. inspect_ai/_view/www/src/state/scrolling.ts +152 -0
  95. inspect_ai/_view/www/src/utils/attachments.ts +7 -0
  96. inspect_ai/_view/www/src/utils/python.ts +18 -0
  97. inspect_ai/_view/www/yarn.lock +290 -33
  98. inspect_ai/agent/_react.py +12 -7
  99. inspect_ai/agent/_run.py +2 -3
  100. inspect_ai/analysis/beta/__init__.py +2 -0
  101. inspect_ai/analysis/beta/_dataframe/samples/table.py +19 -18
  102. inspect_ai/dataset/_sources/csv.py +2 -6
  103. inspect_ai/dataset/_sources/hf.py +2 -6
  104. inspect_ai/dataset/_sources/json.py +2 -6
  105. inspect_ai/dataset/_util.py +23 -0
  106. inspect_ai/log/_log.py +1 -1
  107. inspect_ai/log/_recorders/eval.py +4 -3
  108. inspect_ai/log/_recorders/file.py +2 -9
  109. inspect_ai/log/_recorders/json.py +1 -0
  110. inspect_ai/log/_recorders/recorder.py +1 -0
  111. inspect_ai/log/_transcript.py +1 -1
  112. inspect_ai/model/_call_tools.py +6 -2
  113. inspect_ai/model/_openai.py +1 -1
  114. inspect_ai/model/_openai_responses.py +85 -41
  115. inspect_ai/model/_openai_web_search.py +38 -0
  116. inspect_ai/model/_providers/azureai.py +72 -3
  117. inspect_ai/model/_providers/openai.py +4 -1
  118. inspect_ai/model/_providers/openai_responses.py +5 -1
  119. inspect_ai/scorer/_metric.py +1 -2
  120. inspect_ai/scorer/_reducer/reducer.py +1 -1
  121. inspect_ai/solver/_task_state.py +2 -2
  122. inspect_ai/tool/_tool.py +6 -2
  123. inspect_ai/tool/_tool_def.py +27 -4
  124. inspect_ai/tool/_tool_info.py +2 -0
  125. inspect_ai/tool/_tools/_web_search/_google.py +43 -15
  126. inspect_ai/tool/_tools/_web_search/_tavily.py +46 -13
  127. inspect_ai/tool/_tools/_web_search/_web_search.py +214 -45
  128. inspect_ai/util/__init__.py +4 -0
  129. inspect_ai/util/_json.py +3 -0
  130. inspect_ai/util/_limit.py +230 -20
  131. inspect_ai/util/_sandbox/docker/compose.py +20 -11
  132. inspect_ai/util/_span.py +1 -1
  133. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/METADATA +3 -3
  134. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/RECORD +138 -124
  135. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/WHEEL +1 -1
  136. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/entry_points.txt +0 -0
  137. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/licenses/LICENSE +0 -0
  138. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/top_level.txt +0 -0
@@ -92,10 +92,14 @@ export interface SampleState {
92
92
  sampleError: Error | undefined;
93
93
  sampleNeedsReload: number;
94
94
 
95
+ visiblePopover?: string;
96
+
95
97
  // Events and attachments
96
98
  runningEvents: Event[];
97
- collapsedEvents: Record<string, true> | null;
99
+ collapsedEvents: Record<string, Record<string, boolean>> | null;
98
100
  collapsedIdBuckets: Record<string, Record<string, boolean>>;
101
+
102
+ selectedOutlineId?: string;
99
103
  }
100
104
 
101
105
  export type Event =
@@ -113,11 +113,11 @@ async function eval_log_sample_data(
113
113
  params.append("log", log_file);
114
114
  params.append("id", String(id));
115
115
  params.append("epoch", String(epoch));
116
- if (last_event) {
116
+ if (last_event !== undefined) {
117
117
  params.append("last-event-id", String(last_event));
118
118
  }
119
119
 
120
- if (last_attachment) {
120
+ if (last_attachment !== undefined) {
121
121
  params.append("after-attachment-id", String(last_attachment));
122
122
  }
123
123
 
@@ -36,6 +36,9 @@ interface LiveVirtualListProps<T> {
36
36
  // The initial index to scroll to when loading
37
37
  initialTopMostItemIndex?: number;
38
38
 
39
+ // The offset to use when scrolling items
40
+ offsetTop?: number;
41
+
39
42
  components?: Components<T>;
40
43
  }
41
44
 
@@ -51,6 +54,7 @@ export const LiveVirtualList = <T,>({
51
54
  live,
52
55
  showProgress,
53
56
  initialTopMostItemIndex,
57
+ offsetTop,
54
58
  components,
55
59
  }: LiveVirtualListProps<T>) => {
56
60
  // The list handle and list state management
@@ -170,7 +174,8 @@ export const LiveVirtualList = <T,>({
170
174
  listHandle.current?.scrollToIndex({
171
175
  index: initialTopMostItemIndex,
172
176
  align: "start",
173
- behavior: "auto",
177
+ behavior: "smooth",
178
+ offset: offsetTop ? -offsetTop : undefined,
174
179
  });
175
180
  }, 50);
176
181
 
@@ -59,7 +59,7 @@ export const MarkdownDiv = forwardRef<HTMLDivElement, MarkdownDivProps>(
59
59
  ref={ref}
60
60
  dangerouslySetInnerHTML={markup}
61
61
  style={style}
62
- className={clsx(className, "markdown-content", "text-size-base")}
62
+ className={clsx(className, "markdown-content")}
63
63
  />
64
64
  );
65
65
  },
@@ -0,0 +1,422 @@
1
+ import { Placement } from "@popperjs/core";
2
+ import clsx from "clsx";
3
+ import React, {
4
+ CSSProperties,
5
+ ReactNode,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { createPortal } from "react-dom";
11
+ import { usePopper } from "react-popper";
12
+
13
+ interface PopOverProps {
14
+ id: string;
15
+ isOpen: boolean;
16
+ positionEl: HTMLElement | null;
17
+ placement?: Placement;
18
+ showArrow?: boolean;
19
+ offset?: [number, number];
20
+ usePortal?: boolean;
21
+ hoverDelay?: number;
22
+
23
+ className?: string | string[];
24
+ arrowClassName?: string | string[];
25
+
26
+ children: ReactNode;
27
+ }
28
+
29
+ /**
30
+ * A controlled Popper component for displaying content relative to a reference element
31
+ */
32
+ export const PopOver: React.FC<PopOverProps> = ({
33
+ id,
34
+ isOpen,
35
+ positionEl,
36
+ children,
37
+ placement = "bottom",
38
+ showArrow = true,
39
+ offset = [0, 8],
40
+ className = "",
41
+ arrowClassName = "",
42
+ usePortal = true,
43
+ hoverDelay = 250,
44
+ }) => {
45
+ const popperRef = useRef<HTMLDivElement | null>(null);
46
+ const arrowRef = useRef<HTMLDivElement | null>(null);
47
+ const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(
48
+ null,
49
+ );
50
+
51
+ // For delayed hover functionality
52
+ const [shouldShowPopover, setShouldShowPopover] = useState(false);
53
+ const hoverTimerRef = useRef<number | null>(null);
54
+ const isMouseMovingRef = useRef(false);
55
+
56
+ // Setup hover timer and mouse movement detection
57
+ useEffect(() => {
58
+ if (!isOpen || hoverDelay <= 0) {
59
+ setShouldShowPopover(isOpen);
60
+ return;
61
+ }
62
+
63
+ const handleMouseMove = () => {
64
+ isMouseMovingRef.current = true;
65
+
66
+ // Clear any existing timer when mouse moves
67
+ if (hoverTimerRef.current !== null) {
68
+ window.clearTimeout(hoverTimerRef.current);
69
+ }
70
+
71
+ // Start a new timer to check if mouse has stopped moving
72
+ hoverTimerRef.current = window.setTimeout(() => {
73
+ if (isOpen) {
74
+ isMouseMovingRef.current = false;
75
+ setShouldShowPopover(true);
76
+ }
77
+ }, hoverDelay);
78
+ };
79
+
80
+ const handleMouseLeave = () => {
81
+ if (hoverTimerRef.current !== null) {
82
+ window.clearTimeout(hoverTimerRef.current);
83
+ }
84
+ isMouseMovingRef.current = false;
85
+ setShouldShowPopover(false);
86
+ };
87
+
88
+ const handleMouseDown = () => {
89
+ // Cancel popover on any mouse down
90
+ if (hoverTimerRef.current !== null) {
91
+ window.clearTimeout(hoverTimerRef.current);
92
+ }
93
+ setShouldShowPopover(false);
94
+ };
95
+
96
+ // Add event listeners to the positionEl (the trigger element)
97
+ if (positionEl && isOpen) {
98
+ positionEl.addEventListener("mousemove", handleMouseMove);
99
+ positionEl.addEventListener("mouseleave", handleMouseLeave);
100
+
101
+ // Add document-wide mousedown listener to dismiss on interaction
102
+ document.addEventListener("mousedown", handleMouseDown);
103
+ document.addEventListener("click", handleMouseDown);
104
+
105
+ // Initial mouse move to start the timer
106
+ handleMouseMove();
107
+ } else {
108
+ setShouldShowPopover(false);
109
+ }
110
+
111
+ return () => {
112
+ if (positionEl) {
113
+ positionEl.removeEventListener("mousemove", handleMouseMove);
114
+ positionEl.removeEventListener("mouseleave", handleMouseLeave);
115
+ }
116
+
117
+ // Clean up the document mousedown listener
118
+ document.removeEventListener("mousedown", handleMouseDown);
119
+
120
+ document.removeEventListener("click", handleMouseDown);
121
+
122
+ if (hoverTimerRef.current !== null) {
123
+ window.clearTimeout(hoverTimerRef.current);
124
+ }
125
+ };
126
+ }, [isOpen, positionEl, hoverDelay]);
127
+
128
+ // Effect to create portal container when needed
129
+ useEffect(() => {
130
+ // Only create portal when the popover is open
131
+ if (usePortal && isOpen && shouldShowPopover) {
132
+ let container = document.getElementById(id);
133
+
134
+ if (!container) {
135
+ container = document.createElement("div");
136
+ container.id = id;
137
+ container.style.position = "absolute";
138
+ container.style.top = "0";
139
+ container.style.left = "0";
140
+ container.style.zIndex = "9999";
141
+ container.style.width = "0";
142
+ container.style.height = "0";
143
+ container.style.overflow = "visible";
144
+
145
+ document.body.appendChild(container);
146
+ }
147
+
148
+ setPortalContainer(container);
149
+
150
+ return () => {
151
+ // Clean up only when unmounting or when the popover closes
152
+ if (document.body.contains(container)) {
153
+ document.body.removeChild(container);
154
+ setPortalContainer(null);
155
+ }
156
+ };
157
+ }
158
+
159
+ return undefined;
160
+ }, [usePortal, isOpen, shouldShowPopover, id]);
161
+
162
+ // Configure modifiers for popper
163
+ const modifiers = [
164
+ { name: "offset", options: { offset } },
165
+ { name: "preventOverflow", options: { padding: 8 } },
166
+ {
167
+ name: "arrow",
168
+ enabled: showArrow,
169
+ options: {
170
+ element: arrowRef.current,
171
+ padding: 5, // This keeps the arrow from getting too close to the corner
172
+ },
173
+ },
174
+ {
175
+ name: "computeStyles",
176
+ options: {
177
+ gpuAcceleration: false,
178
+ adaptive: true,
179
+ },
180
+ },
181
+ // Ensure popper is positioned correctly with respect to its reference element
182
+ {
183
+ name: "flip",
184
+ options: {
185
+ fallbackPlacements: ["top", "right", "bottom", "left"],
186
+ },
187
+ },
188
+ ];
189
+
190
+ // Use popper hook with modifiers
191
+ const { styles, attributes, state, update } = usePopper(
192
+ positionEl,
193
+ popperRef.current,
194
+ {
195
+ placement,
196
+ strategy: "fixed",
197
+ modifiers,
198
+ },
199
+ );
200
+
201
+ // Force update when needed refs change
202
+ useEffect(() => {
203
+ if (update && isOpen && shouldShowPopover) {
204
+ // Need to delay the update slightly to ensure refs are properly set
205
+ const timer = setTimeout(() => {
206
+ update();
207
+ }, 10);
208
+ return () => clearTimeout(timer);
209
+ }
210
+ }, [update, isOpen, shouldShowPopover, showArrow, arrowRef.current]);
211
+
212
+ // Define arrow data-* attribute based on placement
213
+ const getArrowDataPlacement = () => {
214
+ if (!state || !state.placement) return placement;
215
+ return state.placement;
216
+ };
217
+
218
+ // Get the actual placement from Popper state
219
+ const actualPlacement = state?.placement || placement;
220
+
221
+ // For a CSS triangle, we use the border trick
222
+ // A CSS triangle doesn't need separate border styling like a rotated square would
223
+
224
+ // Popper container styles
225
+ const defaultPopperStyles: CSSProperties = {
226
+ backgroundColor: "var(--bs-body-bg)",
227
+ padding: "12px",
228
+ borderRadius: "4px",
229
+ boxShadow: "0 2px 10px rgba(0,0,0,0.1)",
230
+ border: "1px solid #eee",
231
+ zIndex: 1200,
232
+ position: "relative",
233
+ // Apply opacity transition to smooth the appearance
234
+ opacity: state?.placement ? 1 : 0,
235
+ transition: "opacity 0.1s",
236
+ };
237
+
238
+ // Early return if not open or should not show due to hover delay
239
+ if (!isOpen || (hoverDelay > 0 && !shouldShowPopover)) {
240
+ return null;
241
+ }
242
+
243
+ // For position-aware rendering
244
+ const positionedStyle =
245
+ state && state.styles && state.styles.popper
246
+ ? {
247
+ ...styles.popper,
248
+ opacity: 1,
249
+ }
250
+ : {
251
+ ...styles.popper,
252
+ opacity: 0,
253
+ // Position offscreen initially to prevent flicker
254
+ position: "fixed" as const,
255
+ top: "-9999px",
256
+ left: "-9999px",
257
+ };
258
+
259
+ // Create the popper content with position-aware styles
260
+ const popperContent = (
261
+ <div
262
+ ref={popperRef}
263
+ style={{ ...defaultPopperStyles, ...positionedStyle }}
264
+ className={clsx(className)}
265
+ {...attributes.popper}
266
+ >
267
+ {children}
268
+
269
+ {showArrow && (
270
+ <>
271
+ {/* Invisible div for Popper.js to use as reference */}
272
+ <div
273
+ ref={arrowRef}
274
+ style={{ position: "absolute", visibility: "hidden" }}
275
+ data-placement={getArrowDataPlacement()}
276
+ />
277
+
278
+ {/* Arrow container - positioned by Popper */}
279
+ <div
280
+ className={clsx("popper-arrow-container", arrowClassName)}
281
+ style={{
282
+ ...styles.arrow,
283
+ position: "absolute",
284
+ zIndex: 1,
285
+ // Size and positioning based on placement - smaller arrow
286
+ ...(actualPlacement.startsWith("top") && {
287
+ bottom: "-8px",
288
+ width: "16px",
289
+ height: "8px",
290
+ }),
291
+ ...(actualPlacement.startsWith("bottom") && {
292
+ top: "-8px",
293
+ width: "16px",
294
+ height: "8px",
295
+ }),
296
+ ...(actualPlacement.startsWith("left") && {
297
+ right: "-8px",
298
+ width: "8px",
299
+ height: "16px",
300
+ }),
301
+ ...(actualPlacement.startsWith("right") && {
302
+ left: "-8px",
303
+ width: "8px",
304
+ height: "16px",
305
+ }),
306
+ // Content positioning
307
+ overflow: "hidden",
308
+ }}
309
+ >
310
+ {/* Border element (rendered behind) */}
311
+ {actualPlacement.startsWith("top") && (
312
+ <div
313
+ style={{
314
+ position: "absolute",
315
+ width: 0,
316
+ height: 0,
317
+ borderStyle: "solid",
318
+ borderWidth: "8px 8px 0 8px",
319
+ borderColor: "#eee transparent transparent transparent",
320
+ top: "0px",
321
+ left: "0px",
322
+ }}
323
+ />
324
+ )}
325
+ {actualPlacement.startsWith("bottom") && (
326
+ <div
327
+ style={{
328
+ position: "absolute",
329
+ width: 0,
330
+ height: 0,
331
+ borderStyle: "solid",
332
+ borderWidth: "0 8px 8px 8px",
333
+ borderColor: "transparent transparent #eee transparent",
334
+ top: "0px",
335
+ left: "0px",
336
+ }}
337
+ />
338
+ )}
339
+ {actualPlacement.startsWith("left") && (
340
+ <div
341
+ style={{
342
+ position: "absolute",
343
+ width: 0,
344
+ height: 0,
345
+ borderStyle: "solid",
346
+ borderWidth: "8px 0 8px 8px",
347
+ borderColor: "transparent transparent transparent #eee",
348
+ top: "0px",
349
+ left: "0px",
350
+ }}
351
+ />
352
+ )}
353
+ {actualPlacement.startsWith("right") && (
354
+ <div
355
+ style={{
356
+ position: "absolute",
357
+ width: 0,
358
+ height: 0,
359
+ borderStyle: "solid",
360
+ borderWidth: "8px 8px 8px 0",
361
+ borderColor: "transparent #eee transparent transparent",
362
+ top: "0px",
363
+ left: "0px",
364
+ }}
365
+ />
366
+ )}
367
+
368
+ {/* Actual triangle created with CSS borders, slightly smaller and offset to create border effect */}
369
+ <div
370
+ style={{
371
+ position: "absolute",
372
+ width: 0,
373
+ height: 0,
374
+ borderStyle: "solid",
375
+ backgroundColor: "transparent",
376
+ // Position relative to border triangle
377
+ left: "0px",
378
+ zIndex: 1,
379
+
380
+ // Top placement - pointing down
381
+ ...(actualPlacement.startsWith("top") && {
382
+ borderWidth: "7px 7px 0 7px",
383
+ borderColor: "white transparent transparent transparent",
384
+ top: "0px",
385
+ }),
386
+
387
+ // Bottom placement - pointing up
388
+ ...(actualPlacement.startsWith("bottom") && {
389
+ borderWidth: "0 7px 7px 7px",
390
+ borderColor: "transparent transparent white transparent",
391
+ top: "1px",
392
+ }),
393
+
394
+ // Left placement - pointing right
395
+ ...(actualPlacement.startsWith("left") && {
396
+ borderWidth: "7px 0 7px 7px",
397
+ borderColor: "transparent transparent transparent white",
398
+ left: "0px",
399
+ }),
400
+
401
+ // Right placement - pointing left
402
+ ...(actualPlacement.startsWith("right") && {
403
+ borderWidth: "7px 7px 7px 0",
404
+ borderColor: "transparent white transparent transparent",
405
+ left: "1px",
406
+ }),
407
+ }}
408
+ />
409
+ </div>
410
+ </>
411
+ )}
412
+ </div>
413
+ );
414
+
415
+ // If using portal and the container exists, render through the portal
416
+ if (usePortal && portalContainer) {
417
+ return createPortal(popperContent, portalContainer);
418
+ }
419
+
420
+ // Otherwise render normally
421
+ return popperContent;
422
+ };
@@ -13,7 +13,7 @@
13
13
  }
14
14
 
15
15
  .small .dotsContainer {
16
- column-gap: 3px;
16
+ column-gap: 2px;
17
17
  }
18
18
 
19
19
  .medium .dotsContainer {
@@ -34,16 +34,16 @@
34
34
  }
35
35
 
36
36
  .subtle {
37
- background-color: var(--bs-primary-bg-subtle);
37
+ background-color: var(--bs-secondary-bg-subtle);
38
38
  }
39
39
 
40
40
  .primary {
41
- background-color: var(--bs-primary);
41
+ background-color: var(--bs-secondary);
42
42
  }
43
43
 
44
44
  .small .dot {
45
- width: 4px;
46
- height: 4px;
45
+ width: 3px;
46
+ height: 3px;
47
47
  }
48
48
 
49
49
  .medium .dot {
@@ -71,11 +71,11 @@
71
71
  @keyframes pulse {
72
72
  0%,
73
73
  100% {
74
- transform: scale(1);
75
- opacity: 0.3;
74
+ transform: scale(0.7);
75
+ opacity: 0.4;
76
76
  }
77
77
  50% {
78
- transform: scale(1.2);
79
- opacity: 1;
78
+ transform: scale(1);
79
+ opacity: 0.8;
80
80
  }
81
81
  }
@@ -7,6 +7,7 @@ interface PulsingDotsProps {
7
7
  dotsCount?: number;
8
8
  subtle?: boolean;
9
9
  size?: "small" | "medium" | "large";
10
+ className?: string | string[];
10
11
  }
11
12
 
12
13
  export const PulsingDots: FC<PulsingDotsProps> = ({
@@ -14,6 +15,7 @@ export const PulsingDots: FC<PulsingDotsProps> = ({
14
15
  dotsCount = 3,
15
16
  subtle = true,
16
17
  size = "small",
18
+ className,
17
19
  }) => {
18
20
  return (
19
21
  <div
@@ -24,6 +26,7 @@ export const PulsingDots: FC<PulsingDotsProps> = ({
24
26
  : size === "medium"
25
27
  ? styles.medium
26
28
  : styles.large,
29
+ className,
27
30
  )}
28
31
  role="status"
29
32
  >
@@ -35,7 +38,7 @@ export const PulsingDots: FC<PulsingDotsProps> = ({
35
38
  styles.dot,
36
39
  subtle ? styles.subtle : styles.primary,
37
40
  )}
38
- style={{ animationDelay: `${index * 0.15}s` }}
41
+ style={{ animationDelay: `${index * 0.2}s` }}
39
42
  />
40
43
  ))}
41
44
  </div>