@runloop/rl-cli 1.7.0 → 1.8.0
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.
- package/dist/commands/menu.js +2 -1
- package/dist/commands/secret/list.js +379 -4
- package/dist/components/BenchmarkMenu.js +88 -0
- package/dist/components/MainMenu.js +63 -22
- package/dist/components/SecretCreatePage.js +185 -0
- package/dist/components/SettingsMenu.js +85 -0
- package/dist/components/StatusBadge.js +73 -0
- package/dist/components/StreamingLogsViewer.js +114 -53
- package/dist/router/Router.js +21 -1
- package/dist/screens/BenchmarkMenuScreen.js +23 -0
- package/dist/screens/BenchmarkRunDetailScreen.js +189 -0
- package/dist/screens/BenchmarkRunListScreen.js +255 -0
- package/dist/screens/MenuScreen.js +5 -2
- package/dist/screens/ScenarioRunDetailScreen.js +220 -0
- package/dist/screens/ScenarioRunListScreen.js +245 -0
- package/dist/screens/SecretCreateScreen.js +7 -0
- package/dist/screens/SecretDetailScreen.js +198 -0
- package/dist/screens/SecretListScreen.js +7 -0
- package/dist/screens/SettingsMenuScreen.js +23 -0
- package/dist/services/benchmarkService.js +73 -0
- package/dist/store/benchmarkStore.js +120 -0
- package/dist/store/betaFeatureStore.js +47 -0
- package/dist/store/index.js +1 -0
- package/dist/utils/config.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BenchmarkRunListScreen - List view for benchmark runs
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { Box, Text, useInput, useApp } from "ink";
|
|
7
|
+
import figures from "figures";
|
|
8
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
9
|
+
import { SpinnerComponent } from "../components/Spinner.js";
|
|
10
|
+
import { ErrorMessage } from "../components/ErrorMessage.js";
|
|
11
|
+
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
12
|
+
import { NavigationTips } from "../components/NavigationTips.js";
|
|
13
|
+
import { Table, createTextColumn, createComponentColumn, } from "../components/Table.js";
|
|
14
|
+
import { ActionsPopup } from "../components/ActionsPopup.js";
|
|
15
|
+
import { formatTimeAgo } from "../components/ResourceListView.js";
|
|
16
|
+
import { SearchBar } from "../components/SearchBar.js";
|
|
17
|
+
import { getStatusDisplay } from "../components/StatusBadge.js";
|
|
18
|
+
import { colors } from "../utils/theme.js";
|
|
19
|
+
import { useViewportHeight } from "../hooks/useViewportHeight.js";
|
|
20
|
+
import { useExitOnCtrlC } from "../hooks/useExitOnCtrlC.js";
|
|
21
|
+
import { useCursorPagination } from "../hooks/useCursorPagination.js";
|
|
22
|
+
import { useListSearch } from "../hooks/useListSearch.js";
|
|
23
|
+
import { listBenchmarkRuns } from "../services/benchmarkService.js";
|
|
24
|
+
export function BenchmarkRunListScreen() {
|
|
25
|
+
const { exit: inkExit } = useApp();
|
|
26
|
+
const { navigate, goBack } = useNavigation();
|
|
27
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
28
|
+
const [showPopup, setShowPopup] = React.useState(false);
|
|
29
|
+
const [selectedOperation, setSelectedOperation] = React.useState(0);
|
|
30
|
+
// Search state
|
|
31
|
+
const search = useListSearch({
|
|
32
|
+
onSearchSubmit: () => setSelectedIndex(0),
|
|
33
|
+
onSearchClear: () => setSelectedIndex(0),
|
|
34
|
+
});
|
|
35
|
+
// Calculate overhead for viewport height
|
|
36
|
+
const overhead = 13 + search.getSearchOverhead();
|
|
37
|
+
const { viewportHeight, terminalWidth } = useViewportHeight({
|
|
38
|
+
overhead,
|
|
39
|
+
minHeight: 5,
|
|
40
|
+
});
|
|
41
|
+
const PAGE_SIZE = viewportHeight;
|
|
42
|
+
// Column widths
|
|
43
|
+
const fixedWidth = 6;
|
|
44
|
+
const idWidth = 25;
|
|
45
|
+
const statusWidth = 12;
|
|
46
|
+
const timeWidth = 18;
|
|
47
|
+
const baseWidth = fixedWidth + idWidth + statusWidth + timeWidth;
|
|
48
|
+
const remainingWidth = terminalWidth - baseWidth;
|
|
49
|
+
const nameWidth = Math.min(80, Math.max(15, remainingWidth));
|
|
50
|
+
// Fetch function for pagination hook
|
|
51
|
+
const fetchPage = React.useCallback(async (params) => {
|
|
52
|
+
const result = await listBenchmarkRuns({
|
|
53
|
+
limit: params.limit,
|
|
54
|
+
startingAfter: params.startingAt,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
items: result.benchmarkRuns,
|
|
58
|
+
hasMore: result.hasMore,
|
|
59
|
+
totalCount: result.totalCount,
|
|
60
|
+
};
|
|
61
|
+
}, []);
|
|
62
|
+
// Use the shared pagination hook
|
|
63
|
+
const { items: benchmarkRuns, loading, navigating, error, currentPage, hasMore, hasPrev, totalCount, nextPage, prevPage, refresh, } = useCursorPagination({
|
|
64
|
+
fetchPage,
|
|
65
|
+
pageSize: PAGE_SIZE,
|
|
66
|
+
getItemId: (run) => run.id,
|
|
67
|
+
pollInterval: 5000,
|
|
68
|
+
pollingEnabled: !showPopup && !search.searchMode,
|
|
69
|
+
deps: [PAGE_SIZE],
|
|
70
|
+
});
|
|
71
|
+
// Operations for benchmark runs
|
|
72
|
+
const operations = React.useMemo(() => [
|
|
73
|
+
{
|
|
74
|
+
key: "view_details",
|
|
75
|
+
label: "View Details",
|
|
76
|
+
color: colors.primary,
|
|
77
|
+
icon: figures.pointer,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "view_scenarios",
|
|
81
|
+
label: "View Scenario Runs",
|
|
82
|
+
color: colors.info,
|
|
83
|
+
icon: figures.arrowRight,
|
|
84
|
+
},
|
|
85
|
+
], []);
|
|
86
|
+
// Build columns
|
|
87
|
+
const columns = React.useMemo(() => [
|
|
88
|
+
createTextColumn("id", "ID", (run) => run.id, {
|
|
89
|
+
width: idWidth + 1,
|
|
90
|
+
color: colors.idColor,
|
|
91
|
+
dimColor: false,
|
|
92
|
+
bold: false,
|
|
93
|
+
}),
|
|
94
|
+
createTextColumn("name", "Name", (run) => run.name || "", {
|
|
95
|
+
width: nameWidth,
|
|
96
|
+
}),
|
|
97
|
+
createComponentColumn("status", "Status", (run, _index, isSelected) => {
|
|
98
|
+
const statusDisplay = getStatusDisplay(run.state);
|
|
99
|
+
const text = statusDisplay.text
|
|
100
|
+
.slice(0, statusWidth)
|
|
101
|
+
.padEnd(statusWidth, " ");
|
|
102
|
+
return (_jsx(Text, { color: isSelected ? colors.text : statusDisplay.color, bold: isSelected, inverse: isSelected, children: text }));
|
|
103
|
+
}, { width: statusWidth }),
|
|
104
|
+
createTextColumn("created", "Created", (run) => run.start_time_ms ? formatTimeAgo(run.start_time_ms) : "", {
|
|
105
|
+
width: timeWidth,
|
|
106
|
+
color: colors.textDim,
|
|
107
|
+
dimColor: false,
|
|
108
|
+
bold: false,
|
|
109
|
+
}),
|
|
110
|
+
], [idWidth, nameWidth, statusWidth, timeWidth]);
|
|
111
|
+
// Handle Ctrl+C to exit
|
|
112
|
+
useExitOnCtrlC();
|
|
113
|
+
// Ensure selected index is within bounds
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (benchmarkRuns.length > 0 && selectedIndex >= benchmarkRuns.length) {
|
|
116
|
+
setSelectedIndex(Math.max(0, benchmarkRuns.length - 1));
|
|
117
|
+
}
|
|
118
|
+
}, [benchmarkRuns.length, selectedIndex]);
|
|
119
|
+
const selectedRun = benchmarkRuns[selectedIndex];
|
|
120
|
+
// Calculate pagination info for display
|
|
121
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
|
122
|
+
const startIndex = currentPage * PAGE_SIZE;
|
|
123
|
+
const endIndex = startIndex + benchmarkRuns.length;
|
|
124
|
+
useInput((input, key) => {
|
|
125
|
+
// Handle search mode input
|
|
126
|
+
if (search.searchMode) {
|
|
127
|
+
if (key.escape) {
|
|
128
|
+
search.cancelSearch();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Handle popup navigation
|
|
133
|
+
if (showPopup) {
|
|
134
|
+
if (key.upArrow && selectedOperation > 0) {
|
|
135
|
+
setSelectedOperation(selectedOperation - 1);
|
|
136
|
+
}
|
|
137
|
+
else if (key.downArrow && selectedOperation < operations.length - 1) {
|
|
138
|
+
setSelectedOperation(selectedOperation + 1);
|
|
139
|
+
}
|
|
140
|
+
else if (key.return) {
|
|
141
|
+
setShowPopup(false);
|
|
142
|
+
const operationKey = operations[selectedOperation].key;
|
|
143
|
+
if (operationKey === "view_details") {
|
|
144
|
+
navigate("benchmark-run-detail", {
|
|
145
|
+
benchmarkRunId: selectedRun.id,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else if (operationKey === "view_scenarios") {
|
|
149
|
+
navigate("scenario-run-list", {
|
|
150
|
+
benchmarkRunId: selectedRun.id,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (input === "v" && selectedRun) {
|
|
155
|
+
setShowPopup(false);
|
|
156
|
+
navigate("benchmark-run-detail", {
|
|
157
|
+
benchmarkRunId: selectedRun.id,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else if (input === "s" && selectedRun) {
|
|
161
|
+
setShowPopup(false);
|
|
162
|
+
navigate("scenario-run-list", {
|
|
163
|
+
benchmarkRunId: selectedRun.id,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (key.escape || input === "q") {
|
|
167
|
+
setShowPopup(false);
|
|
168
|
+
setSelectedOperation(0);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const pageRuns = benchmarkRuns.length;
|
|
173
|
+
// Handle list view navigation
|
|
174
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
175
|
+
setSelectedIndex(selectedIndex - 1);
|
|
176
|
+
}
|
|
177
|
+
else if (key.downArrow && selectedIndex < pageRuns - 1) {
|
|
178
|
+
setSelectedIndex(selectedIndex + 1);
|
|
179
|
+
}
|
|
180
|
+
else if ((input === "n" || key.rightArrow) &&
|
|
181
|
+
!loading &&
|
|
182
|
+
!navigating &&
|
|
183
|
+
hasMore) {
|
|
184
|
+
nextPage();
|
|
185
|
+
setSelectedIndex(0);
|
|
186
|
+
}
|
|
187
|
+
else if ((input === "p" || key.leftArrow) &&
|
|
188
|
+
!loading &&
|
|
189
|
+
!navigating &&
|
|
190
|
+
hasPrev) {
|
|
191
|
+
prevPage();
|
|
192
|
+
setSelectedIndex(0);
|
|
193
|
+
}
|
|
194
|
+
else if (key.return && selectedRun) {
|
|
195
|
+
navigate("benchmark-run-detail", {
|
|
196
|
+
benchmarkRunId: selectedRun.id,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else if (input === "a" && selectedRun) {
|
|
200
|
+
setShowPopup(true);
|
|
201
|
+
setSelectedOperation(0);
|
|
202
|
+
}
|
|
203
|
+
else if (input === "/") {
|
|
204
|
+
search.enterSearchMode();
|
|
205
|
+
}
|
|
206
|
+
else if (key.escape) {
|
|
207
|
+
if (search.handleEscape()) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
goBack();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Loading state
|
|
214
|
+
if (loading && benchmarkRuns.length === 0) {
|
|
215
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
216
|
+
{ label: "Home" },
|
|
217
|
+
{ label: "Benchmarks" },
|
|
218
|
+
{ label: "Benchmark Runs", active: true },
|
|
219
|
+
] }), _jsx(SpinnerComponent, { message: "Loading benchmark runs..." })] }));
|
|
220
|
+
}
|
|
221
|
+
// Error state
|
|
222
|
+
if (error) {
|
|
223
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
224
|
+
{ label: "Home" },
|
|
225
|
+
{ label: "Benchmarks" },
|
|
226
|
+
{ label: "Benchmark Runs", active: true },
|
|
227
|
+
] }), _jsx(ErrorMessage, { message: "Failed to list benchmark runs", error: error })] }));
|
|
228
|
+
}
|
|
229
|
+
// Main list view
|
|
230
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: [
|
|
231
|
+
{ label: "Home" },
|
|
232
|
+
{ label: "Benchmarks" },
|
|
233
|
+
{ label: "Benchmark Runs", active: true },
|
|
234
|
+
] }), _jsx(SearchBar, { searchMode: search.searchMode, searchQuery: search.searchQuery, submittedSearchQuery: search.submittedSearchQuery, resultCount: totalCount, onSearchChange: search.setSearchQuery, onSearchSubmit: search.submitSearch, placeholder: "Search benchmark runs..." }), !showPopup && (_jsx(Table, { data: benchmarkRuns, keyExtractor: (run) => run.id, selectedIndex: selectedIndex, title: `benchmark_runs[${totalCount}]`, columns: columns, emptyState: _jsxs(Text, { color: colors.textDim, children: [figures.info, " No benchmark runs found"] }) })), !showPopup && (_jsxs(Box, { marginTop: 1, paddingX: 1, children: [_jsxs(Text, { color: colors.primary, bold: true, children: [figures.hamburger, " ", totalCount] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "total"] }), totalPages > 1 && (_jsxs(_Fragment, { children: [_jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), navigating ? (_jsxs(Text, { color: colors.warning, children: [figures.pointer, " Loading page ", currentPage + 1, "..."] })) : (_jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Page ", currentPage + 1, " of ", totalPages] }))] })), _jsxs(Text, { color: colors.textDim, dimColor: true, children: [" ", "\u2022", " "] }), _jsxs(Text, { color: colors.textDim, dimColor: true, children: ["Showing ", startIndex + 1, "-", endIndex, " of ", totalCount] })] })), showPopup && selectedRun && (_jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(ActionsPopup, { devbox: selectedRun, operations: operations.map((op) => ({
|
|
235
|
+
key: op.key,
|
|
236
|
+
label: op.label,
|
|
237
|
+
color: op.color,
|
|
238
|
+
icon: op.icon,
|
|
239
|
+
shortcut: op.key === "view_details"
|
|
240
|
+
? "v"
|
|
241
|
+
: op.key === "view_scenarios"
|
|
242
|
+
? "s"
|
|
243
|
+
: "",
|
|
244
|
+
})), selectedOperation: selectedOperation, onClose: () => setShowPopup(false) }) })), _jsx(NavigationTips, { showArrows: true, tips: [
|
|
245
|
+
{
|
|
246
|
+
icon: `${figures.arrowLeft}${figures.arrowRight}`,
|
|
247
|
+
label: "Page",
|
|
248
|
+
condition: hasMore || hasPrev,
|
|
249
|
+
},
|
|
250
|
+
{ key: "Enter", label: "Details" },
|
|
251
|
+
{ key: "a", label: "Actions" },
|
|
252
|
+
{ key: "/", label: "Search" },
|
|
253
|
+
{ key: "Esc", label: "Back" },
|
|
254
|
+
] })] }));
|
|
255
|
+
}
|
|
@@ -14,12 +14,15 @@ export function MenuScreen() {
|
|
|
14
14
|
case "snapshots":
|
|
15
15
|
navigate("snapshot-list");
|
|
16
16
|
break;
|
|
17
|
-
case "
|
|
18
|
-
navigate("
|
|
17
|
+
case "settings":
|
|
18
|
+
navigate("settings-menu");
|
|
19
19
|
break;
|
|
20
20
|
case "objects":
|
|
21
21
|
navigate("object-list");
|
|
22
22
|
break;
|
|
23
|
+
case "benchmarks":
|
|
24
|
+
navigate("benchmark-menu");
|
|
25
|
+
break;
|
|
23
26
|
default:
|
|
24
27
|
// Fallback for any other screen names
|
|
25
28
|
navigate(key);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ScenarioRunDetailScreen - Detail page for scenario runs
|
|
4
|
+
* Uses the generic ResourceDetailPage component
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { Text } from "ink";
|
|
8
|
+
import figures from "figures";
|
|
9
|
+
import { useNavigation } from "../store/navigationStore.js";
|
|
10
|
+
import { useBenchmarkStore, } from "../store/benchmarkStore.js";
|
|
11
|
+
import { ResourceDetailPage, formatTimestamp, } from "../components/ResourceDetailPage.js";
|
|
12
|
+
import { getScenarioRun } from "../services/benchmarkService.js";
|
|
13
|
+
import { SpinnerComponent } from "../components/Spinner.js";
|
|
14
|
+
import { ErrorMessage } from "../components/ErrorMessage.js";
|
|
15
|
+
import { Breadcrumb } from "../components/Breadcrumb.js";
|
|
16
|
+
import { colors } from "../utils/theme.js";
|
|
17
|
+
export function ScenarioRunDetailScreen({ scenarioRunId, benchmarkRunId, }) {
|
|
18
|
+
const { goBack, navigate } = useNavigation();
|
|
19
|
+
const scenarioRuns = useBenchmarkStore((state) => state.scenarioRuns);
|
|
20
|
+
const [loading, setLoading] = React.useState(false);
|
|
21
|
+
const [error, setError] = React.useState(null);
|
|
22
|
+
const [fetchedRun, setFetchedRun] = React.useState(null);
|
|
23
|
+
// Find run in store first
|
|
24
|
+
const runFromStore = scenarioRuns.find((r) => r.id === scenarioRunId);
|
|
25
|
+
// Polling function
|
|
26
|
+
const pollRun = React.useCallback(async () => {
|
|
27
|
+
if (!scenarioRunId)
|
|
28
|
+
return null;
|
|
29
|
+
return getScenarioRun(scenarioRunId);
|
|
30
|
+
}, [scenarioRunId]);
|
|
31
|
+
// Fetch run from API if not in store
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
if (scenarioRunId && !loading && !fetchedRun) {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
setError(null);
|
|
36
|
+
getScenarioRun(scenarioRunId)
|
|
37
|
+
.then((run) => {
|
|
38
|
+
setFetchedRun(run);
|
|
39
|
+
setLoading(false);
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
setError(err);
|
|
43
|
+
setLoading(false);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}, [scenarioRunId, loading, fetchedRun]);
|
|
47
|
+
// Use fetched run for full details, fall back to store for basic display
|
|
48
|
+
const run = fetchedRun || runFromStore;
|
|
49
|
+
// Build breadcrumb items
|
|
50
|
+
const buildBreadcrumbItems = (lastLabel, active = true) => {
|
|
51
|
+
const items = [
|
|
52
|
+
{ label: "Home" },
|
|
53
|
+
{ label: "Benchmarks" },
|
|
54
|
+
];
|
|
55
|
+
if (benchmarkRunId) {
|
|
56
|
+
items.push({ label: `Run: ${benchmarkRunId.substring(0, 8)}...` });
|
|
57
|
+
}
|
|
58
|
+
items.push({ label: "Scenario Runs" });
|
|
59
|
+
items.push({ label: lastLabel, active });
|
|
60
|
+
return items;
|
|
61
|
+
};
|
|
62
|
+
// Show loading state
|
|
63
|
+
if (!run && scenarioRunId && !error) {
|
|
64
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Loading...") }), _jsx(SpinnerComponent, { message: "Loading scenario run details..." })] }));
|
|
65
|
+
}
|
|
66
|
+
// Show error state
|
|
67
|
+
if (error && !run) {
|
|
68
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Error") }), _jsx(ErrorMessage, { message: "Failed to load scenario run details", error: error })] }));
|
|
69
|
+
}
|
|
70
|
+
// Show not found error
|
|
71
|
+
if (!run) {
|
|
72
|
+
return (_jsxs(_Fragment, { children: [_jsx(Breadcrumb, { items: buildBreadcrumbItems("Not Found") }), _jsx(ErrorMessage, { message: `Scenario run ${scenarioRunId || "unknown"} not found`, error: new Error("Scenario run not found") })] }));
|
|
73
|
+
}
|
|
74
|
+
// Build detail sections
|
|
75
|
+
const detailSections = [];
|
|
76
|
+
// Basic details section
|
|
77
|
+
const basicFields = [];
|
|
78
|
+
if (run.start_time_ms) {
|
|
79
|
+
basicFields.push({
|
|
80
|
+
label: "Started",
|
|
81
|
+
value: formatTimestamp(run.start_time_ms),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const endTimeMs = run.start_time_ms && run.duration_ms
|
|
85
|
+
? run.start_time_ms + run.duration_ms
|
|
86
|
+
: undefined;
|
|
87
|
+
if (endTimeMs) {
|
|
88
|
+
basicFields.push({
|
|
89
|
+
label: "Ended",
|
|
90
|
+
value: formatTimestamp(endTimeMs),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (run.scenario_id) {
|
|
94
|
+
basicFields.push({
|
|
95
|
+
label: "Scenario ID",
|
|
96
|
+
value: _jsx(Text, { color: colors.idColor, children: run.scenario_id }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (run.benchmark_run_id) {
|
|
100
|
+
basicFields.push({
|
|
101
|
+
label: "Benchmark Run ID",
|
|
102
|
+
value: _jsx(Text, { color: colors.idColor, children: run.benchmark_run_id }),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (basicFields.length > 0) {
|
|
106
|
+
detailSections.push({
|
|
107
|
+
title: "Details",
|
|
108
|
+
icon: figures.squareSmallFilled,
|
|
109
|
+
color: colors.warning,
|
|
110
|
+
fields: basicFields,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Results section
|
|
114
|
+
const score = run.scoring_contract_result?.score;
|
|
115
|
+
if (score !== undefined) {
|
|
116
|
+
detailSections.push({
|
|
117
|
+
title: "Results",
|
|
118
|
+
icon: figures.tick,
|
|
119
|
+
color: colors.success,
|
|
120
|
+
fields: [
|
|
121
|
+
{
|
|
122
|
+
label: "Score",
|
|
123
|
+
value: _jsx(Text, { color: colors.info, children: score }),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Metadata section
|
|
129
|
+
if (run.metadata && Object.keys(run.metadata).length > 0) {
|
|
130
|
+
const metadataFields = Object.entries(run.metadata).map(([key, value]) => ({
|
|
131
|
+
label: key,
|
|
132
|
+
value: value,
|
|
133
|
+
}));
|
|
134
|
+
detailSections.push({
|
|
135
|
+
title: "Metadata",
|
|
136
|
+
icon: figures.identical,
|
|
137
|
+
color: colors.secondary,
|
|
138
|
+
fields: metadataFields,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Operations available for scenario runs
|
|
142
|
+
const operations = [];
|
|
143
|
+
if (run.benchmark_run_id) {
|
|
144
|
+
operations.push({
|
|
145
|
+
key: "view-benchmark-run",
|
|
146
|
+
label: "View Benchmark Run",
|
|
147
|
+
color: colors.info,
|
|
148
|
+
icon: figures.arrowRight,
|
|
149
|
+
shortcut: "b",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// Handle operation selection
|
|
153
|
+
const handleOperation = async (operation, resource) => {
|
|
154
|
+
switch (operation) {
|
|
155
|
+
case "view-benchmark-run":
|
|
156
|
+
if (resource.benchmark_run_id) {
|
|
157
|
+
navigate("benchmark-run-detail", {
|
|
158
|
+
benchmarkRunId: resource.benchmark_run_id,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
// Build detailed info lines for full details view
|
|
165
|
+
const buildDetailLines = (r) => {
|
|
166
|
+
const lines = [];
|
|
167
|
+
// Core Information
|
|
168
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Scenario Run Details" }, "core-title"));
|
|
169
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "ID: ", r.id] }, "core-id"));
|
|
170
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Name: ", r.name || "(none)"] }, "core-name"));
|
|
171
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Status: ", r.state] }, "core-status"));
|
|
172
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Scenario ID: ", r.scenario_id] }, "core-scenario"));
|
|
173
|
+
if (r.benchmark_run_id) {
|
|
174
|
+
lines.push(_jsxs(Text, { color: colors.idColor, children: [" ", "Benchmark Run ID: ", r.benchmark_run_id] }, "core-benchmark-run"));
|
|
175
|
+
}
|
|
176
|
+
if (r.start_time_ms) {
|
|
177
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Started: ", new Date(r.start_time_ms).toLocaleString()] }, "core-created"));
|
|
178
|
+
}
|
|
179
|
+
const detailEndTimeMs = r.start_time_ms && r.duration_ms
|
|
180
|
+
? r.start_time_ms + r.duration_ms
|
|
181
|
+
: undefined;
|
|
182
|
+
if (detailEndTimeMs) {
|
|
183
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Ended: ", new Date(detailEndTimeMs).toLocaleString()] }, "core-ended"));
|
|
184
|
+
}
|
|
185
|
+
lines.push(_jsx(Text, { children: " " }, "core-space"));
|
|
186
|
+
// Results
|
|
187
|
+
const detailScore = r.scoring_contract_result?.score;
|
|
188
|
+
if (detailScore !== undefined) {
|
|
189
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Results" }, "results-title"));
|
|
190
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", "Score: ", detailScore] }, "results-score"));
|
|
191
|
+
lines.push(_jsx(Text, { children: " " }, "results-space"));
|
|
192
|
+
}
|
|
193
|
+
// Metadata
|
|
194
|
+
if (r.metadata && Object.keys(r.metadata).length > 0) {
|
|
195
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Metadata" }, "meta-title"));
|
|
196
|
+
Object.entries(r.metadata).forEach(([key, value], idx) => {
|
|
197
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", key, ": ", value] }, `meta-${idx}`));
|
|
198
|
+
});
|
|
199
|
+
lines.push(_jsx(Text, { children: " " }, "meta-space"));
|
|
200
|
+
}
|
|
201
|
+
// Raw JSON
|
|
202
|
+
lines.push(_jsx(Text, { color: colors.warning, bold: true, children: "Raw JSON" }, "json-title"));
|
|
203
|
+
const jsonLines = JSON.stringify(r, null, 2).split("\n");
|
|
204
|
+
jsonLines.forEach((line, idx) => {
|
|
205
|
+
lines.push(_jsxs(Text, { dimColor: true, children: [" ", line] }, `json-${idx}`));
|
|
206
|
+
});
|
|
207
|
+
return lines;
|
|
208
|
+
};
|
|
209
|
+
// Check if run is still in progress for polling
|
|
210
|
+
const isRunning = run.state === "running" || run.state === "scoring";
|
|
211
|
+
// Build breadcrumb prefix
|
|
212
|
+
const breadcrumbPrefix = [{ label: "Home" }, { label: "Benchmarks" }];
|
|
213
|
+
if (benchmarkRunId) {
|
|
214
|
+
breadcrumbPrefix.push({
|
|
215
|
+
label: `Run: ${benchmarkRunId.substring(0, 8)}...`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
breadcrumbPrefix.push({ label: "Scenario Runs" });
|
|
219
|
+
return (_jsx(ResourceDetailPage, { resource: run, resourceType: "Scenario Runs", getDisplayName: (r) => r.name || r.id, getId: (r) => r.id, getStatus: (r) => r.state, detailSections: detailSections, operations: operations, onOperation: handleOperation, onBack: goBack, buildDetailLines: buildDetailLines, pollResource: isRunning ? pollRun : undefined, breadcrumbPrefix: breadcrumbPrefix }));
|
|
220
|
+
}
|