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
@@ -1,16 +1,14 @@
1
- import JSON5 from "json5";
2
- import { ApplicationIcons } from "../appearance/icons";
3
-
4
- import { ChatMessageRenderer } from "../../app/samples/chat/ChatMessageRenderer";
5
- import { ANSIDisplay } from "../../components/AnsiDisplay";
6
- import { formatNumber } from "../../utils/format";
7
- import { MetaDataView } from "./MetaDataView";
8
-
9
1
  import clsx from "clsx";
2
+ import JSON5 from "json5";
10
3
  import { FC, Fragment, isValidElement, JSX, ReactNode } from "react";
4
+ import { ANSIDisplay } from "../../components/AnsiDisplay";
11
5
  import JSONPanel from "../../components/JsonPanel";
12
6
  import { MarkdownDiv } from "../../components/MarkdownDiv";
7
+ import { formatNumber } from "../../utils/format";
13
8
  import { isJson } from "../../utils/json";
9
+ import { ApplicationIcons } from "../appearance/icons";
10
+ import { ChatMessageRenderer } from "../samples/chat/ChatMessageRenderer";
11
+ import { MetaDataView } from "./MetaDataView";
14
12
  import styles from "./RenderedContent.module.css";
15
13
  import { Buckets, ContentRenderer, RenderOptions } from "./types";
16
14
 
@@ -18,6 +16,7 @@ interface RenderedContentProps {
18
16
  id: string;
19
17
  entry: { name: string; value: unknown };
20
18
  renderOptions?: RenderOptions;
19
+ renderObject?(entry: any): ReactNode;
21
20
  }
22
21
 
23
22
  /**
@@ -27,15 +26,16 @@ export const RenderedContent: FC<RenderedContentProps> = ({
27
26
  id,
28
27
  entry,
29
28
  renderOptions = { renderString: "markdown" },
29
+ renderObject,
30
30
  }): JSX.Element => {
31
31
  // Explicitly specify return type
32
32
  if (entry.value === null) {
33
33
  return <span>[null]</span>;
34
34
  }
35
-
36
- const renderer = Object.keys(contentRenderers)
35
+ const renderers = contentRenderers(renderObject);
36
+ const renderer = Object.keys(renderers)
37
37
  .map((key) => {
38
- return contentRenderers[key];
38
+ return renderers[key];
39
39
  })
40
40
  .sort((a, b) => {
41
41
  return a.bucket - b.bucket;
@@ -70,216 +70,231 @@ export const RenderedContent: FC<RenderedContentProps> = ({
70
70
  * Object containing different content renderers.
71
71
  * Each renderer is responsible for rendering a specific type of content.
72
72
  */
73
- const contentRenderers: Record<string, ContentRenderer> = {
74
- AnsiString: {
75
- bucket: Buckets.first,
76
- canRender: (entry) => {
77
- return (
78
- typeof entry.value === "string" && entry.value.indexOf("\u001b") > -1
79
- );
80
- },
81
- render: (_id, entry, _options) => {
82
- return {
83
- rendered: <ANSIDisplay output={entry.value} />,
84
- };
85
- },
86
- },
87
- JsonString: {
88
- bucket: Buckets.first,
89
- canRender: (entry) => {
90
- if (typeof entry.value === "string") {
91
- const trimmed = entry.value.trim();
92
- return isJson(trimmed);
93
- }
94
- return false;
95
- },
96
- render: (_id, entry, _options) => {
97
- const obj = JSON5.parse(entry.value);
98
- return { rendered: <JSONPanel data={obj as Record<string, unknown>} /> };
99
- },
100
- },
101
-
102
- Model: {
103
- bucket: Buckets.intermediate,
104
- canRender: (entry) => {
105
- return typeof entry.value === "object" && entry.value._model;
106
- },
107
- render: (_id, entry, _options) => {
108
- return {
109
- rendered: (
110
- <Fragment>
111
- <i className={ApplicationIcons.model} /> {entry.value._model}
112
- </Fragment>
113
- ),
114
- };
115
- },
116
- },
117
- Boolean: {
118
- bucket: Buckets.intermediate,
119
- canRender: (entry) => {
120
- return typeof entry.value === "boolean";
121
- },
122
- render: (id, entry, options) => {
123
- entry.value = entry.value.toString();
124
- return contentRenderers.String.render(id, entry, options);
125
- },
126
- },
127
- Number: {
128
- bucket: Buckets.intermediate,
129
- canRender: (entry) => {
130
- return typeof entry.value === "number";
131
- },
132
- render: (id, entry, options) => {
133
- entry.value = formatNumber(entry.value);
134
- return contentRenderers.String.render(id, entry, options);
135
- },
136
- },
137
- String: {
138
- bucket: Buckets.final,
139
- canRender: (entry) => {
140
- return typeof entry.value === "string";
73
+ const contentRenderers: (
74
+ renderObject?: (object: any) => ReactNode,
75
+ ) => Record<string, ContentRenderer> = (renderObject) => {
76
+ const contentRenderers: Record<string, ContentRenderer> = {
77
+ AnsiString: {
78
+ bucket: Buckets.first,
79
+ canRender: (entry) => {
80
+ return (
81
+ typeof entry.value === "string" && entry.value.indexOf("\u001b") > -1
82
+ );
83
+ },
84
+ render: (_id, entry, _options) => {
85
+ return {
86
+ rendered: <ANSIDisplay output={entry.value} />,
87
+ };
88
+ },
141
89
  },
142
- render: (_id, entry, options) => {
143
- const rendered = entry.value.trim();
144
- if (options.renderString === "markdown") {
90
+ JsonString: {
91
+ bucket: Buckets.first,
92
+ canRender: (entry) => {
93
+ if (typeof entry.value === "string") {
94
+ const trimmed = entry.value.trim();
95
+ return isJson(trimmed);
96
+ }
97
+ return false;
98
+ },
99
+ render: (_id, entry, _options) => {
100
+ const obj = JSON5.parse(entry.value);
145
101
  return {
146
- rendered: <MarkdownDiv markdown={rendered} />,
102
+ rendered: <JSONPanel data={obj as Record<string, unknown>} />,
147
103
  };
148
- } else {
104
+ },
105
+ },
106
+
107
+ Model: {
108
+ bucket: Buckets.intermediate,
109
+ canRender: (entry) => {
110
+ return typeof entry.value === "object" && entry.value._model;
111
+ },
112
+ render: (_id, entry, _options) => {
149
113
  return {
150
114
  rendered: (
151
- <pre className={clsx(styles.preWrap, styles.preCompact)}>
152
- {rendered}
153
- </pre>
115
+ <Fragment>
116
+ <i className={ApplicationIcons.model} /> {entry.value._model}
117
+ </Fragment>
154
118
  ),
155
119
  };
156
- }
120
+ },
157
121
  },
158
- },
159
- Array: {
160
- bucket: Buckets.intermediate,
161
- canRender: (entry) => {
162
- const isArray = Array.isArray(entry.value);
163
- if (isArray) {
164
- if (entry.value.length === 0 || entry.value.length === 1) {
165
- return true;
166
- }
167
- const types = new Set(
168
- entry.value
169
- .filter((e: unknown) => e !== null)
170
- .map((e: unknown) => {
171
- return typeof e;
172
- }),
173
- );
174
- return types.size === 1;
175
- } else {
176
- return false;
177
- }
122
+ Boolean: {
123
+ bucket: Buckets.intermediate,
124
+ canRender: (entry) => {
125
+ return typeof entry.value === "boolean";
126
+ },
127
+ render: (id, entry, options) => {
128
+ entry.value = entry.value.toString();
129
+ return contentRenderers.String.render(id, entry, options);
130
+ },
178
131
  },
179
- render: (id, entry, _options) => {
180
- const arrayMap: Record<string, unknown> = {};
181
- entry.value.forEach((e: unknown, index: number) => {
182
- arrayMap[`[${index}]`] = e;
183
- });
184
-
185
- const arrayRendered = (
186
- <MetaDataView
187
- id={id}
188
- className={"font-size-small"}
189
- entries={arrayMap}
190
- tableOptions="borderless,sm"
191
- compact={true}
192
- />
193
- );
194
- return { rendered: arrayRendered };
132
+ Number: {
133
+ bucket: Buckets.intermediate,
134
+ canRender: (entry) => {
135
+ return typeof entry.value === "number";
136
+ },
137
+ render: (id, entry, options) => {
138
+ entry.value = formatNumber(entry.value);
139
+ return contentRenderers.String.render(id, entry, options);
140
+ },
195
141
  },
196
- },
197
- ChatMessage: ChatMessageRenderer,
198
- web_search: {
199
- bucket: Buckets.intermediate,
200
- canRender: (entry) => {
201
- return typeof entry.value === "object" && entry.name === "web_search";
142
+ String: {
143
+ bucket: Buckets.final,
144
+ canRender: (entry) => {
145
+ return typeof entry.value === "string";
146
+ },
147
+ render: (_id, entry, options) => {
148
+ const rendered = entry.value.trim();
149
+ if (options.renderString === "markdown") {
150
+ return {
151
+ rendered: <MarkdownDiv markdown={rendered} />,
152
+ };
153
+ } else {
154
+ return {
155
+ rendered: (
156
+ <pre className={clsx(styles.preWrap, styles.preCompact)}>
157
+ {rendered}
158
+ </pre>
159
+ ),
160
+ };
161
+ }
162
+ },
202
163
  },
203
- render: (_id, entry, _options) => {
204
- const results: ReactNode[] = [];
205
- results.push(
206
- <div className={styles.query}>
207
- <i className={ApplicationIcons.search}></i> {entry.value.query}
208
- </div>,
209
- );
210
- entry.value.results.forEach(
211
- (result: { url: string; summary: string }) => {
212
- results.push(
213
- <div>
214
- <a href={result.url}>{result.url}</a>
215
- </div>,
164
+ Array: {
165
+ bucket: Buckets.intermediate,
166
+ canRender: (entry) => {
167
+ const isArray = Array.isArray(entry.value);
168
+ if (isArray) {
169
+ if (entry.value.length === 0 || entry.value.length === 1) {
170
+ return true;
171
+ }
172
+ const types = new Set(
173
+ entry.value
174
+ .filter((e: unknown) => e !== null)
175
+ .map((e: unknown) => {
176
+ return typeof e;
177
+ }),
216
178
  );
217
- results.push(
218
- <div className={clsx("text-size-smaller", styles.summary)}>
219
- {result.summary}
220
- </div>,
221
- );
222
- },
223
- );
224
- return {
225
- rendered: results,
226
- };
227
- },
228
- },
229
- web_browser: {
230
- bucket: Buckets.intermediate,
231
- canRender: (entry) => {
232
- return (
233
- typeof entry.value === "string" && entry.name?.startsWith("web_browser")
234
- );
235
- },
236
- render: (_id, entry, _options) => {
237
- return {
238
- rendered: <pre className={styles.preWrap}>{entry.value}</pre>,
239
- };
240
- },
241
- },
242
- Html: {
243
- bucket: Buckets.intermediate,
244
- canRender: (entry) => {
245
- return typeof entry.value === "object" && entry.value._html;
179
+ return types.size === 1;
180
+ } else {
181
+ return false;
182
+ }
183
+ },
184
+ render: (id, entry, _options) => {
185
+ const arrayMap: Record<string, unknown> = {};
186
+ entry.value.forEach((e: unknown, index: number) => {
187
+ arrayMap[`[${index}]`] = e;
188
+ });
189
+
190
+ const arrayRendered = renderObject ? (
191
+ renderObject(arrayMap)
192
+ ) : (
193
+ <MetaDataView
194
+ id={id}
195
+ className={"font-size-small"}
196
+ entries={arrayMap}
197
+ tableOptions="borderless,sm"
198
+ compact={true}
199
+ />
200
+ );
201
+ return { rendered: arrayRendered };
202
+ },
246
203
  },
247
- render: (_id, entry, _options) => {
248
- return {
249
- rendered: entry.value._html,
250
- };
204
+ ChatMessage: ChatMessageRenderer,
205
+ web_search: {
206
+ bucket: Buckets.intermediate,
207
+ canRender: (entry) => {
208
+ return typeof entry.value === "object" && entry.name === "web_search";
209
+ },
210
+ render: (_id, entry, _options) => {
211
+ const results: ReactNode[] = [];
212
+ results.push(
213
+ <div className={styles.query}>
214
+ <i className={ApplicationIcons.search}></i> {entry.value.query}
215
+ </div>,
216
+ );
217
+ entry.value.results.forEach(
218
+ (result: { url: string; summary: string }) => {
219
+ results.push(
220
+ <div>
221
+ <a href={result.url}>{result.url}</a>
222
+ </div>,
223
+ );
224
+ results.push(
225
+ <div className={clsx("text-size-smaller", styles.summary)}>
226
+ {result.summary}
227
+ </div>,
228
+ );
229
+ },
230
+ );
231
+ return {
232
+ rendered: results,
233
+ };
234
+ },
251
235
  },
252
- },
253
- Image: {
254
- bucket: Buckets.intermediate,
255
- canRender: (entry) => {
256
- return (
257
- typeof entry.value === "string" && entry.value.startsWith("data:image/")
258
- );
236
+ web_browser: {
237
+ bucket: Buckets.intermediate,
238
+ canRender: (entry) => {
239
+ return (
240
+ typeof entry.value === "string" &&
241
+ entry.name?.startsWith("web_browser")
242
+ );
243
+ },
244
+ render: (_id, entry, _options) => {
245
+ return {
246
+ rendered: <pre className={styles.preWrap}>{entry.value}</pre>,
247
+ };
248
+ },
259
249
  },
260
- render: (_id, entry, _options) => {
261
- return {
262
- rendered: <img src={entry.value} />,
263
- };
250
+ Html: {
251
+ bucket: Buckets.intermediate,
252
+ canRender: (entry) => {
253
+ return typeof entry.value === "object" && entry.value._html;
254
+ },
255
+ render: (_id, entry, _options) => {
256
+ return {
257
+ rendered: entry.value._html,
258
+ };
259
+ },
264
260
  },
265
- },
266
- Object: {
267
- bucket: Buckets.intermediate,
268
- canRender: (entry) => {
269
- return typeof entry.value === "object";
261
+ Image: {
262
+ bucket: Buckets.intermediate,
263
+ canRender: (entry) => {
264
+ return (
265
+ typeof entry.value === "string" &&
266
+ entry.value.startsWith("data:image/")
267
+ );
268
+ },
269
+ render: (_id, entry, _options) => {
270
+ return {
271
+ rendered: <img src={entry.value} />,
272
+ };
273
+ },
270
274
  },
271
- render: (id, entry, _options) => {
272
- return {
273
- rendered: (
274
- <MetaDataView
275
- id={id}
276
- className={"text-size-smaller"}
277
- entries={entry.value}
278
- tableOptions="borderless,sm"
279
- compact
280
- />
281
- ),
282
- };
275
+ Object: {
276
+ bucket: Buckets.intermediate,
277
+ canRender: (entry) => {
278
+ return typeof entry.value === "object";
279
+ },
280
+ render: (id, entry, _options) => {
281
+ if (renderObject) {
282
+ return { rendered: renderObject(entry.value) };
283
+ } else {
284
+ return {
285
+ rendered: (
286
+ <MetaDataView
287
+ id={id}
288
+ className={"text-size-smaller"}
289
+ entries={entry.value}
290
+ tableOptions="borderless,sm"
291
+ compact
292
+ />
293
+ ),
294
+ };
295
+ }
296
+ },
283
297
  },
284
- },
298
+ };
299
+ return contentRenderers;
285
300
  };
@@ -21,6 +21,7 @@ export const LogViewContainer: FC = () => {
21
21
  epoch?: string;
22
22
  sampleTabId?: string;
23
23
  }>();
24
+
24
25
  const initialState = useStore((state) => state.app.initialState);
25
26
  const clearInitialState = useStore(
26
27
  (state) => state.appActions.clearInitialState,
@@ -80,7 +81,7 @@ export const LogViewContainer: FC = () => {
80
81
  }
81
82
 
82
83
  // Reset the sample
83
- if (logPath !== prevLogPath) {
84
+ if (prevLogPath && logPath !== prevLogPath) {
84
85
  clearSelectedSample();
85
86
 
86
87
  clearSelectedLogSummary();
@@ -105,6 +105,7 @@ export const SamplesTab: FC<SamplesTabProps> = ({ running }) => {
105
105
  const groupBy = useGroupBy();
106
106
  const groupByOrder = useGroupByOrder();
107
107
  const currentScore = useScore();
108
+ const selectSample = useStore((state) => state.logActions.selectSample);
108
109
 
109
110
  const selectedSampleIdentifier = useStore(
110
111
  (state) => state.sample.sample_identifier,
@@ -186,6 +187,10 @@ export const SamplesTab: FC<SamplesTabProps> = ({ running }) => {
186
187
  })
187
188
  : [],
188
189
  );
190
+
191
+ if (sampleSummaries.length === 1) {
192
+ selectSample(0);
193
+ }
189
194
  }, [sampleSummaries, sampleProcessor]);
190
195
 
191
196
  const title =
@@ -47,7 +47,7 @@ const noGrouping = (
47
47
  const itemCount = counter.item();
48
48
  return [
49
49
  {
50
- label: `Sample ${itemCount}`,
50
+ label: `Sample ${sample.id}`,
51
51
  number: itemCount,
52
52
  index: index,
53
53
  data: sample,
@@ -107,10 +107,10 @@ const groupBySample = (
107
107
  if (sample.id !== lastId) {
108
108
  counter.incrementGroup();
109
109
  results.push({
110
- label: `Sample ${itemCount}`,
110
+ label: `Sample ${sample.id}`,
111
111
  number: counter.group(),
112
112
  index: index,
113
- data: `Sample ${counter.group()}`,
113
+ data: `Sample ${sample.id}`,
114
114
  type: "separator",
115
115
  } as SeparatorListItem);
116
116
  counter.resetItem();
@@ -175,7 +175,7 @@ const groupByEpoch = (
175
175
  // Compute the index within the epoch
176
176
  counter.incrementItem();
177
177
  results.push({
178
- label: `Sample ${counter.item()} (Epoch ${counter.group()})`,
178
+ label: `Sample ${sample.id} (Epoch ${sample.epoch})`,
179
179
  number: counter.item(),
180
180
  index: index,
181
181
  data: sample,
@@ -130,29 +130,26 @@ export const useSampleNavigation = () => {
130
130
 
131
131
  // Navigate to a specific sample with index
132
132
  const showSample = useCallback(
133
- (index: number, specifiedSampleTabId?: string) => {
134
- if (sampleSummaries && index >= 0 && index < sampleSummaries.length) {
135
- const sample = sampleSummaries[index];
136
- const resolvedPath = resolveLogPath();
137
-
138
- if (resolvedPath) {
139
- // Update internal state
140
- selectSample(index);
141
- setShowingSampleDialog(true);
142
-
143
- // Use specified sampleTabId if provided, otherwise use current sampleTabId from URL params
144
- const currentSampleTabId = specifiedSampleTabId || sampleTabId;
145
-
146
- const url = sampleUrl(
147
- resolvedPath,
148
- sample.id,
149
- sample.epoch,
150
- currentSampleTabId,
151
- );
152
-
153
- // Navigate to the sample URL
154
- navigate(url);
155
- }
133
+ (
134
+ index: number,
135
+ id: string | number,
136
+ epoch: number,
137
+ specifiedSampleTabId?: string,
138
+ ) => {
139
+ const resolvedPath = resolveLogPath();
140
+
141
+ if (resolvedPath) {
142
+ // Update internal state
143
+ selectSample(index);
144
+ setShowingSampleDialog(true);
145
+
146
+ // Use specified sampleTabId if provided, otherwise use current sampleTabId from URL params
147
+ const currentSampleTabId = specifiedSampleTabId || sampleTabId;
148
+
149
+ const url = sampleUrl(resolvedPath, id, epoch, currentSampleTabId);
150
+
151
+ // Navigate to the sample URL
152
+ navigate(url);
156
153
  }
157
154
  },
158
155
  [
@@ -171,7 +168,7 @@ export const useSampleNavigation = () => {
171
168
  const itemsCount = sampleSummaries.length;
172
169
  const next = Math.min(selectedSampleIndex + 1, itemsCount - 1);
173
170
  if (next > -1) {
174
- showSample(next, sampleTabId);
171
+ selectSample(next);
175
172
  }
176
173
  }, [selectedSampleIndex, showSample, sampleTabId]);
177
174
 
@@ -179,7 +176,7 @@ export const useSampleNavigation = () => {
179
176
  const previousSample = useCallback(() => {
180
177
  const prev = selectedSampleIndex - 1;
181
178
  if (prev > -1) {
182
- showSample(prev, sampleTabId);
179
+ selectSample(prev);
183
180
  }
184
181
  }, [selectedSampleIndex, showSample, sampleTabId]);
185
182