inspect-ai 0.3.94__py3-none-any.whl → 0.3.95__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 (70) hide show
  1. inspect_ai/_eval/loader.py +1 -1
  2. inspect_ai/_eval/task/run.py +12 -6
  3. inspect_ai/_util/exception.py +4 -0
  4. inspect_ai/_util/hash.py +39 -0
  5. inspect_ai/_util/path.py +22 -0
  6. inspect_ai/_util/trace.py +1 -1
  7. inspect_ai/_util/working.py +4 -0
  8. inspect_ai/_view/www/dist/assets/index.css +9 -9
  9. inspect_ai/_view/www/dist/assets/index.js +117 -120
  10. inspect_ai/_view/www/package.json +1 -1
  11. inspect_ai/_view/www/src/app/log-view/navbar/SecondaryBar.tsx +2 -2
  12. inspect_ai/_view/www/src/app/log-view/tabs/SamplesTab.tsx +1 -4
  13. inspect_ai/_view/www/src/app/samples/SamplesTools.tsx +3 -13
  14. inspect_ai/_view/www/src/app/samples/sample-tools/SelectScorer.tsx +45 -48
  15. inspect_ai/_view/www/src/app/samples/sample-tools/filters.ts +16 -15
  16. inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/SampleFilter.tsx +47 -75
  17. inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/completions.ts +9 -9
  18. inspect_ai/_view/www/src/app/types.ts +12 -2
  19. inspect_ai/_view/www/src/components/ExpandablePanel.module.css +1 -1
  20. inspect_ai/_view/www/src/components/ExpandablePanel.tsx +5 -5
  21. inspect_ai/_view/www/src/state/hooks.ts +19 -3
  22. inspect_ai/_view/www/src/state/logSlice.ts +23 -5
  23. inspect_ai/_view/www/yarn.lock +9 -9
  24. inspect_ai/agent/_bridge/patch.py +1 -3
  25. inspect_ai/analysis/__init__.py +0 -0
  26. inspect_ai/analysis/beta/__init__.py +57 -0
  27. inspect_ai/analysis/beta/_dataframe/__init__.py +0 -0
  28. inspect_ai/analysis/beta/_dataframe/columns.py +145 -0
  29. inspect_ai/analysis/beta/_dataframe/evals/__init__.py +0 -0
  30. inspect_ai/analysis/beta/_dataframe/evals/columns.py +132 -0
  31. inspect_ai/analysis/beta/_dataframe/evals/extract.py +23 -0
  32. inspect_ai/analysis/beta/_dataframe/evals/table.py +140 -0
  33. inspect_ai/analysis/beta/_dataframe/events/__init__.py +0 -0
  34. inspect_ai/analysis/beta/_dataframe/events/columns.py +37 -0
  35. inspect_ai/analysis/beta/_dataframe/events/table.py +14 -0
  36. inspect_ai/analysis/beta/_dataframe/extract.py +54 -0
  37. inspect_ai/analysis/beta/_dataframe/messages/__init__.py +0 -0
  38. inspect_ai/analysis/beta/_dataframe/messages/columns.py +60 -0
  39. inspect_ai/analysis/beta/_dataframe/messages/extract.py +21 -0
  40. inspect_ai/analysis/beta/_dataframe/messages/table.py +87 -0
  41. inspect_ai/analysis/beta/_dataframe/record.py +377 -0
  42. inspect_ai/analysis/beta/_dataframe/samples/__init__.py +0 -0
  43. inspect_ai/analysis/beta/_dataframe/samples/columns.py +73 -0
  44. inspect_ai/analysis/beta/_dataframe/samples/extract.py +82 -0
  45. inspect_ai/analysis/beta/_dataframe/samples/table.py +329 -0
  46. inspect_ai/analysis/beta/_dataframe/util.py +157 -0
  47. inspect_ai/analysis/beta/_dataframe/validate.py +171 -0
  48. inspect_ai/log/_file.py +1 -1
  49. inspect_ai/log/_log.py +21 -1
  50. inspect_ai/model/_call_tools.py +2 -1
  51. inspect_ai/model/_model.py +6 -4
  52. inspect_ai/model/_openai_responses.py +17 -18
  53. inspect_ai/model/_providers/anthropic.py +30 -5
  54. inspect_ai/model/_providers/providers.py +1 -1
  55. inspect_ai/solver/_multiple_choice.py +4 -1
  56. inspect_ai/solver/_task_state.py +7 -3
  57. inspect_ai/tool/_mcp/_context.py +3 -5
  58. inspect_ai/tool/_mcp/server.py +1 -1
  59. inspect_ai/tool/_tools/_think.py +1 -1
  60. inspect_ai/tool/_tools/_web_search/__init__.py +3 -0
  61. inspect_ai/tool/_tools/{_web_search.py → _web_search/_google.py} +56 -103
  62. inspect_ai/tool/_tools/_web_search/_tavily.py +77 -0
  63. inspect_ai/tool/_tools/_web_search/_web_search.py +85 -0
  64. inspect_ai/util/_sandbox/events.py +3 -2
  65. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/METADATA +8 -1
  66. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/RECORD +70 -43
  67. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/WHEEL +1 -1
  68. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/entry_points.txt +0 -0
  69. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/licenses/LICENSE +0 -0
  70. {inspect_ai-0.3.94.dist-info → inspect_ai-0.3.95.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  import clsx from "clsx";
2
2
  import { ScoreLabel } from "../../../app/types";
3
3
 
4
- import { ChangeEvent, FC, useCallback } from "react";
4
+ import { ChangeEvent, FC, useCallback, useMemo } from "react";
5
5
  import styles from "./SelectScorer.module.css";
6
6
 
7
7
  interface SelectScorerProps {
@@ -15,23 +15,18 @@ export const SelectScorer: FC<SelectScorerProps> = ({
15
15
  score,
16
16
  setScore,
17
17
  }) => {
18
- const scorers = scores.reduce((accum, scorer) => {
19
- if (
20
- !accum.find((sc) => {
21
- return scorer.scorer === sc.scorer;
22
- })
23
- ) {
24
- accum.push(scorer);
25
- }
26
- return accum;
27
- }, [] as ScoreLabel[]);
28
-
29
- const handleSelectScore = useCallback(
30
- (index: number) => {
31
- setScore(scores[index]);
32
- },
33
- [setScore, scores],
34
- );
18
+ const scorers = useMemo(() => {
19
+ return scores.reduce((accum, scorer) => {
20
+ if (
21
+ !accum.find((sc) => {
22
+ return scorer.scorer === sc.scorer;
23
+ })
24
+ ) {
25
+ accum.push(scorer);
26
+ }
27
+ return accum;
28
+ }, [] as ScoreLabel[]);
29
+ }, [scores]);
35
30
 
36
31
  if (scorers.length === 1) {
37
32
  // There is only a single scorer in play, just show the list of available scores
@@ -50,8 +45,8 @@ export const SelectScorer: FC<SelectScorerProps> = ({
50
45
  </span>
51
46
  <ScoreSelector
52
47
  scores={scores}
53
- selectedIndex={scoreIndex(scores, score)}
54
- setSelectedIndex={handleSelectScore}
48
+ selectedScore={score}
49
+ setSelectedScore={setScore}
55
50
  />
56
51
  </div>
57
52
  );
@@ -79,15 +74,15 @@ export const SelectScorer: FC<SelectScorerProps> = ({
79
74
  </span>
80
75
  <ScorerSelector
81
76
  scorers={scorers}
82
- selectedIndex={scorerIndex(scorers, score)}
83
- setSelectedIndex={handleSelectScore}
77
+ selectedScore={score}
78
+ setSelectedScore={setScore}
84
79
  />
85
80
  {scorerScores.length > 1 ? (
86
81
  <ScoreSelector
87
82
  className={clsx(styles.secondSel)}
88
83
  scores={scorerScores}
89
- selectedIndex={scoreIndex(scorerScores, score)}
90
- setSelectedIndex={handleSelectScore}
84
+ selectedScore={score}
85
+ setSelectedScore={setScore}
91
86
  />
92
87
  ) : undefined}
93
88
  </div>
@@ -97,25 +92,33 @@ export const SelectScorer: FC<SelectScorerProps> = ({
97
92
 
98
93
  interface ScoreSelectorProps {
99
94
  scores: ScoreLabel[];
100
- selectedIndex: number;
101
- setSelectedIndex: (index: number) => void;
95
+ selectedScore?: ScoreLabel;
96
+ setSelectedScore: (score: ScoreLabel) => void;
102
97
  className?: string | string[];
103
98
  }
104
99
 
105
100
  const ScoreSelector: FC<ScoreSelectorProps> = ({
106
101
  scores,
107
- selectedIndex,
108
- setSelectedIndex,
102
+ selectedScore,
103
+ setSelectedScore,
109
104
  className,
110
105
  }) => {
111
106
  const handleChange = useCallback(
112
107
  (e: ChangeEvent<HTMLSelectElement>) => {
113
108
  const sel = e.target as HTMLSelectElement;
114
- setSelectedIndex(sel.selectedIndex);
109
+ setSelectedScore(scores[sel.selectedIndex]);
115
110
  },
116
- [setSelectedIndex],
111
+ [setSelectedScore, scores],
117
112
  );
118
113
 
114
+ const index = scores.findIndex((sc) => {
115
+ return (
116
+ selectedScore &&
117
+ sc.name === selectedScore.name &&
118
+ sc.scorer === selectedScore.scorer
119
+ );
120
+ });
121
+
119
122
  return (
120
123
  <select
121
124
  className={clsx(
@@ -125,7 +128,7 @@ const ScoreSelector: FC<ScoreSelectorProps> = ({
125
128
  className,
126
129
  )}
127
130
  aria-label=".select-scorer-label"
128
- value={scores[selectedIndex].name}
131
+ value={scores[index].name}
129
132
  onChange={handleChange}
130
133
  >
131
134
  {scores.map((score) => {
@@ -141,28 +144,32 @@ const ScoreSelector: FC<ScoreSelectorProps> = ({
141
144
 
142
145
  interface ScorerSelectorProps {
143
146
  scorers: ScoreLabel[];
144
- selectedIndex: number;
145
- setSelectedIndex: (index: number) => void;
147
+ selectedScore?: ScoreLabel;
148
+ setSelectedScore: (score: ScoreLabel) => void;
146
149
  }
147
150
 
148
151
  const ScorerSelector: FC<ScorerSelectorProps> = ({
149
152
  scorers,
150
- selectedIndex,
151
- setSelectedIndex,
153
+ selectedScore,
154
+ setSelectedScore,
152
155
  }) => {
153
156
  const handleChange = useCallback(
154
157
  (e: ChangeEvent<HTMLSelectElement>) => {
155
158
  const sel = e.target as HTMLSelectElement;
156
- setSelectedIndex(sel.selectedIndex);
159
+ setSelectedScore(scorers[sel.selectedIndex]);
157
160
  },
158
- [setSelectedIndex],
161
+ [setSelectedScore, scorers],
159
162
  );
160
163
 
164
+ const index = scorers.findIndex((sc) => {
165
+ return selectedScore && sc.scorer === selectedScore.scorer;
166
+ });
167
+
161
168
  return (
162
169
  <select
163
170
  className={clsx("form-select", "form-select-sm", "text-size-smaller")}
164
171
  aria-label=".epoch-filter-label"
165
- value={scorers[selectedIndex].scorer}
172
+ value={scorers[index].scorer}
166
173
  onChange={handleChange}
167
174
  >
168
175
  {scorers.map((scorer) => {
@@ -175,13 +182,3 @@ const ScorerSelector: FC<ScorerSelectorProps> = ({
175
182
  </select>
176
183
  );
177
184
  };
178
-
179
- const scoreIndex = (scores: ScoreLabel[], score?: ScoreLabel) =>
180
- scores.findIndex((sc) => {
181
- return score && sc.name === score.name && sc.scorer === score.scorer;
182
- });
183
-
184
- const scorerIndex = (scores: ScoreLabel[], score?: ScoreLabel) =>
185
- scores.findIndex((sc) => {
186
- return score && sc.scorer === score.scorer;
187
- });
@@ -1,19 +1,12 @@
1
1
  import { compileExpression } from "filtrex";
2
2
  import { Scores1 } from "../../../@types/log";
3
- import { ScoreLabel } from "../../../app/types";
3
+ import { FilterError, ScoreLabel } from "../../../app/types";
4
4
  import { SampleSummary } from "../../../client/api/types";
5
5
  import { kScoreTypeBoolean } from "../../../constants";
6
6
  import { inputString } from "../../../utils/format";
7
7
  import { EvalDescriptor, ScoreDescriptor } from "../descriptor/types";
8
8
 
9
- export interface FilterError {
10
- from: number;
11
- to: number;
12
- message: string;
13
- severity: "warning" | "error";
14
- }
15
-
16
- export interface ScoreFilterItem {
9
+ export interface SampleFilterItem {
17
10
  shortName?: string;
18
11
  qualifiedName?: string;
19
12
  canonicalName: string;
@@ -120,10 +113,10 @@ const sampleVariables = (sample: SampleSummary): Record<string, unknown> => {
120
113
  * Child metrics are accessed using dot notation (e.g. `scorer_name.score_name`) or
121
114
  * directly by name when it is unique.
122
115
  */
123
- export const scoreFilterItems = (
116
+ export const sampleFilterItems = (
124
117
  evalDescriptor: EvalDescriptor,
125
- ): ScoreFilterItem[] => {
126
- const items: ScoreFilterItem[] = [];
118
+ ): SampleFilterItem[] => {
119
+ const items: SampleFilterItem[] = [];
127
120
  const bannedShortNames = bannedShortScoreNames(evalDescriptor.scores);
128
121
  const valueToString = (value: unknown) =>
129
122
  typeof value === "string" ? `"${value}"` : String(value);
@@ -296,8 +289,13 @@ export const filterSamples = (
296
289
  evalDescriptor: EvalDescriptor,
297
290
  samples: SampleSummary[],
298
291
  filterValue: string,
299
- ): { result: SampleSummary[]; error: FilterError | undefined } => {
300
- var error = undefined;
292
+ ): {
293
+ result: SampleSummary[];
294
+ error: FilterError | undefined;
295
+ allErrors: boolean;
296
+ } => {
297
+ let error = undefined;
298
+ let errorCount = 0;
301
299
  const result = samples.filter((sample) => {
302
300
  if (filterValue) {
303
301
  const { matches, error: sampleError } = filterExpression(
@@ -306,10 +304,13 @@ export const filterSamples = (
306
304
  filterValue,
307
305
  );
308
306
  error ||= sampleError;
307
+ if (sampleError) {
308
+ errorCount++;
309
+ }
309
310
  return matches;
310
311
  } else {
311
312
  return true;
312
313
  }
313
314
  });
314
- return { result, error };
315
+ return { result, error, allErrors: errorCount === samples.length };
315
316
  };
@@ -14,28 +14,18 @@ import {
14
14
  import { tags } from "@lezer/highlight";
15
15
  import clsx from "clsx";
16
16
  import { EditorView, minimalSetup } from "codemirror";
17
- import { FC, useEffect, useMemo, useRef, useState } from "react";
17
+ import { FC, useCallback, useEffect, useMemo, useRef } from "react";
18
18
 
19
- import { ScoreFilter } from "../../../../app/types";
20
- import { SampleSummary } from "../../../../client/api/types";
21
19
  import { useEvalDescriptor } from "../../../../state/hooks";
22
- import { EvalDescriptor } from "../../descriptor/types";
23
- import { FilterError, filterSamples, scoreFilterItems } from "../filters";
20
+ import { useStore } from "../../../../state/store";
21
+ import { debounce } from "../../../../utils/sync";
22
+ import { FilterError } from "../../../types";
23
+ import { sampleFilterItems } from "../filters";
24
24
  import { getCompletions } from "./completions";
25
25
  import styles from "./SampleFilter.module.css";
26
26
  import { language } from "./tokenize";
27
27
 
28
- // Types
29
- interface FilteringResult {
30
- numSamples: number;
31
- error?: FilterError;
32
- }
33
-
34
- interface SampleFilterProps {
35
- samples: SampleSummary[];
36
- scoreFilter: ScoreFilter;
37
- setScoreFilter: (filter: ScoreFilter) => void;
38
- }
28
+ interface SampleFilterProps {}
39
29
 
40
30
  // Constants
41
31
  const FILTER_TOOLTIP = `
@@ -105,20 +95,6 @@ const editorTheme = EditorView.theme({
105
95
  },
106
96
  });
107
97
 
108
- // Helper functions
109
- const getFilteringResult = (
110
- evalDescriptor: EvalDescriptor,
111
- sampleSummaries: SampleSummary[],
112
- filterValue: string,
113
- ): FilteringResult => {
114
- const { result, error } = filterSamples(
115
- evalDescriptor,
116
- sampleSummaries,
117
- filterValue,
118
- );
119
- return { numSamples: result.length, error };
120
- };
121
-
122
98
  const ensureOneLine = (tr: Transaction): TransactionSpec => {
123
99
  const newDoc = tr.newDoc.toString();
124
100
  if (!newDoc.includes("\n")) return tr;
@@ -154,11 +130,7 @@ const getLints = (
154
130
  };
155
131
 
156
132
  // Main component
157
- export const SampleFilter: FC<SampleFilterProps> = ({
158
- samples,
159
- scoreFilter,
160
- setScoreFilter,
161
- }) => {
133
+ export const SampleFilter: FC<SampleFilterProps> = () => {
162
134
  const editorRef = useRef<HTMLDivElement>(null);
163
135
  const editorViewRef = useRef<EditorView>(null);
164
136
  const linterCompartment = useRef<Compartment>(new Compartment());
@@ -167,43 +139,51 @@ export const SampleFilter: FC<SampleFilterProps> = ({
167
139
  const evalDescriptor = useEvalDescriptor();
168
140
 
169
141
  const filterItems = useMemo(
170
- () => (evalDescriptor ? scoreFilterItems(evalDescriptor) : []),
142
+ () => (evalDescriptor ? sampleFilterItems(evalDescriptor) : []),
171
143
  [evalDescriptor],
172
144
  );
173
145
 
174
- const [filteringResultInstant, setFilteringResultInstant] =
175
- useState<FilteringResult | null>(null);
146
+ const filter = useStore((state) => state.log.filter);
147
+ const filterError = useStore((state) => state.log.filterError);
148
+ const setFilter = useStore((state) => state.logActions.setFilter);
176
149
 
177
- const handleFocus = (event: FocusEvent, view: EditorView) => {
150
+ const handleFocus = useCallback((event: FocusEvent, view: EditorView) => {
178
151
  if (event.isTrusted && view.state.doc.toString() === "") {
179
152
  setTimeout(() => startCompletion(view), 0);
180
153
  }
181
- };
154
+ }, []);
182
155
 
183
- const makeAutocompletion = () =>
184
- autocompletion({
185
- override: [(context) => getCompletions(context, filterItems)],
186
- activateOnCompletion: (c) => c.label.endsWith(" "),
187
- });
156
+ const makeAutocompletion = useCallback(
157
+ () =>
158
+ autocompletion({
159
+ override: [(context) => getCompletions(context, filterItems)],
160
+ activateOnCompletion: (c) => c.label.endsWith(" "),
161
+ }),
162
+ [],
163
+ );
188
164
 
189
- const makeLinter = () =>
190
- linter((view) => getLints(view, filteringResultInstant?.error));
165
+ const makeLinter = useCallback(
166
+ () => linter((view) => getLints(view, filterError)),
167
+ [filterError],
168
+ );
191
169
 
192
- const makeUpdateListener = () =>
193
- EditorView.updateListener.of((update) => {
194
- if (update.docChanged && evalDescriptor) {
195
- const newValue = update.state.doc.toString();
196
- const filteringResult = getFilteringResult(
197
- evalDescriptor,
198
- samples,
199
- newValue,
200
- );
201
- if (!filteringResult.error) {
202
- setScoreFilter({ value: newValue });
170
+ const debounceSetFilter = useCallback(
171
+ debounce((value: string) => {
172
+ setFilter(value);
173
+ }, 200),
174
+ [setFilter],
175
+ );
176
+
177
+ const makeUpdateListener = useCallback(
178
+ () =>
179
+ EditorView.updateListener.of((update) => {
180
+ if (update.docChanged && evalDescriptor) {
181
+ const newValue = update.state.doc.toString();
182
+ debounceSetFilter(newValue);
203
183
  }
204
- setFilteringResultInstant(filteringResult);
205
- }
206
- });
184
+ }),
185
+ [setFilter],
186
+ );
207
187
 
208
188
  // Initialize editor
209
189
  useEffect(() => {
@@ -212,7 +192,7 @@ export const SampleFilter: FC<SampleFilterProps> = ({
212
192
  editorViewRef.current = new EditorView({
213
193
  parent: editorRef.current ?? undefined,
214
194
  state: EditorState.create({
215
- doc: scoreFilter.value || "",
195
+ doc: filter || "",
216
196
  extensions: [
217
197
  minimalSetup,
218
198
  bracketMatching(),
@@ -236,21 +216,16 @@ export const SampleFilter: FC<SampleFilterProps> = ({
236
216
  if (!editorViewRef.current) return;
237
217
 
238
218
  const currentValue = editorViewRef.current.state.doc.toString();
239
- if (scoreFilter.value === currentValue) return;
219
+ if (filter === currentValue) return;
240
220
 
241
- if (evalDescriptor) {
242
- setFilteringResultInstant(
243
- getFilteringResult(evalDescriptor, samples, scoreFilter.value || ""),
244
- );
245
- }
246
221
  editorViewRef.current.dispatch({
247
222
  changes: {
248
223
  from: 0,
249
224
  to: currentValue.length,
250
- insert: scoreFilter.value || "",
225
+ insert: filter || "",
251
226
  },
252
227
  });
253
- }, [evalDescriptor, scoreFilter.value]);
228
+ }, [filter]);
254
229
 
255
230
  // Update compartments when dependencies change
256
231
  useEffect(() => {
@@ -271,7 +246,7 @@ export const SampleFilter: FC<SampleFilterProps> = ({
271
246
  editorViewRef.current?.dispatch({
272
247
  effects: linterCompartment.current.reconfigure(makeLinter()),
273
248
  });
274
- }, [filteringResultInstant?.error]);
249
+ }, [filterError]);
275
250
 
276
251
  return (
277
252
  <div style={{ display: "flex" }}>
@@ -288,10 +263,7 @@ export const SampleFilter: FC<SampleFilterProps> = ({
288
263
  </span>
289
264
  <div
290
265
  ref={editorRef}
291
- className={clsx(
292
- filteringResultInstant?.error && "filter-pending",
293
- styles.input,
294
- )}
266
+ className={clsx(filterError && "filter-pending", styles.input)}
295
267
  />
296
268
  <span
297
269
  className={clsx("bi", "bi-question-circle", styles.help)}
@@ -12,7 +12,7 @@ import {
12
12
  kScoreTypeOther,
13
13
  kScoreTypePassFail,
14
14
  } from "../../../../constants";
15
- import { ScoreFilterItem } from "../filters";
15
+ import { SampleFilterItem } from "../filters";
16
16
  import {
17
17
  KEYWORDS,
18
18
  MATH_FUNCTIONS,
@@ -29,7 +29,7 @@ interface CompletionOptions {
29
29
  }
30
30
 
31
31
  interface CanonicalNameCompletionProps {
32
- autoSpaceIf?: (item: ScoreFilterItem) => boolean;
32
+ autoSpaceIf?: (item: SampleFilterItem) => boolean;
33
33
  }
34
34
 
35
35
  const isLiteral = (token: Token): boolean =>
@@ -98,7 +98,7 @@ const makeLiteralCompletion = (k: string): Completion => ({
98
98
  });
99
99
 
100
100
  const makeCanonicalNameCompletion = (
101
- item: ScoreFilterItem,
101
+ item: SampleFilterItem,
102
102
  { autoSpaceIf = () => false }: CanonicalNameCompletionProps = {},
103
103
  ): Completion => ({
104
104
  label: item.canonicalName + (autoSpaceIf(item) ? " " : ""),
@@ -107,7 +107,7 @@ const makeCanonicalNameCompletion = (
107
107
  boost: 30,
108
108
  });
109
109
 
110
- const makeMemberAccessCompletion = (item: ScoreFilterItem): Completion => ({
110
+ const makeMemberAccessCompletion = (item: SampleFilterItem): Completion => ({
111
111
  label: item.qualifiedName?.split(".")[1] || "",
112
112
  type: "variable",
113
113
  info: item.tooltip,
@@ -115,9 +115,9 @@ const makeMemberAccessCompletion = (item: ScoreFilterItem): Completion => ({
115
115
  });
116
116
 
117
117
  const getMemberScoreItems = (
118
- filterItems: ScoreFilterItem[],
118
+ filterItems: SampleFilterItem[],
119
119
  scorer: string,
120
- ): ScoreFilterItem[] =>
120
+ ): SampleFilterItem[] =>
121
121
  filterItems.filter((item) => item?.qualifiedName?.startsWith(`${scorer}.`));
122
122
 
123
123
  /**
@@ -136,7 +136,7 @@ const getMemberScoreItems = (
136
136
  */
137
137
  export function getCompletions(
138
138
  context: CompletionContext,
139
- filterItems: ScoreFilterItem[],
139
+ filterItems: SampleFilterItem[],
140
140
  ): CompletionResult | null {
141
141
  const keywordCompletionItems = KEYWORDS.map(makeKeywordCompletion);
142
142
  const mathFunctionCompletionItems = MATH_FUNCTIONS.map(
@@ -177,7 +177,7 @@ export function getCompletions(
177
177
  const completionStart = currentToken ? currentToken.from : context.pos;
178
178
  const completingAtEnd = context.pos === doc.length;
179
179
 
180
- const findFilterItem = (endIndex: number): ScoreFilterItem | undefined => {
180
+ const findFilterItem = (endIndex: number): SampleFilterItem | undefined => {
181
181
  if (prevToken(endIndex)?.type !== "variable") return undefined;
182
182
 
183
183
  let name = prevToken(endIndex).text;
@@ -267,7 +267,7 @@ export function getCompletions(
267
267
 
268
268
  const variableCompletions = () => makeCompletions(variableCompletionItems);
269
269
 
270
- const memberAccessCompletions = (items: ScoreFilterItem[]) =>
270
+ const memberAccessCompletions = (items: SampleFilterItem[]) =>
271
271
  makeCompletions(items.map(makeMemberAccessCompletion), {
272
272
  autocompleteInTheMiddle: true,
273
273
  includeDefault: false,
@@ -67,7 +67,9 @@ export interface LogState {
67
67
  selectedLogSummary?: EvalSummary;
68
68
  pendingSampleSummaries?: PendingSamples;
69
69
 
70
- filter: ScoreFilter;
70
+ filter: string;
71
+ filterError?: FilterError;
72
+
71
73
  epoch: string;
72
74
  sort: string;
73
75
  score?: ScoreLabel;
@@ -122,8 +124,16 @@ export interface ScoreLabel {
122
124
  scorer: string;
123
125
  }
124
126
 
125
- export interface ScoreFilter {
127
+ export interface SampleFilter {
126
128
  value?: string;
129
+ error?: FilterError;
130
+ }
131
+
132
+ export interface FilterError {
133
+ from: number;
134
+ to: number;
135
+ message: string;
136
+ severity: "warning" | "error";
127
137
  }
128
138
 
129
139
  export type SampleMode = "none" | "single" | "many";
@@ -18,7 +18,7 @@
18
18
  display: flex;
19
19
  margin-top: 0;
20
20
  position: relative;
21
- height: 8px;
21
+ height: 18px;
22
22
  }
23
23
 
24
24
  .moreToggle.bordered {
@@ -27,19 +27,19 @@ export const ExpandablePanel: FC<ExpandablePanelProps> = memo(
27
27
  const [collapsed, setCollapsed] = useCollapsedState(id, collapse);
28
28
 
29
29
  const [showToggle, setShowToggle] = useState(false);
30
- const lineHeightRef = useRef<number>(0);
30
+ const baseFontSizeRef = useRef<number>(0);
31
31
 
32
32
  const checkOverflow = useCallback(
33
33
  (entry: ResizeObserverEntry) => {
34
34
  const element = entry.target as HTMLDivElement;
35
35
 
36
36
  // Calculate line height if we haven't yet
37
- if (!lineHeightRef.current) {
37
+ if (baseFontSizeRef.current === 0) {
38
38
  const computedStyle = window.getComputedStyle(element);
39
- lineHeightRef.current = parseInt(computedStyle.lineHeight) || 16; // fallback to 16px if can't get line height
39
+ const rootFontSize = parseFloat(computedStyle.fontSize);
40
+ baseFontSizeRef.current = rootFontSize;
40
41
  }
41
-
42
- const maxCollapsedHeight = lines * lineHeightRef.current;
42
+ const maxCollapsedHeight = baseFontSizeRef.current * lines;
43
43
  const contentHeight = element.scrollHeight;
44
44
 
45
45
  setShowToggle(contentHeight > maxCollapsedHeight);
@@ -132,6 +132,11 @@ export const useFilteredSamples = () => {
132
132
  const evalDescriptor = useEvalDescriptor();
133
133
  const sampleSummaries = useSampleSummaries();
134
134
  const filter = useStore((state) => state.log.filter);
135
+ const setFilterError = useStore((state) => state.logActions.setFilterError);
136
+ const clearFilterError = useStore(
137
+ (state) => state.logActions.clearFilterError,
138
+ );
139
+
135
140
  const epoch = useStore((state) => state.log.epoch);
136
141
  const sort = useStore((state) => state.log.sort);
137
142
  const samplesDescriptor = useSampleDescriptor();
@@ -139,10 +144,19 @@ export const useFilteredSamples = () => {
139
144
 
140
145
  return useMemo(() => {
141
146
  // Apply filters
147
+ const { result, error, allErrors } =
148
+ evalDescriptor && filter
149
+ ? filterSamples(evalDescriptor, sampleSummaries, filter)
150
+ : { result: sampleSummaries, error: undefined, allErrors: false };
151
+
152
+ if (error && allErrors) {
153
+ setFilterError(error);
154
+ } else {
155
+ clearFilterError();
156
+ }
157
+
142
158
  const prefiltered =
143
- evalDescriptor && filter.value
144
- ? filterSamples(evalDescriptor, sampleSummaries, filter.value).result
145
- : sampleSummaries;
159
+ error === undefined || !allErrors ? result : sampleSummaries;
146
160
 
147
161
  // Filter epochs
148
162
  const filtered =
@@ -160,6 +174,8 @@ export const useFilteredSamples = () => {
160
174
  evalDescriptor,
161
175
  sampleSummaries,
162
176
  filter,
177
+ setFilterError,
178
+ clearFilterError,
163
179
  epoch,
164
180
  sort,
165
181
  samplesDescriptor,