inspect-ai 0.3.81__py3-none-any.whl → 0.3.82__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/_cli/eval.py +35 -2
- inspect_ai/_cli/util.py +44 -1
- inspect_ai/_display/core/config.py +1 -1
- inspect_ai/_display/core/display.py +13 -4
- inspect_ai/_display/core/results.py +1 -1
- inspect_ai/_display/textual/widgets/task_detail.py +5 -4
- inspect_ai/_eval/eval.py +38 -1
- inspect_ai/_eval/evalset.py +5 -0
- inspect_ai/_eval/run.py +5 -2
- inspect_ai/_eval/task/log.py +53 -6
- inspect_ai/_eval/task/run.py +51 -10
- inspect_ai/_util/constants.py +2 -0
- inspect_ai/_util/file.py +17 -1
- inspect_ai/_util/json.py +36 -1
- inspect_ai/_view/server.py +113 -1
- inspect_ai/_view/www/App.css +1 -1
- inspect_ai/_view/www/dist/assets/index.css +518 -296
- inspect_ai/_view/www/dist/assets/index.js +38803 -36307
- inspect_ai/_view/www/eslint.config.mjs +1 -1
- inspect_ai/_view/www/log-schema.json +13 -0
- inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
- inspect_ai/_view/www/package.json +8 -2
- inspect_ai/_view/www/src/App.tsx +151 -855
- inspect_ai/_view/www/src/api/api-browser.ts +176 -5
- inspect_ai/_view/www/src/api/api-vscode.ts +75 -1
- inspect_ai/_view/www/src/api/client-api.ts +66 -10
- inspect_ai/_view/www/src/api/jsonrpc.ts +2 -0
- inspect_ai/_view/www/src/api/types.ts +107 -2
- inspect_ai/_view/www/src/appearance/icons.ts +1 -0
- inspect_ai/_view/www/src/components/AsciinemaPlayer.tsx +3 -3
- inspect_ai/_view/www/src/components/DownloadPanel.tsx +2 -2
- inspect_ai/_view/www/src/components/ExpandablePanel.tsx +56 -61
- inspect_ai/_view/www/src/components/FindBand.tsx +17 -9
- inspect_ai/_view/www/src/components/HumanBaselineView.tsx +1 -1
- inspect_ai/_view/www/src/components/JsonPanel.tsx +14 -24
- inspect_ai/_view/www/src/components/LargeModal.tsx +2 -35
- inspect_ai/_view/www/src/components/LightboxCarousel.tsx +27 -11
- inspect_ai/_view/www/src/components/LiveVirtualList.module.css +11 -0
- inspect_ai/_view/www/src/components/LiveVirtualList.tsx +177 -0
- inspect_ai/_view/www/src/components/MarkdownDiv.tsx +3 -3
- inspect_ai/_view/www/src/components/MessageBand.tsx +14 -9
- inspect_ai/_view/www/src/components/MorePopOver.tsx +3 -3
- inspect_ai/_view/www/src/components/NavPills.tsx +20 -8
- inspect_ai/_view/www/src/components/NoContentsPanel.module.css +12 -0
- inspect_ai/_view/www/src/components/NoContentsPanel.tsx +20 -0
- inspect_ai/_view/www/src/components/ProgressBar.module.css +5 -4
- inspect_ai/_view/www/src/components/ProgressBar.tsx +3 -2
- inspect_ai/_view/www/src/components/PulsingDots.module.css +81 -0
- inspect_ai/_view/www/src/components/PulsingDots.tsx +45 -0
- inspect_ai/_view/www/src/components/TabSet.tsx +4 -37
- inspect_ai/_view/www/src/components/ToolButton.tsx +3 -4
- inspect_ai/_view/www/src/index.tsx +26 -94
- inspect_ai/_view/www/src/logfile/remoteLogFile.ts +9 -1
- inspect_ai/_view/www/src/logfile/remoteZipFile.ts +30 -4
- inspect_ai/_view/www/src/metadata/RenderedContent.tsx +4 -6
- inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +1 -1
- inspect_ai/_view/www/src/samples/InlineSampleDisplay.module.css +9 -1
- inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +67 -28
- inspect_ai/_view/www/src/samples/SampleDialog.tsx +51 -22
- inspect_ai/_view/www/src/samples/SampleDisplay.module.css +4 -0
- inspect_ai/_view/www/src/samples/SampleDisplay.tsx +144 -90
- inspect_ai/_view/www/src/samples/SampleSummaryView.module.css +4 -0
- inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +82 -35
- inspect_ai/_view/www/src/samples/SamplesTools.tsx +23 -30
- inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +1 -1
- inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +45 -53
- inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +4 -1
- inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +3 -0
- inspect_ai/_view/www/src/samples/chat/messages.ts +34 -0
- inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.module.css +3 -0
- inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +10 -1
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +22 -46
- inspect_ai/_view/www/src/samples/descriptor/samplesDescriptor.tsx +25 -17
- inspect_ai/_view/www/src/samples/descriptor/score/ObjectScoreDescriptor.tsx +2 -1
- inspect_ai/_view/www/src/samples/descriptor/types.ts +6 -5
- inspect_ai/_view/www/src/samples/list/SampleFooter.module.css +21 -3
- inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +20 -1
- inspect_ai/_view/www/src/samples/list/SampleList.tsx +105 -85
- inspect_ai/_view/www/src/samples/list/SampleRow.module.css +6 -0
- inspect_ai/_view/www/src/samples/list/SampleRow.tsx +27 -14
- inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +29 -18
- inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +28 -28
- inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +19 -9
- inspect_ai/_view/www/src/samples/sampleDataAdapter.ts +33 -0
- inspect_ai/_view/www/src/samples/sampleLimit.ts +2 -2
- inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +7 -9
- inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +7 -11
- inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +0 -13
- inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +0 -13
- inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +0 -13
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +4 -0
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +10 -24
- inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +0 -13
- inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +4 -22
- inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +15 -24
- inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +0 -13
- inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +6 -28
- inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +24 -34
- inspect_ai/_view/www/src/samples/transcript/ToolEventView.module.css +4 -0
- inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +8 -13
- inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +197 -338
- inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.module.css +16 -0
- inspect_ai/_view/www/src/samples/transcript/TranscriptVirtualListComponent.tsx +44 -0
- inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +7 -4
- inspect_ai/_view/www/src/samples/transcript/event/EventPanel.tsx +52 -58
- inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.module.css +23 -0
- inspect_ai/_view/www/src/samples/transcript/event/EventProgressPanel.tsx +27 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +30 -1
- inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +102 -72
- inspect_ai/_view/www/src/scoring/utils.ts +87 -0
- inspect_ai/_view/www/src/state/appSlice.ts +244 -0
- inspect_ai/_view/www/src/state/hooks.ts +397 -0
- inspect_ai/_view/www/src/state/logPolling.ts +196 -0
- inspect_ai/_view/www/src/state/logSlice.ts +214 -0
- inspect_ai/_view/www/src/state/logsPolling.ts +118 -0
- inspect_ai/_view/www/src/state/logsSlice.ts +181 -0
- inspect_ai/_view/www/src/state/samplePolling.ts +311 -0
- inspect_ai/_view/www/src/state/sampleSlice.ts +127 -0
- inspect_ai/_view/www/src/state/sampleUtils.ts +21 -0
- inspect_ai/_view/www/src/state/scrolling.ts +206 -0
- inspect_ai/_view/www/src/state/store.ts +168 -0
- inspect_ai/_view/www/src/state/store_filter.ts +84 -0
- inspect_ai/_view/www/src/state/utils.ts +23 -0
- inspect_ai/_view/www/src/storage/index.ts +26 -0
- inspect_ai/_view/www/src/types/log.d.ts +2 -0
- inspect_ai/_view/www/src/types.ts +94 -32
- inspect_ai/_view/www/src/utils/attachments.ts +58 -23
- inspect_ai/_view/www/src/utils/logger.ts +52 -0
- inspect_ai/_view/www/src/utils/polling.ts +100 -0
- inspect_ai/_view/www/src/utils/react.ts +30 -0
- inspect_ai/_view/www/src/utils/vscode.ts +1 -1
- inspect_ai/_view/www/src/workspace/WorkSpace.tsx +181 -216
- inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +11 -53
- inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +8 -18
- inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.module.css +1 -0
- inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +40 -22
- inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.module.css +0 -1
- inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +98 -39
- inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.module.css +32 -0
- inspect_ai/_view/www/src/workspace/navbar/RunningStatusPanel.tsx +32 -0
- inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +11 -13
- inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +6 -2
- inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +4 -4
- inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +28 -13
- inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +5 -10
- inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +4 -4
- inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.module.css +22 -0
- inspect_ai/_view/www/src/workspace/tabs/RunningNoSamples.tsx +19 -0
- inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +110 -115
- inspect_ai/_view/www/src/workspace/tabs/grouping.ts +37 -5
- inspect_ai/_view/www/src/workspace/tabs/types.ts +4 -0
- inspect_ai/_view/www/src/workspace/types.ts +4 -3
- inspect_ai/_view/www/src/workspace/utils.ts +4 -4
- inspect_ai/_view/www/vite.config.js +6 -0
- inspect_ai/_view/www/yarn.lock +370 -354
- inspect_ai/log/_condense.py +26 -0
- inspect_ai/log/_log.py +6 -3
- inspect_ai/log/_recorders/buffer/__init__.py +14 -0
- inspect_ai/log/_recorders/buffer/buffer.py +30 -0
- inspect_ai/log/_recorders/buffer/database.py +685 -0
- inspect_ai/log/_recorders/buffer/filestore.py +259 -0
- inspect_ai/log/_recorders/buffer/types.py +84 -0
- inspect_ai/log/_recorders/eval.py +2 -11
- inspect_ai/log/_recorders/types.py +30 -0
- inspect_ai/log/_transcript.py +27 -1
- inspect_ai/model/_call_tools.py +1 -0
- inspect_ai/model/_generate_config.py +2 -2
- inspect_ai/model/_model.py +1 -0
- inspect_ai/tool/_tool_support_helpers.py +4 -4
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +3 -1
- inspect_ai/util/_subtask.py +1 -0
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/METADATA +1 -1
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/RECORD +178 -138
- inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +0 -22
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/WHEEL +0 -0
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/licenses/LICENSE +0 -0
- {inspect_ai-0.3.81.dist-info → inspect_ai-0.3.82.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
import { createLogger } from "../utils/logger";
|
2
|
+
import { createPolling } from "../utils/polling";
|
3
|
+
import { StoreState } from "./store";
|
4
|
+
|
5
|
+
// The logger
|
6
|
+
const log = createLogger("logPolling");
|
7
|
+
|
8
|
+
const kRetries = 10;
|
9
|
+
const kPollingInterval = 2;
|
10
|
+
|
11
|
+
export function createLogPolling(
|
12
|
+
get: () => StoreState,
|
13
|
+
set: (fn: (state: StoreState) => void) => void,
|
14
|
+
) {
|
15
|
+
// Tracks the currently polling instance
|
16
|
+
let currentPolling: ReturnType<typeof createPolling> | null = null;
|
17
|
+
|
18
|
+
// handle aborts
|
19
|
+
let abortController: AbortController;
|
20
|
+
|
21
|
+
// Refresh logs and clear pending in a single transaction
|
22
|
+
const refreshLog = async (logFileName: string, clearPending = false) => {
|
23
|
+
if (abortController?.signal.aborted) {
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
|
27
|
+
const state = get();
|
28
|
+
const api = state.api;
|
29
|
+
const selectedLogFile = state.logsActions.getSelectedLogFile();
|
30
|
+
|
31
|
+
if (!api || !selectedLogFile) {
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
|
35
|
+
log.debug(`refresh: ${selectedLogFile}`);
|
36
|
+
|
37
|
+
try {
|
38
|
+
const logContents = await api.get_log_summary(selectedLogFile);
|
39
|
+
|
40
|
+
set((state) => {
|
41
|
+
// Set the log summary
|
42
|
+
state.log.selectedLogSummary = logContents;
|
43
|
+
log.debug(
|
44
|
+
`Setting refreshed summary ${logContents.sampleSummaries.length} samples`,
|
45
|
+
logContents,
|
46
|
+
);
|
47
|
+
|
48
|
+
// Clear pending summaries if requested
|
49
|
+
if (clearPending) {
|
50
|
+
const pendingSampleSummaries = state.log.pendingSampleSummaries;
|
51
|
+
if ((pendingSampleSummaries?.samples.length || 0) > 0) {
|
52
|
+
log.debug(
|
53
|
+
`Clearing pending summaries during refresh for: ${logFileName}`,
|
54
|
+
);
|
55
|
+
state.log.pendingSampleSummaries = {
|
56
|
+
samples: [],
|
57
|
+
refresh: pendingSampleSummaries?.refresh || 2,
|
58
|
+
};
|
59
|
+
}
|
60
|
+
}
|
61
|
+
});
|
62
|
+
|
63
|
+
return true;
|
64
|
+
} catch (error) {
|
65
|
+
log.error("Error refreshing log:", error);
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
};
|
69
|
+
|
70
|
+
// Function to start polling for a specific log file
|
71
|
+
const startPolling = (logFileName: string) => {
|
72
|
+
// Stop any existing polling
|
73
|
+
if (currentPolling) {
|
74
|
+
currentPolling.stop();
|
75
|
+
}
|
76
|
+
|
77
|
+
// note that we're active
|
78
|
+
abortController = new AbortController();
|
79
|
+
|
80
|
+
// Track whether we ever polled
|
81
|
+
let loadedPendingSamples = false;
|
82
|
+
|
83
|
+
// Create a new polling instance
|
84
|
+
currentPolling = createPolling(
|
85
|
+
`PendingSamples-${logFileName}`,
|
86
|
+
async () => {
|
87
|
+
if (abortController.signal.aborted) {
|
88
|
+
log.debug(`Component unmounted, stopping poll for: ${logFileName}`);
|
89
|
+
return false;
|
90
|
+
}
|
91
|
+
|
92
|
+
// The state for polling
|
93
|
+
const state = get();
|
94
|
+
|
95
|
+
// Don't proceed if API doesn't support it
|
96
|
+
const api = state.api;
|
97
|
+
if (!api?.get_log_pending_samples) {
|
98
|
+
return false;
|
99
|
+
}
|
100
|
+
|
101
|
+
if (abortController.signal.aborted) {
|
102
|
+
return false;
|
103
|
+
}
|
104
|
+
|
105
|
+
// Fetch pending samples
|
106
|
+
log.debug(`Polling running samples: ${logFileName}`);
|
107
|
+
const currentEtag = get().log.pendingSampleSummaries?.etag;
|
108
|
+
const pendingSamples = await api.get_log_pending_samples(
|
109
|
+
logFileName,
|
110
|
+
currentEtag,
|
111
|
+
);
|
112
|
+
log.debug(`Received pending samples`, pendingSamples);
|
113
|
+
|
114
|
+
if (abortController.signal.aborted) {
|
115
|
+
return false;
|
116
|
+
}
|
117
|
+
|
118
|
+
if (pendingSamples.status === "OK" && pendingSamples.pendingSamples) {
|
119
|
+
loadedPendingSamples = true;
|
120
|
+
|
121
|
+
// Update state with new pending samples
|
122
|
+
set((state) => {
|
123
|
+
state.log.pendingSampleSummaries = pendingSamples.pendingSamples;
|
124
|
+
});
|
125
|
+
|
126
|
+
// Refresh the log to get latest data
|
127
|
+
await refreshLog(logFileName, false);
|
128
|
+
|
129
|
+
// Continue polling
|
130
|
+
return true;
|
131
|
+
} else if (pendingSamples.status === "NotFound") {
|
132
|
+
// The eval has completed (no more events/pending samples will be delivered)
|
133
|
+
log.debug(`Stop polling running samples: ${logFileName}`);
|
134
|
+
|
135
|
+
// Clear pending summaries and refresh in one transaction
|
136
|
+
if (loadedPendingSamples) {
|
137
|
+
await refreshLog(logFileName, true);
|
138
|
+
}
|
139
|
+
|
140
|
+
// Stop polling
|
141
|
+
return false;
|
142
|
+
}
|
143
|
+
|
144
|
+
// Continue polling by default
|
145
|
+
return true;
|
146
|
+
},
|
147
|
+
{
|
148
|
+
maxRetries: kRetries,
|
149
|
+
interval: get().log.pendingSampleSummaries?.refresh || kPollingInterval,
|
150
|
+
},
|
151
|
+
);
|
152
|
+
|
153
|
+
// Start the polling
|
154
|
+
currentPolling.start();
|
155
|
+
};
|
156
|
+
|
157
|
+
// Clear pending summaries (now using the transactional approach)
|
158
|
+
const clearPendingSummaries = (logFileName: string) => {
|
159
|
+
if (abortController.signal.aborted) {
|
160
|
+
return false;
|
161
|
+
}
|
162
|
+
|
163
|
+
const pendingSampleSummaries = get().log.pendingSampleSummaries;
|
164
|
+
if ((pendingSampleSummaries?.samples.length || 0) > 0) {
|
165
|
+
log.debug(`Clear pending: ${logFileName}`);
|
166
|
+
return refreshLog(logFileName, true);
|
167
|
+
}
|
168
|
+
|
169
|
+
return false;
|
170
|
+
};
|
171
|
+
|
172
|
+
// Stop polling
|
173
|
+
const stopPolling = () => {
|
174
|
+
if (currentPolling) {
|
175
|
+
currentPolling.stop();
|
176
|
+
currentPolling = null;
|
177
|
+
}
|
178
|
+
};
|
179
|
+
|
180
|
+
// Method to call when component unmounts
|
181
|
+
const cleanup = () => {
|
182
|
+
log.debug(`Cleanup`);
|
183
|
+
abortController.abort();
|
184
|
+
stopPolling();
|
185
|
+
};
|
186
|
+
|
187
|
+
return {
|
188
|
+
startPolling,
|
189
|
+
stopPolling,
|
190
|
+
clearPendingSummaries,
|
191
|
+
cleanup,
|
192
|
+
// Expose the refresh function so components can use it directly
|
193
|
+
refreshLog: (clearPending = false) =>
|
194
|
+
refreshLog(get().logsActions.getSelectedLogFile() || "", clearPending),
|
195
|
+
};
|
196
|
+
}
|
@@ -0,0 +1,214 @@
|
|
1
|
+
import { EvalSummary, PendingSamples } from "../api/types";
|
2
|
+
import { kDefaultSort, kInfoWorkspaceTabId } from "../constants";
|
3
|
+
import { ScorerInfo } from "../scoring/utils";
|
4
|
+
import { LogState, ScoreFilter, ScoreLabel } from "../types";
|
5
|
+
import { createLogger } from "../utils/logger";
|
6
|
+
import { createLogPolling } from "./logPolling";
|
7
|
+
import { StoreState } from "./store";
|
8
|
+
|
9
|
+
const log = createLogger("logSlice");
|
10
|
+
|
11
|
+
export interface LogSlice {
|
12
|
+
log: LogState;
|
13
|
+
logActions: {
|
14
|
+
selectSample: (index: number) => void;
|
15
|
+
|
16
|
+
// Set the selected log summary
|
17
|
+
setSelectedLogSummary: (summary: EvalSummary) => void;
|
18
|
+
|
19
|
+
// Clear the selected log summary
|
20
|
+
clearSelectedLogSummary: () => void;
|
21
|
+
|
22
|
+
// Update pending sample information
|
23
|
+
setPendingSampleSummaries: (samples: PendingSamples) => void;
|
24
|
+
|
25
|
+
// Set filter criteria
|
26
|
+
setFilter: (filter: ScoreFilter) => void;
|
27
|
+
|
28
|
+
// Set epoch filter
|
29
|
+
setEpoch: (epoch: string) => void;
|
30
|
+
|
31
|
+
// Set sort order and update groupBy
|
32
|
+
setSort: (sort: string) => void;
|
33
|
+
|
34
|
+
// Set score label
|
35
|
+
setScore: (score: ScoreLabel) => void;
|
36
|
+
|
37
|
+
// Set available scores
|
38
|
+
setScores: (scores: ScorerInfo[]) => void;
|
39
|
+
|
40
|
+
// Reset filter state to defaults
|
41
|
+
resetFiltering: () => void;
|
42
|
+
|
43
|
+
// Load log
|
44
|
+
loadLog: (logFileName: string) => Promise<void>;
|
45
|
+
|
46
|
+
// Refresh the current log
|
47
|
+
refreshLog: () => Promise<void>;
|
48
|
+
};
|
49
|
+
}
|
50
|
+
|
51
|
+
// Initial state
|
52
|
+
const initialState = {
|
53
|
+
// Log state
|
54
|
+
selectedSampleIndex: -1,
|
55
|
+
selectedLogSummary: undefined,
|
56
|
+
pendingSampleSummaries: undefined,
|
57
|
+
loadedLog: undefined,
|
58
|
+
|
59
|
+
// Filter state
|
60
|
+
filter: {},
|
61
|
+
epoch: "all",
|
62
|
+
sort: kDefaultSort,
|
63
|
+
score: undefined,
|
64
|
+
scores: undefined,
|
65
|
+
};
|
66
|
+
|
67
|
+
// Create the app slice using StoreState directly
|
68
|
+
export const createLogSlice = (
|
69
|
+
set: (fn: (state: StoreState) => void) => void,
|
70
|
+
get: () => StoreState,
|
71
|
+
_store: any,
|
72
|
+
): [LogSlice, () => void] => {
|
73
|
+
const logPolling = createLogPolling(get, set);
|
74
|
+
|
75
|
+
const slice = {
|
76
|
+
// State
|
77
|
+
log: initialState,
|
78
|
+
|
79
|
+
// Actions
|
80
|
+
logActions: {
|
81
|
+
selectSample: (index: number) =>
|
82
|
+
set((state) => {
|
83
|
+
state.log.selectedSampleIndex = index;
|
84
|
+
}),
|
85
|
+
|
86
|
+
setSelectedLogSummary: (selectedLogSummary: EvalSummary) => {
|
87
|
+
set((state) => {
|
88
|
+
state.log.selectedLogSummary = selectedLogSummary;
|
89
|
+
});
|
90
|
+
|
91
|
+
if (
|
92
|
+
selectedLogSummary.status !== "started" &&
|
93
|
+
selectedLogSummary.sampleSummaries.length === 0
|
94
|
+
) {
|
95
|
+
// If there are no samples, use the workspace tab id by default
|
96
|
+
get().appActions.setWorkspaceTab(kInfoWorkspaceTabId);
|
97
|
+
}
|
98
|
+
},
|
99
|
+
|
100
|
+
clearSelectedLogSummary: () => {
|
101
|
+
set((state) => {
|
102
|
+
state.log.selectedLogSummary = undefined;
|
103
|
+
});
|
104
|
+
},
|
105
|
+
setPendingSampleSummaries: (pendingSampleSummaries: PendingSamples) =>
|
106
|
+
set((state) => {
|
107
|
+
state.log.pendingSampleSummaries = pendingSampleSummaries;
|
108
|
+
}),
|
109
|
+
|
110
|
+
setFilter: (filter: ScoreFilter) =>
|
111
|
+
set((state) => {
|
112
|
+
state.log.filter = filter;
|
113
|
+
}),
|
114
|
+
setEpoch: (epoch: string) =>
|
115
|
+
set((state) => {
|
116
|
+
state.log.epoch = epoch;
|
117
|
+
}),
|
118
|
+
setSort: (sort: string) =>
|
119
|
+
set((state) => {
|
120
|
+
state.log.sort = sort;
|
121
|
+
}),
|
122
|
+
setScore: (score: ScoreLabel) =>
|
123
|
+
set((state) => {
|
124
|
+
state.log.score = score;
|
125
|
+
}),
|
126
|
+
setScores: (scores: ScorerInfo[]) =>
|
127
|
+
set((state) => {
|
128
|
+
state.log.scores = scores;
|
129
|
+
}),
|
130
|
+
resetFiltering: () =>
|
131
|
+
set((state) => {
|
132
|
+
state.log.filter = {};
|
133
|
+
state.log.epoch = "all";
|
134
|
+
state.log.sort = kDefaultSort;
|
135
|
+
state.log.score = undefined;
|
136
|
+
}),
|
137
|
+
|
138
|
+
loadLog: async (logFileName: string) => {
|
139
|
+
const state = get();
|
140
|
+
const api = state.api;
|
141
|
+
|
142
|
+
if (!api) {
|
143
|
+
console.error("API not initialized in Store");
|
144
|
+
return;
|
145
|
+
}
|
146
|
+
|
147
|
+
log.debug(`Load log: ${logFileName}`);
|
148
|
+
try {
|
149
|
+
const logContents = await api.get_log_summary(logFileName);
|
150
|
+
state.logActions.setSelectedLogSummary(logContents);
|
151
|
+
state.logActions.setEpoch;
|
152
|
+
|
153
|
+
// Push the updated header information up
|
154
|
+
const header = {
|
155
|
+
[logFileName]: {
|
156
|
+
version: logContents.version,
|
157
|
+
status: logContents.status,
|
158
|
+
eval: logContents.eval,
|
159
|
+
plan: logContents.plan,
|
160
|
+
results:
|
161
|
+
logContents.results !== null ? logContents.results : undefined,
|
162
|
+
stats: logContents.stats,
|
163
|
+
error: logContents.error !== null ? logContents.error : undefined,
|
164
|
+
},
|
165
|
+
};
|
166
|
+
|
167
|
+
state.logsActions.updateLogHeaders(header);
|
168
|
+
set((state) => {
|
169
|
+
state.log.loadedLog = logFileName;
|
170
|
+
}),
|
171
|
+
// Start polling for pending samples
|
172
|
+
logPolling.startPolling(logFileName);
|
173
|
+
} catch (error) {
|
174
|
+
log.error("Error loading log:", error);
|
175
|
+
}
|
176
|
+
},
|
177
|
+
|
178
|
+
refreshLog: async () => {
|
179
|
+
const state = get();
|
180
|
+
const api = state.api;
|
181
|
+
const selectedLogFile = state.logsActions.getSelectedLogFile();
|
182
|
+
|
183
|
+
if (!api || !selectedLogFile) {
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
|
187
|
+
log.debug(`refresh: ${selectedLogFile}`);
|
188
|
+
try {
|
189
|
+
const logContents = await api.get_log_summary(selectedLogFile);
|
190
|
+
state.logActions.setSelectedLogSummary(logContents);
|
191
|
+
} catch (error) {
|
192
|
+
log.error("Error refreshing log:", error);
|
193
|
+
}
|
194
|
+
},
|
195
|
+
},
|
196
|
+
} as const;
|
197
|
+
|
198
|
+
const cleanup = () => {
|
199
|
+
logPolling.cleanup();
|
200
|
+
};
|
201
|
+
|
202
|
+
return [slice, cleanup];
|
203
|
+
};
|
204
|
+
|
205
|
+
// Initialize app slice with StoreState
|
206
|
+
export const initalializeLogSlice = (
|
207
|
+
set: (fn: (state: StoreState) => void) => void,
|
208
|
+
) => {
|
209
|
+
set((state) => {
|
210
|
+
if (!state.log) {
|
211
|
+
state.log = initialState;
|
212
|
+
}
|
213
|
+
});
|
214
|
+
};
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import { EvalLogHeader, LogFiles } from "../api/types";
|
2
|
+
import { createLogger } from "../utils/logger";
|
3
|
+
import { createPolling } from "../utils/polling";
|
4
|
+
import { StoreState } from "./store";
|
5
|
+
|
6
|
+
// The logger
|
7
|
+
const log = createLogger("logsPolling");
|
8
|
+
|
9
|
+
export function createLogsPolling(
|
10
|
+
get: () => StoreState,
|
11
|
+
_set: (fn: (state: StoreState) => void) => void,
|
12
|
+
) {
|
13
|
+
// Tracks the currently polling instance
|
14
|
+
let currentPolling: ReturnType<typeof createPolling> | null = null;
|
15
|
+
|
16
|
+
// Are we active
|
17
|
+
let isActive = true;
|
18
|
+
|
19
|
+
// Function to start polling for a specific log file
|
20
|
+
const startPolling = (logFiles: LogFiles) => {
|
21
|
+
// Get the api
|
22
|
+
const api = get().api;
|
23
|
+
if (!api) {
|
24
|
+
throw new Error("Failed to start polling - no API");
|
25
|
+
}
|
26
|
+
|
27
|
+
// Stop any existing polling
|
28
|
+
if (currentPolling) {
|
29
|
+
currentPolling.stop();
|
30
|
+
}
|
31
|
+
|
32
|
+
// note that we're active
|
33
|
+
isActive = true;
|
34
|
+
|
35
|
+
log.debug("LOADING HEADERS");
|
36
|
+
get().logsActions.setHeadersLoading(true);
|
37
|
+
|
38
|
+
// Group into chunks
|
39
|
+
const chunkSize = 8;
|
40
|
+
const fileLists: string[][] = [];
|
41
|
+
|
42
|
+
for (let i = 0; i < logFiles.files.length; i += chunkSize) {
|
43
|
+
const chunk = logFiles.files
|
44
|
+
.slice(i, i + chunkSize)
|
45
|
+
.map((logFile) => logFile.name);
|
46
|
+
fileLists.push(chunk);
|
47
|
+
}
|
48
|
+
const totalLen = fileLists.length;
|
49
|
+
|
50
|
+
// Create a new polling instance
|
51
|
+
currentPolling = createPolling(
|
52
|
+
`LogHeaders`,
|
53
|
+
async () => {
|
54
|
+
if (!isActive) {
|
55
|
+
get().logsActions.setHeadersLoading(false);
|
56
|
+
return false; // Stop polling
|
57
|
+
}
|
58
|
+
|
59
|
+
log.debug(`POLL HEADERS`);
|
60
|
+
|
61
|
+
const currentFileList = fileLists.shift();
|
62
|
+
if (currentFileList) {
|
63
|
+
log.debug(
|
64
|
+
`LOADING ${totalLen - fileLists.length} of ${totalLen} CHUNKS`,
|
65
|
+
);
|
66
|
+
const headers = await api.get_log_headers(currentFileList);
|
67
|
+
const updatedHeaders: Record<string, EvalLogHeader> = {};
|
68
|
+
|
69
|
+
headers.forEach((header, index) => {
|
70
|
+
const logFile = currentFileList[index];
|
71
|
+
updatedHeaders[logFile] = header as EvalLogHeader;
|
72
|
+
});
|
73
|
+
get().logsActions.updateLogHeaders(updatedHeaders);
|
74
|
+
} else {
|
75
|
+
// Stop polling
|
76
|
+
get().logsActions.setHeadersLoading(false);
|
77
|
+
return false;
|
78
|
+
}
|
79
|
+
|
80
|
+
if (!isActive) {
|
81
|
+
get().logsActions.setHeadersLoading(false);
|
82
|
+
return false; // Stop polling
|
83
|
+
}
|
84
|
+
|
85
|
+
// Continue polling by default
|
86
|
+
return true;
|
87
|
+
},
|
88
|
+
{
|
89
|
+
maxRetries: 10,
|
90
|
+
interval: 5,
|
91
|
+
},
|
92
|
+
);
|
93
|
+
|
94
|
+
// Start the polling
|
95
|
+
currentPolling.start();
|
96
|
+
};
|
97
|
+
|
98
|
+
// Stop polling
|
99
|
+
const stopPolling = () => {
|
100
|
+
if (currentPolling) {
|
101
|
+
currentPolling.stop();
|
102
|
+
currentPolling = null;
|
103
|
+
}
|
104
|
+
};
|
105
|
+
|
106
|
+
// Method to call when component unmounts
|
107
|
+
const cleanup = () => {
|
108
|
+
log.debug(`CLEANUP`);
|
109
|
+
isActive = false;
|
110
|
+
stopPolling();
|
111
|
+
};
|
112
|
+
|
113
|
+
return {
|
114
|
+
startPolling,
|
115
|
+
stopPolling,
|
116
|
+
cleanup,
|
117
|
+
};
|
118
|
+
}
|
@@ -0,0 +1,181 @@
|
|
1
|
+
import { EvalLogHeader, LogFiles } from "../api/types";
|
2
|
+
import { LogsState } from "../types";
|
3
|
+
import { createLogger } from "../utils/logger";
|
4
|
+
import { createLogsPolling } from "./logsPolling";
|
5
|
+
import { StoreState } from "./store";
|
6
|
+
|
7
|
+
const log = createLogger("Log Slice");
|
8
|
+
|
9
|
+
export interface LogsSlice {
|
10
|
+
logs: LogsState;
|
11
|
+
logsActions: {
|
12
|
+
// Update State
|
13
|
+
setLogs: (logs: LogFiles) => void;
|
14
|
+
setLogHeaders: (headers: Record<string, EvalLogHeader>) => void;
|
15
|
+
setHeadersLoading: (loading: boolean) => void;
|
16
|
+
setSelectedLogIndex: (index: number) => void;
|
17
|
+
setSelectedLogFile: (logUrl: string) => void;
|
18
|
+
updateLogHeaders: (headers: Record<string, EvalLogHeader>) => void;
|
19
|
+
|
20
|
+
// Fetch or update logs
|
21
|
+
refreshLogs: () => Promise<void>;
|
22
|
+
selectLogFile: (logUrl: string) => Promise<void>;
|
23
|
+
loadLogs: () => Promise<LogFiles>;
|
24
|
+
|
25
|
+
// Computed values
|
26
|
+
getSelectedLogFile: () => string | undefined;
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
const initialState: LogsState = {
|
31
|
+
logs: { log_dir: "", files: [] },
|
32
|
+
logHeaders: {},
|
33
|
+
headersLoading: false,
|
34
|
+
selectedLogIndex: -1,
|
35
|
+
};
|
36
|
+
|
37
|
+
export const createLogsSlice = (
|
38
|
+
set: (fn: (state: StoreState) => void) => void,
|
39
|
+
get: () => StoreState,
|
40
|
+
_store: any,
|
41
|
+
): [LogsSlice, () => void] => {
|
42
|
+
const logsPolling = createLogsPolling(get, set);
|
43
|
+
|
44
|
+
const slice = {
|
45
|
+
// State
|
46
|
+
logs: initialState,
|
47
|
+
|
48
|
+
// Actions
|
49
|
+
logsActions: {
|
50
|
+
setLogs: (logs: LogFiles) => {
|
51
|
+
set((state) => {
|
52
|
+
state.logs.logs = logs;
|
53
|
+
});
|
54
|
+
|
55
|
+
// If we have files in the logs, load the headers
|
56
|
+
if (logs.files.length > 0) {
|
57
|
+
// ensure state is updated first
|
58
|
+
setTimeout(() => {
|
59
|
+
const currentState = get();
|
60
|
+
if (!currentState.logs.headersLoading) {
|
61
|
+
logsPolling.startPolling(logs);
|
62
|
+
}
|
63
|
+
}, 100);
|
64
|
+
}
|
65
|
+
},
|
66
|
+
setLogHeaders: (headers: Record<string, EvalLogHeader>) =>
|
67
|
+
set((state) => {
|
68
|
+
state.logs.logHeaders = headers;
|
69
|
+
}),
|
70
|
+
setHeadersLoading: (loading: boolean) =>
|
71
|
+
set((state) => {
|
72
|
+
state.logs.headersLoading = loading;
|
73
|
+
}),
|
74
|
+
setSelectedLogIndex: (selectedLogIndex: number) => {
|
75
|
+
set((state) => {
|
76
|
+
state.logs.selectedLogIndex = selectedLogIndex;
|
77
|
+
});
|
78
|
+
},
|
79
|
+
updateLogHeaders: (headers: Record<string, EvalLogHeader>) =>
|
80
|
+
set((state) => {
|
81
|
+
state.logs.logHeaders = { ...get().logs.logHeaders, ...headers };
|
82
|
+
}),
|
83
|
+
|
84
|
+
setSelectedLogFile: (logUrl: string) => {
|
85
|
+
const state = get();
|
86
|
+
const index = state.logs.logs.files.findIndex((val) =>
|
87
|
+
logUrl.endsWith(val.name),
|
88
|
+
);
|
89
|
+
|
90
|
+
if (index > -1) {
|
91
|
+
state.logsActions.setSelectedLogIndex(index);
|
92
|
+
}
|
93
|
+
},
|
94
|
+
|
95
|
+
// Helper function to load logs
|
96
|
+
loadLogs: async () => {
|
97
|
+
const api = get().api;
|
98
|
+
if (!api) {
|
99
|
+
console.error("API not initialized in LogsStore");
|
100
|
+
return { log_dir: "", files: [] };
|
101
|
+
}
|
102
|
+
|
103
|
+
try {
|
104
|
+
log.debug("LOADING LOG FILES");
|
105
|
+
return await api.get_log_paths();
|
106
|
+
} catch (e) {
|
107
|
+
console.log(e);
|
108
|
+
get().appActions.setStatus({ loading: false, error: e as Error });
|
109
|
+
return { log_dir: "", files: [] };
|
110
|
+
}
|
111
|
+
},
|
112
|
+
refreshLogs: async () => {
|
113
|
+
log.debug("REFRESH LOGS");
|
114
|
+
const state = get();
|
115
|
+
const refreshedLogs = await state.logsActions.loadLogs();
|
116
|
+
|
117
|
+
// Set the logs first
|
118
|
+
state.logsActions.setLogs(refreshedLogs || { log_dir: "", files: [] });
|
119
|
+
|
120
|
+
// Preserve the selected log even if new logs appear
|
121
|
+
const currentLog =
|
122
|
+
refreshedLogs.files[
|
123
|
+
state.logs.selectedLogIndex > -1 ? state.logs.selectedLogIndex : 0
|
124
|
+
];
|
125
|
+
|
126
|
+
if (currentLog) {
|
127
|
+
const newIndex = refreshedLogs?.files.findIndex((file) =>
|
128
|
+
currentLog.name.endsWith(file.name),
|
129
|
+
);
|
130
|
+
|
131
|
+
if (newIndex !== undefined && newIndex !== -1) {
|
132
|
+
state.logsActions.setSelectedLogIndex(newIndex);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
},
|
136
|
+
// Select a specific log file
|
137
|
+
selectLogFile: async (logUrl: string) => {
|
138
|
+
const state = get();
|
139
|
+
const index = state.logs.logs.files.findIndex((val) =>
|
140
|
+
val.name.endsWith(logUrl),
|
141
|
+
);
|
142
|
+
|
143
|
+
// It is already loaded
|
144
|
+
if (index > -1) {
|
145
|
+
state.logsActions.setSelectedLogIndex(index);
|
146
|
+
} else {
|
147
|
+
// It isn't yet loaded, so refresh the logs and try to load it from there
|
148
|
+
const result = await state.logsActions.loadLogs();
|
149
|
+
const idx = result?.files.findIndex((file) =>
|
150
|
+
logUrl.endsWith(file.name),
|
151
|
+
);
|
152
|
+
|
153
|
+
state.logsActions.setLogs(result || { log_dir: "", files: [] });
|
154
|
+
state.logsActions.setSelectedLogIndex(
|
155
|
+
idx !== undefined && idx > -1 ? idx : 0,
|
156
|
+
);
|
157
|
+
}
|
158
|
+
},
|
159
|
+
|
160
|
+
getSelectedLogFile: () => {
|
161
|
+
const state = get();
|
162
|
+
const file = state.logs.logs.files[state.logs.selectedLogIndex];
|
163
|
+
return file !== undefined ? file.name : undefined;
|
164
|
+
},
|
165
|
+
},
|
166
|
+
} as const;
|
167
|
+
|
168
|
+
const cleanup = () => {};
|
169
|
+
|
170
|
+
return [slice, cleanup];
|
171
|
+
};
|
172
|
+
|
173
|
+
export const initializeLogsSlice = <T extends LogsSlice>(
|
174
|
+
set: (fn: (state: T) => void) => void,
|
175
|
+
) => {
|
176
|
+
set((state) => {
|
177
|
+
if (!state.logs) {
|
178
|
+
state.logs = initialState;
|
179
|
+
}
|
180
|
+
});
|
181
|
+
};
|