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.
- inspect_ai/_cli/eval.py +2 -1
- inspect_ai/_display/core/config.py +11 -5
- inspect_ai/_display/core/panel.py +66 -2
- inspect_ai/_display/core/textual.py +5 -2
- inspect_ai/_display/plain/display.py +1 -0
- inspect_ai/_display/rich/display.py +2 -2
- inspect_ai/_display/textual/widgets/transcript.py +37 -9
- inspect_ai/_eval/eval.py +13 -1
- inspect_ai/_eval/evalset.py +3 -2
- inspect_ai/_eval/run.py +2 -0
- inspect_ai/_eval/score.py +2 -4
- inspect_ai/_eval/task/log.py +3 -1
- inspect_ai/_eval/task/run.py +59 -81
- inspect_ai/_util/content.py +11 -6
- inspect_ai/_util/interrupt.py +2 -2
- inspect_ai/_util/text.py +7 -0
- inspect_ai/_util/working.py +8 -37
- inspect_ai/_view/__init__.py +0 -0
- inspect_ai/_view/schema.py +2 -1
- inspect_ai/_view/www/CLAUDE.md +15 -0
- inspect_ai/_view/www/dist/assets/index.css +307 -171
- inspect_ai/_view/www/dist/assets/index.js +24733 -21641
- inspect_ai/_view/www/log-schema.json +77 -3
- inspect_ai/_view/www/package.json +9 -5
- inspect_ai/_view/www/src/@types/log.d.ts +9 -0
- inspect_ai/_view/www/src/app/App.tsx +1 -15
- inspect_ai/_view/www/src/app/appearance/icons.ts +4 -1
- inspect_ai/_view/www/src/app/content/MetaDataGrid.tsx +24 -6
- inspect_ai/_view/www/src/app/content/MetadataGrid.module.css +0 -5
- inspect_ai/_view/www/src/app/content/RenderedContent.tsx +220 -205
- inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +2 -1
- inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +5 -0
- inspect_ai/_view/www/src/app/log-view/tabs/grouping.ts +4 -4
- inspect_ai/_view/www/src/app/routing/navigationHooks.ts +22 -25
- inspect_ai/_view/www/src/app/routing/url.ts +84 -4
- inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +0 -5
- inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +1 -1
- inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +7 -0
- inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +24 -17
- inspect_ai/_view/www/src/app/samples/SampleSummaryView.module.css +1 -2
- inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +8 -6
- inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +0 -4
- inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +3 -2
- inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +2 -0
- inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +2 -0
- inspect_ai/_view/www/src/app/samples/chat/messages.ts +1 -0
- inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -0
- inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +17 -5
- inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +1 -2
- inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +1 -2
- inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.module.css +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +3 -2
- inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +4 -5
- inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +1 -2
- inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +1 -3
- inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +1 -2
- inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +3 -4
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.module.css +42 -0
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.tsx +77 -0
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +27 -71
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +13 -3
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +27 -2
- inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +1 -0
- inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +21 -22
- inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.module.css +45 -0
- inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.tsx +223 -0
- inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.module.css +10 -0
- inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +258 -0
- inspect_ai/_view/www/src/app/samples/transcript/outline/tree-visitors.ts +187 -0
- inspect_ai/_view/www/src/app/samples/transcript/state/StateEventRenderers.tsx +8 -1
- inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +3 -4
- inspect_ai/_view/www/src/app/samples/transcript/transform/hooks.ts +78 -0
- inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +340 -135
- inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +3 -0
- inspect_ai/_view/www/src/app/samples/transcript/types.ts +2 -0
- inspect_ai/_view/www/src/app/types.ts +5 -1
- inspect_ai/_view/www/src/client/api/api-browser.ts +2 -2
- inspect_ai/_view/www/src/components/LiveVirtualList.tsx +6 -1
- inspect_ai/_view/www/src/components/MarkdownDiv.tsx +1 -1
- inspect_ai/_view/www/src/components/PopOver.tsx +422 -0
- inspect_ai/_view/www/src/components/PulsingDots.module.css +9 -9
- inspect_ai/_view/www/src/components/PulsingDots.tsx +4 -1
- inspect_ai/_view/www/src/components/StickyScroll.tsx +183 -0
- inspect_ai/_view/www/src/components/TabSet.tsx +4 -0
- inspect_ai/_view/www/src/state/hooks.ts +52 -2
- inspect_ai/_view/www/src/state/logSlice.ts +4 -3
- inspect_ai/_view/www/src/state/samplePolling.ts +8 -0
- inspect_ai/_view/www/src/state/sampleSlice.ts +53 -9
- inspect_ai/_view/www/src/state/scrolling.ts +152 -0
- inspect_ai/_view/www/src/utils/attachments.ts +7 -0
- inspect_ai/_view/www/src/utils/python.ts +18 -0
- inspect_ai/_view/www/yarn.lock +290 -33
- inspect_ai/agent/_react.py +12 -7
- inspect_ai/agent/_run.py +2 -3
- inspect_ai/analysis/beta/__init__.py +2 -0
- inspect_ai/analysis/beta/_dataframe/samples/table.py +19 -18
- inspect_ai/dataset/_sources/csv.py +2 -6
- inspect_ai/dataset/_sources/hf.py +2 -6
- inspect_ai/dataset/_sources/json.py +2 -6
- inspect_ai/dataset/_util.py +23 -0
- inspect_ai/log/_log.py +1 -1
- inspect_ai/log/_recorders/eval.py +4 -3
- inspect_ai/log/_recorders/file.py +2 -9
- inspect_ai/log/_recorders/json.py +1 -0
- inspect_ai/log/_recorders/recorder.py +1 -0
- inspect_ai/log/_transcript.py +1 -1
- inspect_ai/model/_call_tools.py +6 -2
- inspect_ai/model/_openai.py +1 -1
- inspect_ai/model/_openai_responses.py +85 -41
- inspect_ai/model/_openai_web_search.py +38 -0
- inspect_ai/model/_providers/azureai.py +72 -3
- inspect_ai/model/_providers/openai.py +4 -1
- inspect_ai/model/_providers/openai_responses.py +5 -1
- inspect_ai/scorer/_metric.py +1 -2
- inspect_ai/scorer/_reducer/reducer.py +1 -1
- inspect_ai/solver/_task_state.py +2 -2
- inspect_ai/tool/_tool.py +6 -2
- inspect_ai/tool/_tool_def.py +27 -4
- inspect_ai/tool/_tool_info.py +2 -0
- inspect_ai/tool/_tools/_web_search/_google.py +43 -15
- inspect_ai/tool/_tools/_web_search/_tavily.py +46 -13
- inspect_ai/tool/_tools/_web_search/_web_search.py +214 -45
- inspect_ai/util/__init__.py +4 -0
- inspect_ai/util/_json.py +3 -0
- inspect_ai/util/_limit.py +230 -20
- inspect_ai/util/_sandbox/docker/compose.py +20 -11
- inspect_ai/util/_span.py +1 -1
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/METADATA +3 -3
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/RECORD +138 -124
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/WHEEL +1 -1
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/licenses/LICENSE +0 -0
- {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.101.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,223 @@
|
|
1
|
+
import clsx from "clsx";
|
2
|
+
import { FC, ReactNode, useRef } from "react";
|
3
|
+
import { Link } from "react-router-dom";
|
4
|
+
import { PopOver } from "../../../../components/PopOver";
|
5
|
+
import { PulsingDots } from "../../../../components/PulsingDots";
|
6
|
+
import {
|
7
|
+
useCollapseSampleEvent,
|
8
|
+
useSamplePopover,
|
9
|
+
} from "../../../../state/hooks";
|
10
|
+
import { formatDateTime, formatTime } from "../../../../utils/format";
|
11
|
+
import { parsePackageName } from "../../../../utils/python";
|
12
|
+
import { ApplicationIcons } from "../../../appearance/icons";
|
13
|
+
import { MetaDataGrid } from "../../../content/MetaDataGrid";
|
14
|
+
import { useSampleEventUrl } from "../../../routing/url";
|
15
|
+
import { kSandboxSignalName } from "../transform/fixups";
|
16
|
+
import { EventNode } from "../types";
|
17
|
+
import styles from "./OutlineRow.module.css";
|
18
|
+
|
19
|
+
export interface OutlineRowProps {
|
20
|
+
node: EventNode;
|
21
|
+
collapseScope: string;
|
22
|
+
running?: boolean;
|
23
|
+
selected?: boolean;
|
24
|
+
}
|
25
|
+
|
26
|
+
export const OutlineRow: FC<OutlineRowProps> = ({
|
27
|
+
node,
|
28
|
+
collapseScope,
|
29
|
+
running,
|
30
|
+
selected,
|
31
|
+
}) => {
|
32
|
+
const [collapsed, setCollapsed] = useCollapseSampleEvent(
|
33
|
+
collapseScope,
|
34
|
+
node.id,
|
35
|
+
);
|
36
|
+
const icon = iconForNode(node);
|
37
|
+
const toggle = toggleIcon(node, collapsed);
|
38
|
+
|
39
|
+
const popoverId = `${node.id}-popover`;
|
40
|
+
const { isShowing } = useSamplePopover(popoverId);
|
41
|
+
|
42
|
+
const ref = useRef(null);
|
43
|
+
|
44
|
+
// Get all URL parameters at component level
|
45
|
+
const sampleEventUrl = useSampleEventUrl(node.id);
|
46
|
+
|
47
|
+
return (
|
48
|
+
<>
|
49
|
+
<div
|
50
|
+
className={clsx(
|
51
|
+
styles.eventRow,
|
52
|
+
"text-size-smallest",
|
53
|
+
selected ? styles.selected : "",
|
54
|
+
)}
|
55
|
+
style={{ paddingLeft: `${node.depth * 0.4}em` }}
|
56
|
+
>
|
57
|
+
<div
|
58
|
+
className={clsx(styles.toggle)}
|
59
|
+
onClick={() => {
|
60
|
+
setCollapsed(!collapsed);
|
61
|
+
}}
|
62
|
+
>
|
63
|
+
{toggle ? <i className={clsx(toggle)} /> : undefined}
|
64
|
+
</div>
|
65
|
+
<div className={clsx(styles.label)} data-depth={node.depth}>
|
66
|
+
{icon ? <i className={clsx(icon, styles.icon)} /> : undefined}
|
67
|
+
{sampleEventUrl ? (
|
68
|
+
<Link
|
69
|
+
to={sampleEventUrl}
|
70
|
+
className={clsx(styles.eventLink)}
|
71
|
+
ref={ref}
|
72
|
+
>
|
73
|
+
{parsePackageName(labelForNode(node)).module}
|
74
|
+
</Link>
|
75
|
+
) : (
|
76
|
+
<span ref={ref}>{parsePackageName(labelForNode(node)).module}</span>
|
77
|
+
)}
|
78
|
+
{running ? (
|
79
|
+
<PulsingDots
|
80
|
+
size="small"
|
81
|
+
className={clsx(styles.progress)}
|
82
|
+
subtle={false}
|
83
|
+
/>
|
84
|
+
) : undefined}
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
<PopOver
|
88
|
+
id={`${node.id}-popover`}
|
89
|
+
positionEl={ref.current}
|
90
|
+
isOpen={isShowing}
|
91
|
+
className={clsx(styles.popper)}
|
92
|
+
placement="auto-end"
|
93
|
+
>
|
94
|
+
{summarizeNode(node)}
|
95
|
+
</PopOver>
|
96
|
+
</>
|
97
|
+
);
|
98
|
+
};
|
99
|
+
|
100
|
+
const toggleIcon = (
|
101
|
+
node: EventNode,
|
102
|
+
collapsed: boolean,
|
103
|
+
): string | undefined => {
|
104
|
+
if (node.children.length > 0) {
|
105
|
+
return collapsed
|
106
|
+
? ApplicationIcons.chevron.right
|
107
|
+
: ApplicationIcons.chevron.down;
|
108
|
+
}
|
109
|
+
};
|
110
|
+
|
111
|
+
const iconForNode = (node: EventNode): string | undefined => {
|
112
|
+
switch (node.event.event) {
|
113
|
+
case "sample_limit":
|
114
|
+
return ApplicationIcons.limits.custom;
|
115
|
+
|
116
|
+
case "score":
|
117
|
+
return ApplicationIcons.scorer;
|
118
|
+
|
119
|
+
case "error":
|
120
|
+
return ApplicationIcons.error;
|
121
|
+
}
|
122
|
+
};
|
123
|
+
|
124
|
+
const labelForNode = (node: EventNode): string => {
|
125
|
+
if (node.event.event === "span_begin") {
|
126
|
+
switch (node.event.type) {
|
127
|
+
case "solver":
|
128
|
+
return node.event.name;
|
129
|
+
case "tool":
|
130
|
+
return node.event.name;
|
131
|
+
default: {
|
132
|
+
if (node.event.name === kSandboxSignalName) {
|
133
|
+
return "sandbox events";
|
134
|
+
}
|
135
|
+
return node.event.name;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
} else {
|
139
|
+
switch (node.event.event) {
|
140
|
+
case "subtask":
|
141
|
+
return node.event.name;
|
142
|
+
case "approval":
|
143
|
+
switch (node.event.decision) {
|
144
|
+
case "approve":
|
145
|
+
return "approved";
|
146
|
+
case "reject":
|
147
|
+
return "rejected";
|
148
|
+
case "escalate":
|
149
|
+
return "escalated";
|
150
|
+
case "modify":
|
151
|
+
return "modified";
|
152
|
+
case "terminate":
|
153
|
+
return "terminated";
|
154
|
+
default:
|
155
|
+
return node.event.decision;
|
156
|
+
}
|
157
|
+
case "model":
|
158
|
+
return `model${node.event.role ? ` (${node.event.role})` : ""}`;
|
159
|
+
case "score":
|
160
|
+
return "scoring";
|
161
|
+
case "step":
|
162
|
+
if (node.event.name === kSandboxSignalName) {
|
163
|
+
return "sandbox events";
|
164
|
+
}
|
165
|
+
return node.event.name;
|
166
|
+
|
167
|
+
default:
|
168
|
+
return node.event.event;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
};
|
172
|
+
|
173
|
+
export const summarizeNode = (node: EventNode): ReactNode => {
|
174
|
+
let entries: Record<string, unknown> = {};
|
175
|
+
switch (node.event.event) {
|
176
|
+
case "sample_init":
|
177
|
+
entries = {
|
178
|
+
sample_id: node.event.sample.id,
|
179
|
+
sandbox: node.event.sample.sandbox?.type,
|
180
|
+
started: formatDateTime(new Date(node.event.timestamp)),
|
181
|
+
working_start: formatTime(node.event.working_start),
|
182
|
+
};
|
183
|
+
break;
|
184
|
+
|
185
|
+
case "sample_limit":
|
186
|
+
entries = {
|
187
|
+
type: node.event.type,
|
188
|
+
message: node.event.message,
|
189
|
+
limit: node.event.limit,
|
190
|
+
started: formatDateTime(new Date(node.event.timestamp)),
|
191
|
+
working_start: formatTime(node.event.working_start),
|
192
|
+
};
|
193
|
+
break;
|
194
|
+
case "score":
|
195
|
+
entries = {
|
196
|
+
answer: node.event.score.answer,
|
197
|
+
score: node.event.score.value,
|
198
|
+
started: formatDateTime(new Date(node.event.timestamp)),
|
199
|
+
working_start: formatTime(node.event.working_start),
|
200
|
+
};
|
201
|
+
break;
|
202
|
+
case "span_begin":
|
203
|
+
entries = {
|
204
|
+
name: node.event.name,
|
205
|
+
started: formatDateTime(new Date(node.event.timestamp)),
|
206
|
+
working_start: formatTime(node.event.working_start),
|
207
|
+
};
|
208
|
+
break;
|
209
|
+
default:
|
210
|
+
entries = {
|
211
|
+
started: formatDateTime(new Date(node.event.timestamp)),
|
212
|
+
working_start: formatTime(node.event.working_start),
|
213
|
+
};
|
214
|
+
}
|
215
|
+
|
216
|
+
return (
|
217
|
+
<MetaDataGrid
|
218
|
+
entries={entries}
|
219
|
+
size="mini"
|
220
|
+
className={clsx(styles.popover, "text-size-smallest")}
|
221
|
+
/>
|
222
|
+
);
|
223
|
+
};
|
@@ -0,0 +1,258 @@
|
|
1
|
+
import {
|
2
|
+
CSSProperties,
|
3
|
+
FC,
|
4
|
+
RefObject,
|
5
|
+
useCallback,
|
6
|
+
useEffect,
|
7
|
+
useMemo,
|
8
|
+
useRef,
|
9
|
+
} from "react";
|
10
|
+
|
11
|
+
import { EventNode } from "../types";
|
12
|
+
|
13
|
+
import clsx from "clsx";
|
14
|
+
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
|
15
|
+
import { useScrollTrack, useVirtuosoState } from "../../../../state/scrolling";
|
16
|
+
import { useStore } from "../../../../state/store";
|
17
|
+
import { flatTree } from "../transform/treeify";
|
18
|
+
|
19
|
+
import { useSampleDetailNavigation } from "../../../routing/navigationHooks";
|
20
|
+
import { kSandboxSignalName } from "../transform/fixups";
|
21
|
+
import { OutlineRow } from "./OutlineRow";
|
22
|
+
import styles from "./TranscriptOutline.module.css";
|
23
|
+
import {
|
24
|
+
collapseScoring,
|
25
|
+
collapseTurns,
|
26
|
+
makeTurns,
|
27
|
+
noScorerChildren,
|
28
|
+
removeNodeVisitor,
|
29
|
+
removeStepSpanNameVisitor,
|
30
|
+
} from "./tree-visitors";
|
31
|
+
|
32
|
+
const kCollapseScope = "transcript-outline";
|
33
|
+
const kFramesToStabilize = 10;
|
34
|
+
|
35
|
+
interface TranscriptOutlineProps {
|
36
|
+
eventNodes: EventNode[];
|
37
|
+
defaultCollapsedIds: Record<string, boolean>;
|
38
|
+
running?: boolean;
|
39
|
+
className?: string | string[];
|
40
|
+
scrollRef?: RefObject<HTMLDivElement | null>;
|
41
|
+
style?: CSSProperties;
|
42
|
+
}
|
43
|
+
|
44
|
+
// hack: add a padding node to the end of the list so
|
45
|
+
// when the tree is positioned at the bottom of the viewport
|
46
|
+
// it has some breathing room
|
47
|
+
const EventPaddingNode: EventNode = {
|
48
|
+
id: "padding",
|
49
|
+
event: {
|
50
|
+
event: "info",
|
51
|
+
source: "",
|
52
|
+
data: "",
|
53
|
+
timestamp: "",
|
54
|
+
pending: false,
|
55
|
+
working_start: 0,
|
56
|
+
span_id: null,
|
57
|
+
},
|
58
|
+
depth: 0,
|
59
|
+
children: [],
|
60
|
+
};
|
61
|
+
|
62
|
+
export const TranscriptOutline: FC<TranscriptOutlineProps> = ({
|
63
|
+
eventNodes,
|
64
|
+
defaultCollapsedIds,
|
65
|
+
running,
|
66
|
+
className,
|
67
|
+
scrollRef,
|
68
|
+
style,
|
69
|
+
}) => {
|
70
|
+
const id = "transcript-tree";
|
71
|
+
// The virtual list handle and state
|
72
|
+
const listHandle = useRef<VirtuosoHandle | null>(null);
|
73
|
+
const { getRestoreState } = useVirtuosoState(listHandle, id);
|
74
|
+
|
75
|
+
// Collapse state
|
76
|
+
// The list of events that have been collapsed
|
77
|
+
const collapsedEvents = useStore((state) => state.sample.collapsedEvents);
|
78
|
+
const setCollapsedEvents = useStore(
|
79
|
+
(state) => state.sampleActions.setCollapsedEvents,
|
80
|
+
);
|
81
|
+
|
82
|
+
const selectedOutlineId = useStore((state) => state.sample.selectedOutlineId);
|
83
|
+
const setSelectedOutlineId = useStore(
|
84
|
+
(state) => state.sampleActions.setSelectedOutlineId,
|
85
|
+
);
|
86
|
+
const sampleDetailNavigation = useSampleDetailNavigation();
|
87
|
+
|
88
|
+
// Flag to indicate programmatic scrolling is in progress
|
89
|
+
const isProgrammaticScrolling = useRef(false);
|
90
|
+
// Last position to check for scroll stabilization
|
91
|
+
const lastScrollPosition = useRef<number | null>(null);
|
92
|
+
// Frame count for detecting scroll stabilization
|
93
|
+
const stableFrameCount = useRef(0);
|
94
|
+
|
95
|
+
useEffect(() => {
|
96
|
+
if (sampleDetailNavigation.event) {
|
97
|
+
// Set the flag to indicate we're in programmatic scrolling
|
98
|
+
isProgrammaticScrolling.current = true;
|
99
|
+
lastScrollPosition.current = null;
|
100
|
+
stableFrameCount.current = 0;
|
101
|
+
|
102
|
+
setSelectedOutlineId(sampleDetailNavigation.event);
|
103
|
+
|
104
|
+
// Start monitoring to detect when scrolling has stabilized
|
105
|
+
const checkScrollStabilized = () => {
|
106
|
+
if (!isProgrammaticScrolling.current) return;
|
107
|
+
|
108
|
+
const currentPosition = scrollRef?.current?.scrollTop ?? null;
|
109
|
+
|
110
|
+
if (currentPosition === lastScrollPosition.current) {
|
111
|
+
stableFrameCount.current++;
|
112
|
+
|
113
|
+
// If position has been stable for a few frames, consider scrolling complete
|
114
|
+
if (stableFrameCount.current >= kFramesToStabilize) {
|
115
|
+
isProgrammaticScrolling.current = false;
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
} else {
|
119
|
+
// Reset stability counter if position changed
|
120
|
+
stableFrameCount.current = 0;
|
121
|
+
lastScrollPosition.current = currentPosition;
|
122
|
+
}
|
123
|
+
|
124
|
+
// Continue checking until scrolling stabilizes
|
125
|
+
requestAnimationFrame(checkScrollStabilized);
|
126
|
+
};
|
127
|
+
|
128
|
+
// Start the RAF loop to detect scroll stabilization
|
129
|
+
requestAnimationFrame(checkScrollStabilized);
|
130
|
+
}
|
131
|
+
}, [sampleDetailNavigation.event, setSelectedOutlineId, scrollRef]);
|
132
|
+
|
133
|
+
const outlineNodeList = useMemo(() => {
|
134
|
+
// flattten the event tree
|
135
|
+
const nodeList = flatTree(
|
136
|
+
eventNodes,
|
137
|
+
(collapsedEvents ? collapsedEvents[kCollapseScope] : undefined) ||
|
138
|
+
defaultCollapsedIds,
|
139
|
+
[
|
140
|
+
// Strip specific nodes
|
141
|
+
removeNodeVisitor("logger"),
|
142
|
+
removeNodeVisitor("info"),
|
143
|
+
removeNodeVisitor("state"),
|
144
|
+
removeNodeVisitor("store"),
|
145
|
+
removeNodeVisitor("approval"),
|
146
|
+
removeNodeVisitor("input"),
|
147
|
+
removeNodeVisitor("sandbox"),
|
148
|
+
|
149
|
+
// Strip the sandbox wrapper (and children)
|
150
|
+
removeStepSpanNameVisitor(kSandboxSignalName),
|
151
|
+
|
152
|
+
// Remove child events for scorers
|
153
|
+
noScorerChildren(),
|
154
|
+
],
|
155
|
+
);
|
156
|
+
|
157
|
+
return collapseScoring(collapseTurns(makeTurns(nodeList)));
|
158
|
+
}, [eventNodes, collapsedEvents, defaultCollapsedIds]);
|
159
|
+
|
160
|
+
// Event node, for scroll tracking
|
161
|
+
const allNodesList = useMemo(() => {
|
162
|
+
return flatTree(eventNodes, null);
|
163
|
+
}, [eventNodes]);
|
164
|
+
|
165
|
+
const elementIds = allNodesList.map((node) => node.id);
|
166
|
+
const findNearestOutlineAbove = useCallback(
|
167
|
+
(targetId: string): EventNode | null => {
|
168
|
+
const targetIndex = allNodesList.findIndex(
|
169
|
+
(node) => node.id === targetId,
|
170
|
+
);
|
171
|
+
if (targetIndex === -1) return null;
|
172
|
+
|
173
|
+
const outlineIds = new Set(outlineNodeList.map((node) => node.id));
|
174
|
+
|
175
|
+
// Search backwards from target position (inclusive)
|
176
|
+
for (let i = targetIndex; i >= 0; i--) {
|
177
|
+
if (outlineIds.has(allNodesList[i].id)) {
|
178
|
+
return allNodesList[i];
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
return null;
|
183
|
+
},
|
184
|
+
[allNodesList, outlineNodeList],
|
185
|
+
);
|
186
|
+
|
187
|
+
useScrollTrack(
|
188
|
+
elementIds,
|
189
|
+
(id: string) => {
|
190
|
+
if (!isProgrammaticScrolling.current) {
|
191
|
+
// If the ID is not in the list, return
|
192
|
+
const parentNode = findNearestOutlineAbove(id);
|
193
|
+
if (parentNode) {
|
194
|
+
setSelectedOutlineId(parentNode.id);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
},
|
198
|
+
scrollRef,
|
199
|
+
);
|
200
|
+
|
201
|
+
// Update the collapsed events when the default collapsed IDs change
|
202
|
+
// This effect only depends on defaultCollapsedIds, not eventNodes
|
203
|
+
useEffect(() => {
|
204
|
+
// Only initialize collapsedEvents if it's empty
|
205
|
+
if (!collapsedEvents && Object.keys(defaultCollapsedIds).length > 0) {
|
206
|
+
setCollapsedEvents(kCollapseScope, defaultCollapsedIds);
|
207
|
+
}
|
208
|
+
}, [defaultCollapsedIds, collapsedEvents, setCollapsedEvents]);
|
209
|
+
|
210
|
+
const renderRow = useCallback(
|
211
|
+
(index: number, node: EventNode) => {
|
212
|
+
if (node === EventPaddingNode) {
|
213
|
+
return (
|
214
|
+
<div
|
215
|
+
className={styles.eventPadding}
|
216
|
+
key={node.id}
|
217
|
+
style={{ height: "2em" }}
|
218
|
+
></div>
|
219
|
+
);
|
220
|
+
} else {
|
221
|
+
return (
|
222
|
+
<OutlineRow
|
223
|
+
collapseScope={kCollapseScope}
|
224
|
+
node={node}
|
225
|
+
key={node.id}
|
226
|
+
running={running && index === outlineNodeList.length - 1}
|
227
|
+
selected={
|
228
|
+
selectedOutlineId ? selectedOutlineId === node.id : index === 0
|
229
|
+
}
|
230
|
+
/>
|
231
|
+
);
|
232
|
+
}
|
233
|
+
},
|
234
|
+
[outlineNodeList, running, selectedOutlineId],
|
235
|
+
);
|
236
|
+
|
237
|
+
return (
|
238
|
+
<Virtuoso
|
239
|
+
ref={listHandle}
|
240
|
+
customScrollParent={scrollRef?.current ? scrollRef.current : undefined}
|
241
|
+
id={id}
|
242
|
+
style={{ ...style }}
|
243
|
+
data={[...outlineNodeList, EventPaddingNode]}
|
244
|
+
defaultItemHeight={50}
|
245
|
+
itemContent={renderRow}
|
246
|
+
atBottomThreshold={30}
|
247
|
+
increaseViewportBy={{ top: 300, bottom: 300 }}
|
248
|
+
overscan={{
|
249
|
+
main: 10,
|
250
|
+
reverse: 10,
|
251
|
+
}}
|
252
|
+
className={clsx(className, "transcript-outline")}
|
253
|
+
skipAnimationFrameInResizeObserver={true}
|
254
|
+
restoreStateFrom={getRestoreState()}
|
255
|
+
tabIndex={0}
|
256
|
+
/>
|
257
|
+
);
|
258
|
+
};
|
@@ -0,0 +1,187 @@
|
|
1
|
+
import { ScoreEvent, SpanBeginEvent } from "../../../../@types/log";
|
2
|
+
import { TYPE_SCORER, TYPE_SCORERS } from "../transform/utils";
|
3
|
+
import { EventNode } from "../types";
|
4
|
+
|
5
|
+
const kTurnType = "turn";
|
6
|
+
const kTurnsType = "turns";
|
7
|
+
const kCollapsedScoring = "scorings";
|
8
|
+
|
9
|
+
// Visitors are used to transform the event tree
|
10
|
+
export const removeNodeVisitor = (event: string) => {
|
11
|
+
return {
|
12
|
+
visit: (node: EventNode): EventNode[] => {
|
13
|
+
if (node.event.event === event) {
|
14
|
+
return [];
|
15
|
+
}
|
16
|
+
return [node];
|
17
|
+
},
|
18
|
+
};
|
19
|
+
};
|
20
|
+
|
21
|
+
export const removeStepSpanNameVisitor = (name: string) => {
|
22
|
+
return {
|
23
|
+
visit: (node: EventNode): EventNode[] => {
|
24
|
+
if (
|
25
|
+
(node.event.event === "step" || node.event.event === "span_begin") &&
|
26
|
+
node.event.name === name
|
27
|
+
) {
|
28
|
+
return [];
|
29
|
+
}
|
30
|
+
return [node];
|
31
|
+
},
|
32
|
+
};
|
33
|
+
};
|
34
|
+
|
35
|
+
export const noScorerChildren = () => {
|
36
|
+
let inScorers = false;
|
37
|
+
let inScorer = false;
|
38
|
+
let currentDepth = -1;
|
39
|
+
return {
|
40
|
+
visit: (node: EventNode): EventNode[] => {
|
41
|
+
// Note once we're in the scorers span
|
42
|
+
if (
|
43
|
+
node.event.event === "span_begin" &&
|
44
|
+
node.event.type === TYPE_SCORERS
|
45
|
+
) {
|
46
|
+
inScorers = true;
|
47
|
+
return [node];
|
48
|
+
}
|
49
|
+
|
50
|
+
if (
|
51
|
+
(node.event.event === "step" || node.event.event === "span_begin") &&
|
52
|
+
node.event.type === TYPE_SCORER
|
53
|
+
) {
|
54
|
+
inScorer = true;
|
55
|
+
currentDepth = node.depth;
|
56
|
+
return [node];
|
57
|
+
}
|
58
|
+
|
59
|
+
if (inScorers && inScorer && node.depth === currentDepth + 1) {
|
60
|
+
return [];
|
61
|
+
}
|
62
|
+
return [node];
|
63
|
+
},
|
64
|
+
};
|
65
|
+
};
|
66
|
+
|
67
|
+
export const makeTurns = (eventNodes: EventNode[]): EventNode[] => {
|
68
|
+
const results: EventNode[] = [];
|
69
|
+
let modelNode: EventNode | null = null;
|
70
|
+
const toolNodes: EventNode[] = [];
|
71
|
+
let turnCount = 1;
|
72
|
+
|
73
|
+
const makeTurn = (force?: boolean) => {
|
74
|
+
if (modelNode !== null && (force || toolNodes.length > 0)) {
|
75
|
+
// Create a new "turn" node based on the model event
|
76
|
+
const turnNode = new EventNode(
|
77
|
+
modelNode.id,
|
78
|
+
{
|
79
|
+
id: modelNode.id,
|
80
|
+
event: "span_begin",
|
81
|
+
type: kTurnType,
|
82
|
+
name: `turn ${turnCount++}`,
|
83
|
+
pending: false,
|
84
|
+
working_start: modelNode.event.working_start,
|
85
|
+
timestamp: modelNode.event.timestamp,
|
86
|
+
parent_id: null,
|
87
|
+
span_id: modelNode.event.span_id,
|
88
|
+
},
|
89
|
+
modelNode.depth,
|
90
|
+
);
|
91
|
+
|
92
|
+
// Add the original model event and tool events as children
|
93
|
+
turnNode.children = [modelNode, ...toolNodes];
|
94
|
+
results.push(turnNode);
|
95
|
+
}
|
96
|
+
modelNode = null;
|
97
|
+
toolNodes.length = 0;
|
98
|
+
};
|
99
|
+
|
100
|
+
for (const node of eventNodes) {
|
101
|
+
if (node.event.event === "model") {
|
102
|
+
if (modelNode !== null && toolNodes.length === 0) {
|
103
|
+
// back to back model calls are considered a single turn
|
104
|
+
makeTurn(true);
|
105
|
+
} else {
|
106
|
+
makeTurn();
|
107
|
+
modelNode = node;
|
108
|
+
}
|
109
|
+
} else if (node.event.event === "tool") {
|
110
|
+
toolNodes.push(node);
|
111
|
+
} else {
|
112
|
+
makeTurn();
|
113
|
+
results.push(node);
|
114
|
+
}
|
115
|
+
}
|
116
|
+
makeTurn();
|
117
|
+
|
118
|
+
return results;
|
119
|
+
};
|
120
|
+
|
121
|
+
export const collapseTurns = (eventNodes: EventNode[]): EventNode[] => {
|
122
|
+
const results: EventNode[] = [];
|
123
|
+
const collecting: EventNode[] = [];
|
124
|
+
const collect = () => {
|
125
|
+
if (collecting.length > 0) {
|
126
|
+
const numberOfTurns = collecting.length;
|
127
|
+
const firstTurn = collecting[0];
|
128
|
+
const turnNode = new EventNode(
|
129
|
+
firstTurn.id,
|
130
|
+
{
|
131
|
+
...(firstTurn.event as SpanBeginEvent),
|
132
|
+
name: `${numberOfTurns} ${numberOfTurns === 1 ? "turn" : "turns"}`,
|
133
|
+
type: kTurnsType,
|
134
|
+
},
|
135
|
+
firstTurn.depth,
|
136
|
+
);
|
137
|
+
results.push(turnNode);
|
138
|
+
collecting.length = 0;
|
139
|
+
}
|
140
|
+
};
|
141
|
+
|
142
|
+
for (const node of eventNodes) {
|
143
|
+
if (node.event.event === "span_begin" && node.event.type === kTurnType) {
|
144
|
+
collecting.push(node);
|
145
|
+
} else {
|
146
|
+
collect();
|
147
|
+
results.push(node);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
// Handle any remaining collected turns
|
151
|
+
collect();
|
152
|
+
return results;
|
153
|
+
};
|
154
|
+
|
155
|
+
export const collapseScoring = (eventNodes: EventNode[]): EventNode[] => {
|
156
|
+
const results: EventNode[] = [];
|
157
|
+
const collecting: EventNode[] = [];
|
158
|
+
const collect = () => {
|
159
|
+
if (collecting.length > 0) {
|
160
|
+
const firstScore = collecting[0];
|
161
|
+
const turnNode = new EventNode(
|
162
|
+
firstScore.id,
|
163
|
+
{
|
164
|
+
...(firstScore.event as ScoreEvent),
|
165
|
+
name: "scoring",
|
166
|
+
type: kCollapsedScoring,
|
167
|
+
},
|
168
|
+
firstScore.depth,
|
169
|
+
);
|
170
|
+
results.push(turnNode);
|
171
|
+
collecting.length = 0;
|
172
|
+
}
|
173
|
+
};
|
174
|
+
|
175
|
+
for (const node of eventNodes) {
|
176
|
+
if (node.event.event === "score") {
|
177
|
+
collecting.push(node);
|
178
|
+
} else {
|
179
|
+
collect();
|
180
|
+
results.push(node);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
// Handle any remaining collected turns
|
185
|
+
collect();
|
186
|
+
return results;
|
187
|
+
};
|
@@ -35,6 +35,9 @@ const system_msg_added_sig: ChangeType = {
|
|
35
35
|
render: (_changes, resolvedState) => {
|
36
36
|
const messages = resolvedState["messages"] as Array<unknown>;
|
37
37
|
const message = messages[0];
|
38
|
+
if (typeof message !== "object" || !message) {
|
39
|
+
return <></>;
|
40
|
+
}
|
38
41
|
return (
|
39
42
|
<ChatView
|
40
43
|
key="system_msg_event_preview"
|
@@ -172,7 +175,11 @@ const renderTools = (
|
|
172
175
|
return change.path.startsWith("/tool_choice");
|
173
176
|
});
|
174
177
|
if (resolvedState.tool_choice && hasToolChoice) {
|
175
|
-
toolsInfo["Tool Choice"] =
|
178
|
+
toolsInfo["Tool Choice"] = (
|
179
|
+
<span className={clsx("text-size-smaller")}>
|
180
|
+
{toolName(resolvedState.tool_choice)}
|
181
|
+
</span>
|
182
|
+
);
|
176
183
|
}
|
177
184
|
|
178
185
|
// Show either all tools or just the specific tools
|