@krtclcdy/tersio 2.20.1 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/cli/common.js +14 -1
- package/cli/dashboard.js +367 -35
- package/cli/doctor-fix.js +185 -0
- package/cli/doctor.js +120 -53
- package/cli/install.js +46 -60
- package/cli/rtk-wiring.js +1 -1
- package/cli/settings.js +128 -5
- package/cli/uninstall.js +4 -4
- package/cli/update.js +29 -20
- package/cli/usage.js +1 -1
- package/dashboard/{app.js → charts.js} +74 -361
- package/dashboard/core.js +357 -0
- package/dashboard/settings.js +166 -0
- package/dashboard/share.js +218 -0
- package/dashboard/styles.css +87 -12
- package/dashboard/template.html +162 -57
- package/extensions/ai-addons-updater/index.js +13 -30
- package/extensions/ai-addons-updater/index.ts +11 -29
- package/extensions/lib/utils.js +26 -1
- package/extensions/lib/utils.ts +28 -1
- package/extensions/shared/pricing.js +2 -2
- package/extensions/shared/pricing.ts +2 -1
- package/extensions/shared/usage-ledger.js +3 -2
- package/extensions/shared/usage-ledger.ts +3 -2
- package/extensions/shared/usage-store.js +7 -7
- package/extensions/shared/usage-store.ts +7 -6
- package/gain/dist/brand.webp +0 -0
- package/gain/dist/index.html +65 -0
- package/gain/dist/vite.svg +1 -0
- package/package.json +17 -34
- package/tersio.js +3 -2
|
@@ -1,193 +1,9 @@
|
|
|
1
1
|
(function() {
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function fmt(n) { return Number(n).toLocaleString('en-US'); }
|
|
8
|
-
function fmtShort(n) {
|
|
9
|
-
if (n >= 1e12) return (n / 1e12).toFixed(1) + 'T';
|
|
10
|
-
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
|
|
11
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
12
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
13
|
-
return String(Math.round(n));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
var CURS = {
|
|
17
|
-
USD: { s: '$', d: 2 }, PHP: { s: '₱', d: 2 }, EUR: { s: '€', d: 2 }, GBP: { s: '£', d: 2 },
|
|
18
|
-
JPY: { s: '¥', d: 0 }, KRW: { s: '₩', d: 0 }, SGD: { s: 'S$', d: 2 }, AUD: { s: 'A$', d: 2 },
|
|
19
|
-
CAD: { s: 'C$', d: 2 }, INR: { s: '₹', d: 2 }
|
|
20
|
-
};
|
|
21
|
-
var FLAGS = { USD: '🇺🇸', PHP: '🇵🇭', EUR: '🇪🇺', GBP: '🇬🇧', JPY: '🇯🇵', KRW: '🇰🇷', SGD: '🇸🇬', AUD: '🇦🇺', CAD: '🇨🇦', INR: '🇮🇳' };
|
|
22
|
-
// ponytail: static snapshot; live rates from frankfurter replace it when reachable
|
|
23
|
-
var fx = { cur: 'USD', rates: { USD: 1, PHP: 58.7, EUR: 0.92, GBP: 0.79, JPY: 149.8, KRW: 1385, SGD: 1.34, AUD: 1.52, CAD: 1.37, INR: 88.2 }, live: false };
|
|
24
|
-
try {
|
|
25
|
-
var savedCur = localStorage.getItem('tersio-fx-cur');
|
|
26
|
-
if (savedCur && CURS[savedCur]) fx.cur = savedCur;
|
|
27
|
-
var savedFx = JSON.parse(localStorage.getItem('tersio-fx') || 'null');
|
|
28
|
-
if (savedFx && savedFx.rates && savedFx.rates.USD === 1) fx.rates = savedFx.rates;
|
|
29
|
-
} catch (e) { }
|
|
30
|
-
// Magnitude-aware decimals. Model prices keep falling, so a flat 2dp rounds
|
|
31
|
-
// real spend down to "$0.00"; small amounts keep enough digits to stay
|
|
32
|
-
// readable (0.000187, not 0). Larger amounts use the currency's own scale.
|
|
33
|
-
function moneyDecimals(v, base) {
|
|
34
|
-
var a = Math.abs(v);
|
|
35
|
-
if (a >= 0.1 || a === 0) return base;
|
|
36
|
-
if (a >= 0.001) return Math.max(base, 4);
|
|
37
|
-
if (a >= 0.00001) return Math.max(base, 6);
|
|
38
|
-
return Math.max(base, 8);
|
|
39
|
-
}
|
|
40
|
-
function fxMoney(v) {
|
|
41
|
-
var c = CURS[fx.cur] || CURS.USD, r = fx.rates[fx.cur] || 1, amt = v * r;
|
|
42
|
-
var d = moneyDecimals(amt, c.d);
|
|
43
|
-
return c.s + amt.toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
44
|
-
}
|
|
45
|
-
// Tooltips build innerHTML, and error notes come from providers, so they are
|
|
46
|
-
// never interpolated raw.
|
|
47
|
-
function esc(s) {
|
|
48
|
-
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function countUp(el, target, money) {
|
|
52
|
-
function text(v) { return money ? fxMoney(v) : fmt(Math.round(v)); }
|
|
53
|
-
if (!hasGsap || target <= 0) { el.textContent = text(target); return; }
|
|
54
|
-
var o = { v: 0 };
|
|
55
|
-
gsap.to(o, { v: target, duration: 1.1, ease: 'power3.out', onUpdate: function() { el.textContent = text(o.v); } });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function tick() {
|
|
59
|
-
var c = document.getElementById('clock');
|
|
60
|
-
if (c) c.textContent = new Date().toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
61
|
-
}
|
|
62
|
-
tick(); setInterval(tick, 1000);
|
|
63
|
-
function currentTheme() {
|
|
64
|
-
try { return localStorage.getItem('tersio-theme') || 'system'; } catch (e) { return 'system'; }
|
|
65
|
-
}
|
|
66
|
-
function paintThemeTabs() {
|
|
67
|
-
var v = currentTheme();
|
|
68
|
-
Array.prototype.forEach.call(document.querySelectorAll('[data-theme-val]'), function(b) {
|
|
69
|
-
b.classList.toggle('on', b.getAttribute('data-theme-val') === v);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function applyTheme(v) {
|
|
73
|
-
if (v === 'system') document.documentElement.removeAttribute('data-theme');
|
|
74
|
-
else document.documentElement.setAttribute('data-theme', v);
|
|
75
|
-
try { localStorage.setItem('tersio-theme', v); } catch (e) { }
|
|
76
|
-
paintThemeTabs();
|
|
77
|
-
}
|
|
78
|
-
applyTheme(currentTheme());
|
|
79
|
-
(function initRail() {
|
|
80
|
-
var rail = document.getElementById('rail');
|
|
81
|
-
if (!rail || initRail.done) return;
|
|
82
|
-
initRail.done = true;
|
|
83
|
-
var N = 24, ticks = [];
|
|
84
|
-
for (var i = 0; i < N; i++) {
|
|
85
|
-
(function(i) {
|
|
86
|
-
var b = document.createElement('button');
|
|
87
|
-
b.type = 'button'; b.tabIndex = -1; b.setAttribute('aria-hidden', 'true');
|
|
88
|
-
b.addEventListener('click', function() {
|
|
89
|
-
var max = document.documentElement.scrollHeight - window.innerHeight;
|
|
90
|
-
window.scrollTo({ top: max * (i / (N - 1)), behavior: 'smooth' });
|
|
91
|
-
});
|
|
92
|
-
rail.appendChild(b); ticks.push(b);
|
|
93
|
-
})(i);
|
|
94
|
-
}
|
|
95
|
-
var queued = false;
|
|
96
|
-
function paint() {
|
|
97
|
-
queued = false;
|
|
98
|
-
var max = document.documentElement.scrollHeight - window.innerHeight;
|
|
99
|
-
var f = max > 0 ? window.scrollY / max : 0;
|
|
100
|
-
ticks.forEach(function(b, j) { b.classList.toggle('lit', j / (N - 1) <= f); });
|
|
101
|
-
}
|
|
102
|
-
window.addEventListener('scroll', function() {
|
|
103
|
-
if (!queued) { queued = true; requestAnimationFrame(paint); }
|
|
104
|
-
}, { passive: true });
|
|
105
|
-
paint();
|
|
106
|
-
})();
|
|
107
|
-
(function initFx() {
|
|
108
|
-
var btn = document.getElementById('fxBtn'), panel = document.getElementById('fxPanel'), cur = document.getElementById('fxCur');
|
|
109
|
-
var keys = Object.keys(CURS), active = -1;
|
|
110
|
-
function paint() {
|
|
111
|
-
cur.textContent = FLAGS[fx.cur] + ' ' + fx.cur;
|
|
112
|
-
Array.prototype.forEach.call(panel.children, function(o) {
|
|
113
|
-
o.setAttribute('aria-selected', o.dataset.cur === fx.cur ? 'true' : 'false');
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
function setCur(k) {
|
|
117
|
-
fx.cur = k; active = keys.indexOf(k);
|
|
118
|
-
try { localStorage.setItem('tersio-fx-cur', fx.cur); } catch (e) { }
|
|
119
|
-
// Served mode: also persist server-side so close → reopen keeps the
|
|
120
|
-
// choice. Every `tersio gain` run is a fresh ephemeral port (a new
|
|
121
|
-
// origin), so localStorage alone cannot survive a restart.
|
|
122
|
-
if (window.location.protocol !== 'file:' && typeof fetch === 'function') {
|
|
123
|
-
try {
|
|
124
|
-
fetch('currency', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currency: k }) }).catch(function() { });
|
|
125
|
-
} catch (e) { }
|
|
126
|
-
}
|
|
127
|
-
paint();
|
|
128
|
-
if (DATA) render(DATA);
|
|
129
|
-
}
|
|
130
|
-
function open(show) {
|
|
131
|
-
var willOpen = show === undefined ? panel.classList.contains('hidden') : show;
|
|
132
|
-
panel.classList.toggle('hidden', !willOpen);
|
|
133
|
-
btn.setAttribute('aria-expanded', willOpen ? 'true' : 'false');
|
|
134
|
-
if (willOpen) mark(keys.indexOf(fx.cur));
|
|
135
|
-
}
|
|
136
|
-
function mark(i) {
|
|
137
|
-
active = (i + keys.length) % keys.length;
|
|
138
|
-
Array.prototype.forEach.call(panel.children, function(o, j) {
|
|
139
|
-
o.classList.toggle('active', j === active);
|
|
140
|
-
});
|
|
141
|
-
var el = panel.children[active];
|
|
142
|
-
if (el) el.focus();
|
|
143
|
-
}
|
|
144
|
-
keys.forEach(function(k) {
|
|
145
|
-
var o = document.createElement('button');
|
|
146
|
-
o.type = 'button'; o.className = 'fxopt mono'; o.dataset.cur = k;
|
|
147
|
-
o.setAttribute('role', 'option');
|
|
148
|
-
o.innerHTML = '<span>' + FLAGS[k] + ' ' + k + '</span><svg class="tick" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
149
|
-
o.addEventListener('click', function() { setCur(k); open(false); btn.focus(); });
|
|
150
|
-
o.addEventListener('mousemove', function() { mark(keys.indexOf(k)); });
|
|
151
|
-
panel.appendChild(o);
|
|
152
|
-
});
|
|
153
|
-
paint();
|
|
154
|
-
btn.addEventListener('click', function() { open(); });
|
|
155
|
-
btn.addEventListener('keydown', function(ev) {
|
|
156
|
-
if (ev.key === 'ArrowDown' || ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); open(true); }
|
|
157
|
-
});
|
|
158
|
-
panel.addEventListener('keydown', function(ev) {
|
|
159
|
-
if (ev.key === 'Escape') { open(false); btn.focus(); }
|
|
160
|
-
else if (ev.key === 'ArrowDown') { ev.preventDefault(); mark(active + 1); }
|
|
161
|
-
else if (ev.key === 'ArrowUp') { ev.preventDefault(); mark(active - 1); }
|
|
162
|
-
else if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); setCur(keys[active]); open(false); btn.focus(); }
|
|
163
|
-
else if (ev.key === 'Tab') open(false);
|
|
164
|
-
});
|
|
165
|
-
document.addEventListener('click', function(ev) {
|
|
166
|
-
if (!panel.classList.contains('hidden') && !btn.contains(ev.target) && !panel.contains(ev.target)) open(false);
|
|
167
|
-
});
|
|
168
|
-
if (typeof fetch !== 'function' || typeof AbortController === 'undefined') return;
|
|
169
|
-
var ctl = new AbortController();
|
|
170
|
-
var to = setTimeout(function() { ctl.abort(); }, 4000);
|
|
171
|
-
fetch('https://api.frankfurter.app/latest?from=USD', { signal: ctl.signal }).then(function(r) { return r.json(); }).then(function(j) {
|
|
172
|
-
clearTimeout(to);
|
|
173
|
-
if (!j || !j.rates) return;
|
|
174
|
-
var ok = Object.keys(CURS).every(function(k) { return k === 'USD' || typeof j.rates[k] === 'number'; });
|
|
175
|
-
if (!ok) return;
|
|
176
|
-
fx.rates = Object.assign({ USD: 1 }, j.rates); fx.live = true;
|
|
177
|
-
try { localStorage.setItem('tersio-fx', JSON.stringify({ rates: fx.rates, ts: Date.now() })); } catch (e) { }
|
|
178
|
-
if (DATA) render(DATA);
|
|
179
|
-
}).catch(function() { clearTimeout(to); });
|
|
180
|
-
})();
|
|
181
|
-
Array.prototype.forEach.call(document.querySelectorAll('[data-theme-val]'), function(b) {
|
|
182
|
-
b.addEventListener('click', function() { applyTheme(b.getAttribute('data-theme-val')); });
|
|
183
|
-
});
|
|
184
|
-
(function() {
|
|
185
|
-
var sysMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
|
186
|
-
if (sysMedia && typeof sysMedia.addEventListener === 'function') sysMedia.addEventListener('change', function() {
|
|
187
|
-
if (currentTheme() === 'system') applyTheme('system');
|
|
188
|
-
});
|
|
189
|
-
})();
|
|
190
|
-
|
|
2
|
+
var T = window.Tersio;
|
|
3
|
+
var fmt = T.fmt, fmtShort = T.fmtShort, CURS = T.CURS, FLAGS = T.FLAGS, fx = T.fx,
|
|
4
|
+
fxMoney = T.fxMoney, esc = T.esc, countUp = T.countUp,
|
|
5
|
+
showTip = T.showTip, moveTip = T.moveTip, hideTip = T.hideTip,
|
|
6
|
+
reduce = T.reduce, hasGsap = T.hasGsap, hasST = T.hasST;
|
|
191
7
|
var OPENAI_SVG = '<svg viewBox="0 0 24 24" fill="#000" aria-hidden="true"><path d="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"/></svg>';
|
|
192
8
|
var PROVIDERS = [
|
|
193
9
|
[/openai|codex|gpt-|o1/i, 'OpenAI', 'openai', '#fff', OPENAI_SVG],
|
|
@@ -258,7 +74,7 @@
|
|
|
258
74
|
var b = byModel[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
259
75
|
return b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
260
76
|
}
|
|
261
|
-
var
|
|
77
|
+
var cardsShown = false;
|
|
262
78
|
function topModels(byModel, n) {
|
|
263
79
|
return Object.keys(byModel).sort(function(a, b) { return modelTotal(byModel, b) - modelTotal(byModel, a); }).slice(0, n);
|
|
264
80
|
}
|
|
@@ -283,36 +99,11 @@
|
|
|
283
99
|
}
|
|
284
100
|
function allDays() {
|
|
285
101
|
var set = {};
|
|
286
|
-
Object.keys(
|
|
287
|
-
Object.keys(
|
|
102
|
+
Object.keys(T.getData().byDay || {}).forEach(function(d) { set[d] = 1; });
|
|
103
|
+
Object.keys(T.getData().byDayModel || {}).forEach(function(d) { set[d] = 1; });
|
|
288
104
|
return Object.keys(set).sort();
|
|
289
105
|
}
|
|
290
106
|
|
|
291
|
-
var tipEl = null;
|
|
292
|
-
function tip() {
|
|
293
|
-
if (!tipEl) {
|
|
294
|
-
tipEl = document.createElement('div');
|
|
295
|
-
tipEl.id = 'tip'; tipEl.className = 'mono'; tipEl.setAttribute('role', 'tooltip');
|
|
296
|
-
document.body.appendChild(tipEl);
|
|
297
|
-
}
|
|
298
|
-
return tipEl;
|
|
299
|
-
}
|
|
300
|
-
function showTip(html, x, y) {
|
|
301
|
-
var t = tip();
|
|
302
|
-
t.innerHTML = html;
|
|
303
|
-
if (window.lucide) lucide.createIcons();
|
|
304
|
-
t.classList.add('show');
|
|
305
|
-
moveTip(x, y);
|
|
306
|
-
}
|
|
307
|
-
function moveTip(x, y) {
|
|
308
|
-
if (!tipEl) return;
|
|
309
|
-
var w = 300, h = tipEl.offsetHeight || 220;
|
|
310
|
-
var lx = x + 16 > window.innerWidth - w ? x - w - 12 : x + 16;
|
|
311
|
-
var ly = y + 16 > window.innerHeight - h ? y - h - 12 : y + 16;
|
|
312
|
-
tipEl.style.left = Math.max(8, lx) + 'px';
|
|
313
|
-
tipEl.style.top = Math.max(8, ly) + 'px';
|
|
314
|
-
}
|
|
315
|
-
function hideTip() { if (tipEl) tipEl.classList.remove('show'); }
|
|
316
107
|
function dateHead(key) {
|
|
317
108
|
var dt = new Date(key + 'T12:00:00');
|
|
318
109
|
return isNaN(dt) ? key : dt.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase();
|
|
@@ -327,12 +118,12 @@
|
|
|
327
118
|
return h;
|
|
328
119
|
}
|
|
329
120
|
function tipModelHTML(m) {
|
|
330
|
-
var b = (
|
|
331
|
-
var req = ((
|
|
121
|
+
var b = (T.getData().byModel || {})[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
122
|
+
var req = ((T.getData().byModelMessages || {})[m]) || 0;
|
|
332
123
|
var denom = b.input + b.cacheRead;
|
|
333
124
|
var hit = denom ? (b.cacheRead / denom * 100).toFixed(1) + '%' : '-';
|
|
334
125
|
var total = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
335
|
-
var cb = ((
|
|
126
|
+
var cb = ((T.getData().byModelBucketUsd || {})[m]) || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
336
127
|
var h = '<div class="tt">' + shortName(m) + '</div><div class="tv">' + fmtShort(total) + ' total (' + fxMoney(cb.input + cb.output + cb.cacheRead + cb.cacheWrite) + ')</div>';
|
|
337
128
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">input</span><span class="tvr">' + fmt(b.input) + ' (' + fxMoney(cb.input) + ')</span></div>';
|
|
338
129
|
h += '<div class="tr"><span class="sw" style="background:var(--dim)"></span><span class="tn">output</span><span class="tvr">' + fmt(b.output) + ' (' + fxMoney(cb.output) + ')</span></div>';
|
|
@@ -405,17 +196,17 @@
|
|
|
405
196
|
return ['meh', 'low', 'warn'];
|
|
406
197
|
}
|
|
407
198
|
function tipCo2HTML() {
|
|
408
|
-
var z = co2Zone(
|
|
409
|
-
var h = '<div class="tt">CO2</div><div class="tv">~' + (
|
|
199
|
+
var z = co2Zone(T.getData().co2g || 0);
|
|
200
|
+
var h = '<div class="tt">CO2</div><div class="tv">~' + (T.getData().co2g || 0).toFixed(1) + 'g est.</div>';
|
|
410
201
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">zone</span><span class="tvr">' + zoneIcon(z[0], z[2]) + ' ' + z[1] + '</span></div>';
|
|
411
|
-
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">energy</span><span class="tvr">~' + (
|
|
202
|
+
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">energy</span><span class="tvr">~' + (T.getData().energyWh || 0).toFixed(1) + ' Wh</span></div>';
|
|
412
203
|
return h;
|
|
413
204
|
}
|
|
414
205
|
function tipSavedHTML() {
|
|
415
|
-
var usd =
|
|
206
|
+
var usd = T.getData().usd || 0, saved = T.getData().savedUsd || 0;
|
|
416
207
|
var lev = usd ? saved / usd : 0;
|
|
417
208
|
var z = levZone(lev);
|
|
418
|
-
var t =
|
|
209
|
+
var t = T.getData().tokens || { cacheRead: 0 };
|
|
419
210
|
var h = '<div class="tt">Saved by cache</div><div class="tv">' + fxMoney(saved) + ' est.</div>';
|
|
420
211
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">zone</span><span class="tvr">' + zoneIcon(z[0], z[2]) + ' ' + z[1] + '</span></div>';
|
|
421
212
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">leverage</span><span class="tvr">x' + lev.toFixed(1) + ' per $1</span></div>';
|
|
@@ -424,7 +215,7 @@
|
|
|
424
215
|
}
|
|
425
216
|
function tipShareHTML(share, pp) {
|
|
426
217
|
var z = shareZone(share);
|
|
427
|
-
var t =
|
|
218
|
+
var t = T.getData().tokens || { cacheRead: 0, cacheWrite: 0 };
|
|
428
219
|
var h = '<div class="tt">Cache share</div><div class="tv">' + share + '%</div>';
|
|
429
220
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">zone</span><span class="tvr">' + zoneIcon(z[0], z[2]) + ' ' + z[1] + '</span></div>';
|
|
430
221
|
h += '<div class="tr"><span class="sw" style="background:var(--accent)"></span><span class="tn">read</span><span class="tvr">' + fmtShort(t.cacheRead || 0) + '</span></div>';
|
|
@@ -450,7 +241,7 @@
|
|
|
450
241
|
var mdModel = null, mdSpan = 'all', mdHover = { pts: [], days: [] };
|
|
451
242
|
function mdSeries(m) {
|
|
452
243
|
var days = allDays();
|
|
453
|
-
var per =
|
|
244
|
+
var per = T.getData().byDayModel || {};
|
|
454
245
|
return days.map(function(d) { return { day: d, v: ((per[d] || {})[m] || 0) }; });
|
|
455
246
|
}
|
|
456
247
|
function mdSlice(series) {
|
|
@@ -473,18 +264,19 @@
|
|
|
473
264
|
var step = pts.length > 1 ? (W - 16) / (pts.length - 1) : 0;
|
|
474
265
|
function X(i) { return 8 + i * step; }
|
|
475
266
|
function Y(p) { return 14 + (1 - p / max) * (H - 36); }
|
|
267
|
+
// shadcn CartesianGrid language: subtle dashed horizontals only.
|
|
476
268
|
var grid = '';
|
|
477
|
-
for (i = 1; i <= 3; i++) { y = 14 + (H - 36) * i / 3; grid += '<line class="grid" x1="8" y1="' + y.toFixed(1) + '" x2="' + (W - 8) + '" y2="' + y.toFixed(1) + '"/>'; }
|
|
269
|
+
for (i = 1; i <= 3; i++) { y = 14 + (H - 36) * i / 3; grid += '<line class="grid" x1="8" y1="' + y.toFixed(1) + '" x2="' + (W - 8) + '" y2="' + y.toFixed(1) + '" stroke-dasharray="3 4"/>'; }
|
|
478
270
|
var d = pts.map(function(p, i) { return (i ? 'L' : 'M') + X(i).toFixed(1) + ' ' + Y(p).toFixed(1); }).join(' ');
|
|
479
271
|
var last = pts.length ? { x: X(pts.length - 1), y: Y(pts[pts.length - 1]) } : { x: 8, y: H - 22 };
|
|
480
272
|
svg.innerHTML = '<defs><linearGradient id="mdGrad" x1="0" y1="0" x2="0" y2="1">' +
|
|
481
|
-
'<stop offset="0" style="stop-color:var(--accent)" stop-opacity="0.
|
|
482
|
-
'<stop offset="1" style="stop-color:var(--accent)" stop-opacity="0"/></linearGradient></defs>' +
|
|
273
|
+
'<stop offset="0" style="stop-color:var(--accent)" stop-opacity="0.32"/>' +
|
|
274
|
+
'<stop offset="1" style="stop-color:var(--accent)" stop-opacity="0.02"/></linearGradient></defs>' +
|
|
483
275
|
grid +
|
|
484
276
|
(pts.length ? '<path class="area" d="' + d + ' L' + last.x.toFixed(1) + ' ' + (H - 22) + ' L8 ' + (H - 22) + ' Z"/>' : '') +
|
|
485
|
-
(pts.length ? '<path class="line" d="' + d + '"/>' : '') +
|
|
486
|
-
(pts.length ? '<circle class="dot" cx="' + last.x.toFixed(1) + '" cy="' + last.y.toFixed(1) + '" r="
|
|
487
|
-
(pts.length ? '<circle class="hoverdot" id="mdHoverDot" cx="-10" cy="-10" r="
|
|
277
|
+
(pts.length ? '<path class="line" d="' + d + '" stroke-width="2.5"/>' : '') +
|
|
278
|
+
(pts.length ? '<circle class="dot" cx="' + last.x.toFixed(1) + '" cy="' + last.y.toFixed(1) + '" r="5"/>' : '') +
|
|
279
|
+
(pts.length ? '<circle class="hoverdot" id="mdHoverDot" cx="-10" cy="-10" r="5" style="display:none"/>' : '');
|
|
488
280
|
mdHover = { pts: pts, days: days };
|
|
489
281
|
svg.onmousemove = function(ev) {
|
|
490
282
|
var tip = document.getElementById('mdTip');
|
|
@@ -494,7 +286,7 @@
|
|
|
494
286
|
var idx = Math.max(0, Math.min(mdHover.pts.length - 1, Math.round((fx - 8) / (step || 1))));
|
|
495
287
|
var dot = document.getElementById('mdHoverDot');
|
|
496
288
|
if (dot) { dot.setAttribute('cx', X(idx).toFixed(1)); dot.setAttribute('cy', Y(mdHover.pts[idx]).toFixed(1)); dot.style.display = ''; }
|
|
497
|
-
tip.
|
|
289
|
+
tip.innerHTML = '<div class="tt">' + mdShortDay(mdHover.days[idx]) + '</div><div class="tv">' + fmtShort(mdHover.pts[idx]) + ' tokens</div>';
|
|
498
290
|
tip.style.display = 'block';
|
|
499
291
|
// Raw % positioning overflows past the dialog edge on the first/last
|
|
500
292
|
// points (the dialog clips it). Measure and pin inside the card.
|
|
@@ -529,6 +321,13 @@
|
|
|
529
321
|
else if (avg === max) s.className = 'top';
|
|
530
322
|
s.style.height = Math.max(avg ? 5 : 2, Math.round(avg / max * 100)) + '%';
|
|
531
323
|
s.title = mdShortDay(days[i]) + ' · ' + fmtShort(Math.round(sum));
|
|
324
|
+
(function(day, tokens) {
|
|
325
|
+
s.addEventListener('mouseenter', function(ev) {
|
|
326
|
+
showTip('<div class="tt">' + day + '</div><div class="tv">' + tokens + ' tokens</div>', ev.clientX, ev.clientY);
|
|
327
|
+
});
|
|
328
|
+
s.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
|
|
329
|
+
s.addEventListener('mouseleave', hideTip);
|
|
330
|
+
})(mdShortDay(days[i]), fmtShort(Math.round(sum)));
|
|
532
331
|
el.appendChild(s);
|
|
533
332
|
}
|
|
534
333
|
// Sparse models leave the bars mostly flat: name the peak day, its share
|
|
@@ -542,18 +341,31 @@
|
|
|
542
341
|
function mdDonut(svg, parts) {
|
|
543
342
|
var r = 54, C = 2 * Math.PI * r, off = 0;
|
|
544
343
|
var total = parts.reduce(function(a, p) { return a + p.v; }, 0) || 1;
|
|
344
|
+
var live = parts.filter(function(p) { return p.v > 0; });
|
|
345
|
+
// shadcn pie paddingAngle: breathing room between segments.
|
|
346
|
+
var gap = live.length > 1 ? 4 : 0;
|
|
545
347
|
var html = '<circle class="tk" cx="70" cy="70" r="' + r + '"/>';
|
|
546
348
|
parts.forEach(function(p) {
|
|
547
349
|
var len = p.v / total * C;
|
|
548
350
|
if (len <= 0) return;
|
|
549
|
-
html += '<circle cx="70" cy="70" r="' + r + '" style="stroke:' + p.color + '" stroke-dasharray="' + len.toFixed(1) + ' ' + (C - len).toFixed(1) + '" stroke-dashoffset="' + (-off).toFixed(1) + '" stroke-linecap="butt"/>';
|
|
351
|
+
html += '<circle cx="70" cy="70" r="' + r + '" style="stroke:' + p.color + '" stroke-dasharray="' + Math.max(len - gap, 0.5).toFixed(1) + ' ' + (C - len + gap).toFixed(1) + '" stroke-dashoffset="' + (-off - gap / 2).toFixed(1) + '" stroke-linecap="butt"/>';
|
|
550
352
|
off += len;
|
|
551
353
|
});
|
|
552
354
|
svg.innerHTML = html;
|
|
553
355
|
document.getElementById('mdMixTotal').textContent = fmtShort(total);
|
|
554
|
-
document.getElementById('mdMixLegend')
|
|
555
|
-
|
|
356
|
+
var legend = document.getElementById('mdMixLegend');
|
|
357
|
+
legend.innerHTML = live.map(function(p) {
|
|
358
|
+
return '<div class="row" data-mix="' + p.label + '"><span class="dot" style="background:' + p.color + '"></span><span>' + p.label + '</span><span class="pct">' + (p.v / total * 100).toFixed(1) + '% · ' + fmtShort(p.v) + '</span></div>';
|
|
556
359
|
}).join('');
|
|
360
|
+
Array.prototype.forEach.call(legend.children, function(rowEl) {
|
|
361
|
+
var part = live.filter(function(p) { return p.label === rowEl.getAttribute('data-mix'); })[0];
|
|
362
|
+
if (!part) return;
|
|
363
|
+
rowEl.addEventListener('mouseenter', function(ev) {
|
|
364
|
+
showTip('<div class="tt">' + part.label + '</div><div class="tv">' + fmtShort(part.v) + ' (' + (part.v / total * 100).toFixed(1) + '%)</div>', ev.clientX, ev.clientY);
|
|
365
|
+
});
|
|
366
|
+
rowEl.addEventListener('mousemove', function(ev) { moveTip(ev.clientX, ev.clientY); });
|
|
367
|
+
rowEl.addEventListener('mouseleave', hideTip);
|
|
368
|
+
});
|
|
557
369
|
}
|
|
558
370
|
function mdMonths(el, days) {
|
|
559
371
|
el.innerHTML = '';
|
|
@@ -585,7 +397,7 @@
|
|
|
585
397
|
return { t: (pct >= 0 ? '+' : '') + pct.toFixed(0) + '% vs prior 2 wks', up: pct >= 0 };
|
|
586
398
|
}
|
|
587
399
|
function renderModelDialog(m) {
|
|
588
|
-
var byModel =
|
|
400
|
+
var byModel = T.getData().byModel || {};
|
|
589
401
|
var tops = topModels(byModel, Object.keys(byModel).length);
|
|
590
402
|
var rank = tops.indexOf(m) + 1;
|
|
591
403
|
var b = byModel[m] || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
@@ -593,8 +405,8 @@
|
|
|
593
405
|
var grand = 1;
|
|
594
406
|
tops.forEach(function(k) { grand += modelTotal(byModel, k); });
|
|
595
407
|
var v = vendorOf(m);
|
|
596
|
-
var req = ((
|
|
597
|
-
var usd = ((
|
|
408
|
+
var req = ((T.getData().byModelMessages || {})[m]) || 0;
|
|
409
|
+
var usd = ((T.getData().byModelUsd || {})[m]) || 0;
|
|
598
410
|
var denom = b.input + b.cacheRead;
|
|
599
411
|
var hit = denom ? (b.cacheRead / denom * 100) : 0;
|
|
600
412
|
var series = mdSlice(mdSeries(m));
|
|
@@ -659,8 +471,8 @@
|
|
|
659
471
|
if (!dlg.open) dlg.showModal();
|
|
660
472
|
}
|
|
661
473
|
function dayModelRows(key) {
|
|
662
|
-
var per = (
|
|
663
|
-
var tops = topModels(
|
|
474
|
+
var per = (T.getData().byDayModel || {})[key] || {};
|
|
475
|
+
var tops = topModels(T.getData().byModel || {}, 8), rows = [], other = 0;
|
|
664
476
|
tops.forEach(function(m, i) {
|
|
665
477
|
var v = per[m] || 0;
|
|
666
478
|
if (v) rows.push([m, v, PALETTE[i % PALETTE.length]]);
|
|
@@ -682,7 +494,7 @@
|
|
|
682
494
|
sun.setDate(mon.getDate() + 6);
|
|
683
495
|
var head = isNaN(mon) ? monKey : mon.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase() +
|
|
684
496
|
' - ' + (isNaN(sun) ? '' : sun.toLocaleString('en-US', { month: 'short', day: 'numeric' }).toUpperCase());
|
|
685
|
-
var tops = topModels(
|
|
497
|
+
var tops = topModels(T.getData().byModel || {}, 8), rows = [], other = 0;
|
|
686
498
|
tops.forEach(function(m, i) {
|
|
687
499
|
var v = weekPer[m] || 0;
|
|
688
500
|
if (v) rows.push([m, v, PALETTE[i % PALETTE.length]]);
|
|
@@ -729,7 +541,7 @@
|
|
|
729
541
|
if (GMODE === 'weekly' && !isNaN(dt)) {
|
|
730
542
|
var mk = dayKey(mondayOf(dt));
|
|
731
543
|
weekTotal[mk] = (weekTotal[mk] || 0) + (sums[k] || 0);
|
|
732
|
-
var per = (
|
|
544
|
+
var per = (T.getData().byDayModel || {})[k] || {};
|
|
733
545
|
if (!weekPer[mk]) weekPer[mk] = {};
|
|
734
546
|
Object.keys(per).forEach(function(m) { weekPer[mk][m] = (weekPer[mk][m] || 0) + per[m]; });
|
|
735
547
|
}
|
|
@@ -812,7 +624,7 @@
|
|
|
812
624
|
Array.prototype.forEach.call(document.querySelectorAll('#activity [data-gmode]'), function(b) { b.classList.remove('on'); });
|
|
813
625
|
btn.classList.add('on');
|
|
814
626
|
GMODE = btn.getAttribute('data-gmode');
|
|
815
|
-
if (
|
|
627
|
+
if (T.getData()) renderGraph(T.getData().byDay || {});
|
|
816
628
|
});
|
|
817
629
|
});
|
|
818
630
|
}
|
|
@@ -847,7 +659,7 @@
|
|
|
847
659
|
});
|
|
848
660
|
}
|
|
849
661
|
function renderModels() {
|
|
850
|
-
var byModel =
|
|
662
|
+
var byModel = T.getData().byModel || {};
|
|
851
663
|
var tops = topModels(byModel, Object.keys(byModel).length).filter(function(m) { return modelTotal(byModel, m) > 0; });
|
|
852
664
|
var cards = document.getElementById('modelCards');
|
|
853
665
|
cards.innerHTML = '';
|
|
@@ -920,7 +732,7 @@
|
|
|
920
732
|
var right = document.createElement('div'); right.className = 'shrink-0 text-right';
|
|
921
733
|
var n = document.createElement('p'); n.className = 'mono font-bold'; n.textContent = fmt(modelTotal(byModel, m));
|
|
922
734
|
var cost = document.createElement('p'); cost.className = 'mono text-xs'; cost.style.color = 'var(--dim)';
|
|
923
|
-
cost.textContent = fxMoney((
|
|
735
|
+
cost.textContent = fxMoney((T.getData().byModelUsd || {})[m] || 0);
|
|
924
736
|
right.appendChild(n); right.appendChild(cost);
|
|
925
737
|
li.appendChild(badge); li.appendChild(mid); li.appendChild(right);
|
|
926
738
|
hoverModel(li, m);
|
|
@@ -978,7 +790,7 @@
|
|
|
978
790
|
});
|
|
979
791
|
}
|
|
980
792
|
function renderCmd() {
|
|
981
|
-
var g = (
|
|
793
|
+
var g = (T.getData().rtkGain || {});
|
|
982
794
|
var byName = {};
|
|
983
795
|
function add(name, count, saved, avgPct, avgMs) {
|
|
984
796
|
var r = byName[name];
|
|
@@ -986,7 +798,7 @@
|
|
|
986
798
|
r.count += count;
|
|
987
799
|
if (saved !== null && saved !== undefined) r.saved = (r.saved || 0) + saved;
|
|
988
800
|
}
|
|
989
|
-
(
|
|
801
|
+
(T.getData().byTool || []).forEach(function(r) { add(r[0], r[1], null, null, null); });
|
|
990
802
|
(g.byCommand || []).forEach(function(r) { add(r.command, r.count, r.saved, r.avgPct, r.avgMs); });
|
|
991
803
|
cmdRows = Object.keys(byName).map(function(k) { return byName[k]; });
|
|
992
804
|
sortCmd();
|
|
@@ -1198,12 +1010,11 @@
|
|
|
1198
1010
|
|
|
1199
1011
|
function dayTotal(b) { return (b.input || 0) + (b.output || 0) + (b.cacheRead || 0) + (b.cacheWrite || 0); }
|
|
1200
1012
|
|
|
1201
|
-
function
|
|
1202
|
-
DATA = d;
|
|
1013
|
+
function renderMain(d) {
|
|
1203
1014
|
// Server-provided default currency (tersio gain --currency); a saved
|
|
1204
1015
|
// picker choice in localStorage always wins.
|
|
1205
|
-
if (!
|
|
1206
|
-
|
|
1016
|
+
if (!renderMain.fxInit) {
|
|
1017
|
+
renderMain.fxInit = true;
|
|
1207
1018
|
var saved = null;
|
|
1208
1019
|
try { saved = localStorage.getItem('tersio-fx-cur'); } catch (e) { }
|
|
1209
1020
|
if (!saved && d.currency && CURS[d.currency]) {
|
|
@@ -1296,9 +1107,9 @@
|
|
|
1296
1107
|
(function() {
|
|
1297
1108
|
var el = document.getElementById('ticker');
|
|
1298
1109
|
if (!el) return;
|
|
1299
|
-
var tops = topModels(
|
|
1110
|
+
var tops = topModels(T.getData().byModel || {}, 5);
|
|
1300
1111
|
var parts = tops.map(function(m) {
|
|
1301
|
-
var b = (
|
|
1112
|
+
var b = (T.getData().byModel || {})[m];
|
|
1302
1113
|
var v = b.input + b.output + b.cacheRead + b.cacheWrite;
|
|
1303
1114
|
var nm = m.toUpperCase().replace(/-(FREE|CONTRIBUTOR.*|NEXT|LATEST)$/, '').replace(/[-.]?\d[\d.]*/, '').replace(/-V(?=-|$)/, '');
|
|
1304
1115
|
return nm + ' ' + fmtShort(v);
|
|
@@ -1317,11 +1128,11 @@
|
|
|
1317
1128
|
renderCmd();
|
|
1318
1129
|
|
|
1319
1130
|
if (window.lucide) lucide.createIcons();
|
|
1320
|
-
if (hasGsap && !
|
|
1321
|
-
|
|
1131
|
+
if (hasGsap && !renderMain.introDone) {
|
|
1132
|
+
renderMain.introDone = true;
|
|
1322
1133
|
gsap.from('.hero-in', { y: 26, opacity: 0, duration: 0.8, ease: 'power3.out', stagger: 0.08 });
|
|
1323
1134
|
}
|
|
1324
|
-
observe();
|
|
1135
|
+
T.observe();
|
|
1325
1136
|
}
|
|
1326
1137
|
// shadcn/ui badge: default/secondary/destructive/outline carry the meaning,
|
|
1327
1138
|
// the label carries the detail. Built as DOM nodes so provider error notes
|
|
@@ -1347,7 +1158,7 @@
|
|
|
1347
1158
|
function renderRecent() {
|
|
1348
1159
|
var body = document.getElementById('recent');
|
|
1349
1160
|
body.innerHTML = '';
|
|
1350
|
-
var all =
|
|
1161
|
+
var all = T.getData().recent || [];
|
|
1351
1162
|
recentRows = all.slice();
|
|
1352
1163
|
sortRecent();
|
|
1353
1164
|
var recentPages = Math.max(1, Math.ceil(recentRows.length / recentPer));
|
|
@@ -1390,106 +1201,8 @@
|
|
|
1390
1201
|
document.getElementById('recentTable').style.display = all.length ? '' : 'none';
|
|
1391
1202
|
document.getElementById('emptyRecent').classList.toggle('hidden', all.length > 0);
|
|
1392
1203
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
if (!('IntersectionObserver' in window) || reduce) {
|
|
1398
|
-
els.forEach(function(el) { el.classList.add('in'); });
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
var io = new IntersectionObserver(function(entries) {
|
|
1402
|
-
entries.forEach(function(e) {
|
|
1403
|
-
if (e.isIntersecting && !seen.has(e.target)) {
|
|
1404
|
-
seen.add(e.target); e.target.classList.add('in'); io.unobserve(e.target);
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1407
|
-
}, { threshold: 0.12 });
|
|
1408
|
-
els.forEach(function(el) { if (!seen.has(el)) io.observe(el); });
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
var lastJson = '';
|
|
1412
|
-
function load() {
|
|
1413
|
-
// Served mode only: file:// exports have no data.json endpoint, so
|
|
1414
|
-
// polling there would just burn cycles on 404s.
|
|
1415
|
-
if (window.location.protocol === 'file:') return;
|
|
1416
|
-
// Skip background tabs: no render work while hidden, instant refresh on return.
|
|
1417
|
-
if (document.hidden) return;
|
|
1418
|
-
fetch('data.json').then(function(r) { return r.json(); }).then(function(d) {
|
|
1419
|
-
var json = JSON.stringify(d);
|
|
1420
|
-
if (json === lastJson) return;
|
|
1421
|
-
lastJson = json;
|
|
1422
|
-
render(d);
|
|
1423
|
-
}).catch(function() {
|
|
1424
|
-
var cards = document.getElementById('modelCards');
|
|
1425
|
-
if (cards && !cards.children.length) cards.innerHTML = '<div class="empty md:col-span-3">' + emptyState('cloud-off', 'Could not load data', 'Serve with tersio gain instead of opening this file directly.') + '</div>';
|
|
1426
|
-
if (window.lucide) lucide.createIcons();
|
|
1427
|
-
observe();
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
// Live view: re-read data.json every 5s while served and visible; identical
|
|
1431
|
-
// payloads skip render. The manual Reload button stays as an instant refresh.
|
|
1432
|
-
setInterval(load, 5000);
|
|
1433
|
-
document.addEventListener('visibilitychange', function() { if (!document.hidden) load(); });
|
|
1434
|
-
function toast(title, desc, icon) {
|
|
1435
|
-
var wrap = document.querySelector('.toaster');
|
|
1436
|
-
if (!wrap) {
|
|
1437
|
-
wrap = document.createElement('div');
|
|
1438
|
-
wrap.className = 'toaster';
|
|
1439
|
-
wrap.popover = 'manual';
|
|
1440
|
-
document.body.appendChild(wrap);
|
|
1441
|
-
if (wrap.showPopover) wrap.showPopover(); // top layer: paints above the native settings dialog
|
|
1442
|
-
}
|
|
1443
|
-
var el = document.createElement('div');
|
|
1444
|
-
el.className = 'toast';
|
|
1445
|
-
el.innerHTML = '<span class="toast-icon"><i data-lucide="' + (icon || 'check') + '" class="size-4"></i></span><div><p class="toast-title"></p><p class="toast-desc"></p></div>';
|
|
1446
|
-
el.querySelector('.toast-title').textContent = title;
|
|
1447
|
-
el.querySelector('.toast-desc').textContent = desc;
|
|
1448
|
-
wrap.appendChild(el);
|
|
1449
|
-
if (window.lucide) lucide.createIcons();
|
|
1450
|
-
setTimeout(function() {
|
|
1451
|
-
el.classList.add('out');
|
|
1452
|
-
setTimeout(function() { el.remove(); }, 200);
|
|
1453
|
-
}, 4000);
|
|
1454
|
-
}
|
|
1455
|
-
document.getElementById('reload').addEventListener('click', load);
|
|
1456
|
-
(function() {
|
|
1457
|
-
var dlg = document.getElementById('settings');
|
|
1458
|
-
if (dlg && typeof dlg.showModal === 'function') {
|
|
1459
|
-
document.getElementById('settingsBtn').addEventListener('click', function() { dlg.showModal(); });
|
|
1460
|
-
document.getElementById('settingsClose').addEventListener('click', function() { dlg.close(); });
|
|
1461
|
-
dlg.addEventListener('click', function(ev) { if (ev.target === dlg) dlg.close(); });
|
|
1462
|
-
}
|
|
1463
|
-
var md = document.getElementById('modelDialog');
|
|
1464
|
-
if (md && typeof md.showModal === 'function') {
|
|
1465
|
-
document.getElementById('mdClose').addEventListener('click', function() { md.close(); });
|
|
1466
|
-
md.addEventListener('click', function(ev) { if (ev.target === md) md.close(); });
|
|
1467
|
-
}
|
|
1468
|
-
})();
|
|
1469
|
-
(function() {
|
|
1470
|
-
var resetBtn = document.getElementById('reset');
|
|
1471
|
-
if (!resetBtn) return;
|
|
1472
|
-
if (window.location.protocol === 'file:') { resetBtn.style.display = 'none'; return; }
|
|
1473
|
-
var label = document.getElementById('resetLabel');
|
|
1474
|
-
resetBtn.addEventListener('click', function() {
|
|
1475
|
-
if (resetBtn.dataset.armed) {
|
|
1476
|
-
delete resetBtn.dataset.armed;
|
|
1477
|
-
label.textContent = 'Clearing…';
|
|
1478
|
-
fetch('reset', { method: 'POST' }).then(function(r) { return r.json(); }).then(function() {
|
|
1479
|
-
label.textContent = 'Reset';
|
|
1480
|
-
load();
|
|
1481
|
-
toast('Statistics reset', 'The usage statistics view now starts fresh. Transcripts and RTK history were never touched.', 'rotate-ccw');
|
|
1482
|
-
}).catch(function() {
|
|
1483
|
-
label.textContent = 'Reset';
|
|
1484
|
-
toast('Reset failed', 'Could not reach the server. Try again.', 'circle-alert');
|
|
1485
|
-
});
|
|
1486
|
-
} else {
|
|
1487
|
-
resetBtn.dataset.armed = '1';
|
|
1488
|
-
label.textContent = 'Sure?';
|
|
1489
|
-
setTimeout(function() { delete resetBtn.dataset.armed; if (label.textContent === 'Sure?') label.textContent = 'Reset'; }, 3000);
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
})();
|
|
1493
|
-
load();
|
|
1494
|
-
observe();
|
|
1204
|
+
T.renderMain = renderMain;
|
|
1205
|
+
T.openModelDialog = openModelDialog;
|
|
1206
|
+
T.dayTotal = dayTotal;
|
|
1207
|
+
T.emptyState = emptyState;
|
|
1495
1208
|
})();
|