@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,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import type { ProviderState } from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import type { ProviderState } from "../contexts/PluginContext.tsx";
|
|
5
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
6
|
+
import { InlineGauge } from "./InlineGauge.tsx";
|
|
7
|
+
import { InlineSparkline } from "./InlineSparkline.tsx";
|
|
8
|
+
import { ProviderDetailPanel } from "./ProviderDetailPanel.tsx";
|
|
8
9
|
|
|
9
|
-
type FieldState =
|
|
10
|
+
type FieldState = "actual" | "estimated" | "unavailable";
|
|
10
11
|
|
|
11
12
|
interface FieldValue {
|
|
12
13
|
text: string;
|
|
@@ -14,42 +15,55 @@ interface FieldValue {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function pad(str: string, len: number): string {
|
|
17
|
-
return str.length >= len ? str.slice(0, len) : str +
|
|
18
|
+
return str.length >= len ? str.slice(0, len) : str + " ".repeat(len - str.length);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function padStart(str: string, len: number): string {
|
|
21
|
-
return str.length >= len ? str.slice(0, len) :
|
|
22
|
+
return str.length >= len ? str.slice(0, len) : " ".repeat(len - str.length) + str;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function fmtCost(val: number): string {
|
|
26
|
+
if (val >= 1000) return `$${(val / 1000).toFixed(1)}k`;
|
|
27
|
+
if (val >= 100) return `$${Math.round(val)}`;
|
|
28
|
+
if (val >= 10) return `$${val.toFixed(1)}`;
|
|
29
|
+
return `$${val.toFixed(2)}`;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
function formatTokens(val: number | undefined | null): FieldValue {
|
|
25
|
-
if (val === undefined || val === null) return { text:
|
|
26
|
-
const formatted =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
if (val === undefined || val === null) return { text: "—", state: "unavailable" };
|
|
34
|
+
const formatted =
|
|
35
|
+
val >= 1_000_000
|
|
36
|
+
? `${(val / 1_000_000).toFixed(1)}M`
|
|
37
|
+
: val >= 1_000
|
|
38
|
+
? `${(val / 1_000).toFixed(1)}K`
|
|
39
|
+
: String(Math.round(val));
|
|
40
|
+
return { text: formatted, state: "actual" };
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
type StatusGroup =
|
|
43
|
+
type StatusGroup = "error" | "warn" | "ok";
|
|
33
44
|
|
|
34
|
-
function getStatusInfo(
|
|
45
|
+
function getStatusInfo(
|
|
46
|
+
state: ProviderState,
|
|
47
|
+
colors: ReturnType<typeof useColors>,
|
|
48
|
+
): {
|
|
35
49
|
icon: string;
|
|
36
50
|
color: string;
|
|
37
51
|
group: StatusGroup;
|
|
38
52
|
} {
|
|
39
53
|
if (state.usage?.error) {
|
|
40
|
-
return { icon:
|
|
54
|
+
return { icon: "✗", color: colors.error, group: "error" };
|
|
41
55
|
}
|
|
42
56
|
if (state.usage?.limitReached) {
|
|
43
|
-
return { icon:
|
|
57
|
+
return { icon: "⚠", color: colors.warning, group: "warn" };
|
|
44
58
|
}
|
|
45
59
|
const maxUsage = getMaxUsage(state);
|
|
46
60
|
if (maxUsage >= 80) {
|
|
47
|
-
return { icon:
|
|
61
|
+
return { icon: "!", color: colors.warning, group: "warn" };
|
|
48
62
|
}
|
|
49
63
|
if (state.loading) {
|
|
50
|
-
return { icon:
|
|
64
|
+
return { icon: "◌", color: colors.info, group: "ok" };
|
|
51
65
|
}
|
|
52
|
-
return { icon:
|
|
66
|
+
return { icon: "●", color: colors.success, group: "ok" };
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
function getMaxUsage(state: ProviderState): number {
|
|
@@ -64,25 +78,25 @@ function getMaxUsage(state: ProviderState): number {
|
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
function getNextReset(state: ProviderState): string {
|
|
67
|
-
if (!state.usage?.limits) return
|
|
81
|
+
if (!state.usage?.limits) return "—";
|
|
68
82
|
|
|
69
83
|
const items = state.usage.limits.items ?? [];
|
|
70
84
|
let resetsAt: Date | null = null;
|
|
71
85
|
|
|
72
86
|
if (items.length > 0) {
|
|
73
|
-
const withReset = items.filter(i => i.resetsAt);
|
|
87
|
+
const withReset = items.filter((i) => i.resetsAt);
|
|
74
88
|
if (withReset.length > 0) {
|
|
75
|
-
resetsAt = new Date(Math.min(...withReset.map(i => new Date(i.resetsAt!).getTime())));
|
|
89
|
+
resetsAt = new Date(Math.min(...withReset.map((i) => new Date(i.resetsAt!).getTime())));
|
|
76
90
|
}
|
|
77
91
|
} else if (state.usage.limits.primary?.resetsAt) {
|
|
78
92
|
resetsAt = new Date(state.usage.limits.primary.resetsAt);
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
if (!resetsAt) return
|
|
95
|
+
if (!resetsAt) return "—";
|
|
82
96
|
|
|
83
97
|
const now = Date.now();
|
|
84
98
|
const diff = resetsAt.getTime() - now;
|
|
85
|
-
if (diff <= 0) return
|
|
99
|
+
if (diff <= 0) return "now";
|
|
86
100
|
|
|
87
101
|
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
88
102
|
const mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
@@ -95,31 +109,37 @@ function getNextReset(state: ProviderState): string {
|
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
function getCostToday(state: ProviderState): FieldValue {
|
|
98
|
-
if (!state.usage?.cost) return { text:
|
|
112
|
+
if (!state.usage?.cost) return { text: "—", state: "unavailable" };
|
|
99
113
|
const cost = state.usage.cost;
|
|
114
|
+
if (cost.estimatedDaily) {
|
|
115
|
+
return { text: `~${fmtCost(cost.estimatedDaily.total)}`, state: "estimated" };
|
|
116
|
+
}
|
|
100
117
|
if (cost.actual) {
|
|
101
|
-
return { text:
|
|
118
|
+
return { text: fmtCost(cost.actual.total), state: "actual" };
|
|
102
119
|
}
|
|
103
120
|
if (cost.estimated) {
|
|
104
|
-
return { text:
|
|
121
|
+
return { text: `~${fmtCost(cost.estimated.total)}`, state: "estimated" };
|
|
105
122
|
}
|
|
106
|
-
return { text:
|
|
123
|
+
return { text: "—", state: "unavailable" };
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
function getCostMtd(state: ProviderState): FieldValue {
|
|
110
|
-
if (!state.usage?.cost) return { text:
|
|
127
|
+
if (!state.usage?.cost) return { text: "—", state: "unavailable" };
|
|
111
128
|
const cost = state.usage.cost;
|
|
129
|
+
if (cost.estimatedMonthly) {
|
|
130
|
+
return { text: `~${fmtCost(cost.estimatedMonthly.total)}`, state: "estimated" };
|
|
131
|
+
}
|
|
112
132
|
if (cost.actual) {
|
|
113
|
-
return { text:
|
|
133
|
+
return { text: fmtCost(cost.actual.total), state: "actual" };
|
|
114
134
|
}
|
|
115
135
|
if (cost.estimated) {
|
|
116
|
-
return { text:
|
|
136
|
+
return { text: `~${fmtCost(cost.estimated.total)}`, state: "estimated" };
|
|
117
137
|
}
|
|
118
|
-
return { text:
|
|
138
|
+
return { text: "—", state: "unavailable" };
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
function getTotalTokens(state: ProviderState): FieldValue {
|
|
122
|
-
if (!state.usage?.tokens) return { text:
|
|
142
|
+
if (!state.usage?.tokens) return { text: "—", state: "unavailable" };
|
|
123
143
|
const t = state.usage.tokens;
|
|
124
144
|
const total = t.input + t.output + (t.cacheRead ?? 0) + (t.cacheWrite ?? 0);
|
|
125
145
|
return formatTokens(total);
|
|
@@ -142,45 +162,74 @@ interface LayoutTier {
|
|
|
142
162
|
function getLayoutTier(termWidth: number): LayoutTier {
|
|
143
163
|
if (termWidth >= 140) {
|
|
144
164
|
return {
|
|
145
|
-
nameWidth: 20,
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
nameWidth: 20,
|
|
166
|
+
planWidth: 18,
|
|
167
|
+
gaugeWidth: 28,
|
|
168
|
+
showPlan: true,
|
|
169
|
+
showToday: true,
|
|
170
|
+
showMtd: true,
|
|
171
|
+
showTokens: true,
|
|
172
|
+
showReset: true,
|
|
173
|
+
showTrend: true,
|
|
174
|
+
sparkWidth: 12,
|
|
148
175
|
};
|
|
149
176
|
}
|
|
150
177
|
if (termWidth >= 120) {
|
|
151
178
|
return {
|
|
152
|
-
nameWidth: 18,
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
nameWidth: 18,
|
|
180
|
+
planWidth: 16,
|
|
181
|
+
gaugeWidth: 24,
|
|
182
|
+
showPlan: true,
|
|
183
|
+
showToday: true,
|
|
184
|
+
showMtd: true,
|
|
185
|
+
showTokens: true,
|
|
186
|
+
showReset: true,
|
|
187
|
+
showTrend: true,
|
|
188
|
+
sparkWidth: 10,
|
|
155
189
|
};
|
|
156
190
|
}
|
|
157
191
|
if (termWidth >= 100) {
|
|
158
192
|
return {
|
|
159
|
-
nameWidth: 16,
|
|
160
|
-
|
|
161
|
-
|
|
193
|
+
nameWidth: 16,
|
|
194
|
+
planWidth: 12,
|
|
195
|
+
gaugeWidth: 20,
|
|
196
|
+
showPlan: true,
|
|
197
|
+
showToday: true,
|
|
198
|
+
showMtd: true,
|
|
199
|
+
showTokens: true,
|
|
200
|
+
showReset: true,
|
|
201
|
+
showTrend: true,
|
|
202
|
+
sparkWidth: 8,
|
|
162
203
|
};
|
|
163
204
|
}
|
|
164
205
|
if (termWidth >= 80) {
|
|
165
206
|
return {
|
|
166
|
-
nameWidth: 14,
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
nameWidth: 14,
|
|
208
|
+
planWidth: 10,
|
|
209
|
+
gaugeWidth: 18,
|
|
210
|
+
showPlan: true,
|
|
211
|
+
showToday: true,
|
|
212
|
+
showMtd: false,
|
|
213
|
+
showTokens: false,
|
|
214
|
+
showReset: true,
|
|
215
|
+
showTrend: false,
|
|
216
|
+
sparkWidth: 0,
|
|
169
217
|
};
|
|
170
218
|
}
|
|
171
219
|
return {
|
|
172
|
-
nameWidth: 12,
|
|
173
|
-
|
|
174
|
-
|
|
220
|
+
nameWidth: 12,
|
|
221
|
+
planWidth: 0,
|
|
222
|
+
gaugeWidth: 14,
|
|
223
|
+
showPlan: false,
|
|
224
|
+
showToday: true,
|
|
225
|
+
showMtd: false,
|
|
226
|
+
showTokens: false,
|
|
227
|
+
showReset: false,
|
|
228
|
+
showTrend: false,
|
|
229
|
+
sparkWidth: 0,
|
|
175
230
|
};
|
|
176
231
|
}
|
|
177
232
|
|
|
178
|
-
interface GroupedProvider {
|
|
179
|
-
state: ProviderState;
|
|
180
|
-
status: ReturnType<typeof getStatusInfo>;
|
|
181
|
-
originalIndex: number;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
233
|
export interface ProvidersListProps {
|
|
185
234
|
providers: ProviderState[];
|
|
186
235
|
selectedIndex: number | null;
|
|
@@ -188,51 +237,57 @@ export interface ProvidersListProps {
|
|
|
188
237
|
expandedIndex: number | null;
|
|
189
238
|
}
|
|
190
239
|
|
|
191
|
-
export function ProvidersList({
|
|
240
|
+
export function ProvidersList({
|
|
241
|
+
providers,
|
|
242
|
+
selectedIndex,
|
|
243
|
+
onSelect,
|
|
244
|
+
expandedIndex,
|
|
245
|
+
}: ProvidersListProps) {
|
|
192
246
|
const colors = useColors();
|
|
193
247
|
const { width: termWidth } = useTerminalDimensions();
|
|
194
248
|
|
|
195
249
|
const layout = getLayoutTier(termWidth);
|
|
196
250
|
const {
|
|
197
|
-
nameWidth,
|
|
198
|
-
|
|
251
|
+
nameWidth,
|
|
252
|
+
planWidth,
|
|
253
|
+
gaugeWidth,
|
|
254
|
+
sparkWidth,
|
|
255
|
+
showPlan,
|
|
256
|
+
showToday,
|
|
257
|
+
showMtd,
|
|
258
|
+
showTokens,
|
|
259
|
+
showReset,
|
|
260
|
+
showTrend,
|
|
199
261
|
} = layout;
|
|
200
262
|
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const status = getStatusInfo(state, colors);
|
|
206
|
-
groups[status.group].push({ state, status, originalIndex: idx });
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
return groups;
|
|
210
|
-
}, [providers, colors]);
|
|
211
|
-
|
|
212
|
-
const groupOrder: StatusGroup[] = ['error', 'warn', 'ok'];
|
|
263
|
+
const providerStatuses = useMemo(
|
|
264
|
+
() => providers.map((state) => getStatusInfo(state, colors)),
|
|
265
|
+
[providers, colors],
|
|
266
|
+
);
|
|
213
267
|
|
|
214
268
|
const fieldColor = (fv: FieldValue): string => {
|
|
215
|
-
if (fv.state ===
|
|
216
|
-
if (fv.state ===
|
|
269
|
+
if (fv.state === "estimated") return colors.textMuted;
|
|
270
|
+
if (fv.state === "unavailable") return colors.textSubtle;
|
|
217
271
|
return colors.text;
|
|
218
272
|
};
|
|
219
273
|
|
|
220
274
|
const selBg = colors.selection;
|
|
221
275
|
|
|
222
|
-
const renderRow = (
|
|
223
|
-
const
|
|
224
|
-
const
|
|
276
|
+
const renderRow = (idx: number) => {
|
|
277
|
+
const state = providers[idx]!;
|
|
278
|
+
const status = providerStatuses[idx]!;
|
|
279
|
+
const isSelected = idx === selectedIndex;
|
|
225
280
|
const providerColor = state.plugin.meta?.brandColor ?? colors.primary;
|
|
226
281
|
const costToday = getCostToday(state);
|
|
227
282
|
const costMtd = getCostMtd(state);
|
|
228
283
|
const tokens = getTotalTokens(state);
|
|
229
284
|
const maxUsage = getMaxUsage(state);
|
|
230
|
-
const plan = state.usage?.planType ??
|
|
285
|
+
const plan = state.usage?.planType ?? "—";
|
|
231
286
|
const reset = getNextReset(state);
|
|
232
287
|
|
|
233
288
|
const rowBg = isSelected ? selBg : undefined;
|
|
234
289
|
const bgProp = rowBg ? { bg: rowBg } : {};
|
|
235
|
-
const railChar = isSelected ?
|
|
290
|
+
const railChar = isSelected ? "▌" : " ";
|
|
236
291
|
|
|
237
292
|
return (
|
|
238
293
|
<box
|
|
@@ -241,11 +296,11 @@ export function ProvidersList({ providers, selectedIndex, onSelect, expandedInde
|
|
|
241
296
|
height={1}
|
|
242
297
|
flexGrow={1}
|
|
243
298
|
focusable
|
|
244
|
-
onMouseDown={() => onSelect(
|
|
299
|
+
onMouseDown={() => onSelect(idx)}
|
|
245
300
|
{...(rowBg ? { backgroundColor: rowBg } : {})}
|
|
246
301
|
>
|
|
247
302
|
<text width={2} fg={isSelected ? colors.primary : colors.textSubtle} height={1} {...bgProp}>
|
|
248
|
-
{railChar +
|
|
303
|
+
{railChar + " "}
|
|
249
304
|
</text>
|
|
250
305
|
<text width={3} fg={status.color} height={1} {...bgProp}>
|
|
251
306
|
{pad(`${status.icon} `, 3)}
|
|
@@ -262,17 +317,22 @@ export function ProvidersList({ providers, selectedIndex, onSelect, expandedInde
|
|
|
262
317
|
{isSelected ? (
|
|
263
318
|
<text width={gaugeWidth + 5} fg={colors.text} height={1} {...bgProp}>
|
|
264
319
|
{pad(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
320
|
+
"█".repeat(Math.round((maxUsage / 100) * gaugeWidth)) +
|
|
321
|
+
"·".repeat(gaugeWidth - Math.round((maxUsage / 100) * gaugeWidth)) +
|
|
322
|
+
" " +
|
|
323
|
+
(maxUsage > 0 ? `${Math.round(maxUsage)}%` : "—"),
|
|
268
324
|
gaugeWidth + 5,
|
|
269
325
|
)}
|
|
270
326
|
</text>
|
|
271
327
|
) : (
|
|
272
328
|
<>
|
|
273
|
-
<InlineGauge
|
|
329
|
+
<InlineGauge
|
|
330
|
+
percent={maxUsage > 0 ? maxUsage : null}
|
|
331
|
+
width={gaugeWidth}
|
|
332
|
+
color={providerColor}
|
|
333
|
+
/>
|
|
274
334
|
<text width={5} fg={maxUsage >= 80 ? colors.warning : colors.text} height={1}>
|
|
275
|
-
{padStart(maxUsage > 0 ? `${Math.round(maxUsage)}%` :
|
|
335
|
+
{padStart(maxUsage > 0 ? `${Math.round(maxUsage)}%` : "—", 5)}
|
|
276
336
|
</text>
|
|
277
337
|
</>
|
|
278
338
|
)}
|
|
@@ -301,11 +361,13 @@ export function ProvidersList({ providers, selectedIndex, onSelect, expandedInde
|
|
|
301
361
|
<box width={sparkWidth + 1} height={1} overflow="hidden">
|
|
302
362
|
{isSelected ? (
|
|
303
363
|
<text width={sparkWidth + 1} fg={colors.textMuted} height={1} {...bgProp}>
|
|
304
|
-
{
|
|
364
|
+
{" " + "⣀".repeat(sparkWidth)}
|
|
305
365
|
</text>
|
|
306
366
|
) : (
|
|
307
367
|
<>
|
|
308
|
-
<text width={1} height={1}>
|
|
368
|
+
<text width={1} height={1}>
|
|
369
|
+
{" "}
|
|
370
|
+
</text>
|
|
309
371
|
<InlineSparkline history={state.history} width={sparkWidth} />
|
|
310
372
|
</>
|
|
311
373
|
)}
|
|
@@ -315,65 +377,92 @@ export function ProvidersList({ providers, selectedIndex, onSelect, expandedInde
|
|
|
315
377
|
);
|
|
316
378
|
};
|
|
317
379
|
|
|
318
|
-
const renderSeparator = (label: string) => (
|
|
319
|
-
<box height={1} paddingX={1} key={`sep-${label}`}>
|
|
380
|
+
const renderSeparator = (label: string, afterIndex: number) => (
|
|
381
|
+
<box height={1} paddingX={1} key={`sep-${afterIndex}-${label}`}>
|
|
320
382
|
<text fg={colors.textSubtle} height={1}>
|
|
321
|
-
{
|
|
383
|
+
{"─".repeat(3)} {label} {"─".repeat(Math.max(0, termWidth - label.length - 10))}
|
|
322
384
|
</text>
|
|
323
385
|
</box>
|
|
324
386
|
);
|
|
325
387
|
|
|
326
388
|
const renderHeader = () => (
|
|
327
389
|
<box flexDirection="row" height={1}>
|
|
328
|
-
<text width={2} fg={colors.textMuted} height={1}>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
{
|
|
390
|
+
<text width={2} fg={colors.textMuted} height={1}>
|
|
391
|
+
{pad("", 2)}
|
|
392
|
+
</text>
|
|
393
|
+
<text width={3} fg={colors.textMuted} height={1}>
|
|
394
|
+
{pad("", 3)}
|
|
395
|
+
</text>
|
|
396
|
+
<text width={nameWidth} fg={colors.textMuted} height={1}>
|
|
397
|
+
{pad("PROVIDER", nameWidth)}
|
|
398
|
+
</text>
|
|
399
|
+
{showPlan && (
|
|
400
|
+
<text width={planWidth} fg={colors.textMuted} height={1}>
|
|
401
|
+
{pad("PLAN", planWidth)}
|
|
402
|
+
</text>
|
|
403
|
+
)}
|
|
404
|
+
<text width={gaugeWidth + 5} fg={colors.textMuted} height={1}>
|
|
405
|
+
{pad("HEADROOM", gaugeWidth + 5)}
|
|
406
|
+
</text>
|
|
407
|
+
{showToday && (
|
|
408
|
+
<text width={9} fg={colors.textMuted} height={1}>
|
|
409
|
+
{padStart("$TODAY", 9)}
|
|
410
|
+
</text>
|
|
411
|
+
)}
|
|
412
|
+
{showMtd && (
|
|
413
|
+
<text width={9} fg={colors.textMuted} height={1}>
|
|
414
|
+
{padStart("$MTD", 9)}
|
|
415
|
+
</text>
|
|
416
|
+
)}
|
|
417
|
+
{showTokens && (
|
|
418
|
+
<text width={8} fg={colors.textMuted} height={1}>
|
|
419
|
+
{padStart("TOKENS", 8)}
|
|
420
|
+
</text>
|
|
421
|
+
)}
|
|
422
|
+
{showReset && (
|
|
423
|
+
<text width={9} fg={colors.textMuted} height={1}>
|
|
424
|
+
{padStart("RESET", 9)}
|
|
425
|
+
</text>
|
|
426
|
+
)}
|
|
427
|
+
{showTrend && (
|
|
428
|
+
<text width={sparkWidth + 1} fg={colors.textMuted} height={1}>
|
|
429
|
+
{" " + pad("TREND", sparkWidth)}
|
|
430
|
+
</text>
|
|
431
|
+
)}
|
|
338
432
|
</box>
|
|
339
433
|
);
|
|
340
434
|
|
|
341
|
-
const rows:
|
|
342
|
-
let
|
|
435
|
+
const rows: React.ReactNode[] = [];
|
|
436
|
+
let currentGroup: StatusGroup | null = null;
|
|
343
437
|
|
|
344
|
-
for (
|
|
345
|
-
const
|
|
346
|
-
if (items.length === 0) continue;
|
|
438
|
+
for (let idx = 0; idx < providers.length; idx++) {
|
|
439
|
+
const status = providerStatuses[idx]!;
|
|
347
440
|
|
|
348
|
-
if (
|
|
349
|
-
const label =
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
rows.push(renderSeparator(label));
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
for (const item of items) {
|
|
357
|
-
rows.push(renderRow(item));
|
|
358
|
-
if (expandedIndex !== null && item.originalIndex === expandedIndex) {
|
|
359
|
-
rows.push(
|
|
360
|
-
<box key={`detail-${item.state.plugin.id}`} paddingX={1}>
|
|
361
|
-
<ProviderDetailPanel provider={item.state} />
|
|
362
|
-
</box>
|
|
363
|
-
);
|
|
441
|
+
if (status.group !== currentGroup) {
|
|
442
|
+
const label =
|
|
443
|
+
status.group === "error" ? "ERRORS" : status.group === "warn" ? "WARNINGS" : "OK";
|
|
444
|
+
if (currentGroup !== null || status.group !== "ok") {
|
|
445
|
+
rows.push(renderSeparator(label, idx));
|
|
364
446
|
}
|
|
447
|
+
currentGroup = status.group;
|
|
365
448
|
}
|
|
366
449
|
|
|
367
|
-
|
|
450
|
+
rows.push(renderRow(idx));
|
|
451
|
+
|
|
452
|
+
if (expandedIndex !== null && idx === expandedIndex) {
|
|
453
|
+
rows.push(
|
|
454
|
+
<box key={`detail-${providers[idx]!.plugin.id}`} paddingX={1}>
|
|
455
|
+
<ProviderDetailPanel provider={providers[idx]!} />
|
|
456
|
+
</box>,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
368
459
|
}
|
|
369
460
|
|
|
370
461
|
return (
|
|
371
462
|
<box flexDirection="column" flexGrow={1}>
|
|
372
463
|
{renderHeader()}
|
|
373
464
|
<scrollbox flexGrow={1}>
|
|
374
|
-
<box flexDirection="column">
|
|
375
|
-
{rows}
|
|
376
|
-
</box>
|
|
465
|
+
<box flexDirection="column">{rows}</box>
|
|
377
466
|
</scrollbox>
|
|
378
467
|
</box>
|
|
379
468
|
);
|