@tokentop/ttop 0.2.2 → 0.3.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 +32 -32
- 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 +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -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 +66 -57
- package/src/plugins/registry.ts +47 -43
- 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/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- 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 +295 -177
- 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 +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- 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 +193 -144
- 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 +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- 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 +3 -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 +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- 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,31 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useConfig } from "../contexts/ConfigContext.tsx";
|
|
4
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
5
|
+
import { usePulse } from "../hooks/usePulse.ts";
|
|
6
|
+
import { LimitGauge } from "./LimitGauge.tsx";
|
|
7
7
|
|
|
8
8
|
function interpolatePulseColor(intensity: number, baseColor: string, dimColor: string): string {
|
|
9
9
|
const dimAmount = 0.4;
|
|
10
10
|
const t = intensity * dimAmount;
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
const parseHex = (hex: string) => {
|
|
13
|
-
const h = hex.replace(
|
|
13
|
+
const h = hex.replace("#", "");
|
|
14
14
|
return {
|
|
15
15
|
r: parseInt(h.substring(0, 2), 16),
|
|
16
16
|
g: parseInt(h.substring(2, 4), 16),
|
|
17
17
|
b: parseInt(h.substring(4, 6), 16),
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
const base = parseHex(baseColor);
|
|
22
22
|
const dim = parseHex(dimColor);
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
const r = Math.round(base.r + (dim.r - base.r) * t);
|
|
25
25
|
const g = Math.round(base.g + (dim.g - base.g) * t);
|
|
26
26
|
const b = Math.round(base.b + (dim.b - base.b) * t);
|
|
27
|
-
|
|
28
|
-
return `#${r.toString(16).padStart(2,
|
|
27
|
+
|
|
28
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
interface ProviderData {
|
|
@@ -44,7 +44,7 @@ interface BudgetData {
|
|
|
44
44
|
totalCost: number;
|
|
45
45
|
budgetCost: number;
|
|
46
46
|
limit: number | null;
|
|
47
|
-
budgetType:
|
|
47
|
+
budgetType: "daily" | "weekly" | "monthly" | "none";
|
|
48
48
|
budgetTypeLabel: string;
|
|
49
49
|
warningPercent: number;
|
|
50
50
|
criticalPercent: number;
|
|
@@ -58,31 +58,31 @@ interface ProviderLimitsPanelProps {
|
|
|
58
58
|
showBudgetBar?: boolean;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
type LayoutMode =
|
|
61
|
+
type LayoutMode = "hidden" | "compact" | "normal" | "wide";
|
|
62
62
|
|
|
63
63
|
function getLayoutMode(width: number, height: number): LayoutMode {
|
|
64
|
-
if (height < 24) return
|
|
65
|
-
if (height < 30) return
|
|
66
|
-
if (width >= 140) return
|
|
67
|
-
return
|
|
64
|
+
if (height < 24) return "hidden";
|
|
65
|
+
if (height < 30) return "compact";
|
|
66
|
+
if (width >= 140) return "wide";
|
|
67
|
+
return "normal";
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function sortByUrgency(
|
|
71
|
-
providers: ProviderData[],
|
|
72
|
-
warningThreshold: number,
|
|
73
|
-
criticalThreshold: number
|
|
71
|
+
providers: ProviderData[],
|
|
72
|
+
warningThreshold: number,
|
|
73
|
+
criticalThreshold: number,
|
|
74
74
|
): ProviderData[] {
|
|
75
75
|
return [...providers].sort((a, b) => {
|
|
76
76
|
const aIsCritical = a.usedPercent >= criticalThreshold;
|
|
77
77
|
const bIsCritical = b.usedPercent >= criticalThreshold;
|
|
78
78
|
const aIsWarning = a.usedPercent >= warningThreshold;
|
|
79
79
|
const bIsWarning = b.usedPercent >= warningThreshold;
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
if (aIsCritical && !bIsCritical) return -1;
|
|
82
82
|
if (!aIsCritical && bIsCritical) return 1;
|
|
83
83
|
if (aIsWarning && !bIsWarning) return -1;
|
|
84
84
|
if (!aIsWarning && bIsWarning) return 1;
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
return b.usedPercent - a.usedPercent;
|
|
87
87
|
});
|
|
88
88
|
}
|
|
@@ -97,12 +97,12 @@ function formatCurrency(val: number): string {
|
|
|
97
97
|
function makeProgressBar(percent: number, width: number): string {
|
|
98
98
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
99
99
|
const filledWidth = Math.round((clamped / 100) * width);
|
|
100
|
-
return
|
|
100
|
+
return "█".repeat(filledWidth) + "·".repeat(width - filledWidth);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
export function ProviderLimitsPanel({
|
|
104
|
-
providers,
|
|
105
|
-
focused = false,
|
|
103
|
+
export function ProviderLimitsPanel({
|
|
104
|
+
providers,
|
|
105
|
+
focused = false,
|
|
106
106
|
selectedIndex = 0,
|
|
107
107
|
budget,
|
|
108
108
|
showBudgetBar = false,
|
|
@@ -110,24 +110,26 @@ export function ProviderLimitsPanel({
|
|
|
110
110
|
const colors = useColors();
|
|
111
111
|
const { config } = useConfig();
|
|
112
112
|
const { width: termWidth, height: termHeight } = useTerminalDimensions();
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
const warningThreshold = config.alerts.warningPercent;
|
|
115
115
|
const criticalThreshold = config.alerts.criticalPercent;
|
|
116
|
-
|
|
117
|
-
const hasBudgetLimit = !!(budget?.limit && budget.budgetType !==
|
|
118
|
-
const budgetPercent =
|
|
116
|
+
|
|
117
|
+
const hasBudgetLimit = !!(budget?.limit && budget.budgetType !== "none");
|
|
118
|
+
const budgetPercent =
|
|
119
|
+
hasBudgetLimit && budget?.limit ? (budget.budgetCost / budget.limit) * 100 : 0;
|
|
119
120
|
const isBudgetCritical = hasBudgetLimit && budget && budgetPercent >= budget.criticalPercent;
|
|
120
|
-
const isBudgetWarning =
|
|
121
|
-
|
|
121
|
+
const isBudgetWarning =
|
|
122
|
+
hasBudgetLimit && budget && budgetPercent >= budget.warningPercent && !isBudgetCritical;
|
|
123
|
+
|
|
122
124
|
const criticalPulseStep = usePulse({ enabled: !!isBudgetCritical, intervalMs: 80 });
|
|
123
125
|
const warningPulseStep = usePulse({ enabled: !!isBudgetWarning, intervalMs: 200 });
|
|
124
|
-
|
|
125
|
-
const pulseIntensity = isBudgetCritical
|
|
126
|
-
? Math.abs(Math.sin(criticalPulseStep * 0.3))
|
|
127
|
-
: isBudgetWarning
|
|
126
|
+
|
|
127
|
+
const pulseIntensity = isBudgetCritical
|
|
128
|
+
? Math.abs(Math.sin(criticalPulseStep * 0.3))
|
|
129
|
+
: isBudgetWarning
|
|
128
130
|
? Math.abs(Math.sin(warningPulseStep * 0.25))
|
|
129
131
|
: 0;
|
|
130
|
-
|
|
132
|
+
|
|
131
133
|
const getBudgetColor = () => {
|
|
132
134
|
if (!budget?.limit) return colors.textMuted;
|
|
133
135
|
if (isBudgetCritical) {
|
|
@@ -138,37 +140,44 @@ export function ProviderLimitsPanel({
|
|
|
138
140
|
}
|
|
139
141
|
return colors.success;
|
|
140
142
|
};
|
|
141
|
-
|
|
143
|
+
|
|
142
144
|
const budgetColor = getBudgetColor();
|
|
143
|
-
|
|
145
|
+
|
|
144
146
|
const layoutMode = getLayoutMode(termWidth, termHeight);
|
|
145
147
|
const sortedProviders = useMemo(
|
|
146
|
-
() => sortByUrgency(providers, warningThreshold, criticalThreshold),
|
|
147
|
-
[providers, warningThreshold, criticalThreshold]
|
|
148
|
+
() => sortByUrgency(providers, warningThreshold, criticalThreshold),
|
|
149
|
+
[providers, warningThreshold, criticalThreshold],
|
|
148
150
|
);
|
|
149
|
-
|
|
150
|
-
if (layoutMode ===
|
|
151
|
+
|
|
152
|
+
if (layoutMode === "hidden" || sortedProviders.length === 0) {
|
|
151
153
|
return null;
|
|
152
154
|
}
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
const safeSelectedIndex = Math.min(selectedIndex, sortedProviders.length - 1);
|
|
155
|
-
|
|
156
|
-
if (layoutMode ===
|
|
157
|
+
|
|
158
|
+
if (layoutMode === "compact") {
|
|
157
159
|
const baseMaxShow = 4;
|
|
158
160
|
const hasMore = sortedProviders.length > baseMaxShow;
|
|
159
161
|
const maxShow = hasMore ? baseMaxShow - 1 : baseMaxShow;
|
|
160
|
-
const startIndex = Math.max(
|
|
162
|
+
const startIndex = Math.max(
|
|
163
|
+
0,
|
|
164
|
+
Math.min(safeSelectedIndex - maxShow + 1, sortedProviders.length - maxShow),
|
|
165
|
+
);
|
|
161
166
|
const showLeftArrow = hasMore && startIndex > 0;
|
|
162
167
|
const showRightArrow = hasMore && startIndex + maxShow < sortedProviders.length;
|
|
163
168
|
const shown = sortedProviders.slice(startIndex, startIndex + maxShow);
|
|
164
169
|
const remaining = sortedProviders.length - startIndex - maxShow;
|
|
165
|
-
|
|
170
|
+
|
|
166
171
|
return (
|
|
167
172
|
<box flexDirection="column" height={showBudgetBar && budget ? 2 : 1} overflow="hidden">
|
|
168
|
-
|
|
169
|
-
<text fg={colors.textMuted} height={1}>
|
|
173
|
+
<box flexDirection="row" height={1} paddingX={1} gap={1} overflow="hidden">
|
|
174
|
+
<text fg={colors.textMuted} height={1}>
|
|
175
|
+
LIMITS:
|
|
176
|
+
</text>
|
|
170
177
|
{showLeftArrow && (
|
|
171
|
-
<text fg={colors.primary} height={1}
|
|
178
|
+
<text fg={colors.primary} height={1}>
|
|
179
|
+
◀
|
|
180
|
+
</text>
|
|
172
181
|
)}
|
|
173
182
|
{shown.map((p, idx) => (
|
|
174
183
|
<LimitGauge
|
|
@@ -178,61 +187,80 @@ export function ProviderLimitsPanel({
|
|
|
178
187
|
color={p.color}
|
|
179
188
|
{...(p.error ? { error: p.error } : {})}
|
|
180
189
|
compact={true}
|
|
181
|
-
selected={focused &&
|
|
190
|
+
selected={focused && startIndex + idx === safeSelectedIndex}
|
|
182
191
|
warningThreshold={warningThreshold}
|
|
183
192
|
criticalThreshold={criticalThreshold}
|
|
184
193
|
/>
|
|
185
194
|
))}
|
|
186
195
|
{showRightArrow && (
|
|
187
|
-
<text fg={colors.primary} height={1}
|
|
196
|
+
<text fg={colors.primary} height={1}>
|
|
197
|
+
▶ +{remaining}
|
|
198
|
+
</text>
|
|
188
199
|
)}
|
|
189
200
|
</box>
|
|
190
201
|
{showBudgetBar && budget && (
|
|
191
202
|
<box flexDirection="row" height={1} paddingX={1} gap={1} overflow="hidden">
|
|
192
|
-
<text fg={colors.textMuted} height={1}>
|
|
203
|
+
<text fg={colors.textMuted} height={1}>
|
|
204
|
+
{budget.budgetType === "none" ? "TOTAL:" : `${budget.budgetTypeLabel.toUpperCase()}:`}
|
|
205
|
+
</text>
|
|
193
206
|
{hasBudgetLimit ? (
|
|
194
207
|
<>
|
|
195
|
-
<text fg={budgetColor} height={1}>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<text fg={
|
|
208
|
+
<text fg={budgetColor} height={1}>
|
|
209
|
+
{makeProgressBar(budgetPercent, 12)}
|
|
210
|
+
</text>
|
|
211
|
+
<text fg={budgetColor} height={1}>
|
|
212
|
+
{Math.round(budgetPercent)}%
|
|
213
|
+
</text>
|
|
214
|
+
<text fg={colors.text} height={1}>
|
|
215
|
+
{formatCurrency(budget.budgetCost)}
|
|
216
|
+
</text>
|
|
217
|
+
<text fg={colors.textMuted} height={1}>
|
|
218
|
+
/{formatCurrency(budget.limit!)}
|
|
219
|
+
</text>
|
|
199
220
|
</>
|
|
200
221
|
) : (
|
|
201
|
-
<text fg={colors.text} height={1}>
|
|
222
|
+
<text fg={colors.text} height={1}>
|
|
223
|
+
{formatCurrency(budget.totalCost)}
|
|
224
|
+
</text>
|
|
202
225
|
)}
|
|
203
226
|
</box>
|
|
204
227
|
)}
|
|
205
228
|
</box>
|
|
206
229
|
);
|
|
207
230
|
}
|
|
208
|
-
|
|
209
|
-
if (layoutMode ===
|
|
231
|
+
|
|
232
|
+
if (layoutMode === "wide") {
|
|
210
233
|
const baseMaxShow = 5;
|
|
211
234
|
const hasMore = sortedProviders.length > baseMaxShow;
|
|
212
235
|
const maxShow = hasMore ? baseMaxShow - 1 : baseMaxShow;
|
|
213
|
-
const startIndex = Math.max(
|
|
236
|
+
const startIndex = Math.max(
|
|
237
|
+
0,
|
|
238
|
+
Math.min(safeSelectedIndex - maxShow + 1, sortedProviders.length - maxShow),
|
|
239
|
+
);
|
|
214
240
|
const showLeftArrow = hasMore && startIndex > 0;
|
|
215
241
|
const showRightArrow = hasMore && startIndex + maxShow < sortedProviders.length;
|
|
216
242
|
const shown = sortedProviders.slice(startIndex, startIndex + maxShow);
|
|
217
|
-
|
|
243
|
+
|
|
218
244
|
return (
|
|
219
|
-
<box
|
|
220
|
-
flexDirection="column"
|
|
221
|
-
border
|
|
222
|
-
borderStyle={focused ?
|
|
223
|
-
borderColor={focused ? colors.primary : colors.border}
|
|
224
|
-
overflow="hidden"
|
|
225
|
-
height={4}
|
|
245
|
+
<box
|
|
246
|
+
flexDirection="column"
|
|
247
|
+
border
|
|
248
|
+
borderStyle={focused ? "double" : "single"}
|
|
249
|
+
borderColor={focused ? colors.primary : colors.border}
|
|
250
|
+
overflow="hidden"
|
|
251
|
+
height={4}
|
|
226
252
|
flexShrink={0}
|
|
227
253
|
>
|
|
228
254
|
<box flexDirection="row" paddingLeft={2} height={1}>
|
|
229
255
|
<text fg={colors.textMuted} height={1}>
|
|
230
|
-
PROVIDER LIMITS {focused ?
|
|
256
|
+
PROVIDER LIMITS {focused ? "(←→ navigate, Tab exit)" : ""}
|
|
231
257
|
</text>
|
|
232
258
|
</box>
|
|
233
259
|
<box flexDirection="row" gap={3} overflow="hidden" height={1} paddingLeft={2}>
|
|
234
260
|
{showLeftArrow && (
|
|
235
|
-
<text fg={colors.primary} height={1}
|
|
261
|
+
<text fg={colors.primary} height={1}>
|
|
262
|
+
◀
|
|
263
|
+
</text>
|
|
236
264
|
)}
|
|
237
265
|
{shown.map((p, idx) => (
|
|
238
266
|
<LimitGauge
|
|
@@ -244,45 +272,52 @@ export function ProviderLimitsPanel({
|
|
|
244
272
|
{...(p.resetTime ? { resetTime: p.resetTime } : {})}
|
|
245
273
|
labelWidth={14}
|
|
246
274
|
barWidth={12}
|
|
247
|
-
selected={focused &&
|
|
275
|
+
selected={focused && startIndex + idx === safeSelectedIndex}
|
|
248
276
|
warningThreshold={warningThreshold}
|
|
249
277
|
criticalThreshold={criticalThreshold}
|
|
250
278
|
/>
|
|
251
279
|
))}
|
|
252
280
|
{showRightArrow && (
|
|
253
|
-
<text fg={colors.primary} height={1}
|
|
281
|
+
<text fg={colors.primary} height={1}>
|
|
282
|
+
▶
|
|
283
|
+
</text>
|
|
254
284
|
)}
|
|
255
285
|
</box>
|
|
256
286
|
</box>
|
|
257
287
|
);
|
|
258
288
|
}
|
|
259
|
-
|
|
289
|
+
|
|
260
290
|
const baseMaxShow = 4;
|
|
261
291
|
const hasMore = sortedProviders.length > baseMaxShow;
|
|
262
292
|
const maxShow = hasMore ? baseMaxShow - 1 : baseMaxShow;
|
|
263
|
-
const startIndex = Math.max(
|
|
293
|
+
const startIndex = Math.max(
|
|
294
|
+
0,
|
|
295
|
+
Math.min(safeSelectedIndex - maxShow + 1, sortedProviders.length - maxShow),
|
|
296
|
+
);
|
|
264
297
|
const showLeftArrow = hasMore && startIndex > 0;
|
|
265
298
|
const showRightArrow = hasMore && startIndex + maxShow < sortedProviders.length;
|
|
266
299
|
const shown = sortedProviders.slice(startIndex, startIndex + maxShow);
|
|
267
|
-
|
|
300
|
+
|
|
268
301
|
return (
|
|
269
|
-
<box
|
|
270
|
-
flexDirection="column"
|
|
271
|
-
border
|
|
272
|
-
borderStyle={focused ?
|
|
273
|
-
borderColor={focused ? colors.primary : colors.border}
|
|
274
|
-
overflow="hidden"
|
|
275
|
-
height={4}
|
|
302
|
+
<box
|
|
303
|
+
flexDirection="column"
|
|
304
|
+
border
|
|
305
|
+
borderStyle={focused ? "double" : "single"}
|
|
306
|
+
borderColor={focused ? colors.primary : colors.border}
|
|
307
|
+
overflow="hidden"
|
|
308
|
+
height={4}
|
|
276
309
|
flexShrink={0}
|
|
277
310
|
>
|
|
278
311
|
<box flexDirection="row" paddingLeft={2} height={1}>
|
|
279
312
|
<text fg={colors.textMuted} height={1}>
|
|
280
|
-
PROVIDER LIMITS {focused ?
|
|
313
|
+
PROVIDER LIMITS {focused ? "(←→ navigate, Tab exit)" : ""}
|
|
281
314
|
</text>
|
|
282
315
|
</box>
|
|
283
316
|
<box flexDirection="row" gap={2} overflow="hidden" height={1} paddingLeft={2}>
|
|
284
317
|
{showLeftArrow && (
|
|
285
|
-
<text fg={colors.primary} height={1}
|
|
318
|
+
<text fg={colors.primary} height={1}>
|
|
319
|
+
◀
|
|
320
|
+
</text>
|
|
286
321
|
)}
|
|
287
322
|
{shown.map((p, idx) => (
|
|
288
323
|
<LimitGauge
|
|
@@ -294,13 +329,15 @@ export function ProviderLimitsPanel({
|
|
|
294
329
|
{...(p.resetTime ? { resetTime: p.resetTime } : {})}
|
|
295
330
|
labelWidth={12}
|
|
296
331
|
barWidth={10}
|
|
297
|
-
selected={focused &&
|
|
332
|
+
selected={focused && startIndex + idx === safeSelectedIndex}
|
|
298
333
|
warningThreshold={warningThreshold}
|
|
299
334
|
criticalThreshold={criticalThreshold}
|
|
300
335
|
/>
|
|
301
336
|
))}
|
|
302
337
|
{showRightArrow && (
|
|
303
|
-
<text fg={colors.primary} height={1}
|
|
338
|
+
<text fg={colors.primary} height={1}>
|
|
339
|
+
▶
|
|
340
|
+
</text>
|
|
304
341
|
)}
|
|
305
342
|
</box>
|
|
306
343
|
</box>
|