inspect-ai 0.3.108__py3-none-any.whl → 0.3.109__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inspect_ai/_eval/task/log.py +1 -1
- inspect_ai/_eval/task/run.py +1 -1
- inspect_ai/_util/dateutil.py +40 -0
- inspect_ai/_view/schema.py +11 -0
- inspect_ai/_view/www/CLAUDE.md +1 -1
- inspect_ai/_view/www/dist/assets/index.css +2068 -1796
- inspect_ai/_view/www/dist/assets/index.js +7951 -3643
- inspect_ai/_view/www/package.json +3 -2
- inspect_ai/_view/www/src/@types/log.d.ts +5 -5
- inspect_ai/_view/www/src/app/App.css +71 -4
- inspect_ai/_view/www/src/app/App.tsx +7 -0
- inspect_ai/_view/www/src/app/appearance/icons.ts +18 -2
- inspect_ai/_view/www/src/app/content/RenderedContent.tsx +7 -9
- inspect_ai/_view/www/src/app/log-list/LogItem.ts +18 -0
- inspect_ai/_view/www/src/app/log-list/LogListFooter.module.css +55 -0
- inspect_ai/_view/www/src/app/log-list/LogListFooter.tsx +67 -0
- inspect_ai/_view/www/src/app/log-list/LogPager.module.css +29 -0
- inspect_ai/_view/www/src/app/log-list/LogPager.tsx +134 -0
- inspect_ai/_view/www/src/app/log-list/LogsFilterInput.module.css +5 -0
- inspect_ai/_view/www/src/app/log-list/LogsFilterInput.tsx +31 -0
- inspect_ai/_view/www/src/app/log-list/LogsPanel.module.css +12 -0
- inspect_ai/_view/www/src/app/log-list/LogsPanel.tsx +178 -0
- inspect_ai/_view/www/src/app/log-list/grid/LogListGrid.module.css +115 -0
- inspect_ai/_view/www/src/app/log-list/grid/LogListGrid.tsx +304 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/CompletedDate.module.css +6 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/CompletedDate.tsx +64 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/EmptyCell.module.css +3 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/EmptyCell.tsx +7 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/FileName.module.css +20 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/FileName.tsx +52 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Icon.module.css +11 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Icon.tsx +35 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Model.module.css +6 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Model.tsx +34 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Score.module.css +6 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Score.tsx +61 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Status.module.css +15 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Status.tsx +95 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Task.module.css +20 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/Task.tsx +50 -0
- inspect_ai/_view/www/src/app/log-list/grid/columns/columns.ts +27 -0
- inspect_ai/_view/www/src/app/log-view/LogView.tsx +2 -5
- inspect_ai/_view/www/src/app/log-view/LogViewContainer.tsx +4 -30
- inspect_ai/_view/www/src/app/log-view/LogViewLayout.tsx +5 -30
- inspect_ai/_view/www/src/app/log-view/tabs/TaskTab.tsx +4 -7
- inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/PrimaryBar.module.css +2 -0
- inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/PrimaryBar.tsx +3 -31
- inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ResultsPanel.tsx +7 -57
- inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ScoreGrid.tsx +2 -2
- inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/SecondaryBar.tsx +7 -1
- inspect_ai/_view/www/src/app/log-view/{navbar/Navbar.tsx → title-view/TitleView.tsx} +3 -6
- inspect_ai/_view/www/src/app/navbar/Navbar.module.css +57 -0
- inspect_ai/_view/www/src/app/navbar/Navbar.tsx +117 -0
- inspect_ai/_view/www/src/app/navbar/useBreadcrumbTruncation.ts +128 -0
- inspect_ai/_view/www/src/app/plan/DatasetDetailView.tsx +3 -3
- inspect_ai/_view/www/src/app/plan/DetailStep.tsx +6 -6
- inspect_ai/_view/www/src/app/plan/PlanDetailView.module.css +1 -0
- inspect_ai/_view/www/src/app/plan/ScorerDetailView.tsx +1 -1
- inspect_ai/_view/www/src/app/routing/AppRouter.tsx +28 -4
- inspect_ai/_view/www/src/app/routing/RouteDispatcher.tsx +28 -0
- inspect_ai/_view/www/src/app/routing/sampleNavigation.ts +76 -7
- inspect_ai/_view/www/src/app/routing/url.ts +193 -20
- inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +3 -17
- inspect_ai/_view/www/src/app/samples/descriptor/score/ScoreDescriptor.tsx +1 -1
- inspect_ai/_view/www/src/app/samples/transcript/SubtaskEventView.tsx +2 -2
- inspect_ai/_view/www/src/app/samples/transcript/TranscriptPanel.tsx +2 -2
- inspect_ai/_view/www/src/app/samples/transcript/outline/tree-visitors.ts +5 -0
- inspect_ai/_view/www/src/app/samples/transcript/transform/treeify.ts +26 -10
- inspect_ai/_view/www/src/app/types.ts +21 -1
- inspect_ai/_view/www/src/client/api/api-http.ts +2 -1
- inspect_ai/_view/www/src/client/api/api-shared.ts +0 -32
- inspect_ai/_view/www/src/client/api/client-api.ts +1 -1
- inspect_ai/_view/www/src/client/remote/remoteLogFile.ts +38 -6
- inspect_ai/_view/www/src/components/TextInput.module.css +45 -0
- inspect_ai/_view/www/src/components/TextInput.tsx +52 -0
- inspect_ai/_view/www/src/constants.ts +18 -0
- inspect_ai/_view/www/src/img/inspect-16.svg +10 -0
- inspect_ai/_view/www/src/img/inspect-back.svg +5 -0
- inspect_ai/_view/www/src/img/inspect-file.svg +26 -0
- inspect_ai/_view/www/src/img/inspect-forward.svg +7 -0
- inspect_ai/_view/www/src/img/inspect-home.svg +18 -0
- inspect_ai/_view/www/src/scoring/metrics.ts +75 -0
- inspect_ai/_view/www/src/scoring/scores.ts +19 -0
- inspect_ai/_view/www/src/scoring/types.ts +11 -0
- inspect_ai/_view/www/src/state/appSlice.ts +27 -7
- inspect_ai/_view/www/src/state/clientEvents.ts +73 -0
- inspect_ai/_view/www/src/state/clientEventsService.ts +105 -0
- inspect_ai/_view/www/src/state/hooks.ts +118 -1
- inspect_ai/_view/www/src/state/log.ts +19 -0
- inspect_ai/_view/www/src/state/logPolling.ts +3 -1
- inspect_ai/_view/www/src/state/logSlice.ts +9 -0
- inspect_ai/_view/www/src/state/logsSlice.ts +157 -15
- inspect_ai/_view/www/src/state/samplePolling.ts +4 -2
- inspect_ai/_view/www/src/tests/utils/path.test.ts +3 -3
- inspect_ai/_view/www/src/utils/evallog.ts +31 -0
- inspect_ai/_view/www/src/utils/path.ts +28 -0
- inspect_ai/_view/www/src/utils/uri.ts +49 -0
- inspect_ai/_view/www/yarn.lock +54 -17
- inspect_ai/analysis/beta/_dataframe/util.py +106 -10
- inspect_ai/log/_recorders/buffer/database.py +55 -16
- inspect_ai/model/_model.py +1 -1
- inspect_ai/model/_providers/providers.py +2 -2
- inspect_ai/model/_providers/vertex.py +3 -0
- inspect_ai/tool/_mcp/_mcp.py +6 -1
- inspect_ai/tool/_mcp/sampling.py +8 -1
- inspect_ai/tool/_tools/_bash_session.py +3 -6
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +3 -8
- inspect_ai/util/_anyio.py +12 -3
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/METADATA +2 -2
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/RECORD +124 -94
- inspect_ai/_util/datetime.py +0 -10
- inspect_ai/_view/www/src/app/content/MetaDataView.module.css +0 -35
- inspect_ai/_view/www/src/app/content/MetaDataView.tsx +0 -101
- inspect_ai/_view/www/src/app/log-view/utils.ts +0 -34
- inspect_ai/_view/www/src/app/sidebar/EvalStatus.module.css +0 -15
- inspect_ai/_view/www/src/app/sidebar/EvalStatus.tsx +0 -72
- inspect_ai/_view/www/src/app/sidebar/LogDirectoryTitleView.module.css +0 -16
- inspect_ai/_view/www/src/app/sidebar/LogDirectoryTitleView.tsx +0 -70
- inspect_ai/_view/www/src/app/sidebar/Sidebar.module.css +0 -77
- inspect_ai/_view/www/src/app/sidebar/Sidebar.tsx +0 -119
- inspect_ai/_view/www/src/app/sidebar/SidebarLogEntry.module.css +0 -29
- inspect_ai/_view/www/src/app/sidebar/SidebarLogEntry.tsx +0 -96
- inspect_ai/_view/www/src/app/sidebar/SidebarScoreView.module.css +0 -23
- inspect_ai/_view/www/src/app/sidebar/SidebarScoreView.tsx +0 -44
- inspect_ai/_view/www/src/app/sidebar/SidebarScoresView.module.css +0 -35
- inspect_ai/_view/www/src/app/sidebar/SidebarScoresView.tsx +0 -63
- inspect_ai/_view/www/src/state/logsPolling.ts +0 -118
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ModelRolesView.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ModelRolesView.tsx +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ResultsPanel.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/RunningStatusPanel.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/RunningStatusPanel.tsx +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/ScoreGrid.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/SecondaryBar.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/StatusPanel.module.css +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar → title-view}/StatusPanel.tsx +0 -0
- /inspect_ai/_view/www/src/app/log-view/{navbar/Navbar.module.css → title-view/TitleView.module.css} +0 -0
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/WHEEL +0 -0
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/licenses/LICENSE +0 -0
- {inspect_ai-0.3.108.dist-info → inspect_ai-0.3.109.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ import {
|
|
11
11
|
bySample,
|
12
12
|
sortSamples,
|
13
13
|
} from "../app/samples/sample-tools/SortFilter";
|
14
|
-
import { SampleSummary } from "../client/api/types";
|
14
|
+
import { LogFile, SampleSummary } from "../client/api/types";
|
15
15
|
import { kEpochAscVal, kSampleAscVal, kScoreAscVal } from "../constants";
|
16
16
|
import { createLogger } from "../utils/logger";
|
17
17
|
import { getAvailableScorers, getDefaultScorer } from "./scoring";
|
@@ -556,3 +556,120 @@ export const useSamplePopover = (id: string) => {
|
|
556
556
|
isShowing,
|
557
557
|
};
|
558
558
|
};
|
559
|
+
|
560
|
+
export const useLogs = () => {
|
561
|
+
// Loading logs
|
562
|
+
const load = useStore((state) => state.logsActions.loadLogs);
|
563
|
+
const setLogs = useStore((state) => state.logsActions.setLogs);
|
564
|
+
const setStatus = useStore((state) => state.appActions.setStatus);
|
565
|
+
|
566
|
+
const loadLogs = useCallback(async () => {
|
567
|
+
const exec = async () => {
|
568
|
+
setStatus({ loading: true, error: undefined });
|
569
|
+
const logs = await load();
|
570
|
+
setLogs(logs);
|
571
|
+
setStatus({ loading: false, error: undefined });
|
572
|
+
};
|
573
|
+
exec().catch((e) => {
|
574
|
+
log.error("Error loading logs", e);
|
575
|
+
setStatus({ loading: false, error: e });
|
576
|
+
});
|
577
|
+
}, [load, setLogs, setStatus]);
|
578
|
+
|
579
|
+
// Loading headers
|
580
|
+
const storeLoadHeaders = useStore((state) => state.logsActions.loadHeaders);
|
581
|
+
const existingHeaders = useStore((state) => state.logs.logHeaders);
|
582
|
+
const allLogFiles = useStore((state) => state.logs.logs.files);
|
583
|
+
|
584
|
+
const loadHeaders = useCallback(
|
585
|
+
async (logFiles: LogFile[] = allLogFiles) => {
|
586
|
+
await storeLoadHeaders(logFiles);
|
587
|
+
},
|
588
|
+
[storeLoadHeaders, allLogFiles],
|
589
|
+
);
|
590
|
+
|
591
|
+
const loadAllHeaders = useCallback(async () => {
|
592
|
+
const logsToLoad = allLogFiles.filter((logFile) => {
|
593
|
+
const existingHeader = existingHeaders[logFile.name];
|
594
|
+
return !existingHeader || existingHeader.status === "started";
|
595
|
+
});
|
596
|
+
|
597
|
+
if (logsToLoad.length > 0) {
|
598
|
+
await storeLoadHeaders(logsToLoad);
|
599
|
+
}
|
600
|
+
}, [storeLoadHeaders, allLogFiles, existingHeaders]);
|
601
|
+
|
602
|
+
return { loadLogs, loadHeaders, loadAllHeaders };
|
603
|
+
};
|
604
|
+
|
605
|
+
export const usePagination = (name: string, defaultPageSize: number) => {
|
606
|
+
const page = useStore((state) => state.app.pagination[name]?.page || 0);
|
607
|
+
const itemsPerPage = useStore(
|
608
|
+
(state) => state.app.pagination[name]?.pageSize || defaultPageSize,
|
609
|
+
);
|
610
|
+
const setPagination = useStore((state) => state.appActions.setPagination);
|
611
|
+
|
612
|
+
const setPage = useCallback(
|
613
|
+
(newPage: number) => {
|
614
|
+
setPagination(name, { page: newPage, pageSize: itemsPerPage });
|
615
|
+
},
|
616
|
+
[name, setPagination, itemsPerPage],
|
617
|
+
);
|
618
|
+
|
619
|
+
const setPageSize = useCallback(
|
620
|
+
(newPageSize: number) => {
|
621
|
+
setPagination(name, { page, pageSize: newPageSize });
|
622
|
+
},
|
623
|
+
[name, setPagination, page],
|
624
|
+
);
|
625
|
+
|
626
|
+
return {
|
627
|
+
page,
|
628
|
+
itemsPerPage,
|
629
|
+
setPage,
|
630
|
+
setPageSize,
|
631
|
+
};
|
632
|
+
};
|
633
|
+
|
634
|
+
export const useLogsListing = () => {
|
635
|
+
const sorting = useStore((state) => state.logs.listing.sorting);
|
636
|
+
const setSorting = useStore((state) => state.logsActions.setSorting);
|
637
|
+
|
638
|
+
const filtering = useStore((state) => state.logs.listing.filtering);
|
639
|
+
const setFiltering = useStore((state) => state.logsActions.setFiltering);
|
640
|
+
|
641
|
+
const globalFilter = useStore((state) => state.logs.listing.globalFilter);
|
642
|
+
const setGlobalFilter = useStore(
|
643
|
+
(state) => state.logsActions.setGlobalFilter,
|
644
|
+
);
|
645
|
+
|
646
|
+
const columnResizeMode = useStore(
|
647
|
+
(state) => state.logs.listing.columnResizeMode,
|
648
|
+
);
|
649
|
+
const setColumnResizeMode = useStore(
|
650
|
+
(state) => state.logsActions.setColumnResizeMode,
|
651
|
+
);
|
652
|
+
|
653
|
+
const columnSizes = useStore((state) => state.logs.listing.columnSizes);
|
654
|
+
const setColumnSize = useStore((state) => state.logsActions.setColumnSize);
|
655
|
+
|
656
|
+
const filteredCount = useStore((state) => state.logs.listing.filteredCount);
|
657
|
+
const setFilteredCount = useStore(
|
658
|
+
(state) => state.logsActions.setFilteredCount,
|
659
|
+
);
|
660
|
+
|
661
|
+
return {
|
662
|
+
sorting,
|
663
|
+
setSorting,
|
664
|
+
filtering,
|
665
|
+
setFiltering,
|
666
|
+
globalFilter,
|
667
|
+
setGlobalFilter,
|
668
|
+
columnResizeMode,
|
669
|
+
setColumnResizeMode,
|
670
|
+
columnSizes,
|
671
|
+
setColumnSize,
|
672
|
+
filteredCount,
|
673
|
+
setFilteredCount,
|
674
|
+
};
|
675
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { useCallback } from "react";
|
2
|
+
import { useStore } from "./store";
|
3
|
+
|
4
|
+
export const useUnloadLog = () => {
|
5
|
+
const clearSelectedLogSummary = useStore(
|
6
|
+
(state) => state.logActions.clearSelectedLogSummary,
|
7
|
+
);
|
8
|
+
const setSelectedLogIndex = useStore(
|
9
|
+
(state) => state.logsActions.setSelectedLogIndex,
|
10
|
+
);
|
11
|
+
const clearLog = useStore((state) => state.logActions.clearLog);
|
12
|
+
|
13
|
+
const unloadLog = useCallback(() => {
|
14
|
+
clearSelectedLogSummary();
|
15
|
+
setSelectedLogIndex(-1);
|
16
|
+
clearLog();
|
17
|
+
}, [clearLog, clearSelectedLogSummary, setSelectedLogIndex]);
|
18
|
+
return { unloadLog };
|
19
|
+
};
|
@@ -184,7 +184,9 @@ export function createLogPolling(
|
|
184
184
|
// Method to call when component unmounts
|
185
185
|
const cleanup = () => {
|
186
186
|
log.debug(`Cleanup`);
|
187
|
-
abortController
|
187
|
+
if (abortController) {
|
188
|
+
abortController.abort();
|
189
|
+
}
|
188
190
|
stopPolling();
|
189
191
|
};
|
190
192
|
|
@@ -54,6 +54,9 @@ export interface LogSlice {
|
|
54
54
|
|
55
55
|
// Poll the currently selected log
|
56
56
|
pollLog: () => Promise<void>;
|
57
|
+
|
58
|
+
// Clear the currently loaded log
|
59
|
+
clearLog: () => void;
|
57
60
|
};
|
58
61
|
}
|
59
62
|
|
@@ -197,6 +200,12 @@ export const createLogSlice = (
|
|
197
200
|
}
|
198
201
|
},
|
199
202
|
|
203
|
+
clearLog: () => {
|
204
|
+
set((state) => {
|
205
|
+
state.log.loadedLog = undefined;
|
206
|
+
});
|
207
|
+
},
|
208
|
+
|
200
209
|
pollLog: async () => {
|
201
210
|
const currentLog = get().log.loadedLog;
|
202
211
|
if (currentLog) {
|
@@ -1,7 +1,12 @@
|
|
1
|
+
import {
|
2
|
+
ColumnFiltersState,
|
3
|
+
ColumnResizeMode,
|
4
|
+
SortingState,
|
5
|
+
} from "@tanstack/react-table";
|
6
|
+
import { EvalLog } from "../@types/log";
|
1
7
|
import { LogsState } from "../app/types";
|
2
|
-
import { EvalLogHeader, LogFiles } from "../client/api/types";
|
8
|
+
import { EvalLogHeader, LogFile, LogFiles } from "../client/api/types";
|
3
9
|
import { createLogger } from "../utils/logger";
|
4
|
-
import { createLogsPolling } from "./logsPolling";
|
5
10
|
import { StoreState } from "./store";
|
6
11
|
|
7
12
|
const log = createLogger("Log Slice");
|
@@ -17,6 +22,7 @@ export interface LogsSlice {
|
|
17
22
|
// Update State
|
18
23
|
setLogs: (logs: LogFiles) => void;
|
19
24
|
setLogHeaders: (headers: Record<string, EvalLogHeader>) => void;
|
25
|
+
loadHeaders: (logs: LogFile[]) => Promise<EvalLog[]>;
|
20
26
|
setHeadersLoading: (loading: boolean) => void;
|
21
27
|
setSelectedLogIndex: (index: number) => void;
|
22
28
|
setSelectedLogFile: (logUrl: string) => void;
|
@@ -26,6 +32,15 @@ export interface LogsSlice {
|
|
26
32
|
refreshLogs: () => Promise<void>;
|
27
33
|
selectLogFile: (logUrl: string) => Promise<void>;
|
28
34
|
loadLogs: () => Promise<LogFiles>;
|
35
|
+
|
36
|
+
setSorting: (sorting: SortingState) => void;
|
37
|
+
setFiltering: (filtering: ColumnFiltersState) => void;
|
38
|
+
setGlobalFilter: (globalFilter: string) => void;
|
39
|
+
setColumnResizeMode: (mode: ColumnResizeMode) => void;
|
40
|
+
setColumnSize: (columnId: string, size: number) => void;
|
41
|
+
setFilteredCount: (count: number) => void;
|
42
|
+
setWatchedLogs: (logs: LogFile[]) => void;
|
43
|
+
clearWatchedLogs: () => void;
|
29
44
|
};
|
30
45
|
}
|
31
46
|
|
@@ -35,6 +50,9 @@ const initialState: LogsState = {
|
|
35
50
|
headersLoading: false,
|
36
51
|
selectedLogIndex: -1,
|
37
52
|
selectedLogFile: undefined as string | undefined,
|
53
|
+
listing: {},
|
54
|
+
loadingFiles: new Set<string>(),
|
55
|
+
pendingRequests: new Map<string, Promise<EvalLogHeader | null>>(),
|
38
56
|
};
|
39
57
|
|
40
58
|
export const createLogsSlice = (
|
@@ -42,8 +60,6 @@ export const createLogsSlice = (
|
|
42
60
|
get: () => StoreState,
|
43
61
|
_store: any,
|
44
62
|
): [LogsSlice, () => void] => {
|
45
|
-
const logsPolling = createLogsPolling(get, set);
|
46
|
-
|
47
63
|
const slice = {
|
48
64
|
// State
|
49
65
|
logs: initialState,
|
@@ -58,22 +74,105 @@ export const createLogsSlice = (
|
|
58
74
|
? logs.files[state.logs.selectedLogIndex]?.name
|
59
75
|
: undefined;
|
60
76
|
});
|
61
|
-
|
62
|
-
// If we have files in the logs, load the headers
|
63
|
-
if (logs.files.length > 0) {
|
64
|
-
// ensure state is updated first
|
65
|
-
setTimeout(() => {
|
66
|
-
const currentState = get();
|
67
|
-
if (!currentState.logs.headersLoading) {
|
68
|
-
logsPolling.startPolling(logs);
|
69
|
-
}
|
70
|
-
}, 100);
|
71
|
-
}
|
72
77
|
},
|
73
78
|
setLogHeaders: (headers: Record<string, EvalLogHeader>) =>
|
74
79
|
set((state) => {
|
75
80
|
state.logs.logHeaders = headers;
|
76
81
|
}),
|
82
|
+
loadHeaders: async (logs: LogFile[]) => {
|
83
|
+
const state = get();
|
84
|
+
const api = state.api;
|
85
|
+
if (!api) {
|
86
|
+
console.error("API not initialized in LogsStore");
|
87
|
+
return [];
|
88
|
+
}
|
89
|
+
|
90
|
+
// Filter out files that are already loaded or currently loading
|
91
|
+
// reload headers with "started" status as they may have changed
|
92
|
+
const filesToLoad = logs.filter((logFile) => {
|
93
|
+
const existing = state.logs.logHeaders[logFile.name];
|
94
|
+
const isLoading = state.logs.loadingFiles.has(logFile.name);
|
95
|
+
|
96
|
+
// Always load if no existing header
|
97
|
+
if (!existing) {
|
98
|
+
return !isLoading;
|
99
|
+
}
|
100
|
+
|
101
|
+
// Reload if header status is "started" or "error" (but not if already loading)
|
102
|
+
if (existing.status === "started" || existing.status === "error") {
|
103
|
+
return !isLoading;
|
104
|
+
}
|
105
|
+
|
106
|
+
// Skip if already loaded with final status
|
107
|
+
return false;
|
108
|
+
});
|
109
|
+
|
110
|
+
if (filesToLoad.length === 0) {
|
111
|
+
return [];
|
112
|
+
}
|
113
|
+
|
114
|
+
// Mark files as loading
|
115
|
+
set((state) => {
|
116
|
+
filesToLoad.forEach((logFile) => {
|
117
|
+
state.logs.loadingFiles.add(logFile.name);
|
118
|
+
});
|
119
|
+
});
|
120
|
+
|
121
|
+
// Set global loading state if this is the first batch
|
122
|
+
const wasLoading = get().logs.headersLoading;
|
123
|
+
if (!wasLoading) {
|
124
|
+
set((state) => {
|
125
|
+
state.logs.headersLoading = true;
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
try {
|
130
|
+
log.debug(`LOADING LOG HEADERS for ${filesToLoad.length} files`);
|
131
|
+
const headers = await api.get_log_headers(
|
132
|
+
filesToLoad.map((log) => log.name),
|
133
|
+
);
|
134
|
+
|
135
|
+
// Process results and update store
|
136
|
+
const headerMap: Record<string, EvalLogHeader> = {};
|
137
|
+
for (let i = 0; i < filesToLoad.length; i++) {
|
138
|
+
const logFile = filesToLoad[i];
|
139
|
+
const header = headers[i];
|
140
|
+
if (header) {
|
141
|
+
headerMap[logFile.name] = header as EvalLogHeader;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
// Update headers in store
|
146
|
+
set((state) => {
|
147
|
+
state.logs.logHeaders = { ...state.logs.logHeaders, ...headerMap };
|
148
|
+
// Remove from loading state
|
149
|
+
filesToLoad.forEach((logFile) => {
|
150
|
+
state.logs.loadingFiles.delete(logFile.name);
|
151
|
+
});
|
152
|
+
// Update global loading state if no more files are loading
|
153
|
+
if (state.logs.loadingFiles.size === 0) {
|
154
|
+
state.logs.headersLoading = false;
|
155
|
+
}
|
156
|
+
});
|
157
|
+
|
158
|
+
return headers;
|
159
|
+
} catch (error) {
|
160
|
+
log.error("Error loading log headers", error);
|
161
|
+
|
162
|
+
// Clear loading state on error
|
163
|
+
set((state) => {
|
164
|
+
filesToLoad.forEach((logFile) => {
|
165
|
+
state.logs.loadingFiles.delete(logFile.name);
|
166
|
+
});
|
167
|
+
if (state.logs.loadingFiles.size === 0) {
|
168
|
+
state.logs.headersLoading = false;
|
169
|
+
}
|
170
|
+
});
|
171
|
+
|
172
|
+
// Don't throw - just return empty array like the old implementation
|
173
|
+
return [];
|
174
|
+
}
|
175
|
+
},
|
77
176
|
setHeadersLoading: (loading: boolean) =>
|
78
177
|
set((state) => {
|
79
178
|
state.logs.headersLoading = loading;
|
@@ -167,6 +266,49 @@ export const createLogsSlice = (
|
|
167
266
|
);
|
168
267
|
}
|
169
268
|
},
|
269
|
+
setSorting: (sorting: SortingState) => {
|
270
|
+
set((state) => {
|
271
|
+
state.logs.listing.sorting = sorting;
|
272
|
+
});
|
273
|
+
},
|
274
|
+
setFiltering: (filtering: ColumnFiltersState) => {
|
275
|
+
set((state) => {
|
276
|
+
state.logs.listing.filtering = filtering;
|
277
|
+
});
|
278
|
+
},
|
279
|
+
setGlobalFilter: (globalFilter: string) => {
|
280
|
+
set((state) => {
|
281
|
+
state.logs.listing.globalFilter = globalFilter;
|
282
|
+
});
|
283
|
+
},
|
284
|
+
setColumnResizeMode: (mode: ColumnResizeMode) => {
|
285
|
+
set((state) => {
|
286
|
+
state.logs.listing.columnResizeMode = mode;
|
287
|
+
});
|
288
|
+
},
|
289
|
+
setColumnSize: (columnId: string, size: number) => {
|
290
|
+
set((state) => {
|
291
|
+
if (!state.logs.listing.columnSizes) {
|
292
|
+
state.logs.listing.columnSizes = {};
|
293
|
+
}
|
294
|
+
state.logs.listing.columnSizes[columnId] = size;
|
295
|
+
});
|
296
|
+
},
|
297
|
+
setFilteredCount: (count: number) => {
|
298
|
+
set((state) => {
|
299
|
+
state.logs.listing.filteredCount = count;
|
300
|
+
});
|
301
|
+
},
|
302
|
+
setWatchedLogs: (logs: LogFile[]) => {
|
303
|
+
set((state) => {
|
304
|
+
state.logs.listing.watchedLogs = logs;
|
305
|
+
});
|
306
|
+
},
|
307
|
+
clearWatchedLogs: () => {
|
308
|
+
set((state) => {
|
309
|
+
state.logs.listing.watchedLogs = undefined;
|
310
|
+
});
|
311
|
+
},
|
170
312
|
},
|
171
313
|
} as const;
|
172
314
|
|
@@ -241,8 +241,10 @@ export function createSamplePolling(
|
|
241
241
|
};
|
242
242
|
|
243
243
|
const cleanup = () => {
|
244
|
-
log.debug(`
|
245
|
-
abortController
|
244
|
+
log.debug(`Cleanup`);
|
245
|
+
if (abortController) {
|
246
|
+
abortController.abort();
|
247
|
+
}
|
246
248
|
stopPolling();
|
247
249
|
};
|
248
250
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { dirname, filename } from "../../utils/path";
|
2
2
|
|
3
3
|
describe("filename", () => {
|
4
4
|
test("extracts filename without extension from a path", () => {
|
@@ -30,7 +30,7 @@ describe("filename", () => {
|
|
30
30
|
describe("dirname", () => {
|
31
31
|
test("extracts directory name from a path", () => {
|
32
32
|
expect(dirname("/path/to/file.txt")).toBe("/path/to");
|
33
|
-
expect(dirname("/path/to/directory/")).toBe("/path/to
|
33
|
+
expect(dirname("/path/to/directory/")).toBe("/path/to");
|
34
34
|
expect(dirname("/path/to/file")).toBe("/path/to");
|
35
35
|
});
|
36
36
|
|
@@ -49,6 +49,6 @@ describe("dirname", () => {
|
|
49
49
|
});
|
50
50
|
|
51
51
|
test("handles paths with trailing slash", () => {
|
52
|
-
expect(dirname("/path/to/directory/")).toBe("/path/to
|
52
|
+
expect(dirname("/path/to/directory/")).toBe("/path/to");
|
53
53
|
});
|
54
54
|
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { filename } from "./path";
|
2
|
+
|
3
|
+
const kLogFilePattern =
|
4
|
+
/^(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}[-+]\d{2}-\d{2})_(.+)_([0-9A-Za-z]+)\.(eval|json)$/;
|
5
|
+
|
6
|
+
export interface ParsedLogFileName {
|
7
|
+
timestamp?: Date;
|
8
|
+
name: string;
|
9
|
+
taskId?: string;
|
10
|
+
extension: "eval" | "json";
|
11
|
+
}
|
12
|
+
|
13
|
+
export const parseLogFileName = (logFileName: string): ParsedLogFileName => {
|
14
|
+
const match = logFileName.match(kLogFilePattern);
|
15
|
+
if (!match) {
|
16
|
+
// read the extension
|
17
|
+
return {
|
18
|
+
timestamp: undefined,
|
19
|
+
name: filename(logFileName),
|
20
|
+
taskId: undefined,
|
21
|
+
extension: logFileName.endsWith(".eval") ? "eval" : "json",
|
22
|
+
};
|
23
|
+
}
|
24
|
+
|
25
|
+
return {
|
26
|
+
timestamp: new Date(Date.parse(match[1])),
|
27
|
+
name: match[2],
|
28
|
+
taskId: match[3],
|
29
|
+
extension: match[4] as "eval" | "json",
|
30
|
+
};
|
31
|
+
};
|
@@ -5,6 +5,7 @@ export const filename = (path: string): string => {
|
|
5
5
|
if (!path) {
|
6
6
|
return "";
|
7
7
|
}
|
8
|
+
path = path.endsWith("/") ? path.slice(0, -1) : path;
|
8
9
|
|
9
10
|
const pathparts = path.split("/");
|
10
11
|
const basename = pathparts.slice(-1)[0];
|
@@ -22,10 +23,21 @@ export const filename = (path: string): string => {
|
|
22
23
|
}
|
23
24
|
};
|
24
25
|
|
26
|
+
export const basename = (path: string): string => {
|
27
|
+
if (!path) {
|
28
|
+
return "";
|
29
|
+
}
|
30
|
+
path = path.endsWith("/") ? path.slice(0, -1) : path;
|
31
|
+
const pathparts = path.split("/");
|
32
|
+
return pathparts.slice(-1)[0];
|
33
|
+
};
|
34
|
+
|
25
35
|
/**
|
26
36
|
* Extracts the directory name from a given path.
|
27
37
|
*/
|
28
38
|
export const dirname = (path: string): string => {
|
39
|
+
path = path.endsWith("/") ? path.slice(0, -1) : path;
|
40
|
+
|
29
41
|
const pathparts = path.split("/");
|
30
42
|
|
31
43
|
// If the path ends with a filename (or no slashes), remove the last part (filename)
|
@@ -38,3 +50,19 @@ export const dirname = (path: string): string => {
|
|
38
50
|
// If no slashes, return empty string (no directory)
|
39
51
|
return "";
|
40
52
|
};
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Tests whether the given path is in the specified directory.
|
56
|
+
*/
|
57
|
+
export const isInDirectory = (path: string, directory: string): boolean => {
|
58
|
+
directory = directory.endsWith("/") ? directory.slice(0, -1) : directory;
|
59
|
+
|
60
|
+
return dirname(path) === directory;
|
61
|
+
};
|
62
|
+
|
63
|
+
export const ensureTrailingSlash = (path?: string): string => {
|
64
|
+
if (!path) {
|
65
|
+
return "";
|
66
|
+
}
|
67
|
+
return path.endsWith("/") ? path : path + "/";
|
68
|
+
};
|
@@ -30,3 +30,52 @@ export const directoryRelativeUrl = (file: string, dir?: string): string => {
|
|
30
30
|
// If path can't be made relative, return undefined
|
31
31
|
return encodeURIComponent(file);
|
32
32
|
};
|
33
|
+
|
34
|
+
export const join = (file: string, dir?: string): string => {
|
35
|
+
if (!dir) {
|
36
|
+
return file;
|
37
|
+
}
|
38
|
+
|
39
|
+
// Normalize paths to ensure consistent directory separators
|
40
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
41
|
+
const normalizedLogDir = dir.replace(/\\/g, "/");
|
42
|
+
|
43
|
+
// Ensure log_dir ends with a trailing slash
|
44
|
+
const dirWithSlash = normalizedLogDir.endsWith("/")
|
45
|
+
? normalizedLogDir
|
46
|
+
: normalizedLogDir + "/";
|
47
|
+
|
48
|
+
return dirWithSlash + normalizedFile;
|
49
|
+
};
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Encodes the path segments of a URL or relative path to ensure special characters
|
53
|
+
* (like `+`, spaces, etc.) are properly encoded without affecting legal characters like `/`.
|
54
|
+
*
|
55
|
+
* This function will encode file names and path portions of both absolute URLs and
|
56
|
+
* relative paths. It ensures that components of a full URL, such as the protocol and
|
57
|
+
* query parameters, remain intact, while only encoding the path.
|
58
|
+
*/
|
59
|
+
export function encodePathParts(url: string): string {
|
60
|
+
if (!url) return url; // Handle empty strings
|
61
|
+
|
62
|
+
try {
|
63
|
+
// Parse a full Uri
|
64
|
+
const fullUrl = new URL(url);
|
65
|
+
fullUrl.pathname = fullUrl.pathname
|
66
|
+
.split("/")
|
67
|
+
.map((segment) =>
|
68
|
+
segment ? encodeURIComponent(decodeURIComponent(segment)) : "",
|
69
|
+
)
|
70
|
+
.join("/");
|
71
|
+
return fullUrl.toString();
|
72
|
+
} catch {
|
73
|
+
// This is a relative path that isn't parseable as Uri
|
74
|
+
return url
|
75
|
+
.split("/")
|
76
|
+
.map((segment) =>
|
77
|
+
segment ? encodeURIComponent(decodeURIComponent(segment)) : "",
|
78
|
+
)
|
79
|
+
.join("/");
|
80
|
+
}
|
81
|
+
}
|
inspect_ai/_view/www/yarn.lock
CHANGED
@@ -920,6 +920,11 @@
|
|
920
920
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
|
921
921
|
integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
|
922
922
|
|
923
|
+
"@babel/runtime@^7.27.6":
|
924
|
+
version "7.27.6"
|
925
|
+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.6.tgz#ec4070a04d76bae8ddbb10770ba55714a417b7c6"
|
926
|
+
integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==
|
927
|
+
|
923
928
|
"@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.27.0", "@babel/template@^7.3.3":
|
924
929
|
version "7.27.0"
|
925
930
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4"
|
@@ -1633,7 +1638,14 @@
|
|
1633
1638
|
dependencies:
|
1634
1639
|
"@babel/runtime" "^7.27.1"
|
1635
1640
|
|
1636
|
-
"@mui/
|
1641
|
+
"@mui/types@^7.4.3":
|
1642
|
+
version "7.4.3"
|
1643
|
+
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.3.tgz#b205ee3404db0478cd93227fc21967e2cb8630fe"
|
1644
|
+
integrity sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==
|
1645
|
+
dependencies:
|
1646
|
+
"@babel/runtime" "^7.27.1"
|
1647
|
+
|
1648
|
+
"@mui/utils@^7.1.0":
|
1637
1649
|
version "7.1.0"
|
1638
1650
|
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.1.0.tgz#464c0c1bc8f57c07d934ac674f3dcc81ac24f68b"
|
1639
1651
|
integrity sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==
|
@@ -1645,22 +1657,35 @@
|
|
1645
1657
|
prop-types "^15.8.1"
|
1646
1658
|
react-is "^19.1.0"
|
1647
1659
|
|
1648
|
-
"@mui/
|
1649
|
-
version "
|
1650
|
-
resolved "https://registry.yarnpkg.com/@mui/
|
1651
|
-
integrity sha512-
|
1660
|
+
"@mui/utils@^7.1.1":
|
1661
|
+
version "7.1.1"
|
1662
|
+
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.1.1.tgz#de315ec45ac9e16c637dcc2b32cd7912edb4e234"
|
1663
|
+
integrity sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==
|
1652
1664
|
dependencies:
|
1653
1665
|
"@babel/runtime" "^7.27.1"
|
1654
|
-
"@mui/
|
1666
|
+
"@mui/types" "^7.4.3"
|
1667
|
+
"@types/prop-types" "^15.7.14"
|
1668
|
+
clsx "^2.1.1"
|
1669
|
+
prop-types "^15.8.1"
|
1670
|
+
react-is "^19.1.0"
|
1655
1671
|
|
1656
|
-
"@mui/x-
|
1657
|
-
version "8.3
|
1658
|
-
resolved "https://registry.yarnpkg.com/@mui/x-
|
1659
|
-
integrity sha512-
|
1672
|
+
"@mui/x-internals@8.5.3":
|
1673
|
+
version "8.5.3"
|
1674
|
+
resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.5.3.tgz#60756111fae9b5d5c56e6eb72ee0a869dc900441"
|
1675
|
+
integrity sha512-ImCg4E3DT3XoDIZO0pNCbB7iw14N+YCFY3J1V28POwCD7P2f3HSIz4jwzM006oYxI6bqeE6LMfpdPRDW6s6dQw==
|
1660
1676
|
dependencies:
|
1661
|
-
"@babel/runtime" "^7.27.
|
1662
|
-
"@mui/utils" "^7.
|
1663
|
-
|
1677
|
+
"@babel/runtime" "^7.27.6"
|
1678
|
+
"@mui/utils" "^7.1.1"
|
1679
|
+
reselect "^5.1.1"
|
1680
|
+
|
1681
|
+
"@mui/x-tree-view@^8.3.1":
|
1682
|
+
version "8.5.3"
|
1683
|
+
resolved "https://registry.yarnpkg.com/@mui/x-tree-view/-/x-tree-view-8.5.3.tgz#d2e2f3a2019dd88e11b3c64e35016a1cfada9e98"
|
1684
|
+
integrity sha512-mnorFyEtEshcj3AkDwBUu3UtydqsFCrqIoGAmVqO5YmwVEcgXJtg1PcQX3gkZmLDroolAUbBXrlUI8PwSLBtTQ==
|
1685
|
+
dependencies:
|
1686
|
+
"@babel/runtime" "^7.27.6"
|
1687
|
+
"@mui/utils" "^7.1.1"
|
1688
|
+
"@mui/x-internals" "8.5.3"
|
1664
1689
|
"@types/react-transition-group" "^4.4.12"
|
1665
1690
|
clsx "^2.1.1"
|
1666
1691
|
prop-types "^15.8.1"
|
@@ -1813,6 +1838,18 @@
|
|
1813
1838
|
dependencies:
|
1814
1839
|
"@sinonjs/commons" "^3.0.0"
|
1815
1840
|
|
1841
|
+
"@tanstack/react-table@^8.21.3":
|
1842
|
+
version "8.21.3"
|
1843
|
+
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.21.3.tgz#2c38c747a5731c1a07174fda764b9c2b1fb5e91b"
|
1844
|
+
integrity sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==
|
1845
|
+
dependencies:
|
1846
|
+
"@tanstack/table-core" "8.21.3"
|
1847
|
+
|
1848
|
+
"@tanstack/table-core@8.21.3":
|
1849
|
+
version "8.21.3"
|
1850
|
+
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.21.3.tgz#2977727d8fc8dfa079112d9f4d4c019110f1732c"
|
1851
|
+
integrity sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==
|
1852
|
+
|
1816
1853
|
"@testing-library/jest-dom@^6.6.3":
|
1817
1854
|
version "6.6.3"
|
1818
1855
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2"
|
@@ -2229,10 +2266,10 @@ aria-query@^5.0.0:
|
|
2229
2266
|
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59"
|
2230
2267
|
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
|
2231
2268
|
|
2232
|
-
asciinema-player@^3.
|
2233
|
-
version "3.
|
2234
|
-
resolved "https://registry.yarnpkg.com/asciinema-player/-/asciinema-player-3.
|
2235
|
-
integrity sha512-
|
2269
|
+
asciinema-player@^3.10.0:
|
2270
|
+
version "3.10.0"
|
2271
|
+
resolved "https://registry.yarnpkg.com/asciinema-player/-/asciinema-player-3.10.0.tgz#6b4b74b6ce85906b9930f0cf0dc71cf89e50e202"
|
2272
|
+
integrity sha512-shoOK6F606nDKZxDVM7JuGSCAyWLePoGRFNlV+FqiP5Sqvyn0BlE7wlbjZyd2X4P1iRhv/HKfVNtnQIxmgphRA==
|
2236
2273
|
dependencies:
|
2237
2274
|
"@babel/runtime" "^7.21.0"
|
2238
2275
|
solid-js "^1.3.0"
|