@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,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useTerminalDimensions } from
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { RGBA } from "@opentui/core";
|
|
2
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { getSessionActivityTimeline, isDatabaseInitialized } from "@/storage/index.ts";
|
|
5
|
+
import type { AgentSessionAggregate } from "../../agents/types.ts";
|
|
6
|
+
import { usePlugins } from "../contexts/PluginContext.tsx";
|
|
7
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
8
|
+
import { getProviderColor } from "../utils/providerColor.ts";
|
|
7
9
|
|
|
8
10
|
const OVERLAY_BG = RGBA.fromValues(0.0, 0.0, 0.0, 0.5);
|
|
9
11
|
|
|
@@ -13,15 +15,15 @@ interface SessionDetailsDrawerProps {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
function formatTokens(count: number): string {
|
|
16
|
-
if (count >= 1_000_000) return (count / 1_000_000).toFixed(1) +
|
|
17
|
-
if (count >= 1_000) return (count / 1_000).toFixed(1) +
|
|
18
|
+
if (count >= 1_000_000) return (count / 1_000_000).toFixed(1) + "M";
|
|
19
|
+
if (count >= 1_000) return (count / 1_000).toFixed(1) + "K";
|
|
18
20
|
return count.toString();
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
function formatCost(cost: number | undefined): string {
|
|
22
|
-
if (cost === undefined) return
|
|
23
|
-
if (cost < 0.01 && cost > 0) return
|
|
24
|
-
return
|
|
24
|
+
if (cost === undefined) return "$0.00";
|
|
25
|
+
if (cost < 0.01 && cost > 0) return "<$0.01";
|
|
26
|
+
return "$" + cost.toFixed(2);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
function formatDuration(startedAt: number, lastActivityAt: number): string {
|
|
@@ -40,89 +42,93 @@ function formatDuration(startedAt: number, lastActivityAt: number): string {
|
|
|
40
42
|
function formatTimelineLabel(timestamp: number, isMultiDay: boolean): string {
|
|
41
43
|
const date = new Date(timestamp);
|
|
42
44
|
if (isMultiDay) {
|
|
43
|
-
return date.toLocaleDateString([], { month:
|
|
45
|
+
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
44
46
|
}
|
|
45
|
-
return date.toLocaleTimeString([], { hour:
|
|
47
|
+
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
const SPARK_CHARS = [
|
|
50
|
+
const SPARK_CHARS = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
49
51
|
|
|
50
52
|
function buildSparkline(values: number[], width: number): string {
|
|
51
|
-
if (values.length === 0 || width <= 0) return
|
|
52
|
-
|
|
53
|
+
if (values.length === 0 || width <= 0) return "·".repeat(Math.max(width, 1));
|
|
54
|
+
|
|
53
55
|
const bucketSize = Math.ceil(values.length / width);
|
|
54
56
|
const buckets: number[] = [];
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
for (let i = 0; i < width; i++) {
|
|
57
59
|
const start = i * bucketSize;
|
|
58
60
|
const end = Math.min(start + bucketSize, values.length);
|
|
59
61
|
const slice = values.slice(start, end);
|
|
60
62
|
buckets.push(slice.length > 0 ? slice.reduce((a, b) => a + b, 0) : 0);
|
|
61
63
|
}
|
|
62
|
-
|
|
64
|
+
|
|
63
65
|
const maxVal = Math.max(...buckets, 1);
|
|
64
|
-
|
|
65
|
-
return buckets
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
|
|
67
|
+
return buckets
|
|
68
|
+
.map((v) => {
|
|
69
|
+
if (v === 0) return "·";
|
|
70
|
+
const idx = Math.min(Math.floor((v / maxVal) * SPARK_CHARS.length), SPARK_CHARS.length - 1);
|
|
71
|
+
return SPARK_CHARS[idx];
|
|
72
|
+
})
|
|
73
|
+
.join("");
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
function truncateMiddle(str: string, maxLength: number): string {
|
|
73
77
|
if (str.length <= maxLength) return str;
|
|
74
78
|
const side = Math.floor((maxLength - 1) / 2);
|
|
75
|
-
return str.slice(0, side) +
|
|
79
|
+
return str.slice(0, side) + "…" + str.slice(-side);
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
function shortenPath(fullPath: string, maxLength: number): string {
|
|
79
|
-
if (!fullPath) return
|
|
80
|
-
|
|
81
|
-
const normalized = fullPath.replace(/\\/g,
|
|
82
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ??
|
|
83
|
+
if (!fullPath) return "—";
|
|
84
|
+
|
|
85
|
+
const normalized = fullPath.replace(/\\/g, "/");
|
|
86
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
83
87
|
let path = normalized;
|
|
84
|
-
|
|
88
|
+
|
|
85
89
|
if (home && normalized.startsWith(home)) {
|
|
86
|
-
path =
|
|
90
|
+
path = "~" + normalized.slice(home.length);
|
|
87
91
|
}
|
|
88
|
-
|
|
92
|
+
|
|
89
93
|
if (path.length <= maxLength) return path;
|
|
90
|
-
|
|
91
|
-
const parts = path.split(
|
|
94
|
+
|
|
95
|
+
const parts = path.split("/").filter(Boolean);
|
|
92
96
|
if (parts.length <= 2) return truncateMiddle(path, maxLength);
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
const first = parts[0];
|
|
95
99
|
const last = parts[parts.length - 1];
|
|
96
|
-
const secondLast = parts.length > 2 ? parts[parts.length - 2] :
|
|
97
|
-
|
|
98
|
-
const shortened = secondLast
|
|
99
|
-
|
|
100
|
-
: `${first}/…/${last}`;
|
|
101
|
-
|
|
100
|
+
const secondLast = parts.length > 2 ? parts[parts.length - 2] : "";
|
|
101
|
+
|
|
102
|
+
const shortened = secondLast ? `${first}/…/${secondLast}/${last}` : `${first}/…/${last}`;
|
|
103
|
+
|
|
102
104
|
return shortened.length <= maxLength ? shortened : truncateMiddle(path, maxLength);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDetailsDrawerProps) {
|
|
106
108
|
void _onClose;
|
|
107
109
|
const colors = useColors();
|
|
110
|
+
const { providers } = usePlugins();
|
|
108
111
|
const { width: termWidth, height: termHeight } = useTerminalDimensions();
|
|
109
|
-
|
|
112
|
+
|
|
110
113
|
const width = Math.max(70, Math.min(termWidth - 4, 100));
|
|
111
114
|
const height = Math.max(24, Math.min(termHeight - 4, 40));
|
|
112
115
|
const contentWidth = width - 4;
|
|
113
|
-
|
|
116
|
+
|
|
114
117
|
const duration = formatDuration(session.startedAt, session.lastActivityAt);
|
|
115
|
-
const projectName = session.projectPath?.split(
|
|
116
|
-
const fullPath = session.projectPath ??
|
|
117
|
-
|
|
118
|
+
const projectName = session.projectPath?.split("/").pop() ?? "N/A";
|
|
119
|
+
const fullPath = session.projectPath ?? "";
|
|
120
|
+
|
|
118
121
|
const sparklineInnerWidth = Math.max(contentWidth - 6, 20);
|
|
119
122
|
const sparkline = useMemo(() => {
|
|
120
|
-
if (!isDatabaseInitialized()) return
|
|
123
|
+
if (!isDatabaseInitialized()) return "·".repeat(sparklineInnerWidth);
|
|
121
124
|
const activity = getSessionActivityTimeline(session.sessionId);
|
|
122
|
-
if (activity.length === 0) return
|
|
123
|
-
return buildSparkline(
|
|
125
|
+
if (activity.length === 0) return "·".repeat(sparklineInnerWidth);
|
|
126
|
+
return buildSparkline(
|
|
127
|
+
activity.map((a) => a.tokens),
|
|
128
|
+
sparklineInnerWidth,
|
|
129
|
+
);
|
|
124
130
|
}, [session.sessionId, sparklineInnerWidth]);
|
|
125
|
-
|
|
131
|
+
|
|
126
132
|
const cacheRead = session.totals.cacheRead ?? 0;
|
|
127
133
|
const cacheWrite = session.totals.cacheWrite ?? 0;
|
|
128
134
|
const hasCacheData = cacheRead > 0 || cacheWrite > 0;
|
|
@@ -130,7 +136,10 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
130
136
|
const cacheHitRate = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
|
|
131
137
|
|
|
132
138
|
const sessionCostBreakdown = useMemo(() => {
|
|
133
|
-
let input = 0,
|
|
139
|
+
let input = 0,
|
|
140
|
+
output = 0,
|
|
141
|
+
cr = 0,
|
|
142
|
+
cw = 0;
|
|
134
143
|
let hasAny = false;
|
|
135
144
|
for (const stream of session.streams) {
|
|
136
145
|
if (!stream.costBreakdown) continue;
|
|
@@ -149,11 +158,11 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
149
158
|
};
|
|
150
159
|
}, [session.streams]);
|
|
151
160
|
|
|
152
|
-
const isMultiDay =
|
|
161
|
+
const isMultiDay = session.lastActivityAt - session.startedAt > 24 * 60 * 60 * 1000;
|
|
153
162
|
const startTimeStr = formatTimelineLabel(session.startedAt, isMultiDay);
|
|
154
163
|
const endTimeStr = formatTimelineLabel(session.lastActivityAt, isMultiDay);
|
|
155
164
|
const timeGap = Math.max(0, sparklineInnerWidth - startTimeStr.length - endTimeStr.length);
|
|
156
|
-
const timelineLabel = startTimeStr +
|
|
165
|
+
const timelineLabel = startTimeStr + " ".repeat(timeGap) + endTimeStr;
|
|
157
166
|
|
|
158
167
|
return (
|
|
159
168
|
<box
|
|
@@ -167,31 +176,46 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
167
176
|
zIndex={100}
|
|
168
177
|
backgroundColor={OVERLAY_BG}
|
|
169
178
|
>
|
|
170
|
-
<box
|
|
179
|
+
<box
|
|
171
180
|
width={width}
|
|
172
181
|
height={height}
|
|
173
|
-
border
|
|
174
|
-
borderStyle="double"
|
|
175
|
-
borderColor={colors.primary}
|
|
176
|
-
flexDirection="column"
|
|
182
|
+
border
|
|
183
|
+
borderStyle="double"
|
|
184
|
+
borderColor={colors.primary}
|
|
185
|
+
flexDirection="column"
|
|
177
186
|
padding={1}
|
|
178
187
|
backgroundColor={colors.background}
|
|
179
188
|
overflow="hidden"
|
|
180
189
|
>
|
|
181
190
|
<box flexDirection="row" justifyContent="space-between" height={1} marginBottom={1}>
|
|
182
|
-
<text height={1} fg={colors.primary}
|
|
183
|
-
|
|
191
|
+
<text height={1} fg={colors.primary}>
|
|
192
|
+
<strong>SESSION DETAILS</strong>
|
|
193
|
+
</text>
|
|
194
|
+
<text height={1} fg={colors.textMuted}>
|
|
195
|
+
[Esc] Close
|
|
196
|
+
</text>
|
|
184
197
|
</box>
|
|
185
|
-
|
|
186
|
-
<box
|
|
198
|
+
|
|
199
|
+
<box
|
|
200
|
+
flexDirection="column"
|
|
201
|
+
marginBottom={0}
|
|
202
|
+
border
|
|
203
|
+
borderStyle="single"
|
|
204
|
+
borderColor={colors.border}
|
|
205
|
+
paddingX={1}
|
|
206
|
+
paddingY={0}
|
|
207
|
+
flexShrink={0}
|
|
208
|
+
>
|
|
187
209
|
<box flexDirection="row" height={1} justifyContent="space-between" marginTop={0}>
|
|
188
210
|
<text height={1} overflow="hidden">
|
|
189
211
|
<span fg={colors.textMuted}>ID: </span>
|
|
190
|
-
<span fg={colors.text}
|
|
212
|
+
<span fg={colors.text}>
|
|
213
|
+
<strong>{session.sessionId}</strong>
|
|
214
|
+
</span>
|
|
191
215
|
</text>
|
|
192
216
|
<text height={1}>
|
|
193
217
|
<span fg={colors.textMuted}>Status: </span>
|
|
194
|
-
<span fg={session.status ===
|
|
218
|
+
<span fg={session.status === "active" ? colors.success : colors.textMuted}>
|
|
195
219
|
{session.status.toUpperCase()}
|
|
196
220
|
</span>
|
|
197
221
|
</text>
|
|
@@ -201,7 +225,9 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
201
225
|
<box flexDirection="row" height={1}>
|
|
202
226
|
<text height={1} overflow="hidden">
|
|
203
227
|
<span fg={colors.textMuted}>Name: </span>
|
|
204
|
-
<span fg={colors.primary}
|
|
228
|
+
<span fg={colors.primary}>
|
|
229
|
+
<strong>{session.sessionName}</strong>
|
|
230
|
+
</span>
|
|
205
231
|
</text>
|
|
206
232
|
</box>
|
|
207
233
|
)}
|
|
@@ -213,7 +239,9 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
213
239
|
</text>
|
|
214
240
|
<text height={1}>
|
|
215
241
|
<span fg={colors.textMuted}>Project: </span>
|
|
216
|
-
<span fg={colors.text}
|
|
242
|
+
<span fg={colors.text}>
|
|
243
|
+
<strong>{projectName}</strong>
|
|
244
|
+
</span>
|
|
217
245
|
</text>
|
|
218
246
|
</box>
|
|
219
247
|
|
|
@@ -224,11 +252,22 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
224
252
|
</box>
|
|
225
253
|
</box>
|
|
226
254
|
|
|
227
|
-
<box
|
|
255
|
+
<box
|
|
256
|
+
flexDirection="column"
|
|
257
|
+
marginBottom={0}
|
|
258
|
+
border
|
|
259
|
+
borderStyle="single"
|
|
260
|
+
borderColor={colors.border}
|
|
261
|
+
paddingX={1}
|
|
262
|
+
paddingY={0}
|
|
263
|
+
flexShrink={0}
|
|
264
|
+
>
|
|
228
265
|
<box flexDirection="row" height={1} justifyContent="space-between" marginTop={0}>
|
|
229
266
|
<text height={1}>
|
|
230
267
|
<span fg={colors.textMuted}>Cost: </span>
|
|
231
|
-
<span fg={colors.success}
|
|
268
|
+
<span fg={colors.success}>
|
|
269
|
+
<strong>{formatCost(session.totalCostUsd)}</strong>
|
|
270
|
+
</span>
|
|
232
271
|
</text>
|
|
233
272
|
<text height={1}>
|
|
234
273
|
<span fg={colors.textMuted}>Duration: </span>
|
|
@@ -239,7 +278,7 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
239
278
|
{sessionCostBreakdown && (
|
|
240
279
|
<box flexDirection="row" height={1} marginBottom={0}>
|
|
241
280
|
<text height={1} overflow="hidden">
|
|
242
|
-
<span fg={colors.textMuted}>
|
|
281
|
+
<span fg={colors.textMuted}> </span>
|
|
243
282
|
<span fg={colors.text}>In {formatCost(sessionCostBreakdown.input)}</span>
|
|
244
283
|
<span fg={colors.textMuted}> │ </span>
|
|
245
284
|
<span fg={colors.text}>Out {formatCost(sessionCostBreakdown.output)}</span>
|
|
@@ -252,56 +291,114 @@ export function SessionDetailsDrawer({ session, onClose: _onClose }: SessionDeta
|
|
|
252
291
|
{(sessionCostBreakdown.cacheWrite ?? 0) > 0 && (
|
|
253
292
|
<>
|
|
254
293
|
<span fg={colors.textMuted}> │ </span>
|
|
255
|
-
<span fg={colors.text}>
|
|
294
|
+
<span fg={colors.text}>
|
|
295
|
+
Write {formatCost(sessionCostBreakdown.cacheWrite)}
|
|
296
|
+
</span>
|
|
256
297
|
</>
|
|
257
298
|
)}
|
|
258
299
|
</text>
|
|
259
300
|
</box>
|
|
260
301
|
)}
|
|
261
|
-
|
|
302
|
+
|
|
262
303
|
<box flexDirection="row" height={1} marginTop={0}>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
304
|
+
<text height={1}>
|
|
305
|
+
<span fg={colors.textMuted}>Tokens: </span>
|
|
306
|
+
<span fg={colors.text}>
|
|
307
|
+
{formatTokens(session.totals.input + session.totals.output)}
|
|
308
|
+
</span>
|
|
309
|
+
<span fg={colors.textMuted}>
|
|
310
|
+
{" "}
|
|
311
|
+
(In: {formatTokens(session.totals.input)} / Out:{" "}
|
|
312
|
+
{formatTokens(session.totals.output)})
|
|
313
|
+
</span>
|
|
314
|
+
</text>
|
|
270
315
|
</box>
|
|
271
316
|
|
|
272
317
|
{hasCacheData && (
|
|
273
318
|
<box flexDirection="row" height={1} marginBottom={0}>
|
|
274
319
|
<text height={1} overflow="hidden">
|
|
275
320
|
<span fg={colors.textMuted}>Cache: </span>
|
|
276
|
-
<span fg={cacheHitRate >= 50 ? colors.success : colors.warning}>
|
|
277
|
-
|
|
321
|
+
<span fg={cacheHitRate >= 50 ? colors.success : colors.warning}>
|
|
322
|
+
{cacheHitRate}% hit
|
|
323
|
+
</span>
|
|
324
|
+
<span fg={colors.textMuted}>
|
|
325
|
+
{" "}
|
|
326
|
+
— {formatTokens(cacheRead)} read / {formatTokens(cacheWrite)} write
|
|
327
|
+
</span>
|
|
278
328
|
</text>
|
|
279
329
|
</box>
|
|
280
330
|
)}
|
|
281
331
|
</box>
|
|
282
|
-
|
|
283
|
-
<box
|
|
284
|
-
|
|
285
|
-
|
|
332
|
+
|
|
333
|
+
<box
|
|
334
|
+
flexDirection="column"
|
|
335
|
+
height={4}
|
|
336
|
+
marginBottom={0}
|
|
337
|
+
border
|
|
338
|
+
borderColor={colors.border}
|
|
339
|
+
paddingX={1}
|
|
340
|
+
flexShrink={0}
|
|
341
|
+
>
|
|
342
|
+
<text height={1} fg={colors.primary} overflow="hidden">
|
|
343
|
+
{sparkline}
|
|
344
|
+
</text>
|
|
345
|
+
<text height={1} fg={colors.textMuted} overflow="hidden">
|
|
346
|
+
{timelineLabel}
|
|
347
|
+
</text>
|
|
286
348
|
</box>
|
|
287
|
-
|
|
349
|
+
|
|
288
350
|
<box flexDirection="row" height={1} paddingLeft={1} paddingRight={2} marginBottom={0}>
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
351
|
+
<box width={12}>
|
|
352
|
+
<text fg={colors.primary}>
|
|
353
|
+
<strong>PROVIDER</strong>
|
|
354
|
+
</text>
|
|
355
|
+
</box>
|
|
356
|
+
<text flexGrow={1} fg={colors.primary}>
|
|
357
|
+
<strong>MODEL</strong>
|
|
358
|
+
</text>
|
|
359
|
+
<box width={10} justifyContent="flex-end">
|
|
360
|
+
<text fg={colors.primary}>
|
|
361
|
+
<strong>TOKENS</strong>
|
|
362
|
+
</text>
|
|
363
|
+
</box>
|
|
364
|
+
<box width={10} justifyContent="flex-end">
|
|
365
|
+
<text fg={colors.primary}>
|
|
366
|
+
<strong>COST</strong>
|
|
367
|
+
</text>
|
|
368
|
+
</box>
|
|
292
369
|
</box>
|
|
293
|
-
|
|
294
|
-
<box
|
|
370
|
+
|
|
371
|
+
<box
|
|
372
|
+
flexDirection="column"
|
|
373
|
+
flexGrow={1}
|
|
374
|
+
border
|
|
375
|
+
borderColor={colors.border}
|
|
376
|
+
overflow="hidden"
|
|
377
|
+
>
|
|
295
378
|
<scrollbox flexGrow={1}>
|
|
296
379
|
{session.streams.map((stream, idx) => {
|
|
297
|
-
const modelName = stream.modelId.split(
|
|
380
|
+
const modelName = stream.modelId.split("/").pop() ?? stream.modelId;
|
|
381
|
+
const providerName =
|
|
382
|
+
stream.providerId.charAt(0).toUpperCase() + stream.providerId.slice(1);
|
|
383
|
+
const providerColor = getProviderColor(
|
|
384
|
+
stream.providerId,
|
|
385
|
+
providers,
|
|
386
|
+
colors.textMuted,
|
|
387
|
+
);
|
|
298
388
|
return (
|
|
299
|
-
|
|
389
|
+
<box key={`${stream.modelId}-${idx}`} flexDirection="row" height={1} paddingX={1}>
|
|
390
|
+
<box width={12} height={1}>
|
|
391
|
+
<text height={1} fg={providerColor} overflow="hidden">
|
|
392
|
+
{truncateMiddle(providerName, 11)}
|
|
393
|
+
</text>
|
|
394
|
+
</box>
|
|
300
395
|
<text flexGrow={1} height={1} fg={colors.text} overflow="hidden">
|
|
301
|
-
{truncateMiddle(modelName, contentWidth -
|
|
396
|
+
{truncateMiddle(modelName, contentWidth - 37)}
|
|
302
397
|
</text>
|
|
303
398
|
<box width={10} height={1} justifyContent="flex-end">
|
|
304
|
-
<text fg={colors.textMuted}>
|
|
399
|
+
<text fg={colors.textMuted}>
|
|
400
|
+
{formatTokens(stream.tokens.input + stream.tokens.output)}
|
|
401
|
+
</text>
|
|
305
402
|
</box>
|
|
306
403
|
<box width={10} height={1} justifyContent="flex-end">
|
|
307
404
|
<text fg={colors.success}>{formatCost(stream.costUsd)}</text>
|