@krtclcdy/tersio 2.8.0 → 2.9.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/cli/dashboard.html +311 -111
- package/cli/dashboard.js +1 -1
- package/cli/doctor.js +3 -1
- package/cli/install.js +9 -4
- package/cli/update.js +9 -6
- package/cli/usage.js +2 -0
- package/package.json +1 -1
package/cli/dashboard.html
CHANGED
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
}
|
|
35
35
|
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
|
|
36
36
|
.rowline { border-bottom: 1px solid var(--line); }
|
|
37
|
+
.pill { display: inline-flex; align-items: center; gap: 4px; border-radius: 999px; padding: 3px 10px; font-size: 11px; white-space: nowrap; }
|
|
38
|
+
.pill.good { background: var(--accent-soft); color: var(--accent); }
|
|
39
|
+
.pill.bad { background: rgba(248, 113, 113, .12); color: #f87171; }
|
|
40
|
+
.pill.flat { background: var(--track); color: var(--dim); }
|
|
37
41
|
.bar-fill { background: var(--accent); border-radius: 999px; transform-origin: left; }
|
|
38
42
|
.bar-track { background: var(--track); border-radius: 999px; }
|
|
39
43
|
.live-dot { background: var(--accent); box-shadow: 0 0 0 0 var(--accent-soft); animation: ping 2s cubic-bezier(0, 0, .2, 1) infinite; }
|
|
@@ -42,6 +46,9 @@
|
|
|
42
46
|
.rise.in { opacity: 1; transform: none; transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1); }
|
|
43
47
|
.tilt { transition: transform .25s cubic-bezier(.16, 1, .3, 1), background .25s; }
|
|
44
48
|
.tilt:hover { transform: translateX(6px); background: var(--accent-soft); }
|
|
49
|
+
.stripcell { transition: background .25s; }
|
|
50
|
+
.stripcell:hover { background: var(--accent-soft); }
|
|
51
|
+
button, .seg button, .tilt, .modelcard, .mbar, .cell, .stripcell { cursor: pointer; }
|
|
45
52
|
.btn-push { transition: transform .12s, background .2s; }
|
|
46
53
|
.btn-push:hover { background: var(--accent-soft); }
|
|
47
54
|
.btn-push:active { transform: scale(.96); }
|
|
@@ -53,6 +60,11 @@
|
|
|
53
60
|
.mbar { display: flex; flex-direction: column-reverse; border-radius: 6px 6px 2px 2px; overflow: hidden; min-height: 3px; background: var(--track); }
|
|
54
61
|
.mbar > span { display: block; width: 100%; }
|
|
55
62
|
.monogram { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; font-weight: 700; font-size: 13px; color: #fff; flex-shrink: 0; }
|
|
63
|
+
.monogram svg { width: 18px; height: 18px; fill: #fff; }
|
|
64
|
+
.monogram.sm svg { width: 14px; height: 14px; }
|
|
65
|
+
.modelcard { transition: transform .25s cubic-bezier(.16, 1, .3, 1), border-color .25s; }
|
|
66
|
+
.modelcard:hover { transform: translateY(-4px); border-color: var(--accent); }
|
|
67
|
+
.heatmonth { font-size: 10px; }
|
|
56
68
|
.dock {
|
|
57
69
|
position: sticky; top: 12px; z-index: 50;
|
|
58
70
|
background: color-mix(in srgb, var(--panel) 84%, transparent);
|
|
@@ -61,6 +73,17 @@
|
|
|
61
73
|
box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
|
|
62
74
|
}
|
|
63
75
|
.dock img.brand { border-radius: 10px; }
|
|
76
|
+
.dock {
|
|
77
|
+
background:
|
|
78
|
+
linear-gradient(var(--panel), var(--panel)) padding-box,
|
|
79
|
+
linear-gradient(120deg, var(--accent-soft), var(--line) 30%, var(--line) 70%, var(--accent-soft)) border-box;
|
|
80
|
+
border: 1px solid transparent;
|
|
81
|
+
}
|
|
82
|
+
.dock .brand { box-shadow: 0 0 12px var(--accent-soft); }
|
|
83
|
+
#reload:hover { box-shadow: 0 0 14px var(--accent-soft); border-color: var(--accent); }
|
|
84
|
+
.spark { display: flex; align-items: flex-end; gap: 3px; }
|
|
85
|
+
.spark > span { flex: 1; min-width: 3px; border-radius: 2px 2px 0 0; background: var(--accent); opacity: .45; }
|
|
86
|
+
.spark > span.top { opacity: 1; box-shadow: 0 0 8px var(--accent-soft); }
|
|
64
87
|
@media (prefers-reduced-motion: reduce) {
|
|
65
88
|
.rise { opacity: 1; transform: none; }
|
|
66
89
|
.live-dot { animation: none; }
|
|
@@ -80,13 +103,14 @@
|
|
|
80
103
|
<img src="brand.webp" alt="Tersio" width="32" height="32" class="brand size-8 rounded-full">
|
|
81
104
|
<span class="leading-tight text-left">
|
|
82
105
|
<span class="block font-bold tracking-tight text-sm">TERSIO GAIN</span>
|
|
83
|
-
<span class="mono block text-[11px]" style="color: var(--dim)"
|
|
106
|
+
<span class="mono block text-[11px]" style="color: var(--dim)">live ledger</span>
|
|
84
107
|
</span>
|
|
85
108
|
</a>
|
|
86
109
|
<span class="mono hidden shrink-0 sm:flex items-center gap-2 text-xs px-2 ml-auto" style="color: var(--dim)">
|
|
87
110
|
<span class="live-dot inline-block size-2 rounded-full"></span>
|
|
88
111
|
<span id="clock">--:--</span>
|
|
89
112
|
</span>
|
|
113
|
+
<span class="hidden sm:block w-px h-5 shrink-0" style="background: var(--line)" aria-hidden="true"></span>
|
|
90
114
|
<button id="reload" class="btn-push mono flex shrink-0 items-center gap-2 text-xs pl-2.5 pr-3 py-2 rounded-xl" style="border: 1px solid var(--line); color: var(--ink)" aria-label="Reload data">
|
|
91
115
|
<i data-lucide="refresh-cw" class="size-3.5"></i><span class="hidden md:inline">Reload</span>
|
|
92
116
|
</button>
|
|
@@ -99,53 +123,74 @@
|
|
|
99
123
|
<p class="hero-in mono mt-4 text-sm max-w-[62ch] mx-auto" style="color: var(--dim)" id="herosub">session tokens parsed from OMP transcripts. Input, output, cache read, cache write, USD cost, CO2.</p>
|
|
100
124
|
</section>
|
|
101
125
|
|
|
102
|
-
<!-- Savings bento:
|
|
126
|
+
<!-- Savings bento: big USD card + nested right rail -->
|
|
103
127
|
<section class="grid grid-cols-12 grid-flow-dense gap-3" aria-label="Savings">
|
|
104
|
-
<div class="rise col-span-
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<p class="mono text-[11px]
|
|
121
|
-
<p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
|
|
122
|
-
<p class="mono text-[11px] mt-1" style="color: var(--dim)">of tokens cached</p>
|
|
128
|
+
<div class="rise col-span-12 lg:col-span-7 lg:row-span-2 rounded-xl p-6 flex flex-col justify-between" style="border: 1px solid var(--line); background: var(--panel)">
|
|
129
|
+
<div>
|
|
130
|
+
<div class="flex items-center justify-between gap-2">
|
|
131
|
+
<p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">usd cost</p>
|
|
132
|
+
<span class="pill good mono" id="usdDelta">—</span>
|
|
133
|
+
</div>
|
|
134
|
+
<p class="mono font-bold tracking-tighter leading-none mt-2 text-6xl lg:text-7xl" id="usd">-</p>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="mt-4">
|
|
137
|
+
<div class="spark h-16" id="usdSpark"></div>
|
|
138
|
+
<div class="flex gap-4 mt-3 mono text-xs" style="color: var(--dim)">
|
|
139
|
+
<span>~ <span id="usdPerDay" style="color: var(--ink)">-</span>/day</span>
|
|
140
|
+
<span>top <span id="usdTopDay" style="color: var(--ink)">-</span></span>
|
|
141
|
+
<span>x<span id="usdLeverage" style="color: var(--accent)">-</span> cache leverage</span>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<p class="mono text-[11px] mt-3" style="color: var(--dim)" id="usdNote">per-model price table</p>
|
|
123
145
|
</div>
|
|
124
|
-
<div class="
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
<div class="col-span-12 lg:col-span-5 grid grid-cols-2 grid-flow-dense gap-3">
|
|
147
|
+
<div class="rise col-span-2 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
148
|
+
<div class="flex items-center justify-between gap-2">
|
|
149
|
+
<div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">saved by cache</p>
|
|
150
|
+
<p class="mono font-bold text-3xl mt-1" style="color: var(--accent)" id="saved">-</p></div>
|
|
151
|
+
<span class="pill good mono" id="savedDelta">—</span>
|
|
152
|
+
</div>
|
|
153
|
+
<p class="mono text-[11px] mt-2" style="color: var(--dim)">est. vs full input price</p>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="rise col-span-1 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
156
|
+
<div class="flex items-center justify-between gap-2">
|
|
157
|
+
<div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">co2</p>
|
|
158
|
+
<p class="mono font-bold text-3xl mt-1" id="co2">-</p>
|
|
159
|
+
<p class="mt-2"><span class="pill good mono" id="co2Delta">—</span></p></div>
|
|
160
|
+
</div>
|
|
161
|
+
<p class="mono text-[11px] mt-2" style="color: var(--dim)">est. from output</p>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="rise col-span-1 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel)">
|
|
164
|
+
<div class="flex items-center justify-between gap-2">
|
|
165
|
+
<div><p class="mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">cache share</p>
|
|
166
|
+
<p class="mono font-bold text-3xl mt-1" id="cacheShare">-</p>
|
|
167
|
+
<p class="mt-2"><span class="pill good mono" id="cacheDelta">—</span></p></div>
|
|
168
|
+
</div>
|
|
169
|
+
<p class="mono text-[11px] mt-2" style="color: var(--dim)">of tokens cached</p>
|
|
170
|
+
</div>
|
|
128
171
|
</div>
|
|
129
172
|
</section>
|
|
130
173
|
|
|
131
174
|
<!-- Token strip: nonzero buckets only -->
|
|
132
175
|
<section class="rise mt-3 flex rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)" id="tokens" aria-label="Tokens by bucket"></section>
|
|
133
176
|
|
|
134
|
-
<!--
|
|
135
|
-
<section id="activity" class="rise mt-8 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="
|
|
136
|
-
<div class="flex
|
|
137
|
-
<i data-lucide="
|
|
138
|
-
<h2 class="display font-bold tracking-tight text-xl">
|
|
139
|
-
<
|
|
140
|
-
<button data-range="daily" class="on px-3 py-1.5" role="tab">Daily</button>
|
|
141
|
-
<button data-range="weekly" class="px-3 py-1.5" role="tab">Weekly</button>
|
|
142
|
-
<button data-range="cumulative" class="px-3 py-1.5" role="tab">Cumulative</button>
|
|
143
|
-
</div>
|
|
177
|
+
<!-- Activity: contribution heatmap, GitHub style -->
|
|
178
|
+
<section id="activity" class="rise mt-8 rounded-xl p-5 overflow-hidden" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Activity graph">
|
|
179
|
+
<div class="flex items-center gap-2 mb-1">
|
|
180
|
+
<i data-lucide="calendar-days" class="size-4" style="color: var(--accent)"></i>
|
|
181
|
+
<h2 class="display font-bold tracking-tight text-xl">Activity</h2>
|
|
182
|
+
<span class="mono text-xs ml-auto" style="color: var(--dim)" id="graphRange"></span>
|
|
144
183
|
</div>
|
|
145
|
-
<p class="mono text-xs mb-4" style="color: var(--dim)"
|
|
146
|
-
<div class="
|
|
147
|
-
|
|
148
|
-
|
|
184
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)">daily session tokens / last 26 weeks / darker means more</p>
|
|
185
|
+
<div class="overflow-x-auto pb-1">
|
|
186
|
+
<div class="flex gap-[3px] w-max" id="graph"></div>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="mono text-[11px] mt-3 flex items-center gap-1.5" style="color: var(--dim)">
|
|
189
|
+
<span>less</span>
|
|
190
|
+
<span class="cell"></span><span class="cell l1"></span><span class="cell l2"></span><span class="cell l3"></span><span class="cell l4"></span>
|
|
191
|
+
<span>more</span>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="heatmonth mono mt-2 flex" style="color: var(--dim)" id="graphMonths"></div>
|
|
149
194
|
</section>
|
|
150
195
|
|
|
151
196
|
<!-- Top models: 3 cards -->
|
|
@@ -159,39 +204,71 @@
|
|
|
159
204
|
<div class="grid grid-cols-1 md:grid-cols-12 grid-flow-dense gap-3" id="modelCards"></div>
|
|
160
205
|
</section>
|
|
161
206
|
|
|
162
|
-
<!-- Models list
|
|
163
|
-
<
|
|
164
|
-
<div class="
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
<i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
|
|
168
|
-
<h2 class="display font-bold tracking-tight text-xl">Models</h2>
|
|
169
|
-
</div>
|
|
170
|
-
<p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
|
|
171
|
-
<ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
|
|
172
|
-
</section>
|
|
207
|
+
<!-- Models list: full width -->
|
|
208
|
+
<section class="rise mt-8" aria-label="All models">
|
|
209
|
+
<div class="flex items-center gap-2 mb-1">
|
|
210
|
+
<i data-lucide="layers" class="size-4" style="color: var(--accent)"></i>
|
|
211
|
+
<h2 class="display font-bold tracking-tight text-xl">Models</h2>
|
|
173
212
|
</div>
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
</
|
|
213
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)">ranked by tokens / top 8</p>
|
|
214
|
+
<ol id="models" class="rounded-xl overflow-hidden" style="border: 1px solid var(--line); background: var(--panel)"></ol>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
<!-- Top tools: full-width impact table -->
|
|
218
|
+
<section id="tools" class="rise mt-8 rounded-xl p-5 overflow-hidden" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Top tools">
|
|
219
|
+
<div class="flex items-center gap-2 mb-1">
|
|
220
|
+
<i data-lucide="terminal" class="size-4" style="color: var(--accent)"></i>
|
|
221
|
+
<h2 class="display font-bold tracking-tight text-xl">Top tools</h2>
|
|
222
|
+
<span class="mono text-xs ml-auto" style="color: var(--dim)">tool calls in sessions</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="overflow-x-auto">
|
|
225
|
+
<table class="w-full mono text-[13px]" id="topTable">
|
|
226
|
+
<thead>
|
|
227
|
+
<tr class="text-left text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">
|
|
228
|
+
<th class="font-normal text-right pr-3 py-2 w-10">#</th>
|
|
229
|
+
<th class="font-normal py-2 pr-3">Tool</th>
|
|
230
|
+
<th class="font-normal text-right py-2 pr-3">Calls</th>
|
|
231
|
+
<th class="font-normal text-right py-2 pr-3">Share</th>
|
|
232
|
+
<th class="font-normal py-2 min-w-32">Impact</th>
|
|
233
|
+
</tr>
|
|
234
|
+
</thead>
|
|
235
|
+
<tbody id="top"></tbody>
|
|
236
|
+
</table>
|
|
237
|
+
</div>
|
|
238
|
+
<p id="emptyTop" class="mono text-sm py-6 text-center hidden" style="color: var(--dim)">No tool calls yet.</p>
|
|
239
|
+
</section>
|
|
240
|
+
|
|
241
|
+
<!-- Token activity: stacked per-model bars -->
|
|
242
|
+
<section id="modelactivity" class="rise mt-8 rounded-xl p-5" style="border: 1px solid var(--line); background: var(--panel); scroll-margin-top: 90px" aria-label="Token activity">
|
|
243
|
+
<div class="flex flex-wrap items-center gap-3 mb-1">
|
|
244
|
+
<i data-lucide="chart-column" class="size-4" style="color: var(--accent)"></i>
|
|
245
|
+
<h2 class="display font-bold tracking-tight text-xl">Token activity</h2>
|
|
246
|
+
<div class="seg mono text-xs flex items-center gap-1 ml-auto p-1 rounded-[10px]" style="border: 1px solid var(--line)" role="tablist" aria-label="Range">
|
|
247
|
+
<button data-range="daily" class="on px-3 py-1.5" role="tab">Daily</button>
|
|
248
|
+
<button data-range="weekly" class="px-3 py-1.5" role="tab">Weekly</button>
|
|
249
|
+
<button data-range="cumulative" class="px-3 py-1.5" role="tab">Cumulative</button>
|
|
250
|
+
</div>
|
|
184
251
|
</div>
|
|
185
|
-
|
|
252
|
+
<p class="mono text-xs mb-4" style="color: var(--dim)" id="rangeNote">stacked by model / last 60 days</p>
|
|
253
|
+
<div class="flex items-end gap-[3px] h-56" id="bars"></div>
|
|
254
|
+
<div class="flex gap-[3px] mt-2 mono text-[10px]" style="color: var(--dim)" id="barDays"></div>
|
|
255
|
+
<div class="flex flex-wrap gap-x-4 gap-y-1.5 mt-4 mono text-[11px]" style="color: var(--dim)" id="legend"></div>
|
|
256
|
+
</section>
|
|
186
257
|
|
|
187
|
-
<footer class="mono text-xs mt-12 pt-4
|
|
188
|
-
<
|
|
258
|
+
<footer class="mono text-xs mt-12 pt-4" style="color: var(--dim); border-top: 1px solid var(--line)">
|
|
259
|
+
<div class="flex items-center gap-3">
|
|
260
|
+
<span>tersio gain dashboard / single file</span>
|
|
261
|
+
<span class="pill flat" id="verChip">tersio</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="flex items-center gap-4 mt-3">
|
|
264
|
+
<span>session tokens stay local — parsed on this machine, never uploaded</span>
|
|
189
265
|
<span class="ml-auto flex items-center gap-1" style="color: var(--ink)">
|
|
190
266
|
<a href="https://github.com/KurutoDenzeru" target="_blank" rel="noopener" aria-label="GitHub" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></a>
|
|
191
267
|
<a href="https://linkedin.com/in/kurtcalacday/" target="_blank" rel="noopener" aria-label="LinkedIn" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg></a>
|
|
192
268
|
<a href="https://instagram.com/krtclcdy/" target="_blank" rel="noopener" aria-label="Instagram" class="btn-push social grid size-8 place-items-center rounded-lg"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"/></svg></a>
|
|
193
269
|
</span>
|
|
194
|
-
|
|
270
|
+
<a id="rawlink" href="data.json" class="underline underline-offset-2">raw data.json</a>
|
|
271
|
+
</div>
|
|
195
272
|
</footer>
|
|
196
273
|
</div>
|
|
197
274
|
</main>
|
|
@@ -226,24 +303,31 @@
|
|
|
226
303
|
tick(); setInterval(tick, 1000);
|
|
227
304
|
|
|
228
305
|
var VENDORS = [
|
|
229
|
-
[/glm|z-ai|zhipu/i, 'Zhipu', 'Z', '#3b82f6'],
|
|
230
|
-
[/muse/i, 'Meta', 'M', '#0082fb'],
|
|
231
|
-
[/qwen|qwq/i, 'Alibaba', 'Q', '#7c3aed'],
|
|
232
|
-
[/deepseek/i, 'DeepSeek', 'D', '#4d6bfe'],
|
|
233
|
-
[/mimo/i, 'Xiaomi', 'Mi', '#ff6900'],
|
|
234
|
-
[/kimi|moonshot/i, 'Moonshot', 'K', '#a855f7'],
|
|
235
|
-
[/minimax/i, 'MiniMax', 'M', '#e11d48'],
|
|
236
|
-
[/nemotron|nvidia/i, 'NVIDIA', 'N', '#76b900'],
|
|
237
|
-
[/gpt|openai|codex/i, 'OpenAI', 'O', '#10a37f'],
|
|
238
|
-
[/claude|anthropic/i, 'Anthropic', 'A', '#d97757'],
|
|
239
|
-
[/gemini
|
|
240
|
-
[/
|
|
306
|
+
[/glm|z-ai|zhipu/i, 'Zhipu', 'Z', '#3b82f6', ''],
|
|
307
|
+
[/muse/i, 'Meta', 'M', '#0082fb', 'M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z'],
|
|
308
|
+
[/qwen|qwq/i, 'Alibaba', 'Q', '#7c3aed', 'M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z'],
|
|
309
|
+
[/deepseek/i, 'DeepSeek', 'D', '#4d6bfe', 'M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45'],
|
|
310
|
+
[/mimo/i, 'Xiaomi', 'Mi', '#ff6900', 'M12 0C8.016 0 4.756.255 2.493 2.516.23 4.776 0 8.033 0 12.012c0 3.98.23 7.235 2.494 9.497C4.757 23.77 8.017 24 12 24c3.983 0 7.243-.23 9.506-2.491C23.77 19.247 24 15.99 24 12.012c0-3.984-.233-7.243-2.502-9.504C19.234.252 15.978 0 12 0zM4.906 7.405h5.624c1.47 0 3.007.068 3.764.827.746.746.827 2.233.83 3.676v4.54a.15.15 0 0 1-.152.147h-1.947a.15.15 0 0 1-.152-.148V11.83c-.002-.806-.048-1.634-.464-2.051-.358-.36-1.026-.441-1.72-.458H7.158a.15.15 0 0 0-.151.147v6.98a.15.15 0 0 1-.152.148H4.906a.15.15 0 0 1-.15-.148V7.554a.15.15 0 0 1 .15-.149zm12.131 0h1.949a.15.15 0 0 1 .15.15v8.892a.15.15 0 0 1-.15.148h-1.949a.15.15 0 0 1-.151-.148V7.554a.15.15 0 0 1 .151-.149zM8.92 10.948h2.046c.083 0 .15.066.15.147v5.352a.15.15 0 0 1-.15.148H8.92a.15.15 0 0 1-.152-.148v-5.352a.15.15 0 0 1 .152-.147Z'],
|
|
311
|
+
[/kimi|moonshot/i, 'Moonshot', 'K', '#a855f7', ''],
|
|
312
|
+
[/minimax/i, 'MiniMax', 'M', '#e11d48', 'M11.43 3.92a.86.86 0 1 0-1.718 0v14.236a1.999 1.999 0 0 1-3.997 0V9.022a.86.86 0 1 0-1.718 0v3.87a1.999 1.999 0 0 1-3.997 0V11.49a.57.57 0 0 1 1.139 0v1.404a.86.86 0 0 0 1.719 0V9.022a1.999 1.999 0 0 1 3.997 0v9.134a.86.86 0 0 0 1.719 0V3.92a1.998 1.998 0 1 1 3.996 0v11.788a.57.57 0 1 1-1.139 0zm10.572 3.105a2 2 0 0 0-1.999 1.997v7.63a.86.86 0 0 1-1.718 0V3.923a1.999 1.999 0 0 0-3.997 0v16.16a.86.86 0 0 1-1.719 0V18.08a.57.57 0 1 0-1.138 0v2a1.998 1.998 0 0 0 3.996 0V3.92a.86.86 0 0 1 1.719 0v12.73a1.999 1.999 0 0 0 3.996 0V9.023a.86.86 0 1 1 1.72 0v6.686a.57.57 0 0 0 1.138 0V9.022a2 2 0 0 0-1.998-1.997'],
|
|
313
|
+
[/nemotron|nvidia/i, 'NVIDIA', 'N', '#76b900', 'M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z'],
|
|
314
|
+
[/gpt|openai|codex/i, 'OpenAI', 'O', '#10a37f', 'M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z'],
|
|
315
|
+
[/claude|anthropic/i, 'Anthropic', 'A', '#d97757', 'M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z'],
|
|
316
|
+
[/gemini/i, 'Google', 'G', '#4285f4', 'M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81'],
|
|
317
|
+
[/google/i, 'Google', 'G', '#4285f4', 'M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z'],
|
|
318
|
+
[/grok|xai/i, 'xAI', 'X', '#71717a', 'M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z'],
|
|
241
319
|
];
|
|
242
320
|
function vendorOf(model) {
|
|
243
321
|
for (var i = 0; i < VENDORS.length; i++) {
|
|
244
|
-
if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3] };
|
|
322
|
+
if (VENDORS[i][0].test(model)) return { name: VENDORS[i][1], mark: VENDORS[i][2], color: VENDORS[i][3], icon: VENDORS[i][4] };
|
|
245
323
|
}
|
|
246
|
-
return { name: 'Other', mark: (model || '?').charAt(0).toUpperCase(), color: '#71717a' };
|
|
324
|
+
return { name: 'Other', mark: (model || '?').charAt(0).toUpperCase(), color: '#71717a', icon: '' };
|
|
325
|
+
}
|
|
326
|
+
function badgeHTML(v, small) {
|
|
327
|
+
var inner = v.icon
|
|
328
|
+
? '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' + v.icon + '"/></svg>'
|
|
329
|
+
: '<span>' + v.mark + '</span>';
|
|
330
|
+
return '<span class="monogram' + (small ? ' sm' : '') + '" style="background:' + v.color + '" title="' + v.name + '">' + inner + '</span>';
|
|
247
331
|
}
|
|
248
332
|
var PALETTE = ['#34d399', '#818cf8', '#22d3ee', '#fbbf24', '#f472b6', '#a78bfa', '#fb923c', '#2dd4bf'];
|
|
249
333
|
|
|
@@ -255,7 +339,7 @@
|
|
|
255
339
|
return b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
256
340
|
}
|
|
257
341
|
|
|
258
|
-
var DATA = null, RANGE = 'daily';
|
|
342
|
+
var DATA = null, RANGE = 'daily', cardsShown = false;
|
|
259
343
|
|
|
260
344
|
function topModels(byModel, n) {
|
|
261
345
|
return Object.keys(byModel).sort(function (a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
|
|
@@ -268,6 +352,50 @@
|
|
|
268
352
|
return Object.keys(set).sort();
|
|
269
353
|
}
|
|
270
354
|
|
|
355
|
+
function renderGraph(byDay) {
|
|
356
|
+
var g = document.getElementById('graph');
|
|
357
|
+
g.innerHTML = '';
|
|
358
|
+
var today = new Date(); today.setHours(0, 0, 0, 0);
|
|
359
|
+
var start = new Date(today); start.setDate(start.getDate() - (25 * 7) - today.getDay());
|
|
360
|
+
var max = 1, sums = {};
|
|
361
|
+
Object.keys(byDay).forEach(function (day) {
|
|
362
|
+
var b = byDay[day];
|
|
363
|
+
sums[day] = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
364
|
+
if (sums[day] > max) max = sums[day];
|
|
365
|
+
});
|
|
366
|
+
var months = document.getElementById('graphMonths');
|
|
367
|
+
months.innerHTML = '';
|
|
368
|
+
var lastMonth = '';
|
|
369
|
+
for (var w = 0; w < 26; w++) {
|
|
370
|
+
var col = document.createElement('div');
|
|
371
|
+
col.style.cssText = 'display:flex;flex-direction:column;gap:3px;';
|
|
372
|
+
var ml = document.createElement('span');
|
|
373
|
+
ml.className = 'heatmonth w-full truncate';
|
|
374
|
+
for (var i = 0; i < 7; i++) {
|
|
375
|
+
var dt = new Date(start); dt.setDate(dt.getDate() + w * 7 + i);
|
|
376
|
+
var key = dayKey(dt), v = sums[key] || 0, lvl = 0;
|
|
377
|
+
if (v > 0) lvl = Math.min(4, 1 + Math.floor((v / max) * 3.99));
|
|
378
|
+
var cell = document.createElement('span');
|
|
379
|
+
cell.className = 'cell' + (lvl ? ' l' + lvl : '');
|
|
380
|
+
cell.title = key + ': ' + fmt(v) + ' tokens';
|
|
381
|
+
col.appendChild(cell);
|
|
382
|
+
if (i === 0) {
|
|
383
|
+
var mm = dt.toLocaleString('en-US', { month: 'short' });
|
|
384
|
+
ml.textContent = mm !== lastMonth ? mm : '';
|
|
385
|
+
lastMonth = mm;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
g.appendChild(col);
|
|
389
|
+
months.appendChild(ml);
|
|
390
|
+
}
|
|
391
|
+
// Month labels share the column grid.
|
|
392
|
+
months.className = 'heatmonth mono mt-2 flex';
|
|
393
|
+
months.style.cssText = 'display:flex;gap:3px;';
|
|
394
|
+
Array.prototype.forEach.call(months.children, function (m) { m.className += ' flex-1'; });
|
|
395
|
+
document.getElementById('graphRange').textContent =
|
|
396
|
+
dayKey(start) + ' to ' + dayKey(today);
|
|
397
|
+
}
|
|
398
|
+
|
|
271
399
|
// Buckets: [{ key, label, total, segs: [[model, v]] }]
|
|
272
400
|
function buckets() {
|
|
273
401
|
var days = allDays(), tops = topModels(DATA.byModel || {}, 8), out = [];
|
|
@@ -363,7 +491,8 @@
|
|
|
363
491
|
});
|
|
364
492
|
if (hasGsap) {
|
|
365
493
|
gsap.from('#bars > div', { scaleY: 0.2, opacity: 0.2, transformOrigin: 'bottom', duration: 0.5, ease: 'power2.out', stagger: 0.008, overwrite: true });
|
|
366
|
-
if (hasST) {
|
|
494
|
+
if (hasST && !cardsShown) {
|
|
495
|
+
cardsShown = true;
|
|
367
496
|
gsap.from('#modelCards > div', {
|
|
368
497
|
scale: 0.92, opacity: 0.2, duration: 0.6, ease: 'power2.out', stagger: 0.08, overwrite: true,
|
|
369
498
|
scrollTrigger: { trigger: '#modelCards', start: 'top 88%' }
|
|
@@ -390,7 +519,7 @@
|
|
|
390
519
|
tops.slice(0, 3).forEach(function (m, i) {
|
|
391
520
|
var v = vendorOf(m), d = wow(m);
|
|
392
521
|
var card = document.createElement('div');
|
|
393
|
-
card.className = '
|
|
522
|
+
card.className = 'modelcard md:col-span-4 rounded-xl p-5 overflow-hidden relative';
|
|
394
523
|
card.style.cssText = 'border: 1px solid var(--line); background: var(--panel)';
|
|
395
524
|
var ghost = document.createElement('span');
|
|
396
525
|
ghost.className = 'display pointer-events-none select-none absolute -right-2 -bottom-7 font-bold';
|
|
@@ -401,8 +530,9 @@
|
|
|
401
530
|
head.textContent = '0' + (i + 1);
|
|
402
531
|
var row = document.createElement('div');
|
|
403
532
|
row.className = 'flex items-center gap-3';
|
|
404
|
-
var
|
|
405
|
-
|
|
533
|
+
var badgeWrap = document.createElement('span');
|
|
534
|
+
badgeWrap.innerHTML = badgeHTML(v, false);
|
|
535
|
+
var badge = badgeWrap.firstChild;
|
|
406
536
|
var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
|
|
407
537
|
var nm = document.createElement('p'); nm.className = 'mono text-sm font-bold truncate'; nm.textContent = m;
|
|
408
538
|
var vn = document.createElement('p'); vn.className = 'mono text-xs truncate'; vn.style.color = 'var(--dim)'; vn.textContent = v.name;
|
|
@@ -424,10 +554,10 @@
|
|
|
424
554
|
var v = vendorOf(m);
|
|
425
555
|
var li = document.createElement('li');
|
|
426
556
|
li.className = 'tilt flex items-center gap-3 px-4 py-3' + (i < tops.length - 1 ? ' rowline' : '');
|
|
427
|
-
var
|
|
428
|
-
|
|
429
|
-
badge
|
|
430
|
-
badge.
|
|
557
|
+
var badgeWrap = document.createElement('span');
|
|
558
|
+
badgeWrap.innerHTML = badgeHTML(v, true);
|
|
559
|
+
var badge = badgeWrap.firstChild;
|
|
560
|
+
badge.style.cssText += 'width:28px;height:28px;font-size:11px;border-radius:8px;';
|
|
431
561
|
var mid = document.createElement('div'); mid.className = 'min-w-0 flex-1';
|
|
432
562
|
var nm = document.createElement('p'); nm.className = 'mono text-sm truncate'; nm.textContent = m;
|
|
433
563
|
var track = document.createElement('div'); track.className = 'bar-track mt-1.5 h-1.5 overflow-hidden';
|
|
@@ -442,24 +572,29 @@
|
|
|
442
572
|
}
|
|
443
573
|
|
|
444
574
|
function renderTools() {
|
|
445
|
-
var
|
|
446
|
-
|
|
447
|
-
var tools = DATA.byTool || [], max = 1;
|
|
448
|
-
tools.forEach(function (r) { max = Math.max(max, r[1]); });
|
|
449
|
-
tools.forEach(function (r) {
|
|
450
|
-
var
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
575
|
+
var body = document.getElementById('top');
|
|
576
|
+
body.innerHTML = '';
|
|
577
|
+
var tools = DATA.byTool || [], max = 1, sum = 0;
|
|
578
|
+
tools.forEach(function (r) { max = Math.max(max, r[1]); sum += r[1]; });
|
|
579
|
+
tools.forEach(function (r, i) {
|
|
580
|
+
var tr = document.createElement('tr');
|
|
581
|
+
tr.className = 'tilt' + (i < tools.length - 1 ? ' rowline' : '');
|
|
582
|
+
var share = sum ? Math.round(r[1] / sum * 100) : 0;
|
|
583
|
+
tr.innerHTML = '<td class="text-right pr-3 py-2.5 mono text-xs w-10" style="color: var(--dim)"></td>' +
|
|
584
|
+
'<td class="py-2.5 pr-3 truncate" style="max-width: 280px"></td>' +
|
|
585
|
+
'<td class="text-right py-2.5 pr-3 font-bold"></td>' +
|
|
586
|
+
'<td class="text-right py-2.5 pr-3" style="color: var(--dim)"></td>' +
|
|
587
|
+
'<td class="py-2.5 min-w-32"><div class="bar-track h-1.5 overflow-hidden"><div class="bar-fill h-full"></div></div></td>';
|
|
588
|
+
var tds = tr.children;
|
|
589
|
+
tds[0].textContent = String(i + 1).padStart(2, '0');
|
|
590
|
+
tds[1].textContent = r[0];
|
|
591
|
+
tds[1].title = r[0];
|
|
592
|
+
tds[2].textContent = fmt(r[1]);
|
|
593
|
+
tds[3].textContent = share + '%';
|
|
594
|
+
tds[4].firstChild.firstChild.style.width = Math.max(2, Math.round(r[1] / max * 100)) + '%';
|
|
595
|
+
body.appendChild(tr);
|
|
462
596
|
});
|
|
597
|
+
document.getElementById('topTable').style.display = tools.length ? '' : 'none';
|
|
463
598
|
document.getElementById('emptyTop').classList.toggle('hidden', tools.length > 0);
|
|
464
599
|
}
|
|
465
600
|
|
|
@@ -475,7 +610,7 @@
|
|
|
475
610
|
strip.innerHTML = '';
|
|
476
611
|
defs.forEach(function (b, i) {
|
|
477
612
|
var cell = document.createElement('div');
|
|
478
|
-
cell.className = '
|
|
613
|
+
cell.className = 'stripcell flex-1 px-4 py-4' + (i > 0 ? ' sm:border-l' : '');
|
|
479
614
|
cell.style.borderColor = 'var(--line)';
|
|
480
615
|
cell.innerHTML = '<div class="flex items-center gap-2 mono text-[11px] uppercase tracking-[0.14em]" style="color: var(--dim)">' +
|
|
481
616
|
'<i data-lucide="' + b[2] + '" class="size-3.5"></i><span></span></div>' +
|
|
@@ -488,11 +623,61 @@
|
|
|
488
623
|
|
|
489
624
|
function money(v) { return '$' + v.toFixed(2); }
|
|
490
625
|
|
|
626
|
+
// 14-day volume sparkline + blended-rate daily stats for the USD card.
|
|
627
|
+
// Daily cost is a blended estimate (total USD / total tokens x day
|
|
628
|
+
// tokens), always rendered with ~ and never mixed with measured totals.
|
|
629
|
+
function renderSpark(byDay, usd, savedUsd, total) {
|
|
630
|
+
var el = document.getElementById('usdSpark');
|
|
631
|
+
el.innerHTML = '';
|
|
632
|
+
var keys = Object.keys(byDay).sort().slice(-14);
|
|
633
|
+
var rate = total ? usd / total : 0;
|
|
634
|
+
var max = 1, vols = [];
|
|
635
|
+
keys.forEach(function (k) {
|
|
636
|
+
var v = dayTotal(byDay[k] || {});
|
|
637
|
+
vols.push({ k: k, v: v });
|
|
638
|
+
max = Math.max(max, v);
|
|
639
|
+
});
|
|
640
|
+
var top = vols.slice().sort(function (a, b) { return b.v - a.v; })[0] || { k: '', v: 0 };
|
|
641
|
+
vols.forEach(function (r) {
|
|
642
|
+
var s = document.createElement('span');
|
|
643
|
+
if (r.k === top.k && r.v > 0) s.className = 'top';
|
|
644
|
+
s.style.height = Math.max(4, Math.round(r.v / max * 100)) + '%';
|
|
645
|
+
s.title = r.k + ': ' + fmt(r.v) + ' tokens (~$' + (rate * r.v).toFixed(2) + ')';
|
|
646
|
+
el.appendChild(s);
|
|
647
|
+
});
|
|
648
|
+
var days = keys.length || 1;
|
|
649
|
+
document.getElementById('usdPerDay').textContent = '$' + (usd / days).toFixed(2);
|
|
650
|
+
document.getElementById('usdTopDay').textContent = top.k ? top.k.slice(5) + ' ~$' + (rate * top.v).toFixed(2) : '-';
|
|
651
|
+
document.getElementById('usdLeverage').textContent = usd ? (savedUsd / usd).toFixed(1) : '-';
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Week-over-week sums from byDay: { cur, prev } for a token selector.
|
|
655
|
+
function weekSplit(days, pick) {
|
|
656
|
+
var keys = Object.keys(days).sort(), cur = 0, prev = 0;
|
|
657
|
+
var last = keys.slice(-14);
|
|
658
|
+
last.forEach(function (k, i) {
|
|
659
|
+
var v = pick(days[k] || {});
|
|
660
|
+
if (i < last.length - 7) prev += v; else cur += v;
|
|
661
|
+
});
|
|
662
|
+
return { cur: cur, prev: prev };
|
|
663
|
+
}
|
|
664
|
+
function pillHTML(cur, prev, goodWhenUp) {
|
|
665
|
+
if (!prev) return { text: cur ? 'new' : '—', cls: 'flat' };
|
|
666
|
+
var p = (cur - prev) / prev * 100, a = Math.abs(p);
|
|
667
|
+
var cls = a < 0.5 ? 'flat' : ((p > 0) === goodWhenUp ? 'good' : 'bad');
|
|
668
|
+
return { text: (p >= 0 ? '▲ +' : '▼ ') + a.toFixed(1) + '% vs prior 7d', cls: cls };
|
|
669
|
+
}
|
|
670
|
+
function setPill(id, cur, prev, goodWhenUp) {
|
|
671
|
+
var el = document.getElementById(id), r = pillHTML(cur, prev, goodWhenUp);
|
|
672
|
+
el.textContent = r.text;
|
|
673
|
+
el.className = 'pill mono ' + r.cls;
|
|
674
|
+
}
|
|
675
|
+
function dayTotal(b) { return (b.input || 0) + (b.output || 0) + (b.cacheRead || 0) + (b.cacheWrite || 0); }
|
|
676
|
+
|
|
491
677
|
function render(d) {
|
|
492
678
|
DATA = d;
|
|
493
679
|
var t = d.tokens || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
494
680
|
var total = t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
495
|
-
document.getElementById('meta').textContent = fmt(total) + ' session tokens / ' + d.total + ' ledger rows';
|
|
496
681
|
var sub = document.getElementById('herosub');
|
|
497
682
|
sub.innerHTML = '';
|
|
498
683
|
('input ' + fmt(t.input) + ' · output ' + fmt(t.output) + ' · cache read ' + fmt(t.cacheRead) +
|
|
@@ -509,13 +694,30 @@
|
|
|
509
694
|
}
|
|
510
695
|
countUp(document.getElementById('usd'), d.usd || 0, true);
|
|
511
696
|
document.getElementById('usdNote').textContent = d.priced ? 'per-model price table' : 'incl. default pricing';
|
|
697
|
+
var byDay = d.byDay || {};
|
|
698
|
+
renderSpark(byDay, d.usd || 0, d.savedUsd || 0, total);
|
|
699
|
+
document.getElementById('verChip').textContent = 'tersio v' + (d.version || '?');
|
|
700
|
+
var wAll = weekSplit(byDay, dayTotal);
|
|
701
|
+
setPill('usdDelta', wAll.cur, wAll.prev, false);
|
|
512
702
|
countUp(document.getElementById('saved'), d.savedUsd || 0, true);
|
|
703
|
+
var wCache = weekSplit(byDay, function (b) { return b.cacheRead || 0; });
|
|
704
|
+
setPill('savedDelta', wCache.cur, wCache.prev, true);
|
|
513
705
|
document.getElementById('co2').textContent = '~' + (d.co2g || 0).toFixed(1) + 'g';
|
|
706
|
+
var wOut = weekSplit(byDay, function (b) { return b.output || 0; });
|
|
707
|
+
setPill('co2Delta', wOut.cur, wOut.prev, false);
|
|
514
708
|
var share = total ? Math.round((t.cacheRead + t.cacheWrite) / total * 100) : 0;
|
|
515
709
|
document.getElementById('cacheShare').textContent = share + '%';
|
|
516
|
-
|
|
710
|
+
var shareOf = function (b) { var s = dayTotal(b); return s ? ((b.cacheRead || 0) + (b.cacheWrite || 0)) / s * 100 : 0; };
|
|
711
|
+
var keys = Object.keys(byDay).sort(), cPct = 0, pPct = 0, cn = 0, pn = 0;
|
|
712
|
+
keys.slice(-7).forEach(function (k) { cPct += shareOf(byDay[k]); cn++; });
|
|
713
|
+
keys.slice(-14, -7).forEach(function (k) { pPct += shareOf(byDay[k]); pn++; });
|
|
714
|
+
var pp = (cn ? cPct / cn : 0) - (pn ? pPct / pn : 0);
|
|
715
|
+
var cd = document.getElementById('cacheDelta');
|
|
716
|
+
cd.textContent = (pp >= 0 ? '▲ +' : '▼ ') + Math.abs(pp).toFixed(1) + 'pp vs prior 7d';
|
|
717
|
+
cd.className = 'pill mono ' + (Math.abs(pp) < 0.05 ? 'flat' : (pp > 0 ? 'good' : 'bad'));
|
|
517
718
|
|
|
518
719
|
renderStrip(t);
|
|
720
|
+
renderGraph(d.byDay || {});
|
|
519
721
|
renderBars();
|
|
520
722
|
renderModels();
|
|
521
723
|
renderTools();
|
|
@@ -530,9 +732,8 @@
|
|
|
530
732
|
scrollTrigger: { trigger: '#herosub', start: 'top 92%', end: 'top 55%', scrub: true }
|
|
531
733
|
});
|
|
532
734
|
}
|
|
533
|
-
} else {
|
|
534
|
-
observe();
|
|
535
735
|
}
|
|
736
|
+
observe();
|
|
536
737
|
}
|
|
537
738
|
|
|
538
739
|
var seen = new WeakSet();
|
|
@@ -562,10 +763,9 @@
|
|
|
562
763
|
});
|
|
563
764
|
|
|
564
765
|
function load() {
|
|
565
|
-
document.getElementById('meta').textContent = 'connecting to ledger';
|
|
566
766
|
fetch('data.json').then(function (r) { return r.json(); }).then(render).catch(function () {
|
|
567
|
-
document.getElementById('meta').textContent = 'No live data. Open with: tersio gain --open';
|
|
568
767
|
document.getElementById('rawlink').style.display = 'none';
|
|
768
|
+
document.getElementById('topTable').style.display = 'none';
|
|
569
769
|
document.getElementById('emptyTop').classList.remove('hidden');
|
|
570
770
|
if (window.lucide) lucide.createIcons();
|
|
571
771
|
observe();
|
package/cli/dashboard.js
CHANGED
|
@@ -31,7 +31,7 @@ async function runDashboard(options) {
|
|
|
31
31
|
if (options.exportFile) {
|
|
32
32
|
const html = await templateHtml();
|
|
33
33
|
const inline = html
|
|
34
|
-
.replace("fetch('data.json')", `Promise.resolve({ json: function () { return ${dataJson()}; } })
|
|
34
|
+
.replace("fetch('data.json')", `Promise.resolve({ json: function () { return ${dataJson()}; } })`)
|
|
35
35
|
.replace('href="brand.webp"', `href="${await faviconDataUri()}"`)
|
|
36
36
|
.replace('src="brand.webp"', `src="${await faviconDataUri()}"`);
|
|
37
37
|
await fs.writeFile(options.exportFile, inline, 'utf8');
|
package/cli/doctor.js
CHANGED
|
@@ -101,8 +101,10 @@ async function runDoctor() {
|
|
|
101
101
|
section('Environment');
|
|
102
102
|
check('Node', true, process.version);
|
|
103
103
|
check('OMP CLI', ompVersion !== null, ompVersion ?? '');
|
|
104
|
-
if (updateVersion)
|
|
104
|
+
if (typeof updateVersion === 'string')
|
|
105
105
|
warnLine('Tersio CLI', `${updateVersion} available — run tersio update`);
|
|
106
|
+
else if (updateVersion === 'unknown')
|
|
107
|
+
warnLine('Tersio CLI', `${PACKAGE_VERSION} (version check unreachable)`);
|
|
106
108
|
else
|
|
107
109
|
check('Tersio CLI', true, PACKAGE_VERSION);
|
|
108
110
|
console.log(` Home: ${HOME}`);
|
package/cli/install.js
CHANGED
|
@@ -550,7 +550,7 @@ let updatePromptDone = false;
|
|
|
550
550
|
// Scripts, pipes, --yes, and --dry-run keep the old straight-to-install path.
|
|
551
551
|
async function runCommandMenu() {
|
|
552
552
|
const newer = await checkForUpdate();
|
|
553
|
-
if (newer && !dryRun) {
|
|
553
|
+
if (typeof newer === 'string' && !dryRun) {
|
|
554
554
|
const answer = await askInteractiveConfirm(`tersio ${newer} is available (installed ${PACKAGE_VERSION}). Install it now?`);
|
|
555
555
|
if (answer.status === 'confirmed' && answer.value) {
|
|
556
556
|
await runLatestUpdate();
|
|
@@ -582,8 +582,13 @@ async function runCommandMenu() {
|
|
|
582
582
|
await runInstall();
|
|
583
583
|
break;
|
|
584
584
|
case 'check': {
|
|
585
|
-
const latest = await checkForUpdate();
|
|
586
|
-
|
|
585
|
+
const latest = await checkForUpdate(true);
|
|
586
|
+
if (typeof latest === 'string')
|
|
587
|
+
console.log(` [update] tersio ${latest} available (installed ${PACKAGE_VERSION})`);
|
|
588
|
+
else if (latest === 'unknown')
|
|
589
|
+
console.log(` [warn] could not reach the npm registry — run \`tersio update\` to retry`);
|
|
590
|
+
else
|
|
591
|
+
console.log(` [ok] tersio ${PACKAGE_VERSION} is the latest`);
|
|
587
592
|
closeRL();
|
|
588
593
|
break;
|
|
589
594
|
}
|
|
@@ -627,7 +632,7 @@ async function runInstall() {
|
|
|
627
632
|
// the apply-update payload, which is itself an update run.
|
|
628
633
|
if (tty() && !applyUpdate && command !== 'uninstall') {
|
|
629
634
|
const newer = await checkForUpdate();
|
|
630
|
-
if (newer) {
|
|
635
|
+
if (typeof newer === 'string') {
|
|
631
636
|
// Bare `tersio` with an update pending: offer it now (Y/n) instead of
|
|
632
637
|
// burying the banner above the install prompts. Yes runs the full
|
|
633
638
|
// update and stops here — the fresh binary owns what follows.
|
package/cli/update.js
CHANGED
|
@@ -33,13 +33,15 @@ async function latestPublishedVersion() {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
// Returns a newer published version,
|
|
37
|
-
//
|
|
38
|
-
|
|
36
|
+
// Returns a newer published version, null when up to date, or 'unknown' when
|
|
37
|
+
// the registry cannot be reached. Reads the registry at most once per TTL
|
|
38
|
+
// (cached under OMP_PLUGINS_DIR) unless force skips the cache — an explicit
|
|
39
|
+
// "check for updates" must never trust a stale cache.
|
|
40
|
+
async function checkForUpdate(force = false) {
|
|
39
41
|
const cachePath = path.join(OMP_PLUGINS_DIR, 'tersio-update-check.json');
|
|
40
42
|
const cached = parseJsonObject(await readTextIfExists(cachePath));
|
|
41
43
|
const cacheFresh = typeof cached?.lastCheck === 'number' && Date.now() - cached.lastCheck < UPDATE_CHECK_TTL_MS;
|
|
42
|
-
if (cacheFresh && cached?.latest)
|
|
44
|
+
if (!force && cacheFresh && cached?.latest)
|
|
43
45
|
return newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : null;
|
|
44
46
|
const latest = await latestPublishedVersion();
|
|
45
47
|
if (latest) {
|
|
@@ -47,8 +49,9 @@ async function checkForUpdate() {
|
|
|
47
49
|
await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
|
|
48
50
|
return newerThan(latest, PACKAGE_VERSION) ? latest : null;
|
|
49
51
|
}
|
|
50
|
-
// Registry unreachable:
|
|
51
|
-
|
|
52
|
+
// Registry unreachable: a stale cache naming a newer release is still
|
|
53
|
+
// actionable; otherwise report unknown so callers never claim "latest".
|
|
54
|
+
return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : 'unknown';
|
|
52
55
|
}
|
|
53
56
|
// Race a probe against a timeout; slow or failing probes resolve null so the
|
|
54
57
|
// update plan stays best-effort and never blocks the update itself.
|
package/cli/usage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// cli/usage.ts — ledger + session-token usage report, tokscale-style.
|
|
2
2
|
import { co2Grams, importSessionTokens, priceFor, readUsage, usdCost, } from '../extensions/shared/usage-ledger.js';
|
|
3
3
|
import { withInteractiveSpinner } from './interactive.js';
|
|
4
|
+
import { PACKAGE_VERSION } from './common.js';
|
|
4
5
|
export function summarizeUsage(rows) {
|
|
5
6
|
const byKind = {};
|
|
6
7
|
const detailCounts = {};
|
|
@@ -38,6 +39,7 @@ export function summarizeUsage(rows) {
|
|
|
38
39
|
savedUsd,
|
|
39
40
|
costMeasured: session.costMeasured,
|
|
40
41
|
co2g: co2Grams(session.totals.output),
|
|
42
|
+
version: PACKAGE_VERSION,
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
function fmt(n) {
|
package/package.json
CHANGED