@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,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { forwardRef, memo, type Ref, useCallback, useRef } from "react";
|
|
4
|
+
import type { AgentSessionAggregate } from "../../agents/types.ts";
|
|
5
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
6
|
+
import { useAnimatedValue } from "../hooks/useAnimatedValue.ts";
|
|
7
|
+
import { applyEntranceFade, useEntranceAnimation } from "../hooks/useEntranceAnimation.ts";
|
|
8
|
+
import { useExitAnimation } from "../hooks/useExitAnimation.ts";
|
|
9
|
+
import { interpolateColor, useValueFlash } from "../hooks/useValueFlash.ts";
|
|
10
10
|
|
|
11
11
|
interface SessionsTableProps {
|
|
12
12
|
sessions: AgentSessionAggregate[];
|
|
@@ -14,22 +14,22 @@ interface SessionsTableProps {
|
|
|
14
14
|
isLoading: boolean;
|
|
15
15
|
isFiltering: boolean;
|
|
16
16
|
filterQuery: string;
|
|
17
|
-
focusedPanel:
|
|
17
|
+
focusedPanel: "sessions" | "sidebar" | "limits";
|
|
18
18
|
windowLabel: string;
|
|
19
19
|
getProviderColor: (id: string) => string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function extractRepoName(projectPath: string | null): string {
|
|
23
|
-
if (!projectPath || projectPath ===
|
|
24
|
-
const normalized = projectPath.replace(/\\/g,
|
|
25
|
-
return normalized.split(
|
|
23
|
+
if (!projectPath || projectPath === "—") return "—";
|
|
24
|
+
const normalized = projectPath.replace(/\\/g, "/");
|
|
25
|
+
return normalized.split("/").filter(Boolean).pop() ?? projectPath;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function formatRelativeTime(timestamp: number): string {
|
|
29
29
|
const now = Date.now();
|
|
30
30
|
const diffMs = now - timestamp;
|
|
31
31
|
const diffSec = Math.floor(diffMs / 1000);
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
if (diffSec < 60) return `${diffSec}s`;
|
|
34
34
|
const diffMin = Math.floor(diffSec / 60);
|
|
35
35
|
if (diffMin < 60) return `${diffMin}m`;
|
|
@@ -43,7 +43,7 @@ function formatDuration(startedAt: number, endedAt?: number): string {
|
|
|
43
43
|
const end = endedAt ?? Date.now();
|
|
44
44
|
const diffMs = end - startedAt;
|
|
45
45
|
const diffSec = Math.floor(diffMs / 1000);
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
if (diffSec < 60) return `${diffSec}s`;
|
|
48
48
|
const diffMin = Math.floor(diffSec / 60);
|
|
49
49
|
if (diffMin < 60) return `${diffMin}m`;
|
|
@@ -54,23 +54,23 @@ function formatDuration(startedAt: number, endedAt?: number): string {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function formatCost(costUsd: number): string {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
if (costUsd === 0) return "$0";
|
|
58
|
+
if (costUsd < 0.01) return "$0.00";
|
|
59
|
+
if (costUsd < 10) return `$${costUsd.toFixed(2)}`;
|
|
60
|
+
if (costUsd < 100) return `$${costUsd.toFixed(1)}`;
|
|
61
|
+
if (costUsd < 1000) return `$${Math.floor(costUsd)}`;
|
|
62
|
+
return `$${(costUsd / 1000).toFixed(1)}k`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function truncateMiddle(str: string, maxLength: number): string {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
if (str.length <= maxLength) return str;
|
|
67
|
+
const side = Math.floor((maxLength - 1) / 2);
|
|
68
|
+
return str.slice(0, side) + "…" + str.slice(-side);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function formatCostVal(cost: number): string {
|
|
72
|
-
if (cost === 0) return
|
|
73
|
-
if (cost < 0.01) return
|
|
72
|
+
if (cost === 0) return "$0";
|
|
73
|
+
if (cost < 0.01) return "<$0.01";
|
|
74
74
|
if (cost < 10) return `$${cost.toFixed(2)}`;
|
|
75
75
|
if (cost < 100) return `$${cost.toFixed(1)}`;
|
|
76
76
|
if (cost < 1000) return `$${Math.round(cost)}`;
|
|
@@ -96,16 +96,16 @@ function getInspectorData(session: AgentSessionAggregate) {
|
|
|
96
96
|
const cacheWrite = session.totals.cacheWrite ?? 0;
|
|
97
97
|
if (cacheRead === 0 && cacheWrite === 0) return null;
|
|
98
98
|
|
|
99
|
-
let readCost = 0,
|
|
99
|
+
let readCost = 0,
|
|
100
|
+
writeCost = 0;
|
|
100
101
|
for (const stream of session.streams) {
|
|
101
102
|
if (!stream.costBreakdown) continue;
|
|
102
103
|
readCost += stream.costBreakdown.cacheRead ?? 0;
|
|
103
104
|
writeCost += stream.costBreakdown.cacheWrite ?? 0;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
const costPerReq =
|
|
107
|
-
? (session.totalCostUsd ?? 0) / session.requestCount
|
|
108
|
-
: 0;
|
|
107
|
+
const costPerReq =
|
|
108
|
+
session.requestCount > 0 ? (session.totalCostUsd ?? 0) / session.requestCount : 0;
|
|
109
109
|
|
|
110
110
|
return { cacheRead, cacheWrite, readCost, writeCost, costPerReq };
|
|
111
111
|
}
|
|
@@ -130,134 +130,211 @@ function getActivityFadeColor(lastActivityAt: number, baseColor: string, dimColo
|
|
|
130
130
|
return interpolateColor(t, baseColor, dimColor);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
const SessionRow = memo(function SessionRow({
|
|
133
|
+
const SessionRow = memo(function SessionRow({
|
|
134
|
+
session,
|
|
135
|
+
isSelected,
|
|
136
|
+
isWide,
|
|
137
|
+
nameMaxWidth,
|
|
138
|
+
getProviderColor,
|
|
139
|
+
isExiting,
|
|
140
|
+
exitIntensity,
|
|
141
|
+
skipEntrance,
|
|
142
|
+
terminalWidth,
|
|
143
|
+
}: SessionRowProps) {
|
|
134
144
|
const colors = useColors();
|
|
135
|
-
const isActive = session.status ===
|
|
145
|
+
const isActive = session.status === "active";
|
|
136
146
|
const entranceIntensity = useEntranceAnimation({ durationMs: 500 });
|
|
137
147
|
const skipRef = useRef(skipEntrance);
|
|
138
|
-
const currentIntensity = isExiting
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const currentIntensity = isExiting
|
|
149
|
+
? (exitIntensity ?? 0)
|
|
150
|
+
: skipRef.current
|
|
151
|
+
? 1
|
|
152
|
+
: entranceIntensity;
|
|
153
|
+
|
|
154
|
+
const effectiveTokens = session.totals.input + session.totals.output;
|
|
155
|
+
const cacheRead = session.totals.cacheRead ?? 0;
|
|
156
|
+
const hasCacheData = cacheRead > 0;
|
|
157
|
+
const costUsd = session.totalCostUsd ?? 0;
|
|
158
|
+
|
|
159
|
+
const animatedTokens = useAnimatedValue(effectiveTokens, { durationMs: 300, precision: 0 });
|
|
160
|
+
const animatedCost = useAnimatedValue(costUsd, { durationMs: 300, precision: 4 });
|
|
161
|
+
|
|
162
|
+
const { intensity: tokenFlash } = useValueFlash(effectiveTokens, {
|
|
163
|
+
durationMs: 400,
|
|
164
|
+
threshold: 10,
|
|
165
|
+
});
|
|
166
|
+
const { intensity: costFlash } = useValueFlash(costUsd, { durationMs: 400, threshold: 0.001 });
|
|
167
|
+
|
|
151
168
|
const primaryStream = session.streams[0];
|
|
152
|
-
const providerId = primaryStream?.providerId ??
|
|
153
|
-
const modelId = primaryStream?.modelId ??
|
|
169
|
+
const providerId = primaryStream?.providerId ?? "unknown";
|
|
170
|
+
const modelId = primaryStream?.modelId ?? "unknown";
|
|
154
171
|
const baseProviderColor = getProviderColor(providerId);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
const repoName = extractRepoName(session.projectPath ?? "—");
|
|
173
|
+
// In wide mode, project is fixed width (14), so truncate to 13 chars + ellipsis if needed
|
|
174
|
+
// In narrow mode, project is flex, so use nameMaxWidth (which is actually projectMaxWidth in narrow mode context)
|
|
175
|
+
const projectDisplay = isWide
|
|
176
|
+
? repoName.length > 13
|
|
177
|
+
? repoName.slice(0, 12) + "…"
|
|
178
|
+
: repoName.padEnd(13)
|
|
179
|
+
: repoName.length > nameMaxWidth
|
|
180
|
+
? repoName.slice(0, nameMaxWidth - 1) + "…"
|
|
181
|
+
: repoName;
|
|
182
|
+
|
|
162
183
|
const dimColor = colors.background;
|
|
163
184
|
const fade = (color: string) => applyEntranceFade(currentIntensity, color, dimColor);
|
|
164
|
-
|
|
165
|
-
const baseStatusColor = isActive
|
|
185
|
+
|
|
186
|
+
const baseStatusColor = isActive
|
|
166
187
|
? getActivityFadeColor(session.lastActivityAt, colors.success, colors.textMuted)
|
|
167
188
|
: colors.textMuted;
|
|
168
189
|
const statusColor = fade(baseStatusColor);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const tokenColorBeforeFade =
|
|
172
|
-
? interpolateColor(tokenFlash, baseTokenColor,
|
|
173
|
-
: baseTokenColor;
|
|
190
|
+
|
|
191
|
+
const baseTokenColor = colors.text;
|
|
192
|
+
const tokenColorBeforeFade =
|
|
193
|
+
tokenFlash > 0 ? interpolateColor(tokenFlash, baseTokenColor, "#ffffff") : baseTokenColor;
|
|
174
194
|
const tokenColor = fade(tokenColorBeforeFade);
|
|
175
|
-
|
|
195
|
+
|
|
176
196
|
const providerColor = fade(baseProviderColor);
|
|
177
197
|
const textColor = fade(colors.text);
|
|
178
198
|
const textSubtleColor = fade(colors.textSubtle);
|
|
179
199
|
const textMutedColor = fade(colors.textMuted);
|
|
180
200
|
const baseCostColor = colors.warning;
|
|
181
|
-
const costColorBeforeFade =
|
|
182
|
-
? interpolateColor(costFlash, baseCostColor,
|
|
183
|
-
: baseCostColor;
|
|
201
|
+
const costColorBeforeFade =
|
|
202
|
+
costFlash > 0 ? interpolateColor(costFlash, baseCostColor, "#ffffff") : baseCostColor;
|
|
184
203
|
const costColor = fade(costColorBeforeFade);
|
|
185
|
-
|
|
204
|
+
|
|
186
205
|
const formatTokensVal = (val: number): string => {
|
|
187
206
|
if (val >= 1000000) return `${(val / 1000000).toFixed(1)}M`;
|
|
188
207
|
if (val >= 1000) return `${(val / 1000).toFixed(1)}K`;
|
|
189
208
|
return `${val}`;
|
|
190
209
|
};
|
|
191
|
-
|
|
192
|
-
const railChar = isSelected ?
|
|
210
|
+
|
|
211
|
+
const railChar = isSelected ? "▌" : " ";
|
|
193
212
|
const railColor = fade(colors.primary);
|
|
194
213
|
const rowBg = isSelected ? colors.borderMuted : undefined;
|
|
195
|
-
|
|
214
|
+
|
|
196
215
|
const lastActivity = formatRelativeTime(session.lastActivityAt);
|
|
197
216
|
const duration = formatDuration(session.startedAt, session.endedAt);
|
|
198
217
|
const costDisplay = formatCost(animatedCost);
|
|
199
|
-
|
|
200
|
-
const modelDisplay = modelId.split('/').pop()?.slice(0, 15) ?? modelId;
|
|
201
|
-
const streamCount = session.streams.length;
|
|
202
|
-
const modelWithCount = streamCount > 1 ? `${modelDisplay.slice(0, 12)}+${streamCount - 1}` : modelDisplay;
|
|
203
|
-
|
|
204
|
-
const baseLastColor = isActive
|
|
205
|
-
? getActivityFadeColor(session.lastActivityAt, colors.text, colors.textMuted)
|
|
206
|
-
: colors.textMuted;
|
|
207
|
-
const lastColor = fade(baseLastColor);
|
|
208
|
-
|
|
209
|
-
const bgProp = rowBg ? { bg: rowBg } : {};
|
|
210
|
-
|
|
211
|
-
const sessionIdShort = session.sessionId.slice(-7);
|
|
212
|
-
// In wide mode, name is flex, so use nameMaxWidth
|
|
213
|
-
// In narrow mode, name is not shown
|
|
214
|
-
const sessionNameDisplay = session.sessionName ? truncateMiddle(session.sessionName, isWide ? nameMaxWidth : 25) : '—';
|
|
215
|
-
|
|
216
|
-
if (isWide) {
|
|
217
|
-
return (
|
|
218
|
-
<box flexDirection="row" paddingRight={1} height={1} gap={1} {...(rowBg ? { backgroundColor: rowBg } : {})}>
|
|
219
|
-
<text width={2} height={1} fg={railColor} {...bgProp}>{railChar}</text>
|
|
220
|
-
<text width={8} height={1} fg={textMutedColor} {...bgProp}>{sessionIdShort}</text>
|
|
221
|
-
<text width={12} height={1} fg={isSelected ? textColor : textSubtleColor} {...bgProp}>{session.agentName.padEnd(11)}</text>
|
|
222
|
-
<text width={18} height={1} fg={providerColor} {...bgProp}>{modelWithCount.padEnd(17)}</text>
|
|
223
|
-
<text width={5} height={1} fg={textMutedColor} {...bgProp}>{String(session.requestCount).padStart(4)}</text>
|
|
224
|
-
<text width={14} height={1} {...bgProp}>
|
|
225
|
-
<span fg={tokenColor}>{formatTokensVal(animatedTokens).padStart(7)}</span>
|
|
226
|
-
<span fg={colors.textMuted}>{hasCacheData ? ` ↯${formatCacheBadge(cacheRead)}` : ''}</span>
|
|
227
|
-
</text>
|
|
228
|
-
<text width={8} height={1} fg={costColor} {...bgProp}>{costDisplay.padStart(7)}</text>
|
|
229
|
-
<text width={14} height={1} fg={textSubtleColor} {...bgProp}>{projectDisplay}</text>
|
|
230
|
-
<text flexGrow={1} height={1} fg={textMutedColor} {...bgProp}>{sessionNameDisplay}</text>
|
|
231
|
-
<text width={6} height={1} fg={textMutedColor} {...bgProp}>{duration.padStart(5)}</text>
|
|
232
|
-
<text width={5} height={1} fg={lastColor} {...bgProp}>{lastActivity.padStart(4)}</text>
|
|
233
|
-
<text width={2} height={1} fg={statusColor} {...bgProp}>{isActive ? '●' : '○'}</text>
|
|
234
|
-
</box>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const showCacheBadge = terminalWidth >= 105;
|
|
239
|
-
const hasSidebar = terminalWidth >= 85;
|
|
240
|
-
const isTight = !isWide && hasSidebar && terminalWidth < 105;
|
|
241
|
-
const narrowModelW = isTight ? 13 : 16;
|
|
242
|
-
const narrowTokenW = isTight ? 9 : 14;
|
|
243
|
-
const narrowModelMaxChars = narrowModelW - 1;
|
|
244
218
|
|
|
219
|
+
const modelDisplay = modelId.split("/").pop()?.slice(0, 15) ?? modelId;
|
|
220
|
+
const streamCount = session.streams.length;
|
|
221
|
+
const modelWithCount =
|
|
222
|
+
streamCount > 1 ? `${modelDisplay.slice(0, 12)}+${streamCount - 1}` : modelDisplay;
|
|
223
|
+
|
|
224
|
+
const baseLastColor = isActive
|
|
225
|
+
? getActivityFadeColor(session.lastActivityAt, colors.text, colors.textMuted)
|
|
226
|
+
: colors.textMuted;
|
|
227
|
+
const lastColor = fade(baseLastColor);
|
|
228
|
+
|
|
229
|
+
const bgProp = rowBg ? { bg: rowBg } : {};
|
|
230
|
+
|
|
231
|
+
const sessionIdShort = session.sessionId.slice(-7);
|
|
232
|
+
// In wide mode, name is flex, so use nameMaxWidth
|
|
233
|
+
// In narrow mode, name is not shown
|
|
234
|
+
const sessionNameDisplay = session.sessionName
|
|
235
|
+
? truncateMiddle(session.sessionName, isWide ? nameMaxWidth : 25)
|
|
236
|
+
: "—";
|
|
237
|
+
|
|
238
|
+
if (isWide) {
|
|
245
239
|
return (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
240
|
+
<box
|
|
241
|
+
flexDirection="row"
|
|
242
|
+
paddingRight={1}
|
|
243
|
+
height={1}
|
|
244
|
+
gap={1}
|
|
245
|
+
{...(rowBg ? { backgroundColor: rowBg } : {})}
|
|
246
|
+
>
|
|
247
|
+
<text width={2} height={1} fg={railColor} {...bgProp}>
|
|
248
|
+
{railChar}
|
|
249
|
+
</text>
|
|
250
|
+
<text width={8} height={1} fg={textMutedColor} {...bgProp}>
|
|
251
|
+
{sessionIdShort}
|
|
252
|
+
</text>
|
|
253
|
+
<text width={12} height={1} fg={isSelected ? textColor : textSubtleColor} {...bgProp}>
|
|
254
|
+
{session.agentName.padEnd(11)}
|
|
255
|
+
</text>
|
|
256
|
+
<text width={18} height={1} fg={providerColor} {...bgProp}>
|
|
257
|
+
{modelWithCount.padEnd(17)}
|
|
258
|
+
</text>
|
|
259
|
+
<text width={5} height={1} fg={textMutedColor} {...bgProp}>
|
|
260
|
+
{String(session.requestCount).padStart(4)}
|
|
261
|
+
</text>
|
|
262
|
+
<text width={14} height={1} {...bgProp}>
|
|
263
|
+
<span fg={tokenColor}>{formatTokensVal(animatedTokens).padStart(7)}</span>
|
|
264
|
+
<span fg={colors.textMuted}>
|
|
265
|
+
{hasCacheData ? ` ↯${formatCacheBadge(cacheRead)}` : ""}
|
|
266
|
+
</span>
|
|
267
|
+
</text>
|
|
268
|
+
<text width={8} height={1} fg={costColor} {...bgProp}>
|
|
269
|
+
{costDisplay.padStart(7)}
|
|
270
|
+
</text>
|
|
271
|
+
<text width={14} height={1} fg={textSubtleColor} {...bgProp}>
|
|
272
|
+
{projectDisplay}
|
|
273
|
+
</text>
|
|
274
|
+
<text flexGrow={1} height={1} fg={textMutedColor} {...bgProp}>
|
|
275
|
+
{sessionNameDisplay}
|
|
276
|
+
</text>
|
|
277
|
+
<text width={6} height={1} fg={textMutedColor} {...bgProp}>
|
|
278
|
+
{duration.padStart(5)}
|
|
279
|
+
</text>
|
|
280
|
+
<text width={5} height={1} fg={lastColor} {...bgProp}>
|
|
281
|
+
{lastActivity.padStart(4)}
|
|
282
|
+
</text>
|
|
283
|
+
<text width={2} height={1} fg={statusColor} {...bgProp}>
|
|
284
|
+
{isActive ? "●" : "○"}
|
|
285
|
+
</text>
|
|
286
|
+
</box>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const showCacheBadge = terminalWidth >= 105;
|
|
291
|
+
const hasSidebar = terminalWidth >= 85;
|
|
292
|
+
const isTight = !isWide && hasSidebar && terminalWidth < 105;
|
|
293
|
+
const narrowModelW = isTight ? 13 : 16;
|
|
294
|
+
const narrowTokenW = isTight ? 9 : 14;
|
|
295
|
+
const narrowModelMaxChars = narrowModelW - 1;
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<box
|
|
299
|
+
flexDirection="row"
|
|
300
|
+
paddingRight={1}
|
|
301
|
+
height={1}
|
|
302
|
+
{...(rowBg ? { backgroundColor: rowBg } : {})}
|
|
303
|
+
>
|
|
304
|
+
<text width={2} height={1} fg={railColor} {...bgProp}>
|
|
305
|
+
{railChar}
|
|
306
|
+
</text>
|
|
307
|
+
<text width={9} height={1} fg={isSelected ? textColor : textSubtleColor} {...bgProp}>
|
|
308
|
+
{session.agentName.length > 8
|
|
309
|
+
? session.agentName.slice(0, 8) + "…"
|
|
310
|
+
: session.agentName.padEnd(8)}
|
|
311
|
+
</text>
|
|
312
|
+
<text width={narrowModelW} height={1} fg={providerColor} {...bgProp}>
|
|
313
|
+
{modelWithCount.length > narrowModelMaxChars
|
|
314
|
+
? modelWithCount.slice(0, narrowModelMaxChars - 1) + "…"
|
|
315
|
+
: modelWithCount.padEnd(narrowModelMaxChars)}
|
|
316
|
+
</text>
|
|
317
|
+
<text width={narrowTokenW} height={1} {...bgProp}>
|
|
318
|
+
<span fg={tokenColor}>{formatTokensVal(animatedTokens).padStart(7)}</span>
|
|
319
|
+
<span fg={colors.textMuted}>
|
|
320
|
+
{hasCacheData && showCacheBadge ? ` ↯${formatCacheBadge(cacheRead)}` : ""}
|
|
321
|
+
</span>
|
|
322
|
+
</text>
|
|
323
|
+
<text width={7} height={1} fg={costColor} {...bgProp}>
|
|
324
|
+
{formatCostVal(costUsd).padStart(6)}
|
|
325
|
+
</text>
|
|
326
|
+
<text flexGrow={1} height={1} fg={textSubtleColor} {...bgProp}>
|
|
327
|
+
{projectDisplay}
|
|
328
|
+
</text>
|
|
329
|
+
<text width={5} height={1} fg={lastColor} {...bgProp}>
|
|
330
|
+
{lastActivity.padStart(4)}
|
|
331
|
+
</text>
|
|
332
|
+
<text width={2} height={1} fg={statusColor} {...bgProp}>
|
|
333
|
+
{isActive ? "●" : "○"}
|
|
334
|
+
</text>
|
|
335
|
+
</box>
|
|
336
|
+
);
|
|
337
|
+
});
|
|
261
338
|
|
|
262
339
|
const WIDE_THRESHOLD = 140;
|
|
263
340
|
|
|
@@ -272,7 +349,7 @@ export const SessionsTable = forwardRef(function SessionsTable(
|
|
|
272
349
|
windowLabel,
|
|
273
350
|
getProviderColor,
|
|
274
351
|
}: SessionsTableProps,
|
|
275
|
-
ref: Ref<ScrollBoxRenderable
|
|
352
|
+
ref: Ref<ScrollBoxRenderable>,
|
|
276
353
|
) {
|
|
277
354
|
const colors = useColors();
|
|
278
355
|
const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
|
|
@@ -282,13 +359,26 @@ export const SessionsTable = forwardRef(function SessionsTable(
|
|
|
282
359
|
// Sidebar width depends on terminal width (see SmartSidebar.getSidebarWidth).
|
|
283
360
|
// Table inner width = terminalWidth - sidebarWidth - borders(4) - outerMargins(2).
|
|
284
361
|
// Name column = tableInnerWidth - fixedColumnsTotal.
|
|
285
|
-
const sidebarW =
|
|
362
|
+
const sidebarW =
|
|
363
|
+
terminalWidth >= 160
|
|
364
|
+
? 44
|
|
365
|
+
: terminalWidth >= 140
|
|
366
|
+
? 40
|
|
367
|
+
: terminalWidth >= 105
|
|
368
|
+
? 36
|
|
369
|
+
: terminalWidth >= 95
|
|
370
|
+
? 32
|
|
371
|
+
: terminalWidth >= 85
|
|
372
|
+
? 28
|
|
373
|
+
: 0;
|
|
286
374
|
const WIDE_FIXED = 106;
|
|
287
375
|
const hasSidebar = sidebarW > 0;
|
|
288
376
|
const isTight = !isWide && hasSidebar && terminalWidth < 105;
|
|
289
377
|
const narrowFixed = isTight ? 49 : 57;
|
|
290
378
|
const tableInner = terminalWidth - sidebarW - 6;
|
|
291
|
-
const nameMaxWidth = isWide
|
|
379
|
+
const nameMaxWidth = isWide
|
|
380
|
+
? Math.max(8, tableInner - WIDE_FIXED)
|
|
381
|
+
: Math.max(1, tableInner - narrowFixed);
|
|
292
382
|
|
|
293
383
|
const getSessionKey = useCallback((s: AgentSessionAggregate) => s.sessionId, []);
|
|
294
384
|
const { items: animatedSessions, isBulkChange } = useExitAnimation(sessions, {
|
|
@@ -305,50 +395,107 @@ export const SessionsTable = forwardRef(function SessionsTable(
|
|
|
305
395
|
flexDirection="column"
|
|
306
396
|
flexGrow={2}
|
|
307
397
|
border
|
|
308
|
-
borderStyle={focusedPanel ===
|
|
309
|
-
borderColor={focusedPanel ===
|
|
398
|
+
borderStyle={focusedPanel === "sessions" ? "double" : "single"}
|
|
399
|
+
borderColor={focusedPanel === "sessions" ? colors.primary : colors.border}
|
|
310
400
|
overflow="hidden"
|
|
311
401
|
>
|
|
312
|
-
<box
|
|
402
|
+
<box
|
|
403
|
+
flexDirection="row"
|
|
404
|
+
paddingLeft={2}
|
|
405
|
+
paddingRight={1}
|
|
406
|
+
height={1}
|
|
407
|
+
justifyContent="space-between"
|
|
408
|
+
overflow="hidden"
|
|
409
|
+
>
|
|
313
410
|
<text height={1} fg={colors.textMuted}>
|
|
314
|
-
SESSIONS{filterQuery ? ` [${isFiltering ?
|
|
411
|
+
SESSIONS{filterQuery ? ` [${isFiltering ? "Filter: " : ""}${filterQuery}]` : ""}
|
|
412
|
+
{isLoading ? " ⟳" : " "}
|
|
413
|
+
</text>
|
|
414
|
+
<text height={1} fg={colors.textMuted}>
|
|
415
|
+
[{windowLabel}] {sessions.length} sessions
|
|
315
416
|
</text>
|
|
316
|
-
<text height={1} fg={colors.textMuted}>[{windowLabel}] {sessions.length} sessions</text>
|
|
317
417
|
</box>
|
|
318
418
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
419
|
+
{isWide ? (
|
|
420
|
+
<box flexDirection="row" paddingRight={1} height={1} gap={1}>
|
|
421
|
+
<text width={2} height={1} fg={colors.textMuted}>
|
|
422
|
+
{" "}
|
|
423
|
+
</text>
|
|
424
|
+
<text width={8} height={1} fg={colors.textMuted}>
|
|
425
|
+
ID{" "}
|
|
426
|
+
</text>
|
|
427
|
+
<text width={12} height={1} fg={colors.textMuted}>
|
|
428
|
+
AGENT{" "}
|
|
429
|
+
</text>
|
|
430
|
+
<text width={18} height={1} fg={colors.textMuted}>
|
|
431
|
+
MODEL{" "}
|
|
432
|
+
</text>
|
|
433
|
+
<text width={5} height={1} fg={colors.textMuted}>
|
|
434
|
+
{" "}
|
|
435
|
+
REQ{" "}
|
|
436
|
+
</text>
|
|
437
|
+
<text width={14} height={1} fg={colors.textMuted}>
|
|
438
|
+
{" "}
|
|
439
|
+
TOKENS{" "}
|
|
440
|
+
</text>
|
|
441
|
+
<text width={8} height={1} fg={colors.textMuted}>
|
|
442
|
+
{" "}
|
|
443
|
+
COST{" "}
|
|
444
|
+
</text>
|
|
445
|
+
<text width={14} height={1} fg={colors.textMuted}>
|
|
446
|
+
PROJECT{" "}
|
|
447
|
+
</text>
|
|
448
|
+
<text flexGrow={1} height={1} fg={colors.textMuted}>
|
|
449
|
+
NAME
|
|
450
|
+
</text>
|
|
451
|
+
<text width={6} height={1} fg={colors.textMuted}>
|
|
452
|
+
{" "}
|
|
453
|
+
DUR{" "}
|
|
454
|
+
</text>
|
|
455
|
+
<text width={5} height={1} fg={colors.textMuted}>
|
|
456
|
+
LAST{" "}
|
|
457
|
+
</text>
|
|
458
|
+
<text width={2} height={1} fg={colors.textMuted}>
|
|
459
|
+
{" "}
|
|
460
|
+
</text>
|
|
461
|
+
</box>
|
|
462
|
+
) : (
|
|
463
|
+
<box flexDirection="row" paddingRight={1} height={1}>
|
|
464
|
+
<text width={2} height={1} fg={colors.textMuted}>
|
|
465
|
+
{" "}
|
|
466
|
+
</text>
|
|
467
|
+
<text width={9} height={1} fg={colors.textMuted}>
|
|
468
|
+
AGENT{" "}
|
|
469
|
+
</text>
|
|
470
|
+
<text width={isTight ? 13 : 16} height={1} fg={colors.textMuted}>
|
|
471
|
+
{isTight ? "MODEL " : "MODEL "}
|
|
472
|
+
</text>
|
|
473
|
+
<text width={isTight ? 9 : 14} height={1} fg={colors.textMuted}>
|
|
474
|
+
{isTight ? " TOKENS " : " TOKENS "}
|
|
475
|
+
</text>
|
|
476
|
+
<text width={7} height={1} fg={colors.textMuted}>
|
|
477
|
+
{" "}
|
|
478
|
+
COST{" "}
|
|
479
|
+
</text>
|
|
480
|
+
<text flexGrow={1} height={1} fg={colors.textMuted}>
|
|
481
|
+
{nameMaxWidth >= 7 ? "PROJECT" : nameMaxWidth >= 4 ? "PROJ" : ""}
|
|
482
|
+
</text>
|
|
483
|
+
<text width={5} height={1} fg={colors.textMuted}>
|
|
484
|
+
LAST{" "}
|
|
485
|
+
</text>
|
|
486
|
+
<text width={2} height={1} fg={colors.textMuted}>
|
|
487
|
+
{" "}
|
|
488
|
+
</text>
|
|
489
|
+
</box>
|
|
490
|
+
)}
|
|
346
491
|
|
|
347
492
|
<scrollbox ref={ref} flexGrow={1}>
|
|
348
493
|
<box flexDirection="column">
|
|
349
494
|
{sessions.length === 0 && animatedSessions.length === 0 && (
|
|
350
495
|
<box paddingLeft={2}>
|
|
351
|
-
<text fg={colors.textMuted}>
|
|
496
|
+
<text fg={colors.textMuted}>
|
|
497
|
+
{isLoading ? "Loading sessions..." : "No sessions found"}
|
|
498
|
+
</text>
|
|
352
499
|
</box>
|
|
353
500
|
)}
|
|
354
501
|
{animatedSessions.map((entry) => {
|
|
@@ -372,7 +519,7 @@ export const SessionsTable = forwardRef(function SessionsTable(
|
|
|
372
519
|
</scrollbox>
|
|
373
520
|
{terminalHeight >= 30 && (
|
|
374
521
|
<box height={1} paddingX={2} flexShrink={0}>
|
|
375
|
-
{inspector && focusedPanel ===
|
|
522
|
+
{inspector && focusedPanel === "sessions" ? (
|
|
376
523
|
<text height={1} overflow="hidden">
|
|
377
524
|
<span fg={colors.textMuted}>↯ Cache read </span>
|
|
378
525
|
<span fg={colors.text}>{formatTokensCompact(inspector.cacheRead)}</span>
|