@tokentop/ttop 0.2.2 → 0.4.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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +42 -38
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +36 -23
- package/src/plugins/notifications/visual-flash.ts +31 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +68 -57
- package/src/plugins/registry.ts +80 -71
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/persistence-service.ts +206 -0
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/retention.ts +146 -0
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +314 -183
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +320 -169
- package/src/tui/components/SettingsModal.tsx +593 -269
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +164 -151
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +238 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +95 -149
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +7 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +164 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +495 -243
- package/src/tui/views/RealTimeDashboard.tsx +176 -116
- package/src/tui/views/SettingsView.tsx +225 -189
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { useConfig } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
1
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import type { AgentSessionAggregate, AgentSessionStream } from "../../agents/types.ts";
|
|
4
|
+
import { useConfig } from "../contexts/ConfigContext.tsx";
|
|
5
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
6
|
+
import { useTimeWindow } from "../contexts/TimeWindowContext.tsx";
|
|
7
|
+
import { usePulse } from "../hooks/usePulse.ts";
|
|
8
8
|
|
|
9
|
-
export type DriverDimension =
|
|
9
|
+
export type DriverDimension = "model" | "project" | "agent";
|
|
10
10
|
|
|
11
|
-
export type SidebarMode =
|
|
11
|
+
export type SidebarMode = "full" | "compact" | "minimal" | "micro" | "hidden";
|
|
12
12
|
|
|
13
13
|
export interface DriverStats {
|
|
14
14
|
id: string;
|
|
@@ -24,7 +24,7 @@ export interface DriverStats {
|
|
|
24
24
|
export interface SmartSidebarProps {
|
|
25
25
|
sessions: AgentSessionAggregate[];
|
|
26
26
|
budgetCost: number;
|
|
27
|
-
focusedPanel:
|
|
27
|
+
focusedPanel: "sessions" | "sidebar" | "limits";
|
|
28
28
|
dimension: DriverDimension;
|
|
29
29
|
selectedDriverIndex: number;
|
|
30
30
|
activeDriverFilter: string | null;
|
|
@@ -34,24 +34,24 @@ export interface SmartSidebarProps {
|
|
|
34
34
|
function interpolatePulseColor(intensity: number, baseColor: string, dimColor: string): string {
|
|
35
35
|
const dimAmount = 0.4;
|
|
36
36
|
const t = intensity * dimAmount;
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
const parseHex = (hex: string) => {
|
|
39
|
-
const h = hex.replace(
|
|
39
|
+
const h = hex.replace("#", "");
|
|
40
40
|
return {
|
|
41
41
|
r: parseInt(h.substring(0, 2), 16),
|
|
42
42
|
g: parseInt(h.substring(2, 4), 16),
|
|
43
43
|
b: parseInt(h.substring(4, 6), 16),
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
const base = parseHex(baseColor);
|
|
48
48
|
const dim = parseHex(dimColor);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
const r = Math.round(base.r + (dim.r - base.r) * t);
|
|
51
51
|
const g = Math.round(base.g + (dim.g - base.g) * t);
|
|
52
52
|
const b = Math.round(base.b + (dim.b - base.b) * t);
|
|
53
|
-
|
|
54
|
-
return `#${r.toString(16).padStart(2,
|
|
53
|
+
|
|
54
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function formatCurrency(val: number): string {
|
|
@@ -68,51 +68,56 @@ function formatBudget(val: number): string {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function formatDelta(val: number): string {
|
|
71
|
-
if (val < 0.01) return
|
|
71
|
+
if (val < 0.01) return "";
|
|
72
72
|
if (val >= 10) return `+$${val.toFixed(1)}`;
|
|
73
73
|
return `+$${val.toFixed(2)}`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function formatPercent(val: number): string {
|
|
77
|
-
if (val < 1) return
|
|
77
|
+
if (val < 1) return "<1%";
|
|
78
78
|
return `${Math.round(val)}%`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function truncateWithEllipsis(str: string, maxLen: number): string {
|
|
82
82
|
if (str.length <= maxLen) return str;
|
|
83
|
-
return str.slice(0, maxLen - 1) +
|
|
83
|
+
return str.slice(0, maxLen - 1) + "…";
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function getSidebarMode(width: number): SidebarMode {
|
|
87
|
-
if (width >= 140) return
|
|
88
|
-
if (width >= 105) return
|
|
89
|
-
if (width >= 95) return
|
|
90
|
-
if (width >= 85) return
|
|
91
|
-
return
|
|
87
|
+
if (width >= 140) return "full";
|
|
88
|
+
if (width >= 105) return "compact";
|
|
89
|
+
if (width >= 95) return "minimal";
|
|
90
|
+
if (width >= 85) return "micro";
|
|
91
|
+
return "hidden";
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function getSidebarWidth(mode: SidebarMode, terminalWidth: number): number {
|
|
95
|
-
if (mode ===
|
|
96
|
-
if (mode ===
|
|
97
|
-
if (mode ===
|
|
95
|
+
if (mode === "micro") return 28;
|
|
96
|
+
if (mode === "minimal") return 32;
|
|
97
|
+
if (mode === "compact") return 36;
|
|
98
98
|
if (terminalWidth >= 160) return 44;
|
|
99
99
|
if (terminalWidth >= 140) return 40;
|
|
100
100
|
return 38;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function extractRepoName(projectPath: string | undefined | null): string {
|
|
104
|
-
if (!projectPath) return
|
|
105
|
-
const normalized = projectPath.replace(/\\/g,
|
|
106
|
-
return normalized.split(
|
|
104
|
+
if (!projectPath) return "unknown";
|
|
105
|
+
const normalized = projectPath.replace(/\\/g, "/");
|
|
106
|
+
return normalized.split("/").filter(Boolean).pop() ?? "unknown";
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function makeProgressBar(
|
|
109
|
+
function makeProgressBar(
|
|
110
|
+
percent: number,
|
|
111
|
+
width: number,
|
|
112
|
+
filledChar = "█",
|
|
113
|
+
emptyChar = "·",
|
|
114
|
+
): string {
|
|
110
115
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
111
116
|
const filledWidth = Math.round((clamped / 100) * width);
|
|
112
117
|
return filledChar.repeat(filledWidth) + emptyChar.repeat(width - filledWidth);
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
function makeDivider(width: number, char =
|
|
120
|
+
function makeDivider(width: number, char = "─"): string {
|
|
116
121
|
return char.repeat(Math.max(0, width - 2));
|
|
117
122
|
}
|
|
118
123
|
|
|
@@ -127,27 +132,32 @@ function BudgetSection({ totalCost, budgetCost, mode, sidebarWidth }: BudgetSect
|
|
|
127
132
|
const colors = useColors();
|
|
128
133
|
const { config } = useConfig();
|
|
129
134
|
const { budgetType, budgetTypeLabel, budgetLock } = useTimeWindow();
|
|
130
|
-
|
|
131
|
-
const activeBudget =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
|
|
136
|
+
const activeBudget =
|
|
137
|
+
budgetType === "daily"
|
|
138
|
+
? config.budgets.daily
|
|
139
|
+
: budgetType === "weekly"
|
|
140
|
+
? config.budgets.weekly
|
|
141
|
+
: budgetType === "monthly"
|
|
142
|
+
? config.budgets.monthly
|
|
143
|
+
: null;
|
|
144
|
+
|
|
145
|
+
const hasBudgetLimit = activeBudget !== null && budgetType !== "none";
|
|
137
146
|
const budgetUsedPercent = hasBudgetLimit ? (budgetCost / activeBudget) * 100 : 0;
|
|
138
|
-
|
|
147
|
+
|
|
139
148
|
const isCritical = hasBudgetLimit && budgetUsedPercent >= config.alerts.criticalPercent;
|
|
140
|
-
const isWarning =
|
|
141
|
-
|
|
149
|
+
const isWarning =
|
|
150
|
+
hasBudgetLimit && budgetUsedPercent >= config.alerts.warningPercent && !isCritical;
|
|
151
|
+
|
|
142
152
|
const criticalPulseStep = usePulse({ enabled: isCritical, intervalMs: 80 });
|
|
143
153
|
const warningPulseStep = usePulse({ enabled: isWarning, intervalMs: 200 });
|
|
144
|
-
|
|
145
|
-
const pulseIntensity = isCritical
|
|
146
|
-
? Math.abs(Math.sin(criticalPulseStep * 0.3))
|
|
147
|
-
: isWarning
|
|
154
|
+
|
|
155
|
+
const pulseIntensity = isCritical
|
|
156
|
+
? Math.abs(Math.sin(criticalPulseStep * 0.3))
|
|
157
|
+
: isWarning
|
|
148
158
|
? Math.abs(Math.sin(warningPulseStep * 0.25))
|
|
149
159
|
: 0;
|
|
150
|
-
|
|
160
|
+
|
|
151
161
|
const getStatusColor = () => {
|
|
152
162
|
if (isCritical) {
|
|
153
163
|
return interpolatePulseColor(pulseIntensity, colors.error, colors.background);
|
|
@@ -157,82 +167,112 @@ function BudgetSection({ totalCost, budgetCost, mode, sidebarWidth }: BudgetSect
|
|
|
157
167
|
}
|
|
158
168
|
return colors.success;
|
|
159
169
|
};
|
|
160
|
-
|
|
170
|
+
|
|
161
171
|
const statusColor = hasBudgetLimit ? getStatusColor() : colors.text;
|
|
162
172
|
const innerWidth = sidebarWidth - 4;
|
|
163
173
|
const barWidth = Math.max(10, innerWidth - 6);
|
|
164
174
|
const bar = makeProgressBar(budgetUsedPercent, barWidth);
|
|
165
|
-
|
|
166
|
-
const isLocked = budgetLock !==
|
|
167
|
-
const lockSuffix = isLocked ?
|
|
168
|
-
const headerLabel =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
|
|
176
|
+
const isLocked = budgetLock !== "sync";
|
|
177
|
+
const lockSuffix = isLocked ? " [locked]" : "";
|
|
178
|
+
const headerLabel =
|
|
179
|
+
budgetType === "none" ? "TOTAL COST" : `${budgetTypeLabel.toUpperCase()} BUDGET${lockSuffix}`;
|
|
180
|
+
|
|
181
|
+
if (budgetType === "none") {
|
|
182
|
+
if (mode === "micro") {
|
|
183
|
+
return (
|
|
184
|
+
<box flexDirection="row" height={1} paddingX={1}>
|
|
185
|
+
<text fg={colors.textMuted} height={1}>
|
|
186
|
+
Total:{" "}
|
|
187
|
+
</text>
|
|
188
|
+
<text fg={colors.text} height={1}>
|
|
189
|
+
<strong>{formatCurrency(totalCost)}</strong>
|
|
190
|
+
</text>
|
|
191
|
+
</box>
|
|
192
|
+
);
|
|
178
193
|
}
|
|
179
|
-
|
|
194
|
+
|
|
180
195
|
return (
|
|
181
196
|
<box flexDirection="column" paddingX={1}>
|
|
182
|
-
<text fg={colors.textMuted} height={1}
|
|
183
|
-
|
|
197
|
+
<text fg={colors.textMuted} height={1}>
|
|
198
|
+
<strong>{headerLabel}</strong>
|
|
199
|
+
</text>
|
|
200
|
+
<text fg={colors.text} height={1}>
|
|
201
|
+
<strong>{formatCurrency(totalCost)}</strong>
|
|
202
|
+
</text>
|
|
184
203
|
</box>
|
|
185
204
|
);
|
|
186
205
|
}
|
|
187
|
-
|
|
206
|
+
|
|
188
207
|
if (!activeBudget) {
|
|
189
|
-
if (mode ===
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
if (mode === "micro") {
|
|
209
|
+
return (
|
|
210
|
+
<box flexDirection="row" height={1} paddingX={1}>
|
|
211
|
+
<text fg={colors.textMuted} height={1}>
|
|
212
|
+
No {budgetTypeLabel.toLowerCase()} budget
|
|
213
|
+
</text>
|
|
214
|
+
</box>
|
|
215
|
+
);
|
|
195
216
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<box flexDirection="column" paddingX={1}>
|
|
220
|
+
<text fg={colors.textMuted} height={1}>
|
|
221
|
+
<strong>{headerLabel}</strong>
|
|
222
|
+
</text>
|
|
223
|
+
<text fg={colors.textSubtle} height={1}>
|
|
224
|
+
Not set
|
|
225
|
+
</text>
|
|
226
|
+
<text fg={colors.textSubtle} height={1}>
|
|
227
|
+
(, to configure)
|
|
228
|
+
</text>
|
|
229
|
+
</box>
|
|
230
|
+
);
|
|
204
231
|
}
|
|
205
|
-
|
|
206
|
-
if (mode ===
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
232
|
+
|
|
233
|
+
if (mode === "micro") {
|
|
234
|
+
return (
|
|
235
|
+
<box flexDirection="column" paddingX={1}>
|
|
236
|
+
<box flexDirection="row" height={1}>
|
|
237
|
+
<text fg={colors.textMuted} height={1}>
|
|
238
|
+
{budgetTypeLabel}{" "}
|
|
239
|
+
</text>
|
|
240
|
+
<text fg={statusColor} height={1}>
|
|
241
|
+
<strong>{formatPercent(budgetUsedPercent)}</strong>
|
|
242
|
+
</text>
|
|
243
|
+
<text fg={colors.textMuted} height={1}>
|
|
244
|
+
{" "}
|
|
245
|
+
{formatBudget(budgetCost)}/{formatBudget(activeBudget)}
|
|
246
|
+
</text>
|
|
247
|
+
</box>
|
|
248
|
+
</box>
|
|
249
|
+
);
|
|
216
250
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<box flexDirection="column" paddingX={1}>
|
|
254
|
+
<box flexDirection="row" height={1}>
|
|
255
|
+
<text fg={colors.textMuted}>
|
|
256
|
+
<strong>{headerLabel}</strong>
|
|
257
|
+
</text>
|
|
258
|
+
<text flexGrow={1}></text>
|
|
259
|
+
<text fg={statusColor}>
|
|
260
|
+
<strong>{formatPercent(budgetUsedPercent)}</strong>
|
|
261
|
+
</text>
|
|
262
|
+
</box>
|
|
263
|
+
|
|
264
|
+
<box flexDirection="row" height={1}>
|
|
265
|
+
<text fg={statusColor}>{bar}</text>
|
|
266
|
+
</box>
|
|
267
|
+
|
|
268
|
+
<box flexDirection="row" height={1}>
|
|
269
|
+
<text fg={colors.text}>
|
|
270
|
+
<strong>{formatBudget(budgetCost)}</strong>
|
|
271
|
+
</text>
|
|
272
|
+
<text fg={colors.textMuted}>/{formatBudget(activeBudget)}</text>
|
|
273
|
+
</box>
|
|
274
|
+
</box>
|
|
275
|
+
);
|
|
236
276
|
}
|
|
237
277
|
|
|
238
278
|
interface TopDriversSectionProps {
|
|
@@ -248,9 +288,9 @@ interface TopDriversSectionProps {
|
|
|
248
288
|
}
|
|
249
289
|
|
|
250
290
|
const DIMENSION_LABELS: Record<DriverDimension, string> = {
|
|
251
|
-
model:
|
|
252
|
-
project:
|
|
253
|
-
agent:
|
|
291
|
+
model: "MODELS",
|
|
292
|
+
project: "PROJECTS",
|
|
293
|
+
agent: "AGENTS",
|
|
254
294
|
};
|
|
255
295
|
|
|
256
296
|
function TopDriversSection({
|
|
@@ -265,53 +305,64 @@ function TopDriversSection({
|
|
|
265
305
|
getProviderColor,
|
|
266
306
|
}: TopDriversSectionProps) {
|
|
267
307
|
const colors = useColors();
|
|
268
|
-
|
|
308
|
+
|
|
269
309
|
const displayDrivers = drivers.slice(0, maxDrivers);
|
|
270
310
|
const innerWidth = sidebarWidth - 4;
|
|
271
|
-
|
|
272
|
-
const showBars = mode ===
|
|
273
|
-
const showDelta = mode ===
|
|
274
|
-
const showPercent = mode !==
|
|
275
|
-
|
|
311
|
+
|
|
312
|
+
const showBars = mode === "full" || mode === "compact";
|
|
313
|
+
const showDelta = mode === "full";
|
|
314
|
+
const showPercent = mode !== "minimal";
|
|
315
|
+
|
|
276
316
|
const prefixWidth = 2;
|
|
277
|
-
const nameWidth = mode ===
|
|
317
|
+
const nameWidth = mode === "full" ? 14 : mode === "compact" ? 12 : 10;
|
|
278
318
|
const deltaWidth = showDelta ? 7 : 0;
|
|
279
319
|
const percentWidth = showPercent ? 5 : 0;
|
|
280
|
-
const barWidth = showBars
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
320
|
+
const barWidth = showBars
|
|
321
|
+
? Math.max(8, innerWidth - prefixWidth - nameWidth - deltaWidth - percentWidth - 2)
|
|
322
|
+
: 0;
|
|
323
|
+
|
|
324
|
+
const dimensionToggle = ["m", "p", "a"]
|
|
325
|
+
.map((k) => {
|
|
326
|
+
const isActive =
|
|
327
|
+
(k === "m" && dimension === "model") ||
|
|
328
|
+
(k === "p" && dimension === "project") ||
|
|
329
|
+
(k === "a" && dimension === "agent");
|
|
330
|
+
return isActive ? k.toUpperCase() : k;
|
|
331
|
+
})
|
|
332
|
+
.join("/");
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<box flexDirection="column" paddingX={1}>
|
|
336
|
+
<box flexDirection="row" height={1}>
|
|
337
|
+
<text fg={colors.textMuted}>
|
|
338
|
+
<strong>TOP {DIMENSION_LABELS[dimension]}</strong>
|
|
339
|
+
</text>
|
|
340
|
+
<text flexGrow={1}></text>
|
|
341
|
+
<text fg={colors.textSubtle}>[{dimensionToggle}]</text>
|
|
342
|
+
</box>
|
|
343
|
+
|
|
297
344
|
{displayDrivers.length === 0 ? (
|
|
298
|
-
<text fg={colors.textSubtle} height={1}>
|
|
345
|
+
<text fg={colors.textSubtle} height={1}>
|
|
346
|
+
No data
|
|
347
|
+
</text>
|
|
299
348
|
) : (
|
|
300
349
|
displayDrivers.map((driver, idx) => {
|
|
301
350
|
const isSelected = isFocused && idx === selectedIndex;
|
|
302
351
|
const isFiltered = activeFilter === driver.id;
|
|
303
|
-
|
|
304
|
-
const hotPrefix = driver.isHot ?
|
|
305
|
-
const selectPrefix = isSelected ?
|
|
352
|
+
|
|
353
|
+
const hotPrefix = driver.isHot ? "▲" : " ";
|
|
354
|
+
const selectPrefix = isSelected ? "▸" : isFiltered ? "●" : " ";
|
|
306
355
|
const displayName = truncateWithEllipsis(driver.displayName, nameWidth);
|
|
307
356
|
const paddedName = displayName.padEnd(nameWidth);
|
|
308
|
-
const delta = showDelta ? formatDelta(driver.delta5m).padStart(deltaWidth) :
|
|
309
|
-
const percent = showPercent ? formatPercent(driver.costShare).padStart(percentWidth) :
|
|
310
|
-
const bar = showBars ? makeProgressBar(driver.costShare, barWidth) :
|
|
311
|
-
|
|
357
|
+
const delta = showDelta ? formatDelta(driver.delta5m).padStart(deltaWidth) : "";
|
|
358
|
+
const percent = showPercent ? formatPercent(driver.costShare).padStart(percentWidth) : "";
|
|
359
|
+
const bar = showBars ? makeProgressBar(driver.costShare, barWidth) : "";
|
|
360
|
+
|
|
312
361
|
const textColor = isSelected ? colors.text : colors.textSubtle;
|
|
313
|
-
const nameColor = isSelected
|
|
314
|
-
|
|
362
|
+
const nameColor = isSelected
|
|
363
|
+
? colors.primary
|
|
364
|
+
: getProviderColor(driver.providerId ?? driver.id);
|
|
365
|
+
|
|
315
366
|
if (showBars) {
|
|
316
367
|
return (
|
|
317
368
|
<box key={driver.id} flexDirection="column" height={2}>
|
|
@@ -323,12 +374,12 @@ function TopDriversSection({
|
|
|
323
374
|
{showPercent && <text fg={colors.textMuted}>{percent}</text>}
|
|
324
375
|
</box>
|
|
325
376
|
<box flexDirection="row" height={1}>
|
|
326
|
-
<text fg={isSelected ? colors.primary : colors.textSubtle}>
|
|
377
|
+
<text fg={isSelected ? colors.primary : colors.textSubtle}> {bar}</text>
|
|
327
378
|
</box>
|
|
328
379
|
</box>
|
|
329
380
|
);
|
|
330
381
|
}
|
|
331
|
-
|
|
382
|
+
|
|
332
383
|
return (
|
|
333
384
|
<box key={driver.id} flexDirection="row" height={1}>
|
|
334
385
|
<text fg={driver.isHot ? colors.warning : colors.textSubtle}>{hotPrefix}</text>
|
|
@@ -339,9 +390,11 @@ function TopDriversSection({
|
|
|
339
390
|
);
|
|
340
391
|
})
|
|
341
392
|
)}
|
|
342
|
-
|
|
393
|
+
|
|
343
394
|
{isFocused && displayDrivers.length > 0 && (
|
|
344
|
-
<text fg={colors.textSubtle} height={1}
|
|
395
|
+
<text fg={colors.textSubtle} height={1}>
|
|
396
|
+
↑↓ select Enter filter
|
|
397
|
+
</text>
|
|
345
398
|
)}
|
|
346
399
|
</box>
|
|
347
400
|
);
|
|
@@ -353,15 +406,17 @@ interface DividerProps {
|
|
|
353
406
|
|
|
354
407
|
function Divider({ width }: DividerProps) {
|
|
355
408
|
const colors = useColors();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
409
|
+
return (
|
|
410
|
+
<box paddingX={1} height={1}>
|
|
411
|
+
<text fg={colors.border} height={1}>
|
|
412
|
+
{makeDivider(width)}
|
|
413
|
+
</text>
|
|
414
|
+
</box>
|
|
415
|
+
);
|
|
361
416
|
}
|
|
362
417
|
|
|
363
418
|
interface EfficiencyInsight {
|
|
364
|
-
type:
|
|
419
|
+
type: "cache" | "verbosity" | "chatty" | "fallback";
|
|
365
420
|
primaryLabel: string;
|
|
366
421
|
primaryValue: string;
|
|
367
422
|
actionLine: string;
|
|
@@ -376,13 +431,16 @@ interface EfficiencySectionProps {
|
|
|
376
431
|
|
|
377
432
|
function EfficiencySection({ sessions, mode }: EfficiencySectionProps) {
|
|
378
433
|
const colors = useColors();
|
|
379
|
-
|
|
434
|
+
|
|
380
435
|
const metrics = useMemo(() => {
|
|
381
|
-
let totalInput = 0,
|
|
382
|
-
|
|
436
|
+
let totalInput = 0,
|
|
437
|
+
totalOutput = 0,
|
|
438
|
+
totalCacheRead = 0;
|
|
439
|
+
let totalCost = 0,
|
|
440
|
+
totalRequests = 0;
|
|
383
441
|
let hasCacheData = false;
|
|
384
442
|
|
|
385
|
-
sessions.forEach(s => {
|
|
443
|
+
sessions.forEach((s) => {
|
|
386
444
|
s.streams.forEach((st: AgentSessionStream) => {
|
|
387
445
|
totalInput += st.tokens.input;
|
|
388
446
|
totalOutput += st.tokens.output;
|
|
@@ -393,23 +451,33 @@ function EfficiencySection({ sessions, mode }: EfficiencySectionProps) {
|
|
|
393
451
|
});
|
|
394
452
|
});
|
|
395
453
|
|
|
396
|
-
const cacheHitRate =
|
|
397
|
-
? (totalCacheRead / (totalInput + totalCacheRead)) * 100 : 0;
|
|
398
|
-
const outputShare =
|
|
399
|
-
? (totalOutput / (totalInput + totalOutput)) * 100 : 0;
|
|
454
|
+
const cacheHitRate =
|
|
455
|
+
totalInput + totalCacheRead > 0 ? (totalCacheRead / (totalInput + totalCacheRead)) * 100 : 0;
|
|
456
|
+
const outputShare =
|
|
457
|
+
totalInput + totalOutput > 0 ? (totalOutput / (totalInput + totalOutput)) * 100 : 0;
|
|
400
458
|
const costPerReq = totalRequests > 0 ? totalCost / totalRequests : 0;
|
|
401
459
|
|
|
402
|
-
return {
|
|
460
|
+
return {
|
|
461
|
+
totalInput,
|
|
462
|
+
totalOutput,
|
|
463
|
+
totalCacheRead,
|
|
464
|
+
totalCost,
|
|
465
|
+
totalRequests,
|
|
466
|
+
hasCacheData,
|
|
467
|
+
cacheHitRate,
|
|
468
|
+
outputShare,
|
|
469
|
+
costPerReq,
|
|
470
|
+
};
|
|
403
471
|
}, [sessions]);
|
|
404
472
|
|
|
405
473
|
const insight: EfficiencyInsight = useMemo(() => {
|
|
406
474
|
// Priority 1: Cache Leverage
|
|
407
475
|
if (metrics.hasCacheData && metrics.totalInput > 1000 && metrics.cacheHitRate < 80) {
|
|
408
476
|
return {
|
|
409
|
-
type:
|
|
410
|
-
primaryLabel:
|
|
477
|
+
type: "cache",
|
|
478
|
+
primaryLabel: "Cache hit:",
|
|
411
479
|
primaryValue: `${Math.round(metrics.cacheHitRate)}%`,
|
|
412
|
-
actionLine:
|
|
480
|
+
actionLine: "Keep sessions open; avoid resets",
|
|
413
481
|
secondaryLabel: `$/req: ${formatCurrency(metrics.costPerReq)} Out: ${Math.round(metrics.outputShare)}%`,
|
|
414
482
|
isWarning: metrics.cacheHitRate < 20,
|
|
415
483
|
};
|
|
@@ -417,10 +485,10 @@ function EfficiencySection({ sessions, mode }: EfficiencySectionProps) {
|
|
|
417
485
|
// Priority 2: Output Verbosity
|
|
418
486
|
if (metrics.totalInput + metrics.totalOutput > 1000 && metrics.outputShare > 65) {
|
|
419
487
|
return {
|
|
420
|
-
type:
|
|
421
|
-
primaryLabel:
|
|
488
|
+
type: "verbosity",
|
|
489
|
+
primaryLabel: "Output share:",
|
|
422
490
|
primaryValue: `${Math.round(metrics.outputShare)}%`,
|
|
423
|
-
actionLine:
|
|
491
|
+
actionLine: "Ask for diffs; less commentary",
|
|
424
492
|
secondaryLabel: `$/req: ${formatCurrency(metrics.costPerReq)}`,
|
|
425
493
|
isWarning: metrics.outputShare > 75,
|
|
426
494
|
};
|
|
@@ -428,59 +496,79 @@ function EfficiencySection({ sessions, mode }: EfficiencySectionProps) {
|
|
|
428
496
|
// Priority 3: Chatty Usage
|
|
429
497
|
if (metrics.totalRequests > 10 && metrics.costPerReq > 0.15) {
|
|
430
498
|
return {
|
|
431
|
-
type:
|
|
432
|
-
primaryLabel:
|
|
499
|
+
type: "chatty",
|
|
500
|
+
primaryLabel: "$/req:",
|
|
433
501
|
primaryValue: formatCurrency(metrics.costPerReq),
|
|
434
|
-
actionLine:
|
|
502
|
+
actionLine: "Batch asks; fewer back-and-forth",
|
|
435
503
|
secondaryLabel: `${metrics.totalRequests} reqs`,
|
|
436
504
|
isWarning: metrics.costPerReq > 0.25,
|
|
437
505
|
};
|
|
438
506
|
}
|
|
439
507
|
// Priority 4: Fallback
|
|
440
508
|
return {
|
|
441
|
-
type:
|
|
509
|
+
type: "fallback",
|
|
442
510
|
primaryLabel: `$/req: ${formatCurrency(metrics.costPerReq)}`,
|
|
443
511
|
primaryValue: `Out: ${Math.round(metrics.outputShare)}%`,
|
|
444
|
-
actionLine:
|
|
512
|
+
actionLine: "Prefer patches; reduce re-asking",
|
|
445
513
|
isWarning: false,
|
|
446
514
|
};
|
|
447
515
|
}, [metrics]);
|
|
448
516
|
|
|
449
|
-
const isFull = mode ===
|
|
517
|
+
const isFull = mode === "full";
|
|
450
518
|
const metricColor = insight.isWarning ? colors.warning : colors.text;
|
|
451
519
|
|
|
452
|
-
if (insight.type ===
|
|
520
|
+
if (insight.type === "fallback") {
|
|
453
521
|
const cacheLabel = metrics.hasCacheData
|
|
454
522
|
? `Cache: ${Math.round(metrics.cacheHitRate)}%`
|
|
455
|
-
:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
523
|
+
: "Cache: n/a";
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<box flexDirection="column" paddingX={1}>
|
|
527
|
+
<text fg={colors.textMuted} height={1}>
|
|
528
|
+
<strong>EFFICIENCY</strong>
|
|
529
|
+
</text>
|
|
530
|
+
<box flexDirection="row" height={1}>
|
|
531
|
+
<text fg={colors.text} height={1}>
|
|
532
|
+
{insight.primaryLabel} {insight.primaryValue}
|
|
533
|
+
</text>
|
|
534
|
+
</box>
|
|
535
|
+
<text fg={colors.textSubtle} height={1}>
|
|
536
|
+
{"-> "}
|
|
537
|
+
{insight.actionLine}
|
|
538
|
+
</text>
|
|
539
|
+
{isFull && (
|
|
540
|
+
<text fg={colors.textMuted} height={1}>
|
|
541
|
+
Reqs: {metrics.totalRequests} {cacheLabel}
|
|
542
|
+
</text>
|
|
543
|
+
)}
|
|
544
|
+
</box>
|
|
545
|
+
);
|
|
469
546
|
}
|
|
470
547
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
548
|
+
return (
|
|
549
|
+
<box flexDirection="column" paddingX={1}>
|
|
550
|
+
<text fg={colors.textMuted} height={1}>
|
|
551
|
+
<strong>EFFICIENCY</strong>
|
|
552
|
+
</text>
|
|
553
|
+
<box flexDirection="row" height={1}>
|
|
554
|
+
<text fg={colors.textMuted} height={1}>
|
|
555
|
+
{insight.primaryLabel}{" "}
|
|
556
|
+
</text>
|
|
557
|
+
<text fg={metricColor} height={1}>
|
|
558
|
+
{insight.primaryValue}
|
|
559
|
+
</text>
|
|
560
|
+
</box>
|
|
561
|
+
<text fg={colors.textSubtle} height={1}>
|
|
562
|
+
{"-> "}
|
|
563
|
+
{insight.actionLine}
|
|
564
|
+
</text>
|
|
565
|
+
{isFull && insight.secondaryLabel && (
|
|
566
|
+
<text fg={colors.textMuted} height={1}>
|
|
567
|
+
{insight.secondaryLabel}
|
|
568
|
+
</text>
|
|
569
|
+
)}
|
|
570
|
+
</box>
|
|
571
|
+
);
|
|
484
572
|
}
|
|
485
573
|
|
|
486
574
|
export function SmartSidebar({
|
|
@@ -494,63 +582,73 @@ export function SmartSidebar({
|
|
|
494
582
|
}: SmartSidebarProps) {
|
|
495
583
|
const colors = useColors();
|
|
496
584
|
const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
|
|
497
|
-
|
|
585
|
+
|
|
498
586
|
const mode = getSidebarMode(terminalWidth);
|
|
499
587
|
const sidebarWidth = getSidebarWidth(mode, terminalWidth);
|
|
500
|
-
const isFocused = focusedPanel ===
|
|
501
|
-
|
|
502
|
-
const showBars = mode ===
|
|
588
|
+
const isFocused = focusedPanel === "sidebar";
|
|
589
|
+
|
|
590
|
+
const showBars = mode === "full" || mode === "compact";
|
|
503
591
|
const driverRowHeight = showBars ? 2 : 1;
|
|
504
|
-
|
|
592
|
+
|
|
505
593
|
const sidebarInnerHeight = terminalHeight - 24;
|
|
506
594
|
const budgetRows = 3;
|
|
507
595
|
const dividerRows = 1;
|
|
508
596
|
const driversHeaderRow = 1;
|
|
509
597
|
const efficiencyRows = 4;
|
|
510
|
-
|
|
511
|
-
const minHeightForEfficiency =
|
|
598
|
+
|
|
599
|
+
const minHeightForEfficiency =
|
|
600
|
+
budgetRows +
|
|
601
|
+
dividerRows +
|
|
602
|
+
driversHeaderRow +
|
|
603
|
+
2 * driverRowHeight +
|
|
604
|
+
dividerRows +
|
|
605
|
+
efficiencyRows;
|
|
512
606
|
const showEfficiency = sidebarInnerHeight >= minHeightForEfficiency;
|
|
513
|
-
|
|
514
|
-
const reservedRows =
|
|
607
|
+
|
|
608
|
+
const reservedRows =
|
|
609
|
+
budgetRows +
|
|
610
|
+
dividerRows +
|
|
611
|
+
driversHeaderRow +
|
|
612
|
+
(showEfficiency ? dividerRows + efficiencyRows : 0);
|
|
515
613
|
const availableForDrivers = Math.max(0, sidebarInnerHeight - reservedRows);
|
|
516
614
|
const maxDrivers = Math.max(1, Math.min(10, Math.floor(availableForDrivers / driverRowHeight)));
|
|
517
|
-
|
|
518
|
-
const totalCost = useMemo(
|
|
519
|
-
sessions.reduce((sum, s) => sum + (s.totalCostUsd ?? 0), 0),
|
|
520
|
-
[sessions]
|
|
615
|
+
|
|
616
|
+
const totalCost = useMemo(
|
|
617
|
+
() => sessions.reduce((sum, s) => sum + (s.totalCostUsd ?? 0), 0),
|
|
618
|
+
[sessions],
|
|
521
619
|
);
|
|
522
|
-
|
|
620
|
+
|
|
523
621
|
const drivers = useMemo((): DriverStats[] => {
|
|
524
622
|
const stats: Record<string, { cost: number; tokens: number; delta5m: number }> = {};
|
|
525
623
|
const providerForKey: Record<string, string> = {};
|
|
526
624
|
const now = Date.now();
|
|
527
625
|
const fiveMinAgo = now - 5 * 60 * 1000;
|
|
528
|
-
|
|
529
|
-
sessions.forEach(s => {
|
|
626
|
+
|
|
627
|
+
sessions.forEach((s) => {
|
|
530
628
|
s.streams.forEach((st: AgentSessionStream) => {
|
|
531
629
|
let key: string;
|
|
532
630
|
switch (dimension) {
|
|
533
|
-
case
|
|
631
|
+
case "model":
|
|
534
632
|
key = st.modelId;
|
|
535
633
|
if (!providerForKey[key]) providerForKey[key] = st.providerId;
|
|
536
634
|
break;
|
|
537
|
-
case
|
|
635
|
+
case "project":
|
|
538
636
|
key = extractRepoName(s.projectPath);
|
|
539
637
|
break;
|
|
540
|
-
case
|
|
638
|
+
case "agent":
|
|
541
639
|
key = s.agentName;
|
|
542
640
|
break;
|
|
543
641
|
}
|
|
544
|
-
|
|
642
|
+
|
|
545
643
|
if (!stats[key]) {
|
|
546
644
|
stats[key] = { cost: 0, tokens: 0, delta5m: 0 };
|
|
547
645
|
}
|
|
548
|
-
|
|
646
|
+
|
|
549
647
|
const cost = st.costUsd ?? 0;
|
|
550
648
|
const entry = stats[key]!;
|
|
551
649
|
entry.cost += cost;
|
|
552
650
|
entry.tokens += st.tokens.input + st.tokens.output;
|
|
553
|
-
|
|
651
|
+
|
|
554
652
|
if (s.lastActivityAt >= fiveMinAgo) {
|
|
555
653
|
const sessionDuration = Math.max(1, s.lastActivityAt - s.startedAt);
|
|
556
654
|
const recentWindow = Math.min(5 * 60 * 1000, now - fiveMinAgo);
|
|
@@ -559,12 +657,10 @@ export function SmartSidebar({
|
|
|
559
657
|
}
|
|
560
658
|
});
|
|
561
659
|
});
|
|
562
|
-
|
|
660
|
+
|
|
563
661
|
return Object.entries(stats)
|
|
564
662
|
.map(([id, data]): DriverStats => {
|
|
565
|
-
const displayName = dimension ===
|
|
566
|
-
? (id.split('/').pop() ?? id)
|
|
567
|
-
: id;
|
|
663
|
+
const displayName = dimension === "model" ? (id.split("/").pop() ?? id) : id;
|
|
568
664
|
return {
|
|
569
665
|
id,
|
|
570
666
|
...(providerForKey[id] != null ? { providerId: providerForKey[id] } : {}),
|
|
@@ -573,36 +669,36 @@ export function SmartSidebar({
|
|
|
573
669
|
costShare: totalCost > 0 ? (data.cost / totalCost) * 100 : 0,
|
|
574
670
|
tokens: data.tokens,
|
|
575
671
|
delta5m: data.delta5m,
|
|
576
|
-
isHot: data.delta5m > 0.
|
|
672
|
+
isHot: data.delta5m > 0.1,
|
|
577
673
|
};
|
|
578
674
|
})
|
|
579
675
|
.sort((a, b) => b.cost - a.cost);
|
|
580
676
|
}, [sessions, dimension, totalCost]);
|
|
581
|
-
|
|
582
|
-
if (mode ===
|
|
677
|
+
|
|
678
|
+
if (mode === "hidden") {
|
|
583
679
|
return null;
|
|
584
680
|
}
|
|
585
|
-
|
|
681
|
+
|
|
586
682
|
return (
|
|
587
683
|
<box
|
|
588
684
|
flexDirection="column"
|
|
589
685
|
width={sidebarWidth}
|
|
590
686
|
border
|
|
591
|
-
borderStyle={isFocused ?
|
|
687
|
+
borderStyle={isFocused ? "double" : "single"}
|
|
592
688
|
borderColor={isFocused ? colors.primary : colors.border}
|
|
593
689
|
overflow="hidden"
|
|
594
690
|
>
|
|
595
691
|
<box flexDirection="column" flexGrow={1}>
|
|
596
|
-
<BudgetSection
|
|
692
|
+
<BudgetSection
|
|
597
693
|
totalCost={totalCost}
|
|
598
694
|
budgetCost={budgetCost}
|
|
599
695
|
mode={mode}
|
|
600
696
|
sidebarWidth={sidebarWidth}
|
|
601
697
|
/>
|
|
602
|
-
|
|
603
|
-
{mode !==
|
|
604
|
-
|
|
605
|
-
{mode !==
|
|
698
|
+
|
|
699
|
+
{mode !== "micro" && <Divider width={sidebarWidth} />}
|
|
700
|
+
|
|
701
|
+
{mode !== "micro" && (
|
|
606
702
|
<TopDriversSection
|
|
607
703
|
drivers={drivers}
|
|
608
704
|
dimension={dimension}
|
|
@@ -615,26 +711,32 @@ export function SmartSidebar({
|
|
|
615
711
|
getProviderColor={getProviderColor}
|
|
616
712
|
/>
|
|
617
713
|
)}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
714
|
+
|
|
715
|
+
{mode === "micro" && drivers[0] && (
|
|
716
|
+
<box flexDirection="row" paddingX={1} height={1}>
|
|
717
|
+
<text fg={colors.textMuted} height={1}>
|
|
718
|
+
Top:{" "}
|
|
719
|
+
</text>
|
|
720
|
+
<text fg={getProviderColor(drivers[0].providerId ?? drivers[0].id)} height={1}>
|
|
721
|
+
{truncateWithEllipsis(drivers[0].displayName, 10)}
|
|
722
|
+
</text>
|
|
723
|
+
<text fg={colors.text} height={1}>
|
|
724
|
+
{" "}
|
|
725
|
+
{formatCurrency(drivers[0].cost)}
|
|
726
|
+
</text>
|
|
727
|
+
{drivers[0].isHot && (
|
|
728
|
+
<text fg={colors.warning} height={1}>
|
|
729
|
+
▲
|
|
730
|
+
</text>
|
|
731
|
+
)}
|
|
732
|
+
</box>
|
|
733
|
+
)}
|
|
629
734
|
</box>
|
|
630
|
-
|
|
631
|
-
{showEfficiency && mode !==
|
|
735
|
+
|
|
736
|
+
{showEfficiency && mode !== "micro" && (
|
|
632
737
|
<box flexDirection="column">
|
|
633
738
|
<Divider width={sidebarWidth} />
|
|
634
|
-
<EfficiencySection
|
|
635
|
-
sessions={sessions}
|
|
636
|
-
mode={mode}
|
|
637
|
-
/>
|
|
739
|
+
<EfficiencySection sessions={sessions} mode={mode} />
|
|
638
740
|
</box>
|
|
639
741
|
)}
|
|
640
742
|
</box>
|