@tokscale/cli 1.4.3 → 2.0.1
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,380 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Show,
|
|
3
|
-
createSignal,
|
|
4
|
-
createEffect,
|
|
5
|
-
onMount,
|
|
6
|
-
onCleanup,
|
|
7
|
-
} from "solid-js";
|
|
8
|
-
import type {
|
|
9
|
-
SourceType,
|
|
10
|
-
SortType,
|
|
11
|
-
TabType,
|
|
12
|
-
LoadingPhase,
|
|
13
|
-
} from "../types/index.js";
|
|
14
|
-
import type { ColorPaletteName } from "../config/themes.js";
|
|
15
|
-
import type { TotalBreakdown } from "../hooks/useData.js";
|
|
16
|
-
import { getPalette } from "../config/themes.js";
|
|
17
|
-
import { formatTokens } from "../utils/format.js";
|
|
18
|
-
import { isVeryNarrow } from "../utils/responsive.js";
|
|
19
|
-
|
|
20
|
-
interface FooterProps {
|
|
21
|
-
enabledSources: Set<SourceType>;
|
|
22
|
-
sortBy: SortType;
|
|
23
|
-
totals?: TotalBreakdown;
|
|
24
|
-
modelCount: number;
|
|
25
|
-
activeTab: TabType;
|
|
26
|
-
scrollStart?: number;
|
|
27
|
-
scrollEnd?: number;
|
|
28
|
-
totalItems?: number;
|
|
29
|
-
colorPalette: ColorPaletteName;
|
|
30
|
-
statusMessage?: string | null;
|
|
31
|
-
isRefreshing?: boolean;
|
|
32
|
-
loadingPhase?: LoadingPhase;
|
|
33
|
-
cacheTimestamp?: number | null;
|
|
34
|
-
autoRefreshEnabled?: boolean;
|
|
35
|
-
autoRefreshMs?: number;
|
|
36
|
-
width?: number;
|
|
37
|
-
onSourceToggle?: (source: SourceType) => void;
|
|
38
|
-
onSortChange?: (sort: SortType) => void;
|
|
39
|
-
onPaletteChange?: () => void;
|
|
40
|
-
onRefresh?: () => void;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function formatTimeAgo(timestamp: number, now: number): string {
|
|
44
|
-
const seconds = Math.max(Math.floor((now - timestamp) / 1000), 0);
|
|
45
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
46
|
-
const minutes = Math.floor(seconds / 60);
|
|
47
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
48
|
-
const hours = Math.floor(minutes / 60);
|
|
49
|
-
if (hours < 24) return `${hours}h ago`;
|
|
50
|
-
const days = Math.floor(hours / 24);
|
|
51
|
-
return `${days}d ago`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function formatIntervalSeconds(ms: number | undefined): string {
|
|
55
|
-
if (!ms || ms <= 0) return "0s";
|
|
56
|
-
const seconds = Math.round(ms / 1000);
|
|
57
|
-
if (seconds < 60) return `${seconds}s`;
|
|
58
|
-
const minutes = Math.round(seconds / 60);
|
|
59
|
-
return `${minutes}m`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function Footer(props: FooterProps) {
|
|
63
|
-
const palette = () => getPalette(props.colorPalette);
|
|
64
|
-
const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
|
|
65
|
-
const [now, setNow] = createSignal(Date.now());
|
|
66
|
-
let nowInterval: ReturnType<typeof setInterval> | null = null;
|
|
67
|
-
|
|
68
|
-
createEffect(() => {
|
|
69
|
-
if (props.cacheTimestamp) {
|
|
70
|
-
if (!nowInterval) {
|
|
71
|
-
nowInterval = setInterval(() => setNow(Date.now()), 1000);
|
|
72
|
-
}
|
|
73
|
-
} else if (nowInterval) {
|
|
74
|
-
clearInterval(nowInterval);
|
|
75
|
-
nowInterval = null;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
onCleanup(() => {
|
|
80
|
-
if (nowInterval) clearInterval(nowInterval);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const showScrollInfo = () =>
|
|
84
|
-
props.activeTab === "overview" &&
|
|
85
|
-
props.totalItems &&
|
|
86
|
-
props.scrollStart !== undefined &&
|
|
87
|
-
props.scrollEnd !== undefined;
|
|
88
|
-
|
|
89
|
-
const totals = () =>
|
|
90
|
-
props.totals || {
|
|
91
|
-
input: 0,
|
|
92
|
-
output: 0,
|
|
93
|
-
cacheRead: 0,
|
|
94
|
-
cacheWrite: 0,
|
|
95
|
-
reasoning: 0,
|
|
96
|
-
total: 0,
|
|
97
|
-
cost: 0,
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<box flexDirection="column" paddingX={1}>
|
|
102
|
-
<box flexDirection="row" justifyContent="space-between">
|
|
103
|
-
<box flexDirection="row" gap={1}>
|
|
104
|
-
<SourceBadge
|
|
105
|
-
name={isVeryNarrowTerminal() ? "1" : "1:OC"}
|
|
106
|
-
source="opencode"
|
|
107
|
-
enabled={props.enabledSources.has("opencode")}
|
|
108
|
-
onToggle={props.onSourceToggle}
|
|
109
|
-
/>
|
|
110
|
-
<SourceBadge
|
|
111
|
-
name={isVeryNarrowTerminal() ? "2" : "2:CC"}
|
|
112
|
-
source="claude"
|
|
113
|
-
enabled={props.enabledSources.has("claude")}
|
|
114
|
-
onToggle={props.onSourceToggle}
|
|
115
|
-
/>
|
|
116
|
-
<SourceBadge
|
|
117
|
-
name={isVeryNarrowTerminal() ? "3" : "3:CX"}
|
|
118
|
-
source="codex"
|
|
119
|
-
enabled={props.enabledSources.has("codex")}
|
|
120
|
-
onToggle={props.onSourceToggle}
|
|
121
|
-
/>
|
|
122
|
-
<SourceBadge
|
|
123
|
-
name={isVeryNarrowTerminal() ? "4" : "4:CR"}
|
|
124
|
-
source="cursor"
|
|
125
|
-
enabled={props.enabledSources.has("cursor")}
|
|
126
|
-
onToggle={props.onSourceToggle}
|
|
127
|
-
/>
|
|
128
|
-
<SourceBadge
|
|
129
|
-
name={isVeryNarrowTerminal() ? "5" : "5:GM"}
|
|
130
|
-
source="gemini"
|
|
131
|
-
enabled={props.enabledSources.has("gemini")}
|
|
132
|
-
onToggle={props.onSourceToggle}
|
|
133
|
-
/>
|
|
134
|
-
<SourceBadge
|
|
135
|
-
name={isVeryNarrowTerminal() ? "6" : "6:AM"}
|
|
136
|
-
source="amp"
|
|
137
|
-
enabled={props.enabledSources.has("amp")}
|
|
138
|
-
onToggle={props.onSourceToggle}
|
|
139
|
-
/>
|
|
140
|
-
<SourceBadge
|
|
141
|
-
name={isVeryNarrowTerminal() ? "7" : "7:DR"}
|
|
142
|
-
source="droid"
|
|
143
|
-
enabled={props.enabledSources.has("droid")}
|
|
144
|
-
onToggle={props.onSourceToggle}
|
|
145
|
-
/>
|
|
146
|
-
<SourceBadge
|
|
147
|
-
name={isVeryNarrowTerminal() ? "8" : "8:CL"}
|
|
148
|
-
source="openclaw"
|
|
149
|
-
enabled={props.enabledSources.has("openclaw")}
|
|
150
|
-
onToggle={props.onSourceToggle}
|
|
151
|
-
/>
|
|
152
|
-
<SourceBadge
|
|
153
|
-
name={isVeryNarrowTerminal() ? "9" : "9:PI"}
|
|
154
|
-
source="pi"
|
|
155
|
-
enabled={props.enabledSources.has("pi")}
|
|
156
|
-
onToggle={props.onSourceToggle}
|
|
157
|
-
/>
|
|
158
|
-
<SourceBadge
|
|
159
|
-
name={isVeryNarrowTerminal() ? "0" : "0:KM"}
|
|
160
|
-
source="kimi"
|
|
161
|
-
enabled={props.enabledSources.has("kimi")}
|
|
162
|
-
onToggle={props.onSourceToggle}
|
|
163
|
-
/>
|
|
164
|
-
<Show when={!isVeryNarrowTerminal()}>
|
|
165
|
-
<text dim>|</text>
|
|
166
|
-
<SortButton
|
|
167
|
-
label="Date"
|
|
168
|
-
sortType="date"
|
|
169
|
-
active={props.sortBy === "date"}
|
|
170
|
-
onClick={props.onSortChange}
|
|
171
|
-
/>
|
|
172
|
-
<SortButton
|
|
173
|
-
label="Cost"
|
|
174
|
-
sortType="cost"
|
|
175
|
-
active={props.sortBy === "cost"}
|
|
176
|
-
onClick={props.onSortChange}
|
|
177
|
-
/>
|
|
178
|
-
<SortButton
|
|
179
|
-
label="Tokens"
|
|
180
|
-
sortType="tokens"
|
|
181
|
-
active={props.sortBy === "tokens"}
|
|
182
|
-
onClick={props.onSortChange}
|
|
183
|
-
/>
|
|
184
|
-
</Show>
|
|
185
|
-
<Show when={showScrollInfo() && !isVeryNarrowTerminal()}>
|
|
186
|
-
<text dim>|</text>
|
|
187
|
-
<text
|
|
188
|
-
dim
|
|
189
|
-
>{`↓ ${props.scrollStart! + 1}-${props.scrollEnd} of ${props.totalItems}`}</text>
|
|
190
|
-
</Show>
|
|
191
|
-
</box>
|
|
192
|
-
<box flexDirection="row" gap={1}>
|
|
193
|
-
<text fg="cyan">{formatTokens(totals().total)}</text>
|
|
194
|
-
<text dim>tokens</text>
|
|
195
|
-
<text dim>|</text>
|
|
196
|
-
<text fg="green" bold>{`$${totals().cost.toFixed(2)}`}</text>
|
|
197
|
-
<Show when={!isVeryNarrowTerminal()}>
|
|
198
|
-
<text dim>({props.modelCount} models)</text>
|
|
199
|
-
</Show>
|
|
200
|
-
</box>
|
|
201
|
-
</box>
|
|
202
|
-
<box flexDirection="row" gap={1}>
|
|
203
|
-
<Show
|
|
204
|
-
when={props.statusMessage}
|
|
205
|
-
fallback={
|
|
206
|
-
<Show
|
|
207
|
-
when={isVeryNarrowTerminal()}
|
|
208
|
-
fallback={
|
|
209
|
-
<>
|
|
210
|
-
<text dim>↑↓ scroll • ←→/tab view • y copy •</text>
|
|
211
|
-
<box onMouseDown={props.onPaletteChange}>
|
|
212
|
-
<text fg="magenta">{`[p:${palette().name}]`}</text>
|
|
213
|
-
</box>
|
|
214
|
-
<text fg={props.autoRefreshEnabled ? "green" : "gray"}>
|
|
215
|
-
{`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
|
|
216
|
-
</text>
|
|
217
|
-
<text dim>[-/+ interval]•</text>
|
|
218
|
-
<box onMouseDown={props.onRefresh}>
|
|
219
|
-
<text fg="yellow">[r:refresh]</text>
|
|
220
|
-
</box>
|
|
221
|
-
<text dim>• e export • q quit</text>
|
|
222
|
-
</>
|
|
223
|
-
}
|
|
224
|
-
>
|
|
225
|
-
<text dim>↑↓•←→•y•</text>
|
|
226
|
-
<box onMouseDown={props.onPaletteChange}>
|
|
227
|
-
<text fg="magenta">[p]</text>
|
|
228
|
-
</box>
|
|
229
|
-
<text fg={props.autoRefreshEnabled ? "green" : "gray"}>
|
|
230
|
-
{`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
|
|
231
|
-
</text>
|
|
232
|
-
<text dim>-+•</text>
|
|
233
|
-
<box onMouseDown={props.onRefresh}>
|
|
234
|
-
<text fg="yellow">[r]</text>
|
|
235
|
-
</box>
|
|
236
|
-
<text dim>•e•q</text>
|
|
237
|
-
</Show>
|
|
238
|
-
}
|
|
239
|
-
>
|
|
240
|
-
<text fg="green" bold>
|
|
241
|
-
{props.statusMessage}
|
|
242
|
-
</text>
|
|
243
|
-
</Show>
|
|
244
|
-
</box>
|
|
245
|
-
<Show when={props.isRefreshing}>
|
|
246
|
-
<LoadingStatusLine phase={props.loadingPhase} />
|
|
247
|
-
</Show>
|
|
248
|
-
<Show when={!props.isRefreshing && props.cacheTimestamp}>
|
|
249
|
-
<box flexDirection="row" gap={1}>
|
|
250
|
-
<text
|
|
251
|
-
dim
|
|
252
|
-
>{`Last updated: ${formatTimeAgo(props.cacheTimestamp!, now())}`}</text>
|
|
253
|
-
<Show when={props.autoRefreshEnabled}>
|
|
254
|
-
<text
|
|
255
|
-
dim
|
|
256
|
-
>{`• Auto: ${formatIntervalSeconds(props.autoRefreshMs)}`}</text>
|
|
257
|
-
</Show>
|
|
258
|
-
</box>
|
|
259
|
-
</Show>
|
|
260
|
-
</box>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
interface SourceBadgeProps {
|
|
265
|
-
name: string;
|
|
266
|
-
source: SourceType;
|
|
267
|
-
enabled: boolean;
|
|
268
|
-
onToggle?: (source: SourceType) => void;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function SourceBadge(props: SourceBadgeProps) {
|
|
272
|
-
const handleClick = () => props.onToggle?.(props.source);
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<box onMouseDown={handleClick}>
|
|
276
|
-
<text fg={props.enabled ? "green" : "gray"}>
|
|
277
|
-
{`[${props.enabled ? "●" : "○"}${props.name}]`}
|
|
278
|
-
</text>
|
|
279
|
-
</box>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
interface SortButtonProps {
|
|
284
|
-
label: string;
|
|
285
|
-
sortType: SortType;
|
|
286
|
-
active: boolean;
|
|
287
|
-
onClick?: (sort: SortType) => void;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function SortButton(props: SortButtonProps) {
|
|
291
|
-
const handleClick = () => props.onClick?.(props.sortType);
|
|
292
|
-
|
|
293
|
-
return (
|
|
294
|
-
<box onMouseDown={handleClick}>
|
|
295
|
-
<text fg={props.active ? "white" : "gray"} bold={props.active}>
|
|
296
|
-
{props.label}
|
|
297
|
-
</text>
|
|
298
|
-
</box>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const SPINNER_COLORS = [
|
|
303
|
-
"#00FFFF",
|
|
304
|
-
"#00D7D7",
|
|
305
|
-
"#00AFAF",
|
|
306
|
-
"#008787",
|
|
307
|
-
"#666666",
|
|
308
|
-
"#666666",
|
|
309
|
-
];
|
|
310
|
-
const SPINNER_WIDTH = 6;
|
|
311
|
-
const SPINNER_HOLD_START = 20;
|
|
312
|
-
const SPINNER_HOLD_END = 6;
|
|
313
|
-
const SPINNER_TRAIL = 3;
|
|
314
|
-
const SPINNER_INTERVAL = 40;
|
|
315
|
-
|
|
316
|
-
const PHASE_MESSAGES: Record<LoadingPhase, string> = {
|
|
317
|
-
idle: "Initializing...",
|
|
318
|
-
"parsing-sources": "Scanning session data...",
|
|
319
|
-
"loading-pricing": "Loading pricing data...",
|
|
320
|
-
"finalizing-report": "Finalizing report...",
|
|
321
|
-
complete: "Complete",
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
interface LoadingStatusLineProps {
|
|
325
|
-
phase?: LoadingPhase;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function LoadingStatusLine(props: LoadingStatusLineProps) {
|
|
329
|
-
const [frame, setFrame] = createSignal(0);
|
|
330
|
-
|
|
331
|
-
onMount(() => {
|
|
332
|
-
const id = setInterval(() => setFrame((f) => f + 1), SPINNER_INTERVAL);
|
|
333
|
-
onCleanup(() => clearInterval(id));
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
const getSpinnerState = () => {
|
|
337
|
-
const forwardFrames = SPINNER_WIDTH;
|
|
338
|
-
const backwardFrames = SPINNER_WIDTH - 1;
|
|
339
|
-
const totalCycle =
|
|
340
|
-
forwardFrames + SPINNER_HOLD_END + backwardFrames + SPINNER_HOLD_START;
|
|
341
|
-
const normalized = frame() % totalCycle;
|
|
342
|
-
|
|
343
|
-
if (normalized < forwardFrames) {
|
|
344
|
-
return { position: normalized, forward: true };
|
|
345
|
-
} else if (normalized < forwardFrames + SPINNER_HOLD_END) {
|
|
346
|
-
return { position: SPINNER_WIDTH - 1, forward: true };
|
|
347
|
-
} else if (normalized < forwardFrames + SPINNER_HOLD_END + backwardFrames) {
|
|
348
|
-
return {
|
|
349
|
-
position:
|
|
350
|
-
SPINNER_WIDTH - 2 - (normalized - forwardFrames - SPINNER_HOLD_END),
|
|
351
|
-
forward: false,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
return { position: 0, forward: false };
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
const getCharProps = (index: number) => {
|
|
358
|
-
const { position, forward } = getSpinnerState();
|
|
359
|
-
const distance = forward ? position - index : index - position;
|
|
360
|
-
if (distance >= 0 && distance < SPINNER_TRAIL) {
|
|
361
|
-
return { char: "■", color: SPINNER_COLORS[distance] };
|
|
362
|
-
}
|
|
363
|
-
return { char: "⬝", color: "#444444" };
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const message = () =>
|
|
367
|
-
props.phase ? PHASE_MESSAGES[props.phase] : "Refreshing...";
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<box flexDirection="row" gap={1}>
|
|
371
|
-
<box flexDirection="row" gap={0}>
|
|
372
|
-
{Array.from({ length: SPINNER_WIDTH }, (_, i) => {
|
|
373
|
-
const { char, color } = getCharProps(i);
|
|
374
|
-
return <text fg={color}>{char}</text>;
|
|
375
|
-
})}
|
|
376
|
-
</box>
|
|
377
|
-
<text dim>{message()}</text>
|
|
378
|
-
</box>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Show } from "solid-js";
|
|
2
|
-
import { exec } from "node:child_process";
|
|
3
|
-
import type { TabType } from "../types/index.js";
|
|
4
|
-
import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
|
|
5
|
-
|
|
6
|
-
const REPO_URL = "https://github.com/junhoyeo/tokscale";
|
|
7
|
-
|
|
8
|
-
function openUrl(url: string) {
|
|
9
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
10
|
-
exec(`${cmd} ${url}`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface HeaderProps {
|
|
14
|
-
activeTab: TabType;
|
|
15
|
-
onTabClick?: (tab: TabType) => void;
|
|
16
|
-
width?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function Header(props: HeaderProps) {
|
|
20
|
-
const isNarrowTerminal = () => isNarrow(props.width);
|
|
21
|
-
const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
|
|
22
|
-
|
|
23
|
-
const getTabName = (fullName: string, shortName: string) =>
|
|
24
|
-
isVeryNarrowTerminal() ? shortName : fullName;
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<box flexDirection="row" paddingX={1} paddingY={0} justifyContent="space-between">
|
|
28
|
-
<box flexDirection="row" gap={isVeryNarrowTerminal() ? 1 : 2}>
|
|
29
|
-
<Tab name={getTabName("Overview", "Ovw")} tabId="overview" active={props.activeTab === "overview"} onClick={props.onTabClick} />
|
|
30
|
-
<Tab name={getTabName("Models", "Mod")} tabId="model" active={props.activeTab === "model"} onClick={props.onTabClick} />
|
|
31
|
-
<Tab name={getTabName("Daily", "Day")} tabId="daily" active={props.activeTab === "daily"} onClick={props.onTabClick} />
|
|
32
|
-
<Tab name={getTabName("Stats", "Sta")} tabId="stats" active={props.activeTab === "stats"} onClick={props.onTabClick} />
|
|
33
|
-
</box>
|
|
34
|
-
<Show when={!isNarrowTerminal()}>
|
|
35
|
-
<box flexDirection="row" onMouseDown={() => openUrl(REPO_URL)}>
|
|
36
|
-
<text fg="cyan" bold>tokscale</text>
|
|
37
|
-
<text fg="#666666">{" | GitHub"}</text>
|
|
38
|
-
</box>
|
|
39
|
-
</Show>
|
|
40
|
-
</box>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface TabProps {
|
|
45
|
-
name: string;
|
|
46
|
-
tabId: TabType;
|
|
47
|
-
active: boolean;
|
|
48
|
-
onClick?: (tab: TabType) => void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function Tab(props: TabProps) {
|
|
52
|
-
const handleClick = () => props.onClick?.(props.tabId);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<Show
|
|
56
|
-
when={props.active}
|
|
57
|
-
fallback={
|
|
58
|
-
<box onMouseDown={handleClick}>
|
|
59
|
-
<text dim>{props.name}</text>
|
|
60
|
-
</box>
|
|
61
|
-
}
|
|
62
|
-
>
|
|
63
|
-
<box onMouseDown={handleClick}>
|
|
64
|
-
<text bg="cyan" fg="black" bold>{` ${props.name} `}</text>
|
|
65
|
-
</box>
|
|
66
|
-
</Show>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { For, Show } from "solid-js";
|
|
2
|
-
import { getModelColor } from "../utils/colors.js";
|
|
3
|
-
import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
|
|
4
|
-
|
|
5
|
-
interface LegendProps {
|
|
6
|
-
models: string[];
|
|
7
|
-
width?: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function Legend(props: LegendProps) {
|
|
11
|
-
const isNarrowTerminal = () => isNarrow(props.width);
|
|
12
|
-
const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
|
|
13
|
-
|
|
14
|
-
const maxModelNameWidth = () => isVeryNarrowTerminal() ? 12 : isNarrowTerminal() ? 18 : 30;
|
|
15
|
-
const truncateModelName = (name: string) => {
|
|
16
|
-
const max = maxModelNameWidth();
|
|
17
|
-
return name.length > max ? name.slice(0, max - 1) + "…" : name;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const models = () => props.models;
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<Show when={models().length > 0}>
|
|
24
|
-
<box flexDirection="row" gap={1} flexWrap="wrap">
|
|
25
|
-
<For each={models()}>
|
|
26
|
-
{(modelId, i) => (
|
|
27
|
-
<box flexDirection="row" gap={0}>
|
|
28
|
-
<text fg={getModelColor(modelId)}>●</text>
|
|
29
|
-
<text>{` ${truncateModelName(modelId)}`}</text>
|
|
30
|
-
<Show when={i() < models().length - 1}>
|
|
31
|
-
<text dim>{isVeryNarrowTerminal() ? " " : " ·"}</text>
|
|
32
|
-
</Show>
|
|
33
|
-
</box>
|
|
34
|
-
)}
|
|
35
|
-
</For>
|
|
36
|
-
</box>
|
|
37
|
-
</Show>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { createSignal, onMount, onCleanup } from "solid-js";
|
|
2
|
-
import type { LoadingPhase } from "../types/index.js";
|
|
3
|
-
|
|
4
|
-
const COLORS = ["#00FFFF", "#00D7D7", "#00AFAF", "#008787", "#666666", "#666666", "#666666", "#666666"];
|
|
5
|
-
const WIDTH = 8;
|
|
6
|
-
const HOLD_START = 30;
|
|
7
|
-
const HOLD_END = 9;
|
|
8
|
-
const TRAIL_LENGTH = 4;
|
|
9
|
-
const INTERVAL = 40;
|
|
10
|
-
|
|
11
|
-
interface SpinnerState {
|
|
12
|
-
position: number;
|
|
13
|
-
forward: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getScannerState(frame: number): SpinnerState {
|
|
17
|
-
const forwardFrames = WIDTH;
|
|
18
|
-
const backwardFrames = WIDTH - 1;
|
|
19
|
-
const totalCycle = forwardFrames + HOLD_END + backwardFrames + HOLD_START;
|
|
20
|
-
const normalized = frame % totalCycle;
|
|
21
|
-
|
|
22
|
-
if (normalized < forwardFrames) {
|
|
23
|
-
return { position: normalized, forward: true };
|
|
24
|
-
} else if (normalized < forwardFrames + HOLD_END) {
|
|
25
|
-
return { position: WIDTH - 1, forward: true };
|
|
26
|
-
} else if (normalized < forwardFrames + HOLD_END + backwardFrames) {
|
|
27
|
-
return { position: WIDTH - 2 - (normalized - forwardFrames - HOLD_END), forward: false };
|
|
28
|
-
}
|
|
29
|
-
return { position: 0, forward: false };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const PHASE_MESSAGES: Record<LoadingPhase, string> = {
|
|
33
|
-
"idle": "Initializing...",
|
|
34
|
-
"parsing-sources": "Scanning session data...",
|
|
35
|
-
"loading-pricing": "Loading pricing data...",
|
|
36
|
-
"finalizing-report": "Finalizing report...",
|
|
37
|
-
"complete": "Complete",
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
interface LoadingSpinnerProps {
|
|
41
|
-
message?: string;
|
|
42
|
-
phase?: LoadingPhase;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function LoadingSpinner(props: LoadingSpinnerProps) {
|
|
46
|
-
const [frame, setFrame] = createSignal(0);
|
|
47
|
-
|
|
48
|
-
onMount(() => {
|
|
49
|
-
const id = setInterval(() => {
|
|
50
|
-
setFrame((f) => f + 1);
|
|
51
|
-
}, INTERVAL);
|
|
52
|
-
onCleanup(() => clearInterval(id));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const state = () => getScannerState(frame());
|
|
56
|
-
const displayMessage = () => props.message || (props.phase ? PHASE_MESSAGES[props.phase] : "Loading data...");
|
|
57
|
-
|
|
58
|
-
const getCharProps = (index: number) => {
|
|
59
|
-
const { position, forward } = state();
|
|
60
|
-
const distance = forward ? position - index : index - position;
|
|
61
|
-
|
|
62
|
-
if (distance >= 0 && distance < TRAIL_LENGTH) {
|
|
63
|
-
return { char: "■", color: COLORS[distance] };
|
|
64
|
-
}
|
|
65
|
-
return { char: "⬝", color: "#444444" };
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<box flexDirection="column" justifyContent="center" alignItems="center" flexGrow={1}>
|
|
70
|
-
<box flexDirection="row" gap={0}>
|
|
71
|
-
{Array.from({ length: WIDTH }, (_, i) => {
|
|
72
|
-
const { char, color } = getCharProps(i);
|
|
73
|
-
return <text fg={color}>{char}</text>;
|
|
74
|
-
})}
|
|
75
|
-
</box>
|
|
76
|
-
<box marginTop={1}>
|
|
77
|
-
<text dim>{displayMessage()}</text>
|
|
78
|
-
</box>
|
|
79
|
-
</box>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Show, createMemo } from "solid-js";
|
|
2
|
-
import { TokenBreakdown, type TokenBreakdownData } from "./TokenBreakdown.js";
|
|
3
|
-
import { getModelColor } from "../utils/colors.js";
|
|
4
|
-
|
|
5
|
-
interface ModelRowProps {
|
|
6
|
-
modelId: string;
|
|
7
|
-
tokens: TokenBreakdownData;
|
|
8
|
-
percentage?: number;
|
|
9
|
-
isActive?: boolean;
|
|
10
|
-
compact?: boolean;
|
|
11
|
-
indent?: number;
|
|
12
|
-
maxNameWidth?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function ModelRow(props: ModelRowProps) {
|
|
16
|
-
const color = () => getModelColor(props.modelId);
|
|
17
|
-
const bgColor = createMemo(() => props.isActive ? "blue" : undefined);
|
|
18
|
-
|
|
19
|
-
const truncateName = (name: string) => {
|
|
20
|
-
const max = props.maxNameWidth ?? 50;
|
|
21
|
-
return name.length > max ? name.slice(0, max - 1) + "…" : name;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const indentStr = () => " ".repeat(props.indent ?? 0);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<box flexDirection="column">
|
|
28
|
-
<box flexDirection="row" backgroundColor={bgColor()}>
|
|
29
|
-
<Show when={props.indent}>
|
|
30
|
-
<text>{indentStr()}</text>
|
|
31
|
-
</Show>
|
|
32
|
-
<text fg={color()} bg={bgColor()}>●</text>
|
|
33
|
-
<text fg={props.isActive ? "white" : undefined} bg={bgColor()}>{` ${truncateName(props.modelId)}`}</text>
|
|
34
|
-
<Show when={props.percentage !== undefined}>
|
|
35
|
-
<text dim bg={bgColor()}>{` (${props.percentage!.toFixed(1)}%)`}</text>
|
|
36
|
-
</Show>
|
|
37
|
-
</box>
|
|
38
|
-
<box flexDirection="row">
|
|
39
|
-
<TokenBreakdown
|
|
40
|
-
tokens={props.tokens}
|
|
41
|
-
compact={props.compact}
|
|
42
|
-
indent={(props.indent ?? 0) + 2}
|
|
43
|
-
/>
|
|
44
|
-
</box>
|
|
45
|
-
</box>
|
|
46
|
-
);
|
|
47
|
-
}
|