letmecode 0.1.12 → 0.1.15
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/ink-app/dist/index.js +310 -149
- package/ink-app/dist/providers/antigravity.js +140 -555
- package/ink-app/dist/providers/claude.js +31 -10
- package/ink-app/dist/providers/index.js +1 -1
- package/ink-app/dist/reporting.js +11 -2
- package/package.json +1 -2
package/ink-app/dist/index.js
CHANGED
|
@@ -12,43 +12,16 @@ const ENABLE_MOUSE_TRACKING = `${ESC}[?1000h${ESC}[?1006h`;
|
|
|
12
12
|
const DISABLE_MOUSE_TRACKING = `${ESC}[?1006l${ESC}[?1000l`;
|
|
13
13
|
// SGR mouse report: ESC [ < button ; column ; row, ending in M (press) or m (release).
|
|
14
14
|
const SGR_MOUSE_SEQUENCE = new RegExp(`${ESC}\\[<(\\d+);(\\d+);(\\d+)([Mm])`, "g");
|
|
15
|
-
const
|
|
15
|
+
const DETAIL_TABS = [
|
|
16
16
|
{ id: "limit-windows", label: "Limits" },
|
|
17
17
|
{ id: "summary", label: "Summary" },
|
|
18
|
-
{ id: "day-to-day-analyses", label: "
|
|
19
|
-
{ id: "usage-by-model", label: "
|
|
18
|
+
{ id: "day-to-day-analyses", label: "Daily" },
|
|
19
|
+
{ id: "usage-by-model", label: "Models" }
|
|
20
20
|
];
|
|
21
21
|
const CODEX_CREDIT_COST_USD = 0.01;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
window: 8,
|
|
26
|
-
used: 10,
|
|
27
|
-
date: 17,
|
|
28
|
-
value: 10
|
|
29
|
-
};
|
|
30
|
-
const MODEL_USAGE_COLUMNS = {
|
|
31
|
-
model: 17,
|
|
32
|
-
input: 11,
|
|
33
|
-
output: 10,
|
|
34
|
-
cacheRead: 11,
|
|
35
|
-
cacheWrite: 11,
|
|
36
|
-
cacheWrite5m: 10,
|
|
37
|
-
cacheWrite1h: 10,
|
|
38
|
-
credits: 12,
|
|
39
|
-
value: 12
|
|
40
|
-
};
|
|
41
|
-
const DAY_USAGE_COLUMNS = {
|
|
42
|
-
day: 11,
|
|
43
|
-
events: 6,
|
|
44
|
-
input: 11,
|
|
45
|
-
output: 10,
|
|
46
|
-
cacheRead: 11,
|
|
47
|
-
cacheWrite: 11,
|
|
48
|
-
cacheWrite5m: 10,
|
|
49
|
-
cacheWrite1h: 10,
|
|
50
|
-
value: 10
|
|
51
|
-
};
|
|
22
|
+
const LIMIT_TABLE_HEADERS = ["Scope", "Plan", "Window", "Used", "Start", "End", "API eq."];
|
|
23
|
+
const DAILY_TABLE_HEADERS = ["Day", "Ev", "Input", "Output", "C read", "C write", "API eq."];
|
|
24
|
+
const MODEL_TABLE_HEADERS = ["Model", "Input", "Output", "C read", "C write", "API eq."];
|
|
52
25
|
const COPILOT_ACTIONS = [
|
|
53
26
|
{ id: "vscode", label: "Start logging VS Code", enabled: true }
|
|
54
27
|
];
|
|
@@ -65,7 +38,7 @@ function App(props) {
|
|
|
65
38
|
const [providerStates, setProviderStates] = useState(providers.map((provider) => ({ provider, status: "loading" })));
|
|
66
39
|
const [selectedProviderId, setSelectedProviderId] = useState(providers[0]?.id ?? "");
|
|
67
40
|
const [hasUserSelectedProvider, setHasUserSelectedProvider] = useState(false);
|
|
68
|
-
const [
|
|
41
|
+
const [selectedDetailTabIndex, setSelectedDetailTabIndex] = useState(0);
|
|
69
42
|
const [selectedLimitRowIndex, setSelectedLimitRowIndex] = useState(0);
|
|
70
43
|
const [selectedDayRowIndex, setSelectedDayRowIndex] = useState(0);
|
|
71
44
|
const [selectedModelRowIndex, setSelectedModelRowIndex] = useState(0);
|
|
@@ -75,7 +48,7 @@ function App(props) {
|
|
|
75
48
|
const sortedProviderStates = React.useMemo(() => sortProviderStatesByUsage(providerStates), [providerStates]);
|
|
76
49
|
const selectedProviderIndex = Math.max(0, sortedProviderStates.findIndex((state) => state.provider.id === selectedProviderId));
|
|
77
50
|
const selectedProvider = sortedProviderStates[selectedProviderIndex];
|
|
78
|
-
const
|
|
51
|
+
const selectedDetailTab = DETAIL_TABS[selectedDetailTabIndex];
|
|
79
52
|
const limitRows = getLimitRows(selectedProvider);
|
|
80
53
|
const dayRows = getDayRows(selectedProvider);
|
|
81
54
|
const modelRows = getModelRows(selectedProvider);
|
|
@@ -145,9 +118,30 @@ function App(props) {
|
|
|
145
118
|
return;
|
|
146
119
|
}
|
|
147
120
|
if (regionId.startsWith("vtab:")) {
|
|
148
|
-
|
|
121
|
+
setSelectedDetailTabIndex(Number(regionId.slice("vtab:".length)));
|
|
149
122
|
}
|
|
150
123
|
});
|
|
124
|
+
const moveSelectedTableRow = useCallback((delta) => {
|
|
125
|
+
if (selectedDetailTab.id === "limit-windows") {
|
|
126
|
+
setSelectedLimitRowIndex(clampSelectionIndex(activeLimitRowIndex + delta, limitRows.length));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (selectedDetailTab.id === "usage-by-model") {
|
|
130
|
+
setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + delta, modelRows.length));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (selectedDetailTab.id === "day-to-day-analyses") {
|
|
134
|
+
setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + delta, dayRows.length));
|
|
135
|
+
}
|
|
136
|
+
}, [
|
|
137
|
+
activeDayRowIndex,
|
|
138
|
+
activeLimitRowIndex,
|
|
139
|
+
activeModelRowIndex,
|
|
140
|
+
dayRows.length,
|
|
141
|
+
limitRows.length,
|
|
142
|
+
modelRows.length,
|
|
143
|
+
selectedDetailTab.id
|
|
144
|
+
]);
|
|
151
145
|
useInput((input, key) => {
|
|
152
146
|
// Mouse reports arrive as SGR escape sequences and are handled by useMouseClick.
|
|
153
147
|
// Ink strips the leading ESC, leaving e.g. "[<0;10;5M" — never treat that as a key.
|
|
@@ -175,32 +169,12 @@ function App(props) {
|
|
|
175
169
|
return;
|
|
176
170
|
}
|
|
177
171
|
if (key.rightArrow) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (selectedVerticalTab.id === "usage-by-model") {
|
|
183
|
-
setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex + 1, modelRows.length));
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (selectedVerticalTab.id === "day-to-day-analyses") {
|
|
187
|
-
setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex + 1, dayRows.length));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
172
|
+
setSelectedDetailTabIndex((current) => (current + 1) % DETAIL_TABS.length);
|
|
173
|
+
return;
|
|
190
174
|
}
|
|
191
175
|
if (key.leftArrow) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (selectedVerticalTab.id === "usage-by-model") {
|
|
197
|
-
setSelectedModelRowIndex(clampSelectionIndex(activeModelRowIndex - 1, modelRows.length));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (selectedVerticalTab.id === "day-to-day-analyses") {
|
|
201
|
-
setSelectedDayRowIndex(clampSelectionIndex(activeDayRowIndex - 1, dayRows.length));
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
176
|
+
setSelectedDetailTabIndex((current) => (current - 1 + DETAIL_TABS.length) % DETAIL_TABS.length);
|
|
177
|
+
return;
|
|
204
178
|
}
|
|
205
179
|
if ((key.tab && !key.shift) || input === "]") {
|
|
206
180
|
setSelectedProviderId(sortedProviderStates[(selectedProviderIndex + 1) % sortedProviderStates.length].provider.id);
|
|
@@ -214,14 +188,14 @@ function App(props) {
|
|
|
214
188
|
return;
|
|
215
189
|
}
|
|
216
190
|
if (key.downArrow || input === "j") {
|
|
217
|
-
|
|
191
|
+
moveSelectedTableRow(1);
|
|
218
192
|
return;
|
|
219
193
|
}
|
|
220
194
|
if (key.upArrow || input === "k") {
|
|
221
|
-
|
|
195
|
+
moveSelectedTableRow(-1);
|
|
222
196
|
}
|
|
223
197
|
});
|
|
224
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "cyan", children: "
|
|
198
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Provider " }), sortedProviderStates.map((state) => (_jsx(ProviderTab, { label: state.provider.label, active: state.provider.id === selectedProvider.provider.id, status: state.status, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id)))] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "View " }), DETAIL_TABS.map((tab, index) => (_jsx(DetailTab, { label: tab.label, active: index === selectedDetailTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id)))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, overflow: "hidden", children: _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRow: selectedLimitRow, selectedDayRow: selectedDayRow, selectedModelRow: selectedModelRow }), _jsx(CopilotActionsPanel, { providerState: selectedProvider, actionMessage: copilotActionMessage, selectedActionIndex: selectedCopilotActionIndex }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }), _jsx(Text, { color: "gray", children: "Tab provider \u00B7 \u2190/\u2192 view \u00B7 \u2191/\u2193 row \u00B7 q quit" })] }));
|
|
225
199
|
}
|
|
226
200
|
function CopilotActionsPanel(props) {
|
|
227
201
|
if (props.providerState.provider.id !== "copilot") {
|
|
@@ -257,16 +231,23 @@ function formatCopilotLoggingResult(result) {
|
|
|
257
231
|
}
|
|
258
232
|
function ProviderTab(props) {
|
|
259
233
|
const statusColor = props.status === "error" ? "red" : props.status === "loading" ? "yellow" : "green";
|
|
260
|
-
const tabLabel = props.active ? `
|
|
261
|
-
return (_jsx(Box, { marginRight:
|
|
234
|
+
const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
|
|
235
|
+
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { color: statusColor, bold: props.active, children: tabLabel }) }));
|
|
262
236
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
237
|
+
function DetailTab(props) {
|
|
238
|
+
const tabLabel = props.active ? `[${props.label}]` : ` ${props.label} `;
|
|
239
|
+
return (_jsx(Box, { marginRight: 2, ref: props.regionRef, children: _jsx(Text, { wrap: "truncate-end", bold: props.active, children: tabLabel }) }));
|
|
265
240
|
}
|
|
266
241
|
function SummaryPanel(props) {
|
|
267
242
|
const { summary } = props.stats;
|
|
268
|
-
const
|
|
269
|
-
|
|
243
|
+
const totals = summary.totals;
|
|
244
|
+
const period = resolveSummaryPeriod(props.stats.dayUsage);
|
|
245
|
+
const cacheRatio = resolveCacheRatio(totals);
|
|
246
|
+
const averageTokensPerEvent = totals.eventCount > 0 ? totals.totalTokens / totals.eventCount : 0;
|
|
247
|
+
const costPerEvent = totals.eventCount > 0
|
|
248
|
+
? (totals.estimatedCredits * CODEX_CREDIT_COST_USD) / totals.eventCount
|
|
249
|
+
: 0;
|
|
250
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [props.stats.providerLabel, " overview"] }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 45, children: [_jsx(DetailRow, { label: "Plan", value: summary.distinctPlanTypes.join(", ") || "none" }), _jsx(DetailRow, { label: "Models", value: summary.distinctModels.join(", ") || "none" }), _jsx(DetailRow, { label: "Period", value: period }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: "Usage" }), _jsx(DetailRow, { label: "Events", value: formatInteger(totals.eventCount) }), _jsx(DetailRow, { label: "Input", value: formatOverviewTokenCount(totals.inputTokens) }), _jsx(DetailRow, { label: "Output", value: formatOverviewTokenCount(totals.outputTokens) }), _jsx(DetailRow, { label: "Cache read", value: formatCacheOverviewTokenCount(totals, totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Reasoning", value: formatOverviewTokenCount(totals.reasoningOutputTokens) }), _jsx(DetailRow, { label: "Total", value: formatOverviewTokenCount(totals.totalTokens) }), _jsx(DetailRow, { label: "API equiv.", value: formatUsageUsd(totals) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Efficiency" }), _jsx(DetailRow, { label: "Cache ratio", value: formatPercent(cacheRatio) }), _jsx(DetailRow, { label: "Input/output", value: formatInputOutputRatio(totals) }), _jsx(DetailRow, { label: "Avg/event", value: `${formatOverviewTokenCount(averageTokensPerEvent)} tokens` }), _jsx(DetailRow, { label: "Cost/event", value: formatUnitUsd(costPerEvent) }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", children: "Data source" }), _jsx(DetailRow, { label: "Files", value: formatInteger(summary.filesScanned) }), _jsx(DetailRow, { label: "Lines", value: formatInteger(summary.linesRead) }), _jsx(DetailRow, { label: "Path", value: summary.rootPath })] })] })] }));
|
|
270
251
|
}
|
|
271
252
|
function ContentPanel(props) {
|
|
272
253
|
if (props.providerState.status === "loading") {
|
|
@@ -287,62 +268,23 @@ function ContentPanel(props) {
|
|
|
287
268
|
return (_jsx(UsageByModelPanel, { stats: props.providerState.stats, selectedModelId: props.selectedModelId, availableHeight: props.availableHeight }));
|
|
288
269
|
}
|
|
289
270
|
function LimitWindowsPanel(props) {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
{ key: "primary-title", text: "Primary Limit Windows", bold: true },
|
|
293
|
-
...buildLimitWindowSectionLines("primary", props.stats.primaryLimitWindows, props.selectedRowKey),
|
|
294
|
-
...(hasPrimaryWindows
|
|
295
|
-
? [{ key: "section-separator", text: buildLimitSectionSeparatorLine(), color: "gray" }]
|
|
296
|
-
: [{ key: "section-gap", text: "" }]),
|
|
297
|
-
{ key: "secondary-title", text: "Secondary Limit Windows", bold: true },
|
|
298
|
-
...buildLimitWindowSectionLines("secondary", props.stats.secondaryLimitWindows, props.selectedRowKey)
|
|
299
|
-
];
|
|
300
|
-
return (_jsx(ScrollableLineViewport, { bodyLines: bodyLines, selectedBodyLineKey: props.selectedRowKey ? `limit-row:${props.selectedRowKey}` : undefined, availableHeight: props.availableHeight }));
|
|
271
|
+
const tableLines = buildLimitWindowTableLines(props.stats, props.selectedRowKey);
|
|
272
|
+
return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedRowKey ? `limit-row:${props.selectedRowKey}` : undefined, availableHeight: props.availableHeight }));
|
|
301
273
|
}
|
|
302
274
|
function UsageByModelPanel(props) {
|
|
303
275
|
if (props.stats.modelUsage.length === 0) {
|
|
304
276
|
return _jsx(Text, { color: "gray", children: "No model usage found." });
|
|
305
277
|
}
|
|
306
278
|
const totals = props.stats.summary.totals;
|
|
307
|
-
const
|
|
308
|
-
return (_jsx(ScrollableLineViewport, {
|
|
309
|
-
{
|
|
310
|
-
key: "model-header",
|
|
311
|
-
text: isCodexProvider
|
|
312
|
-
? `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} value`
|
|
313
|
-
: `${pad("model", MODEL_USAGE_COLUMNS.model)} ${pad("input", MODEL_USAGE_COLUMNS.input)} ${pad("output", MODEL_USAGE_COLUMNS.output)} ${pad("cacheRead", MODEL_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", MODEL_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", MODEL_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", MODEL_USAGE_COLUMNS.cacheWrite1h)} ${pad("credits", MODEL_USAGE_COLUMNS.credits)} value`,
|
|
314
|
-
color: "gray"
|
|
315
|
-
}
|
|
316
|
-
], bodyLines: props.stats.modelUsage.map((row) => ({
|
|
317
|
-
key: `model-row:${row.modelId}`,
|
|
318
|
-
text: formatModelUsageRow(row.modelId, row.totals, isCodexProvider),
|
|
319
|
-
inverse: props.selectedModelId === row.modelId,
|
|
320
|
-
color: props.selectedModelId === row.modelId ? "cyan" : undefined
|
|
321
|
-
})), footerLines: [
|
|
322
|
-
{
|
|
323
|
-
key: "model-total",
|
|
324
|
-
text: formatModelUsageRow("TOTAL", totals, isCodexProvider),
|
|
325
|
-
color: "cyan"
|
|
326
|
-
}
|
|
327
|
-
], selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
|
|
279
|
+
const tableLines = buildModelUsageTableLines(props.stats.modelUsage, totals, props.selectedModelId);
|
|
280
|
+
return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedModelId ? `model-row:${props.selectedModelId}` : undefined, availableHeight: props.availableHeight }));
|
|
328
281
|
}
|
|
329
282
|
function DayToDayPanel(props) {
|
|
330
283
|
if (props.stats.dayUsage.length === 0) {
|
|
331
284
|
return _jsx(Text, { color: "gray", children: "No day-by-day usage found." });
|
|
332
285
|
}
|
|
333
|
-
const
|
|
334
|
-
return (_jsx(ScrollableLineViewport, {
|
|
335
|
-
{
|
|
336
|
-
key: "day-header",
|
|
337
|
-
text: `${pad("day", DAY_USAGE_COLUMNS.day)} ${pad("events", DAY_USAGE_COLUMNS.events)} ${pad("input", DAY_USAGE_COLUMNS.input)} ${pad("output", DAY_USAGE_COLUMNS.output)} ${pad("cacheRead", DAY_USAGE_COLUMNS.cacheRead)} ${pad("cacheWrite", DAY_USAGE_COLUMNS.cacheWrite)} ${pad("cacheW5m", DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad("cacheW1h", DAY_USAGE_COLUMNS.cacheWrite1h)} value`,
|
|
338
|
-
color: "gray"
|
|
339
|
-
}
|
|
340
|
-
], bodyLines: props.stats.dayUsage.map((row) => ({
|
|
341
|
-
key: `day-row:${row.dayKey}`,
|
|
342
|
-
text: `${pad(formatUtcDay(row.dayKey), DAY_USAGE_COLUMNS.day)} ${pad(formatCompactTokenCount(row.totals.eventCount), DAY_USAGE_COLUMNS.events)} ${pad(formatCompactTokenCount(row.totals.inputTokens), DAY_USAGE_COLUMNS.input)} ${pad(formatCompactTokenCount(row.totals.outputTokens), DAY_USAGE_COLUMNS.output)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens), DAY_USAGE_COLUMNS.cacheRead)} ${pad(formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens), DAY_USAGE_COLUMNS.cacheWrite)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite5mInputTokens), DAY_USAGE_COLUMNS.cacheWrite5m)} ${pad(formatOptionalCompactTokens(row.totals.cacheWrite1hInputTokens), DAY_USAGE_COLUMNS.cacheWrite1h)} ${pad(formatUsageUsd(row.totals), DAY_USAGE_COLUMNS.value)}`,
|
|
343
|
-
inverse: props.selectedDayKey === row.dayKey,
|
|
344
|
-
color: props.selectedDayKey === row.dayKey ? "cyan" : undefined
|
|
345
|
-
})), selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
|
|
286
|
+
const tableLines = buildDailyUsageTableLines(props.stats.dayUsage, props.selectedDayKey);
|
|
287
|
+
return (_jsx(ScrollableLineViewport, { ...tableLines, selectedBodyLineKey: props.selectedDayKey ? `day-row:${props.selectedDayKey}` : undefined, availableHeight: props.availableHeight }));
|
|
346
288
|
}
|
|
347
289
|
function ScrollableLineViewport(props) {
|
|
348
290
|
const headerLines = props.headerLines ?? [];
|
|
@@ -362,29 +304,153 @@ function ScrollableLineViewport(props) {
|
|
|
362
304
|
function ScrollableViewportLine(props) {
|
|
363
305
|
return (_jsx(Text, { bold: props.line.bold, color: props.line.color, inverse: props.line.inverse, wrap: "truncate-end", children: props.line.text }));
|
|
364
306
|
}
|
|
365
|
-
function
|
|
366
|
-
|
|
367
|
-
|
|
307
|
+
function createTextTable(headers, rows) {
|
|
308
|
+
return {
|
|
309
|
+
headers: [...headers],
|
|
310
|
+
widths: headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)))
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function buildTableBorder(table, left, middle, right) {
|
|
314
|
+
return `${left}${table.widths.map((width) => "─".repeat(width + 2)).join(middle)}${right}`;
|
|
315
|
+
}
|
|
316
|
+
function buildTableRow(table, cells) {
|
|
317
|
+
return `│${table.widths.map((width, index) => ` ${pad(cells[index] ?? "", width)} `).join("│")}│`;
|
|
318
|
+
}
|
|
319
|
+
function buildTextTableLines(options) {
|
|
320
|
+
if (options.rows.length === 0 && !options.totalRow) {
|
|
321
|
+
return {
|
|
322
|
+
bodyLines: [
|
|
323
|
+
{ key: `${options.lineKeyPrefix}-title`, text: options.title, bold: true },
|
|
324
|
+
{ key: `${options.lineKeyPrefix}-empty`, text: "No rows found.", color: "gray" }
|
|
325
|
+
]
|
|
326
|
+
};
|
|
368
327
|
}
|
|
369
|
-
|
|
328
|
+
const tableRows = options.totalRow
|
|
329
|
+
? [...options.rows, options.totalRow]
|
|
330
|
+
: options.rows;
|
|
331
|
+
const table = createTextTable(options.headers, tableRows.map((row) => row.cells));
|
|
332
|
+
const headerLines = [
|
|
333
|
+
{ key: `${options.lineKeyPrefix}-title`, text: options.title, bold: true },
|
|
370
334
|
{
|
|
371
|
-
key: `${
|
|
372
|
-
text:
|
|
335
|
+
key: `${options.lineKeyPrefix}-top-border`,
|
|
336
|
+
text: buildTableBorder(table, "┌", "┬", "┐"),
|
|
373
337
|
color: "gray"
|
|
374
338
|
},
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
339
|
+
{
|
|
340
|
+
key: `${options.lineKeyPrefix}-header`,
|
|
341
|
+
text: buildTableRow(table, table.headers),
|
|
342
|
+
color: "gray"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
key: `${options.lineKeyPrefix}-header-border`,
|
|
346
|
+
text: buildTableBorder(table, "├", "┼", "┤"),
|
|
347
|
+
color: "gray"
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
const bodyLines = options.rows.flatMap((row) => {
|
|
351
|
+
const isSelected = options.selectedRowKey === row.key;
|
|
352
|
+
const lines = [{
|
|
353
|
+
key: row.key,
|
|
354
|
+
text: buildTableRow(table, row.cells),
|
|
383
355
|
inverse: isSelected,
|
|
384
356
|
color: isSelected ? "cyan" : undefined
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
|
|
357
|
+
}];
|
|
358
|
+
if (options.separatorAfterRowKeys?.has(row.key)) {
|
|
359
|
+
lines.push({
|
|
360
|
+
key: `${row.key}-separator`,
|
|
361
|
+
text: buildTableBorder(table, "├", "┼", "┤"),
|
|
362
|
+
color: "gray"
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return lines;
|
|
366
|
+
});
|
|
367
|
+
const footerLines = [];
|
|
368
|
+
if (options.totalRow) {
|
|
369
|
+
footerLines.push({
|
|
370
|
+
key: `${options.lineKeyPrefix}-total-border`,
|
|
371
|
+
text: buildTableBorder(table, "├", "┼", "┤"),
|
|
372
|
+
color: "gray"
|
|
373
|
+
}, {
|
|
374
|
+
key: options.totalRow.key,
|
|
375
|
+
text: buildTableRow(table, options.totalRow.cells),
|
|
376
|
+
color: "cyan"
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
footerLines.push({
|
|
380
|
+
key: `${options.lineKeyPrefix}-bottom-border`,
|
|
381
|
+
text: buildTableBorder(table, "└", "┴", "┘"),
|
|
382
|
+
color: "gray"
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
headerLines,
|
|
386
|
+
bodyLines,
|
|
387
|
+
footerLines
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function buildLimitWindowTableLines(stats, selectedRowKey) {
|
|
391
|
+
const primaryRows = stats.primaryLimitWindows.map((window) => buildLimitWindowTableRow(window));
|
|
392
|
+
const secondaryRows = stats.secondaryLimitWindows.map((window) => buildLimitWindowTableRow(window));
|
|
393
|
+
const separatorAfterRowKeys = primaryRows.length > 0 && secondaryRows.length > 0
|
|
394
|
+
? new Set([primaryRows[primaryRows.length - 1].key])
|
|
395
|
+
: undefined;
|
|
396
|
+
return buildTextTableLines({
|
|
397
|
+
title: "Limits",
|
|
398
|
+
lineKeyPrefix: "limits",
|
|
399
|
+
headers: LIMIT_TABLE_HEADERS,
|
|
400
|
+
rows: [...primaryRows, ...secondaryRows],
|
|
401
|
+
selectedRowKey: selectedRowKey ? `limit-row:${selectedRowKey}` : undefined,
|
|
402
|
+
separatorAfterRowKeys
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function buildLimitWindowTableRow(window) {
|
|
406
|
+
return {
|
|
407
|
+
key: `limit-row:${getLimitRowKey(window)}`,
|
|
408
|
+
cells: [
|
|
409
|
+
window.scope,
|
|
410
|
+
window.planType,
|
|
411
|
+
formatCompactWindowMinutes(window.windowMinutes),
|
|
412
|
+
formatUsedPercentRange(window.minUsedPercent, window.maxUsedPercent),
|
|
413
|
+
formatCompactLocalDateTime(window.startTimeUtcIso),
|
|
414
|
+
formatCompactLocalDateTime(window.endTimeUtcIso),
|
|
415
|
+
formatUsd(window.totals.estimatedCredits * CODEX_CREDIT_COST_USD)
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function buildDailyUsageTableLines(rows, selectedDayKey) {
|
|
420
|
+
return buildTextTableLines({
|
|
421
|
+
title: "Daily usage",
|
|
422
|
+
lineKeyPrefix: "daily",
|
|
423
|
+
headers: DAILY_TABLE_HEADERS,
|
|
424
|
+
rows: rows.map((row) => ({
|
|
425
|
+
key: `day-row:${row.dayKey}`,
|
|
426
|
+
cells: [
|
|
427
|
+
formatUtcDay(row.dayKey),
|
|
428
|
+
formatCompactTokenCount(row.totals.eventCount),
|
|
429
|
+
formatCompactTokenCount(row.totals.inputTokens),
|
|
430
|
+
formatCompactTokenCount(row.totals.outputTokens),
|
|
431
|
+
formatCompactCacheTokens(row.totals, row.totals.cacheReadInputTokens),
|
|
432
|
+
formatCompactCacheTokens(row.totals, row.totals.cacheWriteInputTokens),
|
|
433
|
+
formatUsageUsd(row.totals)
|
|
434
|
+
]
|
|
435
|
+
})),
|
|
436
|
+
selectedRowKey: selectedDayKey ? `day-row:${selectedDayKey}` : undefined
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function buildModelUsageTableLines(rows, totals, selectedModelId) {
|
|
440
|
+
return buildTextTableLines({
|
|
441
|
+
title: "Model usage",
|
|
442
|
+
lineKeyPrefix: "model",
|
|
443
|
+
headers: MODEL_TABLE_HEADERS,
|
|
444
|
+
rows: rows.map((row) => ({
|
|
445
|
+
key: `model-row:${row.modelId}`,
|
|
446
|
+
cells: formatModelUsageTableCells(row.modelId, row.totals)
|
|
447
|
+
})),
|
|
448
|
+
selectedRowKey: selectedModelId ? `model-row:${selectedModelId}` : undefined,
|
|
449
|
+
totalRow: {
|
|
450
|
+
key: "model-total",
|
|
451
|
+
cells: formatModelUsageTableCells("TOTAL", totals)
|
|
452
|
+
}
|
|
453
|
+
});
|
|
388
454
|
}
|
|
389
455
|
function SelectionDetailsPanel(props) {
|
|
390
456
|
if (props.providerState.status !== "ready") {
|
|
@@ -392,17 +458,23 @@ function SelectionDetailsPanel(props) {
|
|
|
392
458
|
}
|
|
393
459
|
if (props.tabId === "limit-windows" && props.selectedLimitRow) {
|
|
394
460
|
const row = props.selectedLimitRow;
|
|
395
|
-
return (_jsxs(Box, {
|
|
461
|
+
return (_jsx(DetailsPanelFrame, { children: _jsxs(Box, { children: [_jsxs(Box, { flexDirection: "column", width: 25, children: [_jsx(DetailRow, { label: "Plan", value: row.planType }), _jsx(DetailRow, { label: "Window", value: formatCompactWindowMinutes(row.windowMinutes) }), _jsx(DetailRow, { label: "Usage", value: formatUsedPercentRange(row.minUsedPercent, row.maxUsedPercent) }), _jsx(DetailRow, { label: "Events", value: formatInteger(row.eventCount) }), _jsx(DetailRow, { label: "API eq.", value: formatUsageUsd(row.totals) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(DetailRow, { label: "Period", value: `${formatCompactLocalDateTime(row.startTimeUtcIso)} → ${formatCompactLocalDateTime(row.endTimeUtcIso)}` }), _jsx(DetailRow, { label: "Input", value: formatInteger(row.totals.inputTokens) }), _jsx(DetailRow, { label: "Cache read", value: formatCacheTokens(row.totals, row.totals.cacheReadInputTokens) }), _jsx(DetailRow, { label: "Cache write", value: formatCacheTokens(row.totals, row.totals.cacheWriteInputTokens) }), _jsx(DetailRow, { label: "Output", value: formatInteger(row.totals.outputTokens) }), _jsx(DetailRow, { label: "Total", value: formatInteger(row.totals.totalTokens) })] })] }) }));
|
|
396
462
|
}
|
|
397
463
|
if (props.tabId === "day-to-day-analyses" && props.selectedDayRow) {
|
|
398
464
|
const row = props.selectedDayRow;
|
|
399
|
-
return (_jsxs(
|
|
465
|
+
return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Text, { children: ["day: ", formatUtcDay(row.dayKey), " events: ", formatInteger(row.totals.eventCount), " models: ", formatInteger(row.distinctModels.length), " plans: ", formatInteger(row.distinctPlanTypes.length)] }), _jsxs(Text, { children: ["range: ", formatEventRange(row.firstEventUtcIso, row.lastEventUtcIso)] }), _jsxs(Text, { children: ["models: ", row.distinctModels.join(", ") || "none"] }), _jsxs(Text, { children: ["plans: ", row.distinctPlanTypes.join(", ") || "none"] }), _jsx(UsageTotalsDetails, { totals: row.totals })] }));
|
|
400
466
|
}
|
|
401
467
|
if (props.tabId === "usage-by-model" && props.selectedModelRow) {
|
|
402
|
-
return (_jsxs(
|
|
468
|
+
return (_jsxs(DetailsPanelFrame, { children: [_jsxs(Text, { children: ["model: ", props.selectedModelRow.modelId, " events: ", formatInteger(props.selectedModelRow.totals.eventCount)] }), _jsx(UsageTotalsDetails, { totals: props.selectedModelRow.totals, modelId: props.selectedModelRow.modelId })] }));
|
|
403
469
|
}
|
|
404
470
|
return null;
|
|
405
471
|
}
|
|
472
|
+
function DetailsPanelFrame(props) {
|
|
473
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Details" }), props.children] }));
|
|
474
|
+
}
|
|
475
|
+
function DetailRow(props) {
|
|
476
|
+
return (_jsxs(Text, { children: [pad(props.label, 14), props.value] }));
|
|
477
|
+
}
|
|
406
478
|
function UsageTotalsDetails(props) {
|
|
407
479
|
const { totals } = props;
|
|
408
480
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(UsageBreakdownLines, { totals: totals }), _jsxs(Text, { children: ["Burned tokens for: ", formatUsageUsd(totals, props.modelId)] }), _jsxs(Text, { children: ["IpO: ", formatInputPerOutput(totals)] })] }));
|
|
@@ -410,6 +482,28 @@ function UsageTotalsDetails(props) {
|
|
|
410
482
|
function formatInteger(value) {
|
|
411
483
|
return Math.round(value).toLocaleString("en-US");
|
|
412
484
|
}
|
|
485
|
+
function formatOverviewTokenCount(value) {
|
|
486
|
+
const roundedValue = Math.round(value);
|
|
487
|
+
if (roundedValue >= 1000000000) {
|
|
488
|
+
return `${formatFixedCompactNumber(roundedValue / 1000000000)}B`;
|
|
489
|
+
}
|
|
490
|
+
if (roundedValue >= 1000000) {
|
|
491
|
+
return `${formatFixedCompactNumber(roundedValue / 1000000)}M`;
|
|
492
|
+
}
|
|
493
|
+
if (roundedValue >= 1000) {
|
|
494
|
+
return `${formatFixedCompactNumber(roundedValue / 1000)}K`;
|
|
495
|
+
}
|
|
496
|
+
return formatInteger(roundedValue);
|
|
497
|
+
}
|
|
498
|
+
function formatCacheOverviewTokenCount(totals, value) {
|
|
499
|
+
return totals.cacheStatus === "unavailable" ? "-" : formatOverviewTokenCount(value);
|
|
500
|
+
}
|
|
501
|
+
function formatFixedCompactNumber(value) {
|
|
502
|
+
return value.toLocaleString("en-US", {
|
|
503
|
+
maximumFractionDigits: value < 10 ? 2 : 1,
|
|
504
|
+
minimumFractionDigits: 0
|
|
505
|
+
});
|
|
506
|
+
}
|
|
413
507
|
function formatCompactTokenCount(value) {
|
|
414
508
|
const roundedValue = Math.round(value);
|
|
415
509
|
if (roundedValue < 1000) {
|
|
@@ -465,18 +559,51 @@ function formatUsd(value) {
|
|
|
465
559
|
maximumFractionDigits: 2
|
|
466
560
|
});
|
|
467
561
|
}
|
|
562
|
+
function formatUnitUsd(value) {
|
|
563
|
+
if (!Number.isFinite(value)) {
|
|
564
|
+
return "-";
|
|
565
|
+
}
|
|
566
|
+
return value.toLocaleString("en-US", {
|
|
567
|
+
currency: "USD",
|
|
568
|
+
style: "currency",
|
|
569
|
+
minimumFractionDigits: value < 0.01 && value > 0 ? 4 : 3,
|
|
570
|
+
maximumFractionDigits: value < 0.01 && value > 0 ? 4 : 3
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function formatPercent(value) {
|
|
574
|
+
if (!Number.isFinite(value)) {
|
|
575
|
+
return "-";
|
|
576
|
+
}
|
|
577
|
+
return `${value.toLocaleString("en-US", {
|
|
578
|
+
maximumFractionDigits: 1,
|
|
579
|
+
minimumFractionDigits: 0
|
|
580
|
+
})}%`;
|
|
581
|
+
}
|
|
582
|
+
function resolveCacheRatio(totals) {
|
|
583
|
+
if (totals.cacheStatus === "unavailable") {
|
|
584
|
+
return NaN;
|
|
585
|
+
}
|
|
586
|
+
const inputPool = totals.inputTokens +
|
|
587
|
+
totals.cacheReadInputTokens +
|
|
588
|
+
totals.cacheWriteInputTokens;
|
|
589
|
+
return inputPool > 0
|
|
590
|
+
? (totals.cacheReadInputTokens / inputPool) * 100
|
|
591
|
+
: 0;
|
|
592
|
+
}
|
|
593
|
+
function formatInputOutputRatio(totals) {
|
|
594
|
+
if (totals.outputTokens <= 0) {
|
|
595
|
+
return "-";
|
|
596
|
+
}
|
|
597
|
+
return `${(totals.inputTokens / totals.outputTokens).toLocaleString("en-US", {
|
|
598
|
+
maximumFractionDigits: 1,
|
|
599
|
+
minimumFractionDigits: 0
|
|
600
|
+
})} : 1`;
|
|
601
|
+
}
|
|
468
602
|
function formatUsedPercentRange(minUsedPercent, maxUsedPercent) {
|
|
603
|
+
const fmt = (v) => `${Math.round(v)}%`;
|
|
469
604
|
return minUsedPercent === maxUsedPercent
|
|
470
|
-
?
|
|
471
|
-
: `${minUsedPercent}
|
|
472
|
-
}
|
|
473
|
-
function buildLimitSectionSeparatorLine() {
|
|
474
|
-
return "─".repeat(LIMIT_WINDOW_COLUMNS.plan +
|
|
475
|
-
LIMIT_WINDOW_COLUMNS.window +
|
|
476
|
-
LIMIT_WINDOW_COLUMNS.used +
|
|
477
|
-
LIMIT_WINDOW_COLUMNS.date * 2 +
|
|
478
|
-
LIMIT_WINDOW_COLUMNS.value +
|
|
479
|
-
5);
|
|
605
|
+
? fmt(minUsedPercent)
|
|
606
|
+
: `${fmt(minUsedPercent)}–${fmt(maxUsedPercent)}`;
|
|
480
607
|
}
|
|
481
608
|
function formatWindowMinutes(value) {
|
|
482
609
|
const hours = value / 60;
|
|
@@ -485,6 +612,13 @@ function formatWindowMinutes(value) {
|
|
|
485
612
|
}
|
|
486
613
|
return `${hours.toFixed(2)}h`;
|
|
487
614
|
}
|
|
615
|
+
function formatCompactWindowMinutes(value) {
|
|
616
|
+
const hours = value / 60;
|
|
617
|
+
if (hours >= 24) {
|
|
618
|
+
return `${formatCompactNumber(hours / 24)}d`;
|
|
619
|
+
}
|
|
620
|
+
return `${formatCompactNumber(hours)}h`;
|
|
621
|
+
}
|
|
488
622
|
function formatLocalDateTime(value) {
|
|
489
623
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
490
624
|
month: "short",
|
|
@@ -498,6 +632,29 @@ function formatLocalDateTime(value) {
|
|
|
498
632
|
const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
499
633
|
return `${lookup.day} ${lookup.month} ${lookup.year} ${lookup.hour}:${lookup.minute}`;
|
|
500
634
|
}
|
|
635
|
+
function formatCompactLocalDateTime(value) {
|
|
636
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
637
|
+
month: "short",
|
|
638
|
+
day: "2-digit",
|
|
639
|
+
hour: "2-digit",
|
|
640
|
+
minute: "2-digit",
|
|
641
|
+
hour12: false,
|
|
642
|
+
hourCycle: "h23"
|
|
643
|
+
}).formatToParts(new Date(value));
|
|
644
|
+
const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
645
|
+
return `${lookup.day} ${lookup.month} ${lookup.hour}:${lookup.minute}`;
|
|
646
|
+
}
|
|
647
|
+
function resolveSummaryPeriod(dayUsage) {
|
|
648
|
+
const timestamps = dayUsage.flatMap((row) => [
|
|
649
|
+
row.firstEventUtcIso,
|
|
650
|
+
row.lastEventUtcIso
|
|
651
|
+
]).filter((value) => Boolean(value));
|
|
652
|
+
if (timestamps.length === 0) {
|
|
653
|
+
return "-";
|
|
654
|
+
}
|
|
655
|
+
const sortedTimestamps = timestamps.sort();
|
|
656
|
+
return `${formatCompactLocalDateTime(sortedTimestamps[0])} → ${formatCompactLocalDateTime(sortedTimestamps[sortedTimestamps.length - 1])}`;
|
|
657
|
+
}
|
|
501
658
|
function formatUtcDay(value) {
|
|
502
659
|
if (value === "unknown") {
|
|
503
660
|
return "-";
|
|
@@ -520,12 +677,16 @@ function formatEventRange(firstEventUtcIso, lastEventUtcIso) {
|
|
|
520
677
|
function pad(value, length) {
|
|
521
678
|
return value.length >= length ? value.slice(0, length) : value.padEnd(length);
|
|
522
679
|
}
|
|
523
|
-
function
|
|
680
|
+
function formatModelUsageTableCells(modelId, totals) {
|
|
524
681
|
const displayModelId = modelId === "unknown" ? "-" : modelId;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
682
|
+
return [
|
|
683
|
+
displayModelId,
|
|
684
|
+
formatCompactTokenCount(totals.inputTokens),
|
|
685
|
+
formatCompactTokenCount(totals.outputTokens),
|
|
686
|
+
formatCompactCacheTokens(totals, totals.cacheReadInputTokens),
|
|
687
|
+
formatCompactCacheTokens(totals, totals.cacheWriteInputTokens),
|
|
688
|
+
formatUsageUsd(totals, modelId)
|
|
689
|
+
];
|
|
529
690
|
}
|
|
530
691
|
function UsageBreakdownLines(props) {
|
|
531
692
|
const { totals } = props;
|