inspect-ai 0.3.99__py3-none-any.whl → 0.3.100__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 (120) hide show
  1. inspect_ai/_display/core/config.py +11 -5
  2. inspect_ai/_display/core/panel.py +66 -2
  3. inspect_ai/_display/core/textual.py +5 -2
  4. inspect_ai/_display/plain/display.py +1 -0
  5. inspect_ai/_display/rich/display.py +2 -2
  6. inspect_ai/_display/textual/widgets/transcript.py +37 -9
  7. inspect_ai/_eval/score.py +2 -4
  8. inspect_ai/_eval/task/run.py +59 -81
  9. inspect_ai/_util/content.py +11 -6
  10. inspect_ai/_util/interrupt.py +2 -2
  11. inspect_ai/_util/text.py +7 -0
  12. inspect_ai/_util/working.py +8 -37
  13. inspect_ai/_view/__init__.py +0 -0
  14. inspect_ai/_view/schema.py +2 -1
  15. inspect_ai/_view/www/CLAUDE.md +15 -0
  16. inspect_ai/_view/www/dist/assets/index.css +263 -159
  17. inspect_ai/_view/www/dist/assets/index.js +22153 -19093
  18. inspect_ai/_view/www/log-schema.json +77 -3
  19. inspect_ai/_view/www/package.json +5 -1
  20. inspect_ai/_view/www/src/@types/log.d.ts +9 -0
  21. inspect_ai/_view/www/src/app/App.tsx +1 -15
  22. inspect_ai/_view/www/src/app/appearance/icons.ts +4 -1
  23. inspect_ai/_view/www/src/app/content/MetaDataGrid.tsx +24 -6
  24. inspect_ai/_view/www/src/app/content/MetadataGrid.module.css +0 -5
  25. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +220 -205
  26. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +2 -1
  27. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +5 -0
  28. inspect_ai/_view/www/src/app/routing/url.ts +84 -4
  29. inspect_ai/_view/www/src/app/samples/InlineSampleDisplay.module.css +0 -5
  30. inspect_ai/_view/www/src/app/samples/SampleDialog.module.css +1 -1
  31. inspect_ai/_view/www/src/app/samples/SampleDisplay.module.css +7 -0
  32. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +24 -17
  33. inspect_ai/_view/www/src/app/samples/SampleSummaryView.module.css +1 -2
  34. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +8 -6
  35. inspect_ai/_view/www/src/app/samples/chat/ChatMessageRow.tsx +0 -4
  36. inspect_ai/_view/www/src/app/samples/chat/ChatViewVirtualList.tsx +3 -2
  37. inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +2 -0
  38. inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +2 -0
  39. inspect_ai/_view/www/src/app/samples/chat/messages.ts +1 -0
  40. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +1 -0
  41. inspect_ai/_view/www/src/app/samples/list/SampleRow.tsx +1 -1
  42. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +1 -2
  43. inspect_ai/_view/www/src/app/samples/transcript/InfoEventView.tsx +1 -1
  44. inspect_ai/_view/www/src/app/samples/transcript/InputEventView.tsx +1 -2
  45. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.module.css +1 -1
  46. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
  47. inspect_ai/_view/www/src/app/samples/transcript/SampleInitEventView.tsx +1 -1
  48. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +3 -2
  49. inspect_ai/_view/www/src/app/samples/transcript/SandboxEventView.tsx +4 -5
  50. inspect_ai/_view/www/src/app/samples/transcript/ScoreEventView.tsx +1 -1
  51. inspect_ai/_view/www/src/app/samples/transcript/SpanEventView.tsx +1 -2
  52. inspect_ai/_view/www/src/app/samples/transcript/StepEventView.tsx +1 -3
  53. inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +1 -2
  54. inspect_ai/_view/www/src/app/samples/transcript/ToolEventView.tsx +3 -4
  55. inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.module.css +42 -0
  56. inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.tsx +77 -0
  57. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualList.tsx +27 -71
  58. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.module.css +13 -3
  59. inspect_ai/_view/www/src/app/samples/transcript/TranscriptVirtualListComponent.tsx +27 -2
  60. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.module.css +1 -0
  61. inspect_ai/_view/www/src/app/samples/transcript/event/EventPanel.tsx +21 -22
  62. inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.module.css +45 -0
  63. inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.tsx +223 -0
  64. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.module.css +10 -0
  65. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +258 -0
  66. inspect_ai/_view/www/src/app/samples/transcript/outline/tree-visitors.ts +187 -0
  67. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventRenderers.tsx +8 -1
  68. inspect_ai/_view/www/src/app/samples/transcript/state/StateEventView.tsx +3 -4
  69. inspect_ai/_view/www/src/app/samples/transcript/transform/hooks.ts +78 -0
  70. inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +340 -135
  71. inspect_ai/_view/www/src/app/samples/transcript/transform/utils.ts +3 -0
  72. inspect_ai/_view/www/src/app/samples/transcript/types.ts +2 -0
  73. inspect_ai/_view/www/src/app/types.ts +5 -1
  74. inspect_ai/_view/www/src/client/api/api-browser.ts +2 -2
  75. inspect_ai/_view/www/src/components/LiveVirtualList.tsx +6 -1
  76. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +1 -1
  77. inspect_ai/_view/www/src/components/PopOver.tsx +422 -0
  78. inspect_ai/_view/www/src/components/PulsingDots.module.css +9 -9
  79. inspect_ai/_view/www/src/components/PulsingDots.tsx +4 -1
  80. inspect_ai/_view/www/src/components/StickyScroll.tsx +183 -0
  81. inspect_ai/_view/www/src/components/TabSet.tsx +4 -0
  82. inspect_ai/_view/www/src/state/hooks.ts +52 -2
  83. inspect_ai/_view/www/src/state/logSlice.ts +4 -3
  84. inspect_ai/_view/www/src/state/samplePolling.ts +8 -0
  85. inspect_ai/_view/www/src/state/sampleSlice.ts +53 -9
  86. inspect_ai/_view/www/src/state/scrolling.ts +152 -0
  87. inspect_ai/_view/www/src/utils/attachments.ts +7 -0
  88. inspect_ai/_view/www/src/utils/python.ts +18 -0
  89. inspect_ai/_view/www/yarn.lock +269 -6
  90. inspect_ai/agent/_react.py +12 -7
  91. inspect_ai/agent/_run.py +2 -3
  92. inspect_ai/analysis/beta/_dataframe/samples/table.py +19 -18
  93. inspect_ai/log/_log.py +1 -1
  94. inspect_ai/log/_recorders/file.py +2 -9
  95. inspect_ai/log/_transcript.py +1 -1
  96. inspect_ai/model/_call_tools.py +6 -2
  97. inspect_ai/model/_openai.py +1 -1
  98. inspect_ai/model/_openai_responses.py +78 -39
  99. inspect_ai/model/_openai_web_search.py +31 -0
  100. inspect_ai/model/_providers/azureai.py +72 -3
  101. inspect_ai/model/_providers/openai.py +2 -1
  102. inspect_ai/scorer/_metric.py +1 -2
  103. inspect_ai/solver/_task_state.py +2 -2
  104. inspect_ai/tool/_tool.py +6 -2
  105. inspect_ai/tool/_tool_def.py +27 -4
  106. inspect_ai/tool/_tool_info.py +2 -0
  107. inspect_ai/tool/_tools/_web_search/_google.py +15 -4
  108. inspect_ai/tool/_tools/_web_search/_tavily.py +35 -12
  109. inspect_ai/tool/_tools/_web_search/_web_search.py +214 -45
  110. inspect_ai/util/__init__.py +4 -0
  111. inspect_ai/util/_json.py +3 -0
  112. inspect_ai/util/_limit.py +230 -20
  113. inspect_ai/util/_sandbox/docker/compose.py +20 -11
  114. inspect_ai/util/_span.py +1 -1
  115. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.dist-info}/METADATA +3 -3
  116. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.dist-info}/RECORD +120 -106
  117. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.dist-info}/WHEEL +1 -1
  118. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.dist-info}/entry_points.txt +0 -0
  119. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.dist-info}/licenses/LICENSE +0 -0
  120. {inspect_ai-0.3.99.dist-info → inspect_ai-0.3.100.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 =
@@ -1,4 +1,7 @@
1
+ import { useMemo } from "react";
2
+ import { useParams } from "react-router-dom";
1
3
  import { kSampleMessagesTabId, kSampleTranscriptTabId } from "../../constants";
4
+ import { useStore } from "../../state/store";
2
5
  import { directoryRelativeUrl } from "../../utils/uri";
3
6
 
4
7
  export const kLogRouteUrlPattern = "/logs/:logPath/:tabId?/:sampleTabId?";
@@ -46,6 +49,80 @@ export const sampleEventUrl = (
46
49
  return `${baseUrl}?event=${eventId}`;
47
50
  };
48
51
 
52
+ export const useSampleMessageUrl = (
53
+ messageId: string | null,
54
+ sampleId?: string | number,
55
+ sampleEpoch?: string | number,
56
+ ) => {
57
+ const {
58
+ logPath: urlLogPath,
59
+ sampleId: urlSampleId,
60
+ epoch: urlEpoch,
61
+ } = useParams<{
62
+ logPath?: string;
63
+ tabId?: string;
64
+ sampleId?: string;
65
+ epoch?: string;
66
+ }>();
67
+
68
+ const log_file = useStore((state) => state.logs.selectedLogFile);
69
+ const log_dir = useStore((state) => state.logs.logs.log_dir);
70
+
71
+ let targetLogPath = urlLogPath;
72
+ if (!targetLogPath && log_file) {
73
+ targetLogPath = makeLogPath(log_file, log_dir);
74
+ }
75
+
76
+ const eventUrl = useMemo(() => {
77
+ return messageId && targetLogPath
78
+ ? sampleMessageUrl(
79
+ messageId,
80
+ targetLogPath,
81
+ sampleId || urlSampleId,
82
+ sampleEpoch || urlEpoch,
83
+ )
84
+ : undefined;
85
+ }, [targetLogPath, messageId, sampleId, urlSampleId, sampleEpoch, urlEpoch]);
86
+ return eventUrl;
87
+ };
88
+
89
+ export const useSampleEventUrl = (
90
+ eventId: string,
91
+ sampleId?: string | number,
92
+ sampleEpoch?: string | number,
93
+ ) => {
94
+ const {
95
+ logPath: urlLogPath,
96
+ sampleId: urlSampleId,
97
+ epoch: urlEpoch,
98
+ } = useParams<{
99
+ logPath?: string;
100
+ tabId?: string;
101
+ sampleId?: string;
102
+ epoch?: string;
103
+ }>();
104
+
105
+ const log_file = useStore((state) => state.logs.selectedLogFile);
106
+ const log_dir = useStore((state) => state.logs.logs.log_dir);
107
+
108
+ let targetLogPath = urlLogPath;
109
+ if (!targetLogPath && log_file) {
110
+ targetLogPath = makeLogPath(log_file, log_dir);
111
+ }
112
+
113
+ const eventUrl = useMemo(() => {
114
+ return targetLogPath
115
+ ? sampleEventUrl(
116
+ eventId,
117
+ targetLogPath,
118
+ sampleId || urlSampleId,
119
+ sampleEpoch || urlEpoch,
120
+ )
121
+ : undefined;
122
+ }, [targetLogPath, eventId, sampleId, urlSampleId, sampleEpoch, urlEpoch]);
123
+ return eventUrl;
124
+ };
125
+
49
126
  export const sampleMessageUrl = (
50
127
  messageId: string,
51
128
  logPath: string,
@@ -63,8 +140,12 @@ export const sampleMessageUrl = (
63
140
  };
64
141
 
65
142
  export const logUrl = (log_file: string, log_dir?: string, tabId?: string) => {
143
+ return logUrlRaw(makeLogPath(log_file, log_dir), tabId);
144
+ };
145
+
146
+ export const makeLogPath = (log_file: string, log_dir?: string) => {
66
147
  const pathSegment = directoryRelativeUrl(log_file, log_dir);
67
- return logUrlRaw(pathSegment, tabId);
148
+ return pathSegment;
68
149
  };
69
150
 
70
151
  export const logUrlRaw = (log_segment: string, tabId?: string) => {
@@ -77,9 +158,8 @@ export const logUrlRaw = (log_segment: string, tabId?: string) => {
77
158
 
78
159
  export const supportsLinking = () => {
79
160
  return (
80
- location.hostname !== "localhost" &&
81
- location.hostname !== "127.0.0.1" &&
82
- location.protocol !== "vscode-webview:"
161
+ //location.hostname !== "localhost" &&
162
+ location.hostname !== "127.0.0.1" && location.protocol !== "vscode-webview:"
83
163
  );
84
164
  };
85
165
 
@@ -4,11 +4,6 @@
4
4
  flex-direction: column;
5
5
  }
6
6
 
7
- .body {
8
- margin-top: 1em;
9
- margin-bottom: 1em;
10
- }
11
-
12
7
  .scroller {
13
8
  overflow-y: auto;
14
9
  width: 100%;
@@ -1,3 +1,3 @@
1
1
  .modalBody {
2
- padding: 1em 0;
2
+ padding: 0 0;
3
3
  }
@@ -2,6 +2,13 @@
2
2
  padding-bottom: 1em;
3
3
  }
4
4
 
5
+ .tabControls {
6
+ position: sticky;
7
+ z-index: 1001;
8
+ top: 0;
9
+ background-color: var(--bs-body-bg);
10
+ }
11
+
5
12
  .fullWidth {
6
13
  width: 100%;
7
14
  }