@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,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useTerminalDimensions } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { HelpOverlay } from '../components/HelpOverlay.tsx';
|
|
1
|
+
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { notificationBus } from "@/plugins/notification-bus.ts";
|
|
5
|
+
import { HelpOverlay } from "../components/HelpOverlay.tsx";
|
|
6
|
+
import { KpiStrip } from "../components/KpiStrip.tsx";
|
|
7
|
+
import { ProviderLimitsPanel } from "../components/ProviderLimitsPanel.tsx";
|
|
8
|
+
import { SessionsTable } from "../components/SessionsTable.tsx";
|
|
9
|
+
import { type DriverDimension, getSidebarMode, SmartSidebar } from "../components/SmartSidebar.tsx";
|
|
10
|
+
import { useAgentSessions } from "../contexts/AgentSessionContext.tsx";
|
|
11
|
+
import { useConfig } from "../contexts/ConfigContext.tsx";
|
|
12
|
+
import { useDashboardRuntime } from "../contexts/DashboardRuntimeContext.tsx";
|
|
13
|
+
import { useDrawer } from "../contexts/DrawerContext.tsx";
|
|
14
|
+
import { usePlugins } from "../contexts/PluginContext.tsx";
|
|
15
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
16
|
+
import { useTimeWindow } from "../contexts/TimeWindowContext.tsx";
|
|
17
|
+
import { useDashboardKeyboard } from "../hooks/useDashboardKeyboard.ts";
|
|
18
|
+
import { getProviderColor } from "../utils/providerColor.ts";
|
|
20
19
|
|
|
21
20
|
export function RealTimeDashboard() {
|
|
22
21
|
const colors = useColors();
|
|
@@ -27,51 +26,61 @@ export function RealTimeDashboard() {
|
|
|
27
26
|
const { config } = useConfig();
|
|
28
27
|
const { activity, sparkData, deltas } = useDashboardRuntime();
|
|
29
28
|
|
|
30
|
-
|
|
31
29
|
const sidebarMode = getSidebarMode(terminalWidth);
|
|
32
30
|
const showLargeHeader = terminalHeight >= 35;
|
|
33
31
|
const showProviderLimitsPanel = terminalHeight >= 24;
|
|
34
32
|
|
|
35
33
|
// Visible-row count for scroll tracking — every non-session-data line must be counted.
|
|
36
34
|
const visibleRows = (() => {
|
|
37
|
-
const appChrome = (showLargeHeader ? 7 : 1) + 1;
|
|
38
|
-
const outerPadding = 2;
|
|
39
|
-
const kpi = 5;
|
|
40
|
-
const limits = !showProviderLimitsPanel
|
|
41
|
-
|
|
42
|
-
:
|
|
35
|
+
const appChrome = (showLargeHeader ? 7 : 1) + 1; // App.tsx: Header + StatusBar
|
|
36
|
+
const outerPadding = 2; // padding={1} top + bottom
|
|
37
|
+
const kpi = 5; // KpiStrip fragment: h4 cards + h1 rule
|
|
38
|
+
const limits = !showProviderLimitsPanel
|
|
39
|
+
? 0 // ProviderLimitsPanel
|
|
40
|
+
: terminalHeight < 30
|
|
41
|
+
? sidebarMode === "hidden"
|
|
42
|
+
? 2
|
|
43
|
+
: 1 // compact (+budget bar)
|
|
44
|
+
: 4; // normal/wide bordered box
|
|
43
45
|
const tableChrome = 4 + (terminalHeight >= 30 ? 1 : 0); // borders + header + columns + inspector
|
|
44
|
-
const footer = 1;
|
|
46
|
+
const footer = 1; // keyboard shortcut bar
|
|
45
47
|
// gap={1} between rendered children; KpiStrip fragment injects 2 children
|
|
46
48
|
const children = 2 + (showProviderLimitsPanel ? 1 : 0) + 1 + 1;
|
|
47
49
|
const gaps = children - 1;
|
|
48
|
-
return Math.max(
|
|
50
|
+
return Math.max(
|
|
51
|
+
1,
|
|
52
|
+
terminalHeight - appChrome - outerPadding - kpi - limits - tableChrome - footer - gaps,
|
|
53
|
+
);
|
|
49
54
|
})();
|
|
50
55
|
|
|
51
56
|
const { showDrawer, isOpen: showSessionDrawer } = useDrawer();
|
|
52
|
-
|
|
57
|
+
|
|
53
58
|
const [showHelp, setShowHelp] = useState(false);
|
|
54
59
|
const [selectedRow, setSelectedRow] = useState(0);
|
|
55
|
-
const [focusedPanel, setFocusedPanel] = useState<
|
|
60
|
+
const [focusedPanel, setFocusedPanel] = useState<"sessions" | "sidebar" | "limits">("sessions");
|
|
56
61
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(config.display.sidebarCollapsed);
|
|
57
|
-
const [filterQuery, setFilterQuery] = useState(
|
|
62
|
+
const [filterQuery, setFilterQuery] = useState("");
|
|
58
63
|
const [isFiltering, setIsFiltering] = useState(false);
|
|
59
|
-
const [sortField, setSortField] = useState<
|
|
64
|
+
const [sortField, setSortField] = useState<"cost" | "tokens" | "time">("cost");
|
|
60
65
|
const [pendingG, setPendingG] = useState(false);
|
|
61
|
-
const
|
|
66
|
+
const scrollOffsetRef = useRef(0);
|
|
67
|
+
const setScrollOffset = useCallback((val: number) => {
|
|
68
|
+
scrollOffsetRef.current = val;
|
|
69
|
+
sessionsScrollboxRef.current?.scrollTo(val);
|
|
70
|
+
}, []);
|
|
62
71
|
const [limitSelectedIndex, setLimitSelectedIndex] = useState(0);
|
|
63
|
-
const [driverDimension, setDriverDimension] = useState<DriverDimension>(
|
|
72
|
+
const [driverDimension, setDriverDimension] = useState<DriverDimension>("model");
|
|
64
73
|
const [selectedDriverIndex, setSelectedDriverIndex] = useState(0);
|
|
65
74
|
const [activeDriverFilter, setActiveDriverFilter] = useState<string | null>(null);
|
|
66
75
|
|
|
67
|
-
const effectiveSidebarCollapsed = sidebarCollapsed || sidebarMode ===
|
|
68
|
-
const showBudgetInLimits = sidebarMode ===
|
|
76
|
+
const effectiveSidebarCollapsed = sidebarCollapsed || sidebarMode === "hidden";
|
|
77
|
+
const showBudgetInLimits = sidebarMode === "hidden";
|
|
69
78
|
|
|
70
79
|
const sessionsScrollboxRef = useRef<ScrollBoxRenderable>(null);
|
|
71
80
|
|
|
72
81
|
const configuredProviders = useMemo(() => {
|
|
73
82
|
return Array.from(providers.values())
|
|
74
|
-
.filter(p => p.configured)
|
|
83
|
+
.filter((p) => p.configured)
|
|
75
84
|
.sort((a, b) => getMaxUsedPercent(b) - getMaxUsedPercent(a));
|
|
76
85
|
}, [providers]);
|
|
77
86
|
|
|
@@ -91,19 +100,20 @@ export function RealTimeDashboard() {
|
|
|
91
100
|
|
|
92
101
|
const baseFilteredSessions = useMemo(() => {
|
|
93
102
|
let result = [...agentSessions];
|
|
94
|
-
|
|
103
|
+
|
|
95
104
|
if (windowMs !== null) {
|
|
96
105
|
const cutoff = Date.now() - windowMs;
|
|
97
|
-
result = result.filter(s => s.lastActivityAt >= cutoff);
|
|
106
|
+
result = result.filter((s) => s.lastActivityAt >= cutoff);
|
|
98
107
|
}
|
|
99
|
-
|
|
108
|
+
|
|
100
109
|
if (filterQuery) {
|
|
101
110
|
const q = filterQuery.toLowerCase();
|
|
102
|
-
result = result.filter(
|
|
103
|
-
s
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
result = result.filter(
|
|
112
|
+
(s) =>
|
|
113
|
+
s.agentName.toLowerCase().includes(q) ||
|
|
114
|
+
s.streams.some((st) => st.modelId.toLowerCase().includes(q)) ||
|
|
115
|
+
(s.projectPath?.toLowerCase().includes(q) ?? false) ||
|
|
116
|
+
(s.sessionName?.toLowerCase().includes(q) ?? false),
|
|
107
117
|
);
|
|
108
118
|
}
|
|
109
119
|
|
|
@@ -112,13 +122,19 @@ export function RealTimeDashboard() {
|
|
|
112
122
|
|
|
113
123
|
const drivers = useMemo(() => {
|
|
114
124
|
const stats: Record<string, { cost: number }> = {};
|
|
115
|
-
baseFilteredSessions.forEach(s => {
|
|
116
|
-
s.streams.forEach(st => {
|
|
125
|
+
baseFilteredSessions.forEach((s) => {
|
|
126
|
+
s.streams.forEach((st) => {
|
|
117
127
|
let key: string;
|
|
118
128
|
switch (driverDimension) {
|
|
119
|
-
case
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
case "model":
|
|
130
|
+
key = st.modelId;
|
|
131
|
+
break;
|
|
132
|
+
case "project":
|
|
133
|
+
key = s.projectPath?.split("/").pop() ?? "unknown";
|
|
134
|
+
break;
|
|
135
|
+
case "agent":
|
|
136
|
+
key = s.agentName;
|
|
137
|
+
break;
|
|
122
138
|
}
|
|
123
139
|
if (!stats[key]) stats[key] = { cost: 0 };
|
|
124
140
|
stats[key]!.cost += st.costUsd ?? 0;
|
|
@@ -138,7 +154,7 @@ export function RealTimeDashboard() {
|
|
|
138
154
|
}, [drivers.length, selectedDriverIndex]);
|
|
139
155
|
|
|
140
156
|
useEffect(() => {
|
|
141
|
-
if (activeDriverFilter ===
|
|
157
|
+
if (activeDriverFilter === "__TOGGLE_SELECTED__") {
|
|
142
158
|
const driver = drivers[selectedDriverIndex];
|
|
143
159
|
setActiveDriverFilter(driver?.id ?? null);
|
|
144
160
|
}
|
|
@@ -146,27 +162,30 @@ export function RealTimeDashboard() {
|
|
|
146
162
|
|
|
147
163
|
const processedSessions = useMemo(() => {
|
|
148
164
|
let result = [...baseFilteredSessions];
|
|
149
|
-
|
|
150
|
-
if (activeDriverFilter && activeDriverFilter !==
|
|
151
|
-
result = result.filter(s => {
|
|
165
|
+
|
|
166
|
+
if (activeDriverFilter && activeDriverFilter !== "__TOGGLE_SELECTED__") {
|
|
167
|
+
result = result.filter((s) => {
|
|
152
168
|
switch (driverDimension) {
|
|
153
|
-
case
|
|
154
|
-
return s.streams.some(st => st.modelId === activeDriverFilter);
|
|
155
|
-
case
|
|
156
|
-
return (s.projectPath?.split(
|
|
157
|
-
case
|
|
169
|
+
case "model":
|
|
170
|
+
return s.streams.some((st) => st.modelId === activeDriverFilter);
|
|
171
|
+
case "project":
|
|
172
|
+
return (s.projectPath?.split("/").pop() ?? "unknown") === activeDriverFilter;
|
|
173
|
+
case "agent":
|
|
158
174
|
return s.agentName === activeDriverFilter;
|
|
175
|
+
default:
|
|
176
|
+
return true;
|
|
159
177
|
}
|
|
160
178
|
});
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
result.sort((a, b) => {
|
|
164
|
-
const aActive = a.status ===
|
|
165
|
-
const bActive = b.status ===
|
|
182
|
+
const aActive = a.status === "active" ? 1 : 0;
|
|
183
|
+
const bActive = b.status === "active" ? 1 : 0;
|
|
166
184
|
if (bActive !== aActive) return bActive - aActive;
|
|
167
|
-
|
|
168
|
-
if (sortField ===
|
|
169
|
-
if (sortField ===
|
|
185
|
+
|
|
186
|
+
if (sortField === "cost") return (b.totalCostUsd ?? 0) - (a.totalCostUsd ?? 0);
|
|
187
|
+
if (sortField === "tokens")
|
|
188
|
+
return b.totals.input + b.totals.output - (a.totals.input + a.totals.output);
|
|
170
189
|
return b.lastActivityAt - a.lastActivityAt;
|
|
171
190
|
});
|
|
172
191
|
|
|
@@ -176,34 +195,31 @@ export function RealTimeDashboard() {
|
|
|
176
195
|
useEffect(() => {
|
|
177
196
|
if (processedSessions.length === 0) {
|
|
178
197
|
if (selectedRow !== 0) setSelectedRow(0);
|
|
179
|
-
if (
|
|
198
|
+
if (scrollOffsetRef.current !== 0) setScrollOffset(0);
|
|
180
199
|
return;
|
|
181
200
|
}
|
|
182
|
-
|
|
201
|
+
|
|
183
202
|
const maxRow = processedSessions.length - 1;
|
|
184
203
|
if (selectedRow > maxRow) {
|
|
185
204
|
setSelectedRow(maxRow);
|
|
186
205
|
return;
|
|
187
206
|
}
|
|
188
|
-
}, [processedSessions.length, selectedRow,
|
|
207
|
+
}, [processedSessions.length, selectedRow, setScrollOffset]);
|
|
189
208
|
|
|
190
|
-
|
|
209
|
+
useLayoutEffect(() => {
|
|
191
210
|
if (!sessionsScrollboxRef.current || processedSessions.length === 0) return;
|
|
192
211
|
|
|
193
|
-
let newOffset =
|
|
212
|
+
let newOffset = scrollOffsetRef.current;
|
|
194
213
|
|
|
195
|
-
if (selectedRow <
|
|
214
|
+
if (selectedRow < newOffset) {
|
|
196
215
|
newOffset = selectedRow;
|
|
197
|
-
} else if (selectedRow >=
|
|
216
|
+
} else if (selectedRow >= newOffset + visibleRows) {
|
|
198
217
|
newOffset = selectedRow - visibleRows + 1;
|
|
199
218
|
}
|
|
200
219
|
|
|
201
|
-
|
|
202
|
-
setScrollOffset(newOffset);
|
|
203
|
-
}
|
|
204
|
-
|
|
220
|
+
scrollOffsetRef.current = newOffset;
|
|
205
221
|
sessionsScrollboxRef.current.scrollTo(newOffset);
|
|
206
|
-
}, [selectedRow, processedSessions.length,
|
|
222
|
+
}, [selectedRow, processedSessions.length, visibleRows]);
|
|
207
223
|
|
|
208
224
|
const openSessionDrawer = useCallback(() => {
|
|
209
225
|
const session = processedSessions[selectedRow];
|
|
@@ -225,7 +241,7 @@ export function RealTimeDashboard() {
|
|
|
225
241
|
isFiltering,
|
|
226
242
|
sortField,
|
|
227
243
|
pendingG,
|
|
228
|
-
scrollOffset,
|
|
244
|
+
scrollOffset: scrollOffsetRef.current,
|
|
229
245
|
limitSelectedIndex,
|
|
230
246
|
providerCount: configuredProviders.length,
|
|
231
247
|
driverDimension,
|
|
@@ -253,14 +269,16 @@ export function RealTimeDashboard() {
|
|
|
253
269
|
processedSessions,
|
|
254
270
|
});
|
|
255
271
|
|
|
256
|
-
const windowedKpis = useMemo(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
272
|
+
const windowedKpis = useMemo(
|
|
273
|
+
() => ({
|
|
274
|
+
cost: processedSessions.reduce((acc, s) => acc + (s.totalCostUsd ?? 0), 0),
|
|
275
|
+
tokens: processedSessions.reduce((acc, s) => acc + s.totals.input + s.totals.output, 0),
|
|
276
|
+
requests: processedSessions.reduce((acc, s) => acc + s.requestCount, 0),
|
|
277
|
+
}),
|
|
278
|
+
[processedSessions],
|
|
279
|
+
);
|
|
261
280
|
|
|
262
281
|
const budgetPeriodCost = useMemo(() => {
|
|
263
|
-
|
|
264
282
|
let dailyCost = 0;
|
|
265
283
|
let weeklyCost = 0;
|
|
266
284
|
let monthlyCost = 0;
|
|
@@ -275,10 +293,14 @@ export function RealTimeDashboard() {
|
|
|
275
293
|
}, [agentSessions]);
|
|
276
294
|
|
|
277
295
|
useEffect(() => {
|
|
278
|
-
const checks: Array<{
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
296
|
+
const checks: Array<{
|
|
297
|
+
cost: number;
|
|
298
|
+
limit: number | null;
|
|
299
|
+
type: "daily" | "weekly" | "monthly";
|
|
300
|
+
}> = [
|
|
301
|
+
{ cost: budgetPeriodCost.daily, limit: config.budgets.daily, type: "daily" },
|
|
302
|
+
{ cost: budgetPeriodCost.weekly, limit: config.budgets.weekly, type: "weekly" },
|
|
303
|
+
{ cost: budgetPeriodCost.monthly, limit: config.budgets.monthly, type: "monthly" },
|
|
282
304
|
];
|
|
283
305
|
for (const { cost, limit, type } of checks) {
|
|
284
306
|
if (limit !== null && limit > 0) {
|
|
@@ -289,20 +311,23 @@ export function RealTimeDashboard() {
|
|
|
289
311
|
|
|
290
312
|
const getBudgetCost = () => {
|
|
291
313
|
switch (budgetType) {
|
|
292
|
-
case
|
|
293
|
-
|
|
294
|
-
case
|
|
295
|
-
|
|
314
|
+
case "daily":
|
|
315
|
+
return budgetPeriodCost.daily;
|
|
316
|
+
case "weekly":
|
|
317
|
+
return budgetPeriodCost.weekly;
|
|
318
|
+
case "monthly":
|
|
319
|
+
return budgetPeriodCost.monthly;
|
|
320
|
+
default:
|
|
321
|
+
return windowedKpis.cost;
|
|
296
322
|
}
|
|
297
323
|
};
|
|
298
324
|
|
|
299
|
-
const activeCount = agentSessions.filter(s => s.status ===
|
|
325
|
+
const activeCount = agentSessions.filter((s) => s.status === "active").length;
|
|
300
326
|
|
|
301
327
|
return (
|
|
302
328
|
<box flexDirection="column" flexGrow={1} padding={1} gap={1} overflow="hidden">
|
|
303
329
|
{showHelp && <HelpOverlay />}
|
|
304
330
|
|
|
305
|
-
|
|
306
331
|
<KpiStrip
|
|
307
332
|
totalCost={windowedKpis.cost}
|
|
308
333
|
totalTokens={windowedKpis.tokens}
|
|
@@ -314,10 +339,14 @@ export function RealTimeDashboard() {
|
|
|
314
339
|
activity={activity}
|
|
315
340
|
sparkData={sparkData}
|
|
316
341
|
budget={{
|
|
317
|
-
limit:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
342
|
+
limit:
|
|
343
|
+
budgetType === "daily"
|
|
344
|
+
? config.budgets.daily
|
|
345
|
+
: budgetType === "weekly"
|
|
346
|
+
? config.budgets.weekly
|
|
347
|
+
: budgetType === "monthly"
|
|
348
|
+
? config.budgets.monthly
|
|
349
|
+
: null,
|
|
321
350
|
budgetCost: getBudgetCost(),
|
|
322
351
|
budgetType,
|
|
323
352
|
budgetTypeLabel,
|
|
@@ -327,23 +356,27 @@ export function RealTimeDashboard() {
|
|
|
327
356
|
/>
|
|
328
357
|
|
|
329
358
|
<ProviderLimitsPanel
|
|
330
|
-
providers={configuredProviders.map(p => ({
|
|
359
|
+
providers={configuredProviders.map((p) => ({
|
|
331
360
|
id: p.plugin.id,
|
|
332
361
|
name: p.plugin.name,
|
|
333
362
|
usedPercent: getMaxUsedPercent(p),
|
|
334
363
|
color: providerColorOf(p.plugin.id),
|
|
335
364
|
...(p.usage?.error ? { error: p.usage.error } : {}),
|
|
336
365
|
}))}
|
|
337
|
-
focused={focusedPanel ===
|
|
366
|
+
focused={focusedPanel === "limits"}
|
|
338
367
|
selectedIndex={limitSelectedIndex}
|
|
339
368
|
showBudgetBar={showBudgetInLimits}
|
|
340
369
|
budget={{
|
|
341
370
|
totalCost: windowedKpis.cost,
|
|
342
371
|
budgetCost: getBudgetCost(),
|
|
343
|
-
limit:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
372
|
+
limit:
|
|
373
|
+
budgetType === "daily"
|
|
374
|
+
? config.budgets.daily
|
|
375
|
+
: budgetType === "weekly"
|
|
376
|
+
? config.budgets.weekly
|
|
377
|
+
: budgetType === "monthly"
|
|
378
|
+
? config.budgets.monthly
|
|
379
|
+
: null,
|
|
347
380
|
budgetType,
|
|
348
381
|
budgetTypeLabel,
|
|
349
382
|
warningPercent: config.alerts.warningPercent,
|
|
@@ -376,16 +409,22 @@ export function RealTimeDashboard() {
|
|
|
376
409
|
/>
|
|
377
410
|
)}
|
|
378
411
|
</box>
|
|
379
|
-
|
|
412
|
+
|
|
380
413
|
<box flexDirection="row" paddingLeft={1} height={1} flexShrink={0}>
|
|
381
414
|
<text fg={colors.textSubtle} height={1} flexGrow={1}>
|
|
382
|
-
{isFiltering
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
415
|
+
{isFiltering
|
|
416
|
+
? "Type to filter Esc cancel Enter apply"
|
|
417
|
+
: filterQuery
|
|
418
|
+
? `Esc clear / edit filter ↑↓ navigate s sort`
|
|
419
|
+
: activeDriverFilter
|
|
420
|
+
? `Filter: ${activeDriverFilter} Esc clear`
|
|
421
|
+
: focusedPanel === "sessions"
|
|
422
|
+
? "/ filter ↑↓ navigate Enter details s sort l limits Tab next ⇧Tab prev"
|
|
423
|
+
: focusedPanel === "limits"
|
|
424
|
+
? "←→ select provider Tab next ⇧Tab prev Esc back"
|
|
425
|
+
: focusedPanel === "sidebar"
|
|
426
|
+
? "↑↓ select Enter filter m/p/a dimension b budget Tab next ⇧Tab prev"
|
|
427
|
+
: "/ filter i sidebar Tab/⇧Tab switch ? help"}
|
|
389
428
|
</text>
|
|
390
429
|
</box>
|
|
391
430
|
</box>
|