inspect-ai 0.3.98__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 (131) hide show
  1. inspect_ai/__init__.py +2 -0
  2. inspect_ai/_cli/log.py +1 -1
  3. inspect_ai/_display/core/config.py +11 -5
  4. inspect_ai/_display/core/panel.py +66 -2
  5. inspect_ai/_display/core/textual.py +5 -2
  6. inspect_ai/_display/plain/display.py +1 -0
  7. inspect_ai/_display/rich/display.py +2 -2
  8. inspect_ai/_display/textual/widgets/transcript.py +41 -1
  9. inspect_ai/_eval/run.py +12 -4
  10. inspect_ai/_eval/score.py +2 -4
  11. inspect_ai/_eval/task/log.py +1 -1
  12. inspect_ai/_eval/task/run.py +59 -81
  13. inspect_ai/_eval/task/task.py +1 -1
  14. inspect_ai/_util/_async.py +1 -1
  15. inspect_ai/_util/content.py +11 -6
  16. inspect_ai/_util/interrupt.py +2 -2
  17. inspect_ai/_util/text.py +7 -0
  18. inspect_ai/_util/working.py +8 -37
  19. inspect_ai/_view/__init__.py +0 -0
  20. inspect_ai/_view/schema.py +3 -1
  21. inspect_ai/_view/view.py +14 -0
  22. inspect_ai/_view/www/CLAUDE.md +15 -0
  23. inspect_ai/_view/www/dist/assets/index.css +273 -169
  24. inspect_ai/_view/www/dist/assets/index.js +20079 -17019
  25. inspect_ai/_view/www/log-schema.json +122 -8
  26. inspect_ai/_view/www/package.json +5 -1
  27. inspect_ai/_view/www/src/@types/log.d.ts +20 -2
  28. inspect_ai/_view/www/src/app/App.tsx +1 -15
  29. inspect_ai/_view/www/src/app/appearance/icons.ts +4 -1
  30. inspect_ai/_view/www/src/app/content/MetaDataGrid.tsx +24 -6
  31. inspect_ai/_view/www/src/app/content/MetadataGrid.module.css +0 -5
  32. inspect_ai/_view/www/src/app/content/RenderedContent.tsx +221 -205
  33. inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +2 -1
  34. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +5 -0
  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 +26 -19
  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/SampleRow.tsx +1 -1
  49. inspect_ai/_view/www/src/app/samples/scores/SampleScoresGrid.module.css +2 -2
  50. inspect_ai/_view/www/src/app/samples/transcript/ErrorEventView.tsx +2 -3
  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 +269 -6
  98. inspect_ai/agent/_react.py +12 -7
  99. inspect_ai/agent/_run.py +46 -11
  100. inspect_ai/analysis/beta/_dataframe/samples/table.py +19 -18
  101. inspect_ai/log/_bundle.py +5 -3
  102. inspect_ai/log/_log.py +3 -3
  103. inspect_ai/log/_recorders/file.py +2 -9
  104. inspect_ai/log/_transcript.py +1 -1
  105. inspect_ai/model/_call_tools.py +6 -2
  106. inspect_ai/model/_openai.py +1 -1
  107. inspect_ai/model/_openai_responses.py +78 -39
  108. inspect_ai/model/_openai_web_search.py +31 -0
  109. inspect_ai/model/_providers/anthropic.py +3 -6
  110. inspect_ai/model/_providers/azureai.py +72 -3
  111. inspect_ai/model/_providers/openai.py +2 -1
  112. inspect_ai/model/_providers/providers.py +1 -1
  113. inspect_ai/scorer/_metric.py +1 -2
  114. inspect_ai/solver/_task_state.py +2 -2
  115. inspect_ai/tool/_tool.py +6 -2
  116. inspect_ai/tool/_tool_def.py +27 -4
  117. inspect_ai/tool/_tool_info.py +2 -0
  118. inspect_ai/tool/_tools/_web_search/_google.py +15 -4
  119. inspect_ai/tool/_tools/_web_search/_tavily.py +35 -12
  120. inspect_ai/tool/_tools/_web_search/_web_search.py +214 -45
  121. inspect_ai/util/__init__.py +6 -0
  122. inspect_ai/util/_json.py +3 -0
  123. inspect_ai/util/_limit.py +374 -141
  124. inspect_ai/util/_sandbox/docker/compose.py +20 -11
  125. inspect_ai/util/_span.py +1 -1
  126. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/METADATA +3 -3
  127. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/RECORD +131 -117
  128. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/WHEEL +1 -1
  129. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/entry_points.txt +0 -0
  130. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/licenses/LICENSE +0 -0
  131. {inspect_ai-0.3.98.dist-info → inspect_ai-0.3.100.dist-info}/top_level.txt +0 -0
@@ -1,15 +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";
6
+ import { MarkdownDiv } from "../../components/MarkdownDiv";
7
+ import { formatNumber } from "../../utils/format";
12
8
  import { isJson } from "../../utils/json";
9
+ import { ApplicationIcons } from "../appearance/icons";
10
+ import { ChatMessageRenderer } from "../samples/chat/ChatMessageRenderer";
11
+ import { MetaDataView } from "./MetaDataView";
13
12
  import styles from "./RenderedContent.module.css";
14
13
  import { Buckets, ContentRenderer, RenderOptions } from "./types";
15
14
 
@@ -17,6 +16,7 @@ interface RenderedContentProps {
17
16
  id: string;
18
17
  entry: { name: string; value: unknown };
19
18
  renderOptions?: RenderOptions;
19
+ renderObject?(entry: any): ReactNode;
20
20
  }
21
21
 
22
22
  /**
@@ -26,15 +26,16 @@ export const RenderedContent: FC<RenderedContentProps> = ({
26
26
  id,
27
27
  entry,
28
28
  renderOptions = { renderString: "markdown" },
29
+ renderObject,
29
30
  }): JSX.Element => {
30
31
  // Explicitly specify return type
31
32
  if (entry.value === null) {
32
33
  return <span>[null]</span>;
33
34
  }
34
-
35
- const renderer = Object.keys(contentRenderers)
35
+ const renderers = contentRenderers(renderObject);
36
+ const renderer = Object.keys(renderers)
36
37
  .map((key) => {
37
- return contentRenderers[key];
38
+ return renderers[key];
38
39
  })
39
40
  .sort((a, b) => {
40
41
  return a.bucket - b.bucket;
@@ -69,216 +70,231 @@ export const RenderedContent: FC<RenderedContentProps> = ({
69
70
  * Object containing different content renderers.
70
71
  * Each renderer is responsible for rendering a specific type of content.
71
72
  */
72
- const contentRenderers: Record<string, ContentRenderer> = {
73
- AnsiString: {
74
- bucket: Buckets.first,
75
- canRender: (entry) => {
76
- return (
77
- typeof entry.value === "string" && entry.value.indexOf("\u001b") > -1
78
- );
79
- },
80
- render: (_id, entry, _options) => {
81
- return {
82
- rendered: <ANSIDisplay output={entry.value} />,
83
- };
84
- },
85
- },
86
- JsonString: {
87
- bucket: Buckets.first,
88
- canRender: (entry) => {
89
- if (typeof entry.value === "string") {
90
- const trimmed = entry.value.trim();
91
- return isJson(trimmed);
92
- }
93
- return false;
94
- },
95
- render: (_id, entry, _options) => {
96
- const obj = JSON5.parse(entry.value);
97
- return { rendered: <JSONPanel data={obj as Record<string, unknown>} /> };
98
- },
99
- },
100
-
101
- Model: {
102
- bucket: Buckets.intermediate,
103
- canRender: (entry) => {
104
- return typeof entry.value === "object" && entry.value._model;
105
- },
106
- render: (_id, entry, _options) => {
107
- return {
108
- rendered: (
109
- <Fragment>
110
- <i className={ApplicationIcons.model} /> {entry.value._model}
111
- </Fragment>
112
- ),
113
- };
114
- },
115
- },
116
- Boolean: {
117
- bucket: Buckets.intermediate,
118
- canRender: (entry) => {
119
- return typeof entry.value === "boolean";
120
- },
121
- render: (id, entry, options) => {
122
- entry.value = entry.value.toString();
123
- return contentRenderers.String.render(id, entry, options);
124
- },
125
- },
126
- Number: {
127
- bucket: Buckets.intermediate,
128
- canRender: (entry) => {
129
- return typeof entry.value === "number";
130
- },
131
- render: (id, entry, options) => {
132
- entry.value = formatNumber(entry.value);
133
- return contentRenderers.String.render(id, entry, options);
134
- },
135
- },
136
- String: {
137
- bucket: Buckets.final,
138
- canRender: (entry) => {
139
- 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
+ },
140
89
  },
141
- render: (_id, entry, options) => {
142
- const rendered = entry.value.trim();
143
- 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);
144
101
  return {
145
- rendered: rendered,
102
+ rendered: <JSONPanel data={obj as Record<string, unknown>} />,
146
103
  };
147
- } 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) => {
148
113
  return {
149
114
  rendered: (
150
- <pre className={clsx(styles.preWrap, styles.preCompact)}>
151
- {rendered}
152
- </pre>
115
+ <Fragment>
116
+ <i className={ApplicationIcons.model} /> {entry.value._model}
117
+ </Fragment>
153
118
  ),
154
119
  };
155
- }
120
+ },
156
121
  },
157
- },
158
- Array: {
159
- bucket: Buckets.intermediate,
160
- canRender: (entry) => {
161
- const isArray = Array.isArray(entry.value);
162
- if (isArray) {
163
- if (entry.value.length === 0 || entry.value.length === 1) {
164
- return true;
165
- }
166
- const types = new Set(
167
- entry.value
168
- .filter((e: unknown) => e !== null)
169
- .map((e: unknown) => {
170
- return typeof e;
171
- }),
172
- );
173
- return types.size === 1;
174
- } else {
175
- return false;
176
- }
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
+ },
177
131
  },
178
- render: (id, entry, _options) => {
179
- const arrayMap: Record<string, unknown> = {};
180
- entry.value.forEach((e: unknown, index: number) => {
181
- arrayMap[`[${index}]`] = e;
182
- });
183
-
184
- const arrayRendered = (
185
- <MetaDataView
186
- id={id}
187
- className={"font-size-small"}
188
- entries={arrayMap}
189
- tableOptions="borderless,sm"
190
- compact={true}
191
- />
192
- );
193
- 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
+ },
194
141
  },
195
- },
196
- ChatMessage: ChatMessageRenderer,
197
- web_search: {
198
- bucket: Buckets.intermediate,
199
- canRender: (entry) => {
200
- 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
+ },
201
163
  },
202
- render: (_id, entry, _options) => {
203
- const results: ReactNode[] = [];
204
- results.push(
205
- <div className={styles.query}>
206
- <i className={ApplicationIcons.search}></i> {entry.value.query}
207
- </div>,
208
- );
209
- entry.value.results.forEach(
210
- (result: { url: string; summary: string }) => {
211
- results.push(
212
- <div>
213
- <a href={result.url}>{result.url}</a>
214
- </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
+ }),
215
178
  );
216
- results.push(
217
- <div className={clsx("text-size-smaller", styles.summary)}>
218
- {result.summary}
219
- </div>,
220
- );
221
- },
222
- );
223
- return {
224
- rendered: results,
225
- };
226
- },
227
- },
228
- web_browser: {
229
- bucket: Buckets.intermediate,
230
- canRender: (entry) => {
231
- return (
232
- typeof entry.value === "string" && entry.name?.startsWith("web_browser")
233
- );
234
- },
235
- render: (_id, entry, _options) => {
236
- return {
237
- rendered: <pre className={styles.preWrap}>{entry.value}</pre>,
238
- };
239
- },
240
- },
241
- Html: {
242
- bucket: Buckets.intermediate,
243
- canRender: (entry) => {
244
- 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
+ },
245
203
  },
246
- render: (_id, entry, _options) => {
247
- return {
248
- rendered: entry.value._html,
249
- };
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
+ },
250
235
  },
251
- },
252
- Image: {
253
- bucket: Buckets.intermediate,
254
- canRender: (entry) => {
255
- return (
256
- typeof entry.value === "string" && entry.value.startsWith("data:image/")
257
- );
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
+ },
258
249
  },
259
- render: (_id, entry, _options) => {
260
- return {
261
- rendered: <img src={entry.value} />,
262
- };
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
+ },
263
260
  },
264
- },
265
- Object: {
266
- bucket: Buckets.intermediate,
267
- canRender: (entry) => {
268
- 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
+ },
269
274
  },
270
- render: (id, entry, _options) => {
271
- return {
272
- rendered: (
273
- <MetaDataView
274
- id={id}
275
- className={"text-size-smaller"}
276
- entries={entry.value}
277
- tableOptions="borderless,sm"
278
- compact
279
- />
280
- ),
281
- };
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
+ },
282
297
  },
283
- },
298
+ };
299
+ return contentRenderers;
284
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
  }