@tokscale/cli 1.4.3 → 2.0.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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -26
- package/dist/auth.d.ts +0 -17
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -162
- package/dist/auth.js.map +0 -1
- package/dist/cli.d.ts +0 -9
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -1550
- package/dist/cli.js.map +0 -1
- package/dist/credentials.d.ts +0 -50
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js +0 -151
- package/dist/credentials.js.map +0 -1
- package/dist/cursor.d.ts +0 -167
- package/dist/cursor.d.ts.map +0 -1
- package/dist/cursor.js +0 -906
- package/dist/cursor.js.map +0 -1
- package/dist/date-utils.d.ts +0 -10
- package/dist/date-utils.d.ts.map +0 -1
- package/dist/date-utils.js +0 -47
- package/dist/date-utils.js.map +0 -1
- package/dist/graph-types.d.ts +0 -142
- package/dist/graph-types.d.ts.map +0 -1
- package/dist/graph-types.js +0 -6
- package/dist/graph-types.js.map +0 -1
- package/dist/native-runner.d.ts +0 -11
- package/dist/native-runner.d.ts.map +0 -1
- package/dist/native-runner.js +0 -77
- package/dist/native-runner.js.map +0 -1
- package/dist/native.d.ts +0 -106
- package/dist/native.d.ts.map +0 -1
- package/dist/native.js +0 -302
- package/dist/native.js.map +0 -1
- package/dist/sessions/types.d.ts +0 -28
- package/dist/sessions/types.d.ts.map +0 -1
- package/dist/sessions/types.js +0 -27
- package/dist/sessions/types.js.map +0 -1
- package/dist/spinner.d.ts +0 -75
- package/dist/spinner.d.ts.map +0 -1
- package/dist/spinner.js +0 -203
- package/dist/spinner.js.map +0 -1
- package/dist/submit.d.ts +0 -23
- package/dist/submit.d.ts.map +0 -1
- package/dist/submit.js +0 -294
- package/dist/submit.js.map +0 -1
- package/dist/table.d.ts +0 -42
- package/dist/table.d.ts.map +0 -1
- package/dist/table.js +0 -181
- package/dist/table.js.map +0 -1
- package/dist/tui/App.d.ts +0 -4
- package/dist/tui/App.d.ts.map +0 -1
- package/dist/tui/App.js +0 -333
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/components/BarChart.d.ts +0 -17
- package/dist/tui/components/BarChart.d.ts.map +0 -1
- package/dist/tui/components/BarChart.js +0 -146
- package/dist/tui/components/BarChart.js.map +0 -1
- package/dist/tui/components/DailyView.d.ts +0 -13
- package/dist/tui/components/DailyView.d.ts.map +0 -1
- package/dist/tui/components/DailyView.js +0 -86
- package/dist/tui/components/DailyView.js.map +0 -1
- package/dist/tui/components/DateBreakdownPanel.d.ts +0 -7
- package/dist/tui/components/DateBreakdownPanel.d.ts.map +0 -1
- package/dist/tui/components/DateBreakdownPanel.js +0 -36
- package/dist/tui/components/DateBreakdownPanel.js.map +0 -1
- package/dist/tui/components/Footer.d.ts +0 -28
- package/dist/tui/components/Footer.d.ts.map +0 -1
- package/dist/tui/components/Footer.js +0 -130
- package/dist/tui/components/Footer.js.map +0 -1
- package/dist/tui/components/Header.d.ts +0 -9
- package/dist/tui/components/Header.d.ts.map +0 -1
- package/dist/tui/components/Header.js +0 -20
- package/dist/tui/components/Header.js.map +0 -1
- package/dist/tui/components/Legend.d.ts +0 -7
- package/dist/tui/components/Legend.d.ts.map +0 -1
- package/dist/tui/components/Legend.js +0 -16
- package/dist/tui/components/Legend.js.map +0 -1
- package/dist/tui/components/LoadingSpinner.d.ts +0 -8
- package/dist/tui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/tui/components/LoadingSpinner.js +0 -55
- package/dist/tui/components/LoadingSpinner.js.map +0 -1
- package/dist/tui/components/ModelRow.d.ts +0 -13
- package/dist/tui/components/ModelRow.d.ts.map +0 -1
- package/dist/tui/components/ModelRow.js +0 -15
- package/dist/tui/components/ModelRow.js.map +0 -1
- package/dist/tui/components/ModelView.d.ts +0 -13
- package/dist/tui/components/ModelView.d.ts.map +0 -1
- package/dist/tui/components/ModelView.js +0 -96
- package/dist/tui/components/ModelView.js.map +0 -1
- package/dist/tui/components/OverviewView.d.ts +0 -14
- package/dist/tui/components/OverviewView.d.ts.map +0 -1
- package/dist/tui/components/OverviewView.js +0 -65
- package/dist/tui/components/OverviewView.js.map +0 -1
- package/dist/tui/components/StatsView.d.ts +0 -14
- package/dist/tui/components/StatsView.d.ts.map +0 -1
- package/dist/tui/components/StatsView.js +0 -102
- package/dist/tui/components/StatsView.js.map +0 -1
- package/dist/tui/components/TokenBreakdown.d.ts +0 -14
- package/dist/tui/components/TokenBreakdown.d.ts.map +0 -1
- package/dist/tui/components/TokenBreakdown.js +0 -10
- package/dist/tui/components/TokenBreakdown.js.map +0 -1
- package/dist/tui/components/index.d.ts +0 -16
- package/dist/tui/components/index.d.ts.map +0 -1
- package/dist/tui/components/index.js +0 -13
- package/dist/tui/components/index.js.map +0 -1
- package/dist/tui/config/settings.d.ts +0 -15
- package/dist/tui/config/settings.d.ts.map +0 -1
- package/dist/tui/config/settings.js +0 -147
- package/dist/tui/config/settings.js.map +0 -1
- package/dist/tui/config/themes.d.ts +0 -15
- package/dist/tui/config/themes.d.ts.map +0 -1
- package/dist/tui/config/themes.js +0 -82
- package/dist/tui/config/themes.js.map +0 -1
- package/dist/tui/hooks/useData.d.ts +0 -19
- package/dist/tui/hooks/useData.d.ts.map +0 -1
- package/dist/tui/hooks/useData.js +0 -468
- package/dist/tui/hooks/useData.js.map +0 -1
- package/dist/tui/index.d.ts +0 -4
- package/dist/tui/index.d.ts.map +0 -1
- package/dist/tui/index.js +0 -36
- package/dist/tui/index.js.map +0 -1
- package/dist/tui/types/index.d.ts +0 -137
- package/dist/tui/types/index.d.ts.map +0 -1
- package/dist/tui/types/index.js +0 -26
- package/dist/tui/types/index.js.map +0 -1
- package/dist/tui/utils/cleanup.d.ts +0 -22
- package/dist/tui/utils/cleanup.d.ts.map +0 -1
- package/dist/tui/utils/cleanup.js +0 -59
- package/dist/tui/utils/cleanup.js.map +0 -1
- package/dist/tui/utils/colors.d.ts +0 -19
- package/dist/tui/utils/colors.d.ts.map +0 -1
- package/dist/tui/utils/colors.js +0 -71
- package/dist/tui/utils/colors.js.map +0 -1
- package/dist/tui/utils/format.d.ts +0 -7
- package/dist/tui/utils/format.d.ts.map +0 -1
- package/dist/tui/utils/format.js +0 -45
- package/dist/tui/utils/format.js.map +0 -1
- package/dist/tui/utils/responsive.d.ts +0 -5
- package/dist/tui/utils/responsive.d.ts.map +0 -1
- package/dist/tui/utils/responsive.js +0 -5
- package/dist/tui/utils/responsive.js.map +0 -1
- package/dist/wrapped.d.ts +0 -43
- package/dist/wrapped.d.ts.map +0 -1
- package/dist/wrapped.js +0 -719
- package/dist/wrapped.js.map +0 -1
- package/src/auth.ts +0 -211
- package/src/cli.ts +0 -1892
- package/src/credentials.ts +0 -176
- package/src/cursor.ts +0 -1044
- package/src/date-utils.ts +0 -51
- package/src/graph-types.ts +0 -175
- package/src/native-runner.js +0 -4
- package/src/native-runner.ts +0 -91
- package/src/native.ts +0 -633
- package/src/sessions/types.ts +0 -59
- package/src/spinner.ts +0 -283
- package/src/submit.ts +0 -360
- package/src/table.ts +0 -233
- package/src/tui/App.tsx +0 -453
- package/src/tui/components/BarChart.tsx +0 -205
- package/src/tui/components/DailyView.tsx +0 -132
- package/src/tui/components/DateBreakdownPanel.tsx +0 -79
- package/src/tui/components/Footer.tsx +0 -380
- package/src/tui/components/Header.tsx +0 -68
- package/src/tui/components/Legend.tsx +0 -39
- package/src/tui/components/LoadingSpinner.tsx +0 -81
- package/src/tui/components/ModelRow.tsx +0 -47
- package/src/tui/components/ModelView.tsx +0 -147
- package/src/tui/components/OverviewView.tsx +0 -121
- package/src/tui/components/StatsView.tsx +0 -249
- package/src/tui/components/TokenBreakdown.tsx +0 -46
- package/src/tui/components/index.ts +0 -15
- package/src/tui/config/settings.ts +0 -183
- package/src/tui/config/themes.ts +0 -115
- package/src/tui/hooks/useData.ts +0 -558
- package/src/tui/index.tsx +0 -44
- package/src/tui/opentui.d.ts +0 -166
- package/src/tui/types/index.ts +0 -173
- package/src/tui/utils/cleanup.ts +0 -65
- package/src/tui/utils/colors.ts +0 -78
- package/src/tui/utils/format.ts +0 -36
- package/src/tui/utils/responsive.ts +0 -8
- package/src/types.d.ts +0 -28
- package/src/wrapped.ts +0 -848
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { For, createMemo, type Accessor } from "solid-js";
|
|
2
|
-
import type { TUIData, SortType } from "../hooks/useData.js";
|
|
3
|
-
import { getModelColor, getSourceDisplayName } from "../utils/colors.js";
|
|
4
|
-
import { formatTokensCompact, formatCostFull } from "../utils/format.js";
|
|
5
|
-
import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
|
|
6
|
-
|
|
7
|
-
const STRIPE_BG = "#232328";
|
|
8
|
-
|
|
9
|
-
const INPUT_COL_WIDTH = 12;
|
|
10
|
-
const OUTPUT_COL_WIDTH = 12;
|
|
11
|
-
const CACHE_READ_COL_WIDTH = 10;
|
|
12
|
-
const CACHE_WRITE_COL_WIDTH = 10;
|
|
13
|
-
const TOTAL_COL_WIDTH = 14;
|
|
14
|
-
const COST_COL_WIDTH = 12;
|
|
15
|
-
const METRIC_COLUMNS_WIDTH_FULL = INPUT_COL_WIDTH + OUTPUT_COL_WIDTH + CACHE_READ_COL_WIDTH + CACHE_WRITE_COL_WIDTH + TOTAL_COL_WIDTH + COST_COL_WIDTH;
|
|
16
|
-
const METRIC_COLUMNS_WIDTH_NARROW = TOTAL_COL_WIDTH + COST_COL_WIDTH;
|
|
17
|
-
const SIDE_PADDING = 2;
|
|
18
|
-
const MIN_NAME_COLUMN = 16;
|
|
19
|
-
const MIN_NAME_COLUMN_NARROW = 12;
|
|
20
|
-
|
|
21
|
-
interface ModelViewProps {
|
|
22
|
-
data: TUIData;
|
|
23
|
-
sortBy: SortType;
|
|
24
|
-
sortDesc: boolean;
|
|
25
|
-
selectedIndex: Accessor<number>;
|
|
26
|
-
height: number;
|
|
27
|
-
width: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function ModelView(props: ModelViewProps) {
|
|
31
|
-
const sortedEntries = createMemo(() => {
|
|
32
|
-
const entries = props.data.modelEntries;
|
|
33
|
-
const sortBy = props.sortBy;
|
|
34
|
-
const sortDesc = props.sortDesc;
|
|
35
|
-
|
|
36
|
-
return [...entries].sort((a, b) => {
|
|
37
|
-
let cmp = 0;
|
|
38
|
-
if (sortBy === "cost") cmp = a.cost - b.cost;
|
|
39
|
-
else if (sortBy === "tokens") cmp = a.total - b.total;
|
|
40
|
-
else cmp = a.model.localeCompare(b.model);
|
|
41
|
-
return sortDesc ? -cmp : cmp;
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const isNarrowTerminal = () => isNarrow(props.width);
|
|
46
|
-
const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
|
|
47
|
-
|
|
48
|
-
const nameColumnWidths = createMemo(() => {
|
|
49
|
-
const metricWidth = isNarrowTerminal() ? METRIC_COLUMNS_WIDTH_NARROW : METRIC_COLUMNS_WIDTH_FULL;
|
|
50
|
-
const minName = isNarrowTerminal() ? MIN_NAME_COLUMN_NARROW : MIN_NAME_COLUMN;
|
|
51
|
-
const available = Math.max(props.width - SIDE_PADDING - metricWidth, minName);
|
|
52
|
-
const nameColumn = Math.max(minName, available);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
column: nameColumn,
|
|
56
|
-
text: Math.max(nameColumn - 1, 1),
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const visibleEntries = createMemo(() => {
|
|
61
|
-
const maxRows = Math.max(props.height - 3, 0);
|
|
62
|
-
return sortedEntries().slice(0, maxRows);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const formattedRows = createMemo(() => {
|
|
66
|
-
const nameWidth = nameColumnWidths().text;
|
|
67
|
-
return visibleEntries().map((entry) => {
|
|
68
|
-
const sourceLabel = getSourceDisplayName(entry.source);
|
|
69
|
-
const fullName = `${sourceLabel} ${entry.model}`;
|
|
70
|
-
let displayName = fullName;
|
|
71
|
-
if (fullName.length > nameWidth) {
|
|
72
|
-
displayName = nameWidth > 1 ? `${fullName.slice(0, nameWidth - 1)}…` : fullName.slice(0, 1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
entry,
|
|
77
|
-
displayName,
|
|
78
|
-
nameWidth,
|
|
79
|
-
input: formatTokensCompact(entry.input),
|
|
80
|
-
output: formatTokensCompact(entry.output),
|
|
81
|
-
cacheRead: formatTokensCompact(entry.cacheRead),
|
|
82
|
-
cacheWrite: formatTokensCompact(entry.cacheWrite),
|
|
83
|
-
total: formatTokensCompact(entry.total),
|
|
84
|
-
cost: formatCostFull(entry.cost),
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const sortArrow = () => (props.sortDesc ? "▼" : "▲");
|
|
90
|
-
const nameHeader = () => isVeryNarrowTerminal()
|
|
91
|
-
? ` Model`
|
|
92
|
-
: ` Source/Model`;
|
|
93
|
-
const totalHeader = () => (props.sortBy === "tokens" ? `${sortArrow()} Total` : "Total");
|
|
94
|
-
const costHeader = () => (props.sortBy === "cost" ? `${sortArrow()} Cost` : "Cost");
|
|
95
|
-
|
|
96
|
-
const renderHeader = () => {
|
|
97
|
-
if (isNarrowTerminal()) {
|
|
98
|
-
return `${nameHeader().padEnd(nameColumnWidths().column)}${totalHeader().padStart(TOTAL_COL_WIDTH)}${costHeader().padStart(COST_COL_WIDTH)}`;
|
|
99
|
-
}
|
|
100
|
-
return `${nameHeader().padEnd(nameColumnWidths().column)}${"Input".padStart(INPUT_COL_WIDTH)}${"Output".padStart(OUTPUT_COL_WIDTH)}${"C.Read".padStart(CACHE_READ_COL_WIDTH)}${"C.Write".padStart(CACHE_WRITE_COL_WIDTH)}${totalHeader().padStart(TOTAL_COL_WIDTH)}${costHeader().padStart(COST_COL_WIDTH)}`;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const renderRowData = (row: typeof formattedRows extends () => (infer T)[] ? T : never) => {
|
|
104
|
-
if (isNarrowTerminal()) {
|
|
105
|
-
return `${row.displayName.padEnd(row.nameWidth)}${row.total.padStart(TOTAL_COL_WIDTH)}`;
|
|
106
|
-
}
|
|
107
|
-
return `${row.displayName.padEnd(row.nameWidth)}${row.input.padStart(INPUT_COL_WIDTH)}${row.output.padStart(OUTPUT_COL_WIDTH)}${row.cacheRead.padStart(CACHE_READ_COL_WIDTH)}${row.cacheWrite.padStart(CACHE_WRITE_COL_WIDTH)}${row.total.padStart(TOTAL_COL_WIDTH)}`;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<box flexDirection="column">
|
|
112
|
-
<box flexDirection="row">
|
|
113
|
-
<text fg="cyan" bold>
|
|
114
|
-
{renderHeader()}
|
|
115
|
-
</text>
|
|
116
|
-
</box>
|
|
117
|
-
|
|
118
|
-
<For each={formattedRows()}>
|
|
119
|
-
{(row, i) => {
|
|
120
|
-
const isActive = createMemo(() => i() === props.selectedIndex());
|
|
121
|
-
const rowBg = createMemo(() => isActive() ? "blue" : (i() % 2 === 1 ? STRIPE_BG : undefined));
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<box flexDirection="row">
|
|
125
|
-
<text
|
|
126
|
-
fg={getModelColor(row.entry.model)}
|
|
127
|
-
bg={rowBg()}
|
|
128
|
-
>●</text>
|
|
129
|
-
<text
|
|
130
|
-
bg={rowBg()}
|
|
131
|
-
fg={isActive() ? "white" : undefined}
|
|
132
|
-
>
|
|
133
|
-
{renderRowData(row)}
|
|
134
|
-
</text>
|
|
135
|
-
<text
|
|
136
|
-
fg="green"
|
|
137
|
-
bg={rowBg()}
|
|
138
|
-
>
|
|
139
|
-
{row.cost.padStart(COST_COL_WIDTH)}
|
|
140
|
-
</text>
|
|
141
|
-
</box>
|
|
142
|
-
);
|
|
143
|
-
}}
|
|
144
|
-
</For>
|
|
145
|
-
</box>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { Show, For, createMemo, type Accessor } from "solid-js";
|
|
2
|
-
import { BarChart } from "./BarChart.js";
|
|
3
|
-
import { Legend } from "./Legend.js";
|
|
4
|
-
import { ModelRow } from "./ModelRow.js";
|
|
5
|
-
import type { TUIData, SortType } from "../hooks/useData.js";
|
|
6
|
-
import { formatCost } from "../utils/format.js";
|
|
7
|
-
import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
|
|
8
|
-
|
|
9
|
-
const CHART_MAX_DAYS = 60;
|
|
10
|
-
|
|
11
|
-
function getDateDaysAgo(days: number): string {
|
|
12
|
-
const date = new Date();
|
|
13
|
-
date.setDate(date.getDate() - days);
|
|
14
|
-
return date.toISOString().split("T")[0];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface OverviewViewProps {
|
|
18
|
-
data: TUIData;
|
|
19
|
-
sortBy: SortType;
|
|
20
|
-
sortDesc: boolean;
|
|
21
|
-
selectedIndex: Accessor<number>;
|
|
22
|
-
scrollOffset: Accessor<number>;
|
|
23
|
-
height: number;
|
|
24
|
-
width: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function OverviewView(props: OverviewViewProps) {
|
|
28
|
-
const safeHeight = () => Math.max(props.height, 12);
|
|
29
|
-
const chartHeight = () => Math.max(5, Math.floor(safeHeight() * 0.35));
|
|
30
|
-
const listHeight = () => Math.max(4, safeHeight() - chartHeight() - 4);
|
|
31
|
-
const itemsPerPage = () => Math.max(1, Math.floor(listHeight() / 2));
|
|
32
|
-
|
|
33
|
-
const isNarrowTerminal = () => isNarrow(props.width);
|
|
34
|
-
const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
|
|
35
|
-
|
|
36
|
-
const recentChartData = createMemo(() => {
|
|
37
|
-
const cutoffDate = getDateDaysAgo(CHART_MAX_DAYS);
|
|
38
|
-
return props.data.chartData.filter(d => d.date >= cutoffDate);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const legendModelLimit = () => isVeryNarrowTerminal() ? 3 : 5;
|
|
42
|
-
const topModelsForLegend = () => props.data.topModels.slice(0, legendModelLimit()).map(m => m.modelId);
|
|
43
|
-
|
|
44
|
-
const maxModelNameWidth = () => isVeryNarrowTerminal() ? 20 : isNarrowTerminal() ? 30 : 50;
|
|
45
|
-
|
|
46
|
-
const sortedModels = createMemo(() => {
|
|
47
|
-
const models = [...props.data.topModels];
|
|
48
|
-
return models.sort((a, b) => {
|
|
49
|
-
let cmp = 0;
|
|
50
|
-
if (props.sortBy === "cost") cmp = a.cost - b.cost;
|
|
51
|
-
else if (props.sortBy === "tokens") cmp = a.totalTokens - b.totalTokens;
|
|
52
|
-
return props.sortDesc ? -cmp : cmp;
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const totalForPercentage = createMemo(() => {
|
|
57
|
-
const models = props.data.topModels;
|
|
58
|
-
if (props.sortBy === "tokens") {
|
|
59
|
-
return models.reduce((sum, m) => sum + m.totalTokens, 0) || 1;
|
|
60
|
-
}
|
|
61
|
-
return models.reduce((sum, m) => sum + m.cost, 0) || 1;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const getPercentage = (model: typeof props.data.topModels[0]) => {
|
|
65
|
-
if (props.sortBy === "tokens") {
|
|
66
|
-
return (model.totalTokens / totalForPercentage()) * 100;
|
|
67
|
-
}
|
|
68
|
-
return (model.cost / totalForPercentage()) * 100;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const visibleModels = () => sortedModels().slice(props.scrollOffset(), props.scrollOffset() + itemsPerPage());
|
|
72
|
-
const totalModels = () => sortedModels().length;
|
|
73
|
-
const endIndex = () => Math.min(props.scrollOffset() + visibleModels().length, totalModels());
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<box flexDirection="column" gap={1}>
|
|
77
|
-
<box flexDirection="column">
|
|
78
|
-
<BarChart data={recentChartData()} width={props.width - 2} height={chartHeight()} />
|
|
79
|
-
<Legend models={topModelsForLegend()} width={props.width} />
|
|
80
|
-
</box>
|
|
81
|
-
|
|
82
|
-
<box flexDirection="column">
|
|
83
|
-
<box flexDirection="row" justifyContent="space-between" marginBottom={0}>
|
|
84
|
-
<text bold>{isVeryNarrowTerminal() ? "Top Models" : `Models by ${props.sortBy === "tokens" ? "Tokens" : "Cost"}`}</text>
|
|
85
|
-
<box flexDirection="row">
|
|
86
|
-
<text dim>{isVeryNarrowTerminal() ? "" : "Total: "}</text>
|
|
87
|
-
<text fg="green">{formatCost(props.data.totalCost)}</text>
|
|
88
|
-
</box>
|
|
89
|
-
</box>
|
|
90
|
-
|
|
91
|
-
<box flexDirection="column">
|
|
92
|
-
<For each={visibleModels()}>
|
|
93
|
-
{(model, i) => {
|
|
94
|
-
const isActive = createMemo(() => i() === props.selectedIndex());
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<ModelRow
|
|
98
|
-
modelId={model.modelId}
|
|
99
|
-
tokens={{
|
|
100
|
-
input: model.inputTokens,
|
|
101
|
-
output: model.outputTokens,
|
|
102
|
-
cacheRead: model.cacheReadTokens,
|
|
103
|
-
cacheWrite: model.cacheWriteTokens,
|
|
104
|
-
}}
|
|
105
|
-
percentage={getPercentage(model)}
|
|
106
|
-
isActive={isActive()}
|
|
107
|
-
compact={isVeryNarrowTerminal()}
|
|
108
|
-
maxNameWidth={maxModelNameWidth()}
|
|
109
|
-
/>
|
|
110
|
-
);
|
|
111
|
-
}}
|
|
112
|
-
</For>
|
|
113
|
-
</box>
|
|
114
|
-
|
|
115
|
-
<Show when={totalModels() > visibleModels().length}>
|
|
116
|
-
<text dim>{`↓ ${props.scrollOffset() + 1}-${endIndex()} of ${totalModels()} models (↑↓ to scroll)`}</text>
|
|
117
|
-
</Show>
|
|
118
|
-
</box>
|
|
119
|
-
</box>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { For, Show, createMemo, createSignal } from "solid-js";
|
|
2
|
-
import type { TUIData } from "../hooks/useData.js";
|
|
3
|
-
import type { ColorPaletteName } from "../config/themes.js";
|
|
4
|
-
import type { SortType, GridCell } from "../types/index.js";
|
|
5
|
-
import { getPalette, getGradeColor } from "../config/themes.js";
|
|
6
|
-
import { getModelColor } from "../utils/colors.js";
|
|
7
|
-
import { formatTokens } from "../utils/format.js";
|
|
8
|
-
import { isNarrow } from "../utils/responsive.js";
|
|
9
|
-
import { DateBreakdownPanel } from "./DateBreakdownPanel.js";
|
|
10
|
-
|
|
11
|
-
interface StatsViewProps {
|
|
12
|
-
data: TUIData;
|
|
13
|
-
height: number;
|
|
14
|
-
colorPalette: ColorPaletteName;
|
|
15
|
-
width?: number;
|
|
16
|
-
selectedDate?: string | null;
|
|
17
|
-
sortBy?: SortType;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
21
|
-
const MONTHS_SHORT = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
|
|
22
|
-
const DAYS = ["", "Mon", "", "Wed", "", "Fri", ""];
|
|
23
|
-
|
|
24
|
-
interface MonthLabel {
|
|
25
|
-
month: string;
|
|
26
|
-
weekIndex: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function StatsView(props: StatsViewProps) {
|
|
30
|
-
const palette = () => getPalette(props.colorPalette);
|
|
31
|
-
const isNarrowTerminal = () => isNarrow(props.width);
|
|
32
|
-
const metric = () => props.sortBy ?? "tokens";
|
|
33
|
-
const cellWidth = 2;
|
|
34
|
-
|
|
35
|
-
const grid = createMemo((): GridCell[][] => {
|
|
36
|
-
const contributions = props.data.contributions;
|
|
37
|
-
const baseGrid = props.data.contributionGrid;
|
|
38
|
-
|
|
39
|
-
const values = contributions.map(c => metric() === "tokens" ? c.tokens : c.cost);
|
|
40
|
-
const maxValue = Math.max(1, ...values);
|
|
41
|
-
|
|
42
|
-
const levelMap = new Map<string, number>();
|
|
43
|
-
for (const c of contributions) {
|
|
44
|
-
const value = metric() === "tokens" ? c.tokens : c.cost;
|
|
45
|
-
const level = value === 0 ? 0 : Math.min(4, Math.ceil((value / maxValue) * 4));
|
|
46
|
-
levelMap.set(c.date, level);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return baseGrid.map(row =>
|
|
50
|
-
row.map(cell => ({
|
|
51
|
-
date: cell.date,
|
|
52
|
-
level: cell.date ? (levelMap.get(cell.date) ?? 0) : 0,
|
|
53
|
-
}))
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const [clickedCell, setClickedCell] = createSignal<string | null>(null);
|
|
58
|
-
|
|
59
|
-
const selectedBreakdown = createMemo(() => {
|
|
60
|
-
const date = clickedCell();
|
|
61
|
-
if (!date) return null;
|
|
62
|
-
if (!props.data.dailyBreakdowns) return null;
|
|
63
|
-
if (!(props.data.dailyBreakdowns instanceof Map)) return null;
|
|
64
|
-
return props.data.dailyBreakdowns.get(date) || null;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const monthPositions = createMemo(() => {
|
|
68
|
-
const sundayRow = grid()[0] || [];
|
|
69
|
-
if (sundayRow.length === 0) return [];
|
|
70
|
-
|
|
71
|
-
const positions: MonthLabel[] = [];
|
|
72
|
-
let lastMonth = -1;
|
|
73
|
-
const monthNames = isNarrowTerminal() ? MONTHS_SHORT : MONTHS;
|
|
74
|
-
|
|
75
|
-
for (let weekIdx = 0; weekIdx < sundayRow.length; weekIdx++) {
|
|
76
|
-
const cell = sundayRow[weekIdx];
|
|
77
|
-
if (!cell.date) continue;
|
|
78
|
-
const month = new Date(cell.date + "T00:00:00").getMonth();
|
|
79
|
-
if (month !== lastMonth) {
|
|
80
|
-
positions.push({ month: monthNames[month], weekIndex: weekIdx });
|
|
81
|
-
lastMonth = month;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return positions;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const totalWeeks = createMemo(() => (grid()[0] || []).length);
|
|
88
|
-
|
|
89
|
-
const monthLabelRow = createMemo(() => {
|
|
90
|
-
const weeks = totalWeeks();
|
|
91
|
-
const positions = monthPositions();
|
|
92
|
-
const chars: string[] = new Array(weeks * cellWidth).fill(" ");
|
|
93
|
-
|
|
94
|
-
for (const pos of positions) {
|
|
95
|
-
const startIdx = pos.weekIndex * cellWidth;
|
|
96
|
-
const monthChars = pos.month.split("");
|
|
97
|
-
for (let i = 0; i < monthChars.length && startIdx + i < chars.length; i++) {
|
|
98
|
-
chars[startIdx + i] = monthChars[i];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return chars.join("");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const dayLabelWidth = () => isNarrowTerminal() ? 2 : 4;
|
|
106
|
-
|
|
107
|
-
const isSelected = (cellDate: string | null) =>
|
|
108
|
-
cellDate && (clickedCell() === cellDate || props.selectedDate === cellDate);
|
|
109
|
-
|
|
110
|
-
const getCellColor = (level: number) =>
|
|
111
|
-
level === 0 ? "#666666" : getGradeColor(palette(), level as 0 | 1 | 2 | 3 | 4);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return (
|
|
116
|
-
<box flexDirection="column" gap={1}>
|
|
117
|
-
<box flexDirection="column">
|
|
118
|
-
<box flexDirection="row">
|
|
119
|
-
<text dim>{" ".repeat(dayLabelWidth())}</text>
|
|
120
|
-
<text dim>{monthLabelRow()}</text>
|
|
121
|
-
</box>
|
|
122
|
-
|
|
123
|
-
<box onMouseDown={(e: { x: number; y: number }) => {
|
|
124
|
-
const labelW = dayLabelWidth();
|
|
125
|
-
const col = Math.floor((e.x - labelW) / cellWidth);
|
|
126
|
-
const row = e.y - 2;
|
|
127
|
-
const gridRows = grid().length;
|
|
128
|
-
|
|
129
|
-
if (row < 0 || row >= gridRows) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (col < 0) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const rowData = grid()[row];
|
|
137
|
-
if (!rowData || col >= rowData.length) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const cell = rowData[col];
|
|
142
|
-
if (!cell?.date) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const newDate = clickedCell() === cell.date ? null : cell.date;
|
|
147
|
-
setClickedCell(newDate);
|
|
148
|
-
}}>
|
|
149
|
-
<For each={DAYS}>
|
|
150
|
-
{(day, dayIndex) => (
|
|
151
|
-
<box flexDirection="row">
|
|
152
|
-
<text dim>{isNarrowTerminal() ? " " : day.padStart(3) + " "}</text>
|
|
153
|
-
<For each={grid()[dayIndex()] || []}>
|
|
154
|
-
{(cell) => (
|
|
155
|
-
<text
|
|
156
|
-
fg={isSelected(cell.date) ? "#ffffff" : getCellColor(cell.level)}
|
|
157
|
-
bg={isSelected(cell.date) ? getCellColor(cell.level) : undefined}
|
|
158
|
-
>
|
|
159
|
-
{isSelected(cell.date) ? "▓▓" : (cell.level === 0 ? "· " : "██")}
|
|
160
|
-
</text>
|
|
161
|
-
)}
|
|
162
|
-
</For>
|
|
163
|
-
</box>
|
|
164
|
-
)}
|
|
165
|
-
</For>
|
|
166
|
-
</box>
|
|
167
|
-
</box>
|
|
168
|
-
|
|
169
|
-
<box flexDirection="row" gap={2} marginTop={1}>
|
|
170
|
-
<text dim>Less</text>
|
|
171
|
-
<box flexDirection="row" gap={0}>
|
|
172
|
-
<For each={[0, 1, 2, 3, 4]}>
|
|
173
|
-
{(level) => (
|
|
174
|
-
<text
|
|
175
|
-
fg={level === 0 ? "#666666" : getGradeColor(palette(), level as 0 | 1 | 2 | 3 | 4)}
|
|
176
|
-
>
|
|
177
|
-
{level === 0 ? "· " : "██"}
|
|
178
|
-
</text>
|
|
179
|
-
)}
|
|
180
|
-
</For>
|
|
181
|
-
</box>
|
|
182
|
-
<text dim>More</text>
|
|
183
|
-
<Show when={!isNarrowTerminal()}>
|
|
184
|
-
<text dim>|</text>
|
|
185
|
-
<text dim>Click on a day to see breakdown</text>
|
|
186
|
-
</Show>
|
|
187
|
-
</box>
|
|
188
|
-
|
|
189
|
-
<Show when={selectedBreakdown()}>
|
|
190
|
-
<DateBreakdownPanel breakdown={selectedBreakdown()!} isNarrow={isNarrowTerminal()} />
|
|
191
|
-
</Show>
|
|
192
|
-
|
|
193
|
-
<Show when={!selectedBreakdown()}>
|
|
194
|
-
<box flexDirection="column" marginTop={1}>
|
|
195
|
-
<box flexDirection={isNarrowTerminal() ? "column" : "row"} gap={isNarrowTerminal() ? 0 : 4}>
|
|
196
|
-
<box flexDirection="column">
|
|
197
|
-
<box flexDirection="row" gap={1}>
|
|
198
|
-
<text dim>{isNarrowTerminal() ? "Model:" : "Favorite model:"}</text>
|
|
199
|
-
<text fg={getModelColor(props.data.stats.favoriteModel)}>{props.data.stats.favoriteModel}</text>
|
|
200
|
-
</box>
|
|
201
|
-
<box flexDirection="row" gap={1}>
|
|
202
|
-
<text dim>Sessions:</text>
|
|
203
|
-
<text fg="cyan">{props.data.stats.sessions.toLocaleString()}</text>
|
|
204
|
-
</box>
|
|
205
|
-
<box flexDirection="row" gap={1}>
|
|
206
|
-
<text dim>{isNarrowTerminal() ? "Streak:" : "Current streak:"}</text>
|
|
207
|
-
<text fg="cyan">{`${props.data.stats.currentStreak} days`}</text>
|
|
208
|
-
</box>
|
|
209
|
-
<box flexDirection="row" gap={1}>
|
|
210
|
-
<text dim>{isNarrowTerminal() ? "Active:" : "Active days:"}</text>
|
|
211
|
-
<text fg="cyan">{`${props.data.stats.activeDays}/${props.data.stats.totalDays}`}</text>
|
|
212
|
-
</box>
|
|
213
|
-
</box>
|
|
214
|
-
|
|
215
|
-
<box flexDirection="column">
|
|
216
|
-
<box flexDirection="row" gap={1}>
|
|
217
|
-
<text dim>{isNarrowTerminal() ? "Tokens:" : "Total tokens:"}</text>
|
|
218
|
-
<text fg="cyan">{formatTokens(props.data.stats.totalTokens)}</text>
|
|
219
|
-
</box>
|
|
220
|
-
<box flexDirection="row" gap={1}>
|
|
221
|
-
<text dim>{isNarrowTerminal() ? "Session:" : "Longest session:"}</text>
|
|
222
|
-
<text fg="cyan">{props.data.stats.longestSession}</text>
|
|
223
|
-
</box>
|
|
224
|
-
<box flexDirection="row" gap={1}>
|
|
225
|
-
<text dim>{isNarrowTerminal() ? "Max streak:" : "Longest streak:"}</text>
|
|
226
|
-
<text fg="cyan">{`${props.data.stats.longestStreak} days`}</text>
|
|
227
|
-
</box>
|
|
228
|
-
<box flexDirection="row" gap={1}>
|
|
229
|
-
<text dim>{isNarrowTerminal() ? "Peak:" : "Peak hour:"}</text>
|
|
230
|
-
<text fg="cyan">{props.data.stats.peakHour}</text>
|
|
231
|
-
</box>
|
|
232
|
-
</box>
|
|
233
|
-
</box>
|
|
234
|
-
</box>
|
|
235
|
-
|
|
236
|
-
<Show when={!isNarrowTerminal()}>
|
|
237
|
-
<box marginTop={1}>
|
|
238
|
-
<text fg="yellow" italic>{`Your total spending is $${props.data.totalCost.toFixed(2)} on AI coding assistants!`}</text>
|
|
239
|
-
</box>
|
|
240
|
-
</Show>
|
|
241
|
-
<Show when={isNarrowTerminal()}>
|
|
242
|
-
<box marginTop={1}>
|
|
243
|
-
<text fg="yellow" italic>{`Total: $${props.data.totalCost.toFixed(2)}`}</text>
|
|
244
|
-
</box>
|
|
245
|
-
</Show>
|
|
246
|
-
</Show>
|
|
247
|
-
</box>
|
|
248
|
-
);
|
|
249
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { formatTokens, formatTokensCompact } from "../utils/format.js";
|
|
2
|
-
|
|
3
|
-
export interface TokenBreakdownData {
|
|
4
|
-
input: number;
|
|
5
|
-
output: number;
|
|
6
|
-
cacheRead: number;
|
|
7
|
-
cacheWrite: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface TokenBreakdownProps {
|
|
11
|
-
tokens: TokenBreakdownData;
|
|
12
|
-
compact?: boolean;
|
|
13
|
-
indent?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function TokenBreakdown(props: TokenBreakdownProps) {
|
|
17
|
-
const indentStr = () => " ".repeat(props.indent ?? 0);
|
|
18
|
-
|
|
19
|
-
if (props.compact) {
|
|
20
|
-
return (
|
|
21
|
-
<box flexDirection="row">
|
|
22
|
-
<text fg="#666666">{indentStr()}</text>
|
|
23
|
-
<text fg="#AAAAAA">{formatTokensCompact(props.tokens.input)}</text>
|
|
24
|
-
<text fg="#666666">{"/"}</text>
|
|
25
|
-
<text fg="#AAAAAA">{formatTokensCompact(props.tokens.output)}</text>
|
|
26
|
-
<text fg="#666666">{"/"}</text>
|
|
27
|
-
<text fg="#AAAAAA">{formatTokensCompact(props.tokens.cacheRead)}</text>
|
|
28
|
-
<text fg="#666666">{"/"}</text>
|
|
29
|
-
<text fg="#AAAAAA">{formatTokensCompact(props.tokens.cacheWrite)}</text>
|
|
30
|
-
</box>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<box flexDirection="row">
|
|
36
|
-
<text fg="#666666">{`${indentStr()}In: `}</text>
|
|
37
|
-
<text fg="#AAAAAA">{formatTokens(props.tokens.input)}</text>
|
|
38
|
-
<text fg="#666666">{" · Out: "}</text>
|
|
39
|
-
<text fg="#AAAAAA">{formatTokens(props.tokens.output)}</text>
|
|
40
|
-
<text fg="#666666">{" · CR: "}</text>
|
|
41
|
-
<text fg="#AAAAAA">{formatTokens(props.tokens.cacheRead)}</text>
|
|
42
|
-
<text fg="#666666">{" · CW: "}</text>
|
|
43
|
-
<text fg="#AAAAAA">{formatTokens(props.tokens.cacheWrite)}</text>
|
|
44
|
-
</box>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export { Header } from "./Header.js";
|
|
2
|
-
export { Footer } from "./Footer.js";
|
|
3
|
-
export { OverviewView } from "./OverviewView.js";
|
|
4
|
-
export { ModelView } from "./ModelView.js";
|
|
5
|
-
export { DailyView } from "./DailyView.js";
|
|
6
|
-
export { StatsView } from "./StatsView.js";
|
|
7
|
-
export { BarChart } from "./BarChart.js";
|
|
8
|
-
export type { ChartDataPoint } from "./BarChart.js";
|
|
9
|
-
export { Legend } from "./Legend.js";
|
|
10
|
-
export { LoadingSpinner } from "./LoadingSpinner.js";
|
|
11
|
-
export { ModelRow } from "./ModelRow.js";
|
|
12
|
-
export { TokenBreakdown } from "./TokenBreakdown.js";
|
|
13
|
-
export type { TokenBreakdownData } from "./TokenBreakdown.js";
|
|
14
|
-
export { DateBreakdownPanel } from "./DateBreakdownPanel.js";
|
|
15
|
-
export type { DateBreakdownPanelProps } from "./DateBreakdownPanel.js";
|