token-usage-dashboard 1.0.6
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/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/ai-usage-dashboard.js +92 -0
- package/build/client/_app/immutable/assets/0.fjR0gTLF.css +1 -0
- package/build/client/_app/immutable/assets/0.fjR0gTLF.css.br +0 -0
- package/build/client/_app/immutable/assets/0.fjR0gTLF.css.gz +0 -0
- package/build/client/_app/immutable/assets/2.BABRsmGA.css +1 -0
- package/build/client/_app/immutable/assets/2.BABRsmGA.css.br +0 -0
- package/build/client/_app/immutable/assets/2.BABRsmGA.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bjy-W4x2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DC6tN2WI.js +3 -0
- package/build/client/_app/immutable/chunks/DC6tN2WI.js.br +0 -0
- package/build/client/_app/immutable/chunks/DC6tN2WI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DRLSLo2t.js +2 -0
- package/build/client/_app/immutable/chunks/DRLSLo2t.js.br +0 -0
- package/build/client/_app/immutable/chunks/DRLSLo2t.js.gz +0 -0
- package/build/client/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/build/client/_app/immutable/chunks/xihTtKlq.js.br +1 -0
- package/build/client/_app/immutable/chunks/xihTtKlq.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.C7Gg5PnI.js +2 -0
- package/build/client/_app/immutable/entry/app.C7Gg5PnI.js.br +0 -0
- package/build/client/_app/immutable/entry/app.C7Gg5PnI.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.fbJ-_JRS.js +1 -0
- package/build/client/_app/immutable/entry/start.fbJ-_JRS.js.br +0 -0
- package/build/client/_app/immutable/entry/start.fbJ-_JRS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BNwlucmP.js +1 -0
- package/build/client/_app/immutable/nodes/0.BNwlucmP.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BNwlucmP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js +1 -0
- package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Dj3Fzim2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.Csi9nKxw.js +2 -0
- package/build/client/_app/immutable/nodes/2.Csi9nKxw.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Csi9nKxw.js.gz +0 -0
- package/build/client/_app/version.json +1 -0
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/client/favicon.svg +1 -0
- package/build/client/favicon.svg.br +0 -0
- package/build/client/favicon.svg.gz +0 -0
- package/build/client/robots.txt +2 -0
- package/build/client/robots.txt.br +3 -0
- package/build/client/robots.txt.gz +0 -0
- package/build/env.js +70 -0
- package/build/handler.js +22 -0
- package/build/index.js +145 -0
- package/build/server/chunks/chunks/client.js-C1_n_a2s.js +28 -0
- package/build/server/chunks/chunks/env.js-l0sNRNKZ.js +2 -0
- package/build/server/chunks/chunks/exports.js-Dd48p7Ih.js +263 -0
- package/build/server/chunks/chunks/internal.js-b-1wrnfA.js +1201 -0
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js +22 -0
- package/build/server/chunks/chunks/queries.js-aTxz7ubg.js +676 -0
- package/build/server/chunks/chunks/range.js-CLYFtcRE.js +117 -0
- package/build/server/chunks/chunks/refresh.js-DM1hnB2Y.js +107 -0
- package/build/server/chunks/chunks/request.js-B3j1_Icg.js +20 -0
- package/build/server/chunks/chunks/server.js-mTMwhapa.js +5374 -0
- package/build/server/chunks/chunks/shared.js-7DyfRqo3.js +255 -0
- package/build/server/chunks/chunks/utils.js-DzwqnkBT.js +1049 -0
- package/build/server/chunks/entries/endpoints/api/refresh/_server.ts.js-CJa6pKKD.js +21 -0
- package/build/server/chunks/entries/endpoints/api/settings/_server.ts.js-DQt7Bc2q.js +24 -0
- package/build/server/chunks/entries/endpoints/api/usage/_server.ts.js-kInjrhtE.js +17 -0
- package/build/server/chunks/entries/fallbacks/error.svelte.js-CVKDebWa.js +68 -0
- package/build/server/chunks/entries/hooks.server.js-BbojNy1X.js +16 -0
- package/build/server/chunks/entries/pages/_layout.svelte.js-C9kkUOBf.js +9 -0
- package/build/server/chunks/entries/pages/_page.server.ts.js-Oa3Sfml2.js +17 -0
- package/build/server/chunks/entries/pages/_page.svelte.js-D91hy9R4.js +1255 -0
- package/build/server/chunks/handler-br42Ds2q.js +1475 -0
- package/build/server/chunks/index.js-CBAFVV7O.js +3972 -0
- package/build/server/chunks/manifest.js-s70ajt69.js +63 -0
- package/build/server/chunks/nodes/0.js-Bzngkdxl.js +9 -0
- package/build/server/chunks/nodes/1.js-Cvu2_eGw.js +9 -0
- package/build/server/chunks/nodes/2.js-7xvomkGu.js +20 -0
- package/build/shims.js +33 -0
- package/package.json +64 -0
|
@@ -0,0 +1,1255 @@
|
|
|
1
|
+
import '../../chunks/internal.js-b-1wrnfA.js';
|
|
2
|
+
import { a0 as run, aj as head, ak as attr_class, ai as escape_html, al as attr, am as ensure_array_like, an as bind_props, ao as stringify, P as derived, ap as attr_style, aq as element } from '../../chunks/server.js-mTMwhapa.js';
|
|
3
|
+
import { R as RANGES, a as rangeLabel, b as RATES } from '../../chunks/range.js-CLYFtcRE.js';
|
|
4
|
+
import '../../chunks/client.js-C1_n_a2s.js';
|
|
5
|
+
import '../../chunks/shared.js-7DyfRqo3.js';
|
|
6
|
+
import '../../chunks/exports.js-Dd48p7Ih.js';
|
|
7
|
+
import '../../chunks/utils.js-DzwqnkBT.js';
|
|
8
|
+
|
|
9
|
+
//#region src/lib/components/Panel.svelte
|
|
10
|
+
function Panel($$renderer, $$props) {
|
|
11
|
+
let { title, hint, span = 12, actions, children } = $$props;
|
|
12
|
+
$$renderer.push(`<section class="panel svelte-hxsa5u"${attr_style(`--span: ${stringify(span)}`)}><header class="svelte-hxsa5u"><h2 class="svelte-hxsa5u">${escape_html(title)}</h2> `);
|
|
13
|
+
if (actions) {
|
|
14
|
+
$$renderer.push(`<!--[0--><div class="actions svelte-hxsa5u">`);
|
|
15
|
+
actions($$renderer);
|
|
16
|
+
$$renderer.push(`<!----></div>`);
|
|
17
|
+
} else $$renderer.push("<!--[-1-->");
|
|
18
|
+
$$renderer.push(`<!--]--></header> <div class="body svelte-hxsa5u">`);
|
|
19
|
+
children($$renderer);
|
|
20
|
+
$$renderer.push(`<!----></div></section>`);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/lib/components/Segmented.svelte
|
|
24
|
+
function Segmented($$renderer, $$props) {
|
|
25
|
+
$$renderer.component(($$renderer) => {
|
|
26
|
+
let { options, value = void 0, label, size = "md" } = $$props;
|
|
27
|
+
$$renderer.push(`<div${attr_class("segmented svelte-1dkbyok", void 0, { "sm": size === "sm" })} role="group"${attr("aria-label", label)}><!--[-->`);
|
|
28
|
+
const each_array = ensure_array_like(options);
|
|
29
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
30
|
+
let option = each_array[$$index];
|
|
31
|
+
$$renderer.push(`<button type="button"${attr("aria-pressed", value === option.id)}${attr_class("svelte-1dkbyok", void 0, { "on": value === option.id })}>${escape_html(option.label)}</button>`);
|
|
32
|
+
}
|
|
33
|
+
$$renderer.push(`<!--]--></div>`);
|
|
34
|
+
bind_props($$props, { value });
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/lib/components/MultiSelect.svelte
|
|
39
|
+
function MultiSelect($$renderer, $$props) {
|
|
40
|
+
$$renderer.component(($$renderer) => {
|
|
41
|
+
let { label, options, selected = void 0, format = (value) => value } = $$props;
|
|
42
|
+
let open = false;
|
|
43
|
+
$$renderer.push(`<div class="select svelte-vbwhwd"><button type="button"${attr_class("trigger svelte-vbwhwd", void 0, { "on": selected.length > 0 })}${attr("aria-expanded", open)}>${escape_html(label)} `);
|
|
44
|
+
if (selected.length) $$renderer.push(`<!--[0--><b class="num svelte-vbwhwd">${escape_html(selected.length)}</b>`);
|
|
45
|
+
else $$renderer.push("<!--[-1-->");
|
|
46
|
+
$$renderer.push(`<!--]--> <svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true"><path d="M2 4.5 6 8.5 10 4.5" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></button> `);
|
|
47
|
+
$$renderer.push("<!--[-1-->");
|
|
48
|
+
$$renderer.push(`<!--]--></div>`);
|
|
49
|
+
bind_props($$props, { selected });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/lib/format.ts
|
|
54
|
+
var UNITS = [
|
|
55
|
+
{
|
|
56
|
+
at: 0xe8d4a51000,
|
|
57
|
+
suffix: "T"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
at: 1e9,
|
|
61
|
+
suffix: "B"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
at: 1e6,
|
|
65
|
+
suffix: "M"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
at: 1e3,
|
|
69
|
+
suffix: "k"
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
function compact(value) {
|
|
73
|
+
const size = UNITS.find((unit) => Math.abs(value) >= unit.at);
|
|
74
|
+
if (!size) return String(Math.round(value));
|
|
75
|
+
const scaled = value / size.at;
|
|
76
|
+
return `${scaled.toFixed(scaled < 10 ? 1 : 0)}${size.suffix}`;
|
|
77
|
+
}
|
|
78
|
+
var full = (value) => Math.round(value).toLocaleString("en-GB");
|
|
79
|
+
function money(value) {
|
|
80
|
+
if (value === null) return "--";
|
|
81
|
+
if (value === 0) return "$0";
|
|
82
|
+
if (Math.abs(value) < .01) return "<$0.01";
|
|
83
|
+
return `$${value.toLocaleString("en-GB", {
|
|
84
|
+
minimumFractionDigits: 2,
|
|
85
|
+
maximumFractionDigits: 2
|
|
86
|
+
})}`;
|
|
87
|
+
}
|
|
88
|
+
function moneyCompact(value) {
|
|
89
|
+
if (value === null) return "--";
|
|
90
|
+
if (Math.abs(value) >= 1e3) return `$${compact(value)}`;
|
|
91
|
+
return money(value);
|
|
92
|
+
}
|
|
93
|
+
var RELATIVE = new Intl.RelativeTimeFormat("en-GB", { numeric: "auto" });
|
|
94
|
+
var STEPS = [
|
|
95
|
+
["second", 60],
|
|
96
|
+
["minute", 60],
|
|
97
|
+
["hour", 24],
|
|
98
|
+
["day", 7],
|
|
99
|
+
["week", 4.35],
|
|
100
|
+
["month", 12]
|
|
101
|
+
];
|
|
102
|
+
function relative(at, now = Date.now()) {
|
|
103
|
+
if (!at) return "never";
|
|
104
|
+
let gap = (at - now) / 1e3;
|
|
105
|
+
if (Math.abs(gap) < 45) return gap < 0 ? "just now" : "in a moment";
|
|
106
|
+
for (const [unit, span] of STEPS) {
|
|
107
|
+
if (Math.abs(gap) < span) return RELATIVE.format(Math.round(gap), unit);
|
|
108
|
+
gap /= span;
|
|
109
|
+
}
|
|
110
|
+
return RELATIVE.format(Math.round(gap), "year");
|
|
111
|
+
}
|
|
112
|
+
var dayLabel = (day) => (/* @__PURE__ */ new Date(`${day}T00:00:00`)).toLocaleDateString("en-GB", {
|
|
113
|
+
weekday: "short",
|
|
114
|
+
day: "numeric",
|
|
115
|
+
month: "short"
|
|
116
|
+
});
|
|
117
|
+
var clockLabel = (at) => new Date(at).toLocaleString("en-GB", {
|
|
118
|
+
day: "numeric",
|
|
119
|
+
month: "short",
|
|
120
|
+
hour: "2-digit",
|
|
121
|
+
minute: "2-digit"
|
|
122
|
+
});
|
|
123
|
+
/** Directories are shown by their last segment, with the full path in the tooltip. */
|
|
124
|
+
var projectName = (path) => path ? path.replace(/\/+$/, "").split("/").pop() || path : "unknown";
|
|
125
|
+
/** Drops the vendor prefix and the release stamp vendors tack on the end. */
|
|
126
|
+
var modelName = (model) => (model.includes("/") ? model.slice(model.indexOf("/") + 1) : model).replace(/-\d{8}$/, "");
|
|
127
|
+
function delta(now, before) {
|
|
128
|
+
if (!before) return null;
|
|
129
|
+
const change = (now - before) / before * 100;
|
|
130
|
+
if (!Number.isFinite(change) || Math.abs(change) < 1) return {
|
|
131
|
+
text: "flat",
|
|
132
|
+
tone: "even"
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
text: `${change > 0 ? "+" : ""}${change.toFixed(0)}%`,
|
|
136
|
+
tone: change > 0 ? "up" : "down"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/lib/components/Figures.svelte
|
|
141
|
+
function Figures($$renderer, $$props) {
|
|
142
|
+
$$renderer.component(($$renderer) => {
|
|
143
|
+
let { totals, previous, activeDays, rangeLabel } = $$props;
|
|
144
|
+
const change = derived(() => delta(totals.total, previous.total));
|
|
145
|
+
const costChange = derived(() => delta(totals.cost ?? 0, previous.cost ?? 0));
|
|
146
|
+
const perDay = derived(() => activeDays ? totals.total / activeDays : 0);
|
|
147
|
+
$$renderer.push(`<div class="figures svelte-l3stpt"><div class="lead svelte-l3stpt"><span class="cap svelte-l3stpt">Tokens, ${escape_html(rangeLabel.toLowerCase())}</span> <strong class="num svelte-l3stpt">${escape_html(compact(totals.total))}</strong> `);
|
|
148
|
+
if (change()) $$renderer.push(`<!--[0--><span class="delta svelte-l3stpt">${escape_html(change().text)}</span>`);
|
|
149
|
+
else $$renderer.push("<!--[-1-->");
|
|
150
|
+
$$renderer.push(`<!--]--></div> <dl class="svelte-l3stpt"><div class="svelte-l3stpt"><dt class="svelte-l3stpt">Est. cost</dt> <dd class="num svelte-l3stpt">${escape_html(money(totals.cost))} `);
|
|
151
|
+
if (costChange()) $$renderer.push(`<!--[0--><i${attr_class(`delta cost ${stringify(costChange().tone)}`, "svelte-l3stpt")}>${escape_html(costChange().text)}</i>`);
|
|
152
|
+
else $$renderer.push("<!--[-1-->");
|
|
153
|
+
$$renderer.push(`<!--]--></dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Sessions</dt> <dd class="num svelte-l3stpt">${escape_html(full(totals.sessions))}</dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Active days</dt> <dd class="num svelte-l3stpt">${escape_html(full(activeDays))}</dd></div> <div class="svelte-l3stpt"><dt class="svelte-l3stpt">Daily pace</dt> <dd class="num svelte-l3stpt">${escape_html(compact(perDay()))}</dd></div></dl></div>`);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/components/Calendar.svelte
|
|
158
|
+
function Calendar($$renderer, $$props) {
|
|
159
|
+
$$renderer.component(($$renderer) => {
|
|
160
|
+
let { days, metric, oldest } = $$props;
|
|
161
|
+
const GAP = 3;
|
|
162
|
+
const LABELS = 32;
|
|
163
|
+
const iso = (date) => date.toLocaleDateString("en-CA");
|
|
164
|
+
const value = (day) => !day ? 0 : metric === "cost" ? day.cost ?? 0 : day.total;
|
|
165
|
+
const byDay = derived(() => new Map(days.map((day) => [day.day, day])));
|
|
166
|
+
const start = derived(() => oldest ? iso(new Date(oldest)) : days[0]?.day ?? "");
|
|
167
|
+
/** Shows at least a quarter, so a fresh install still reads as a calendar. */
|
|
168
|
+
const grid = derived(() => {
|
|
169
|
+
const today = /* @__PURE__ */ new Date();
|
|
170
|
+
today.setHours(0, 0, 0, 0);
|
|
171
|
+
const first = new Date(days.length ? `${days[0].day}T00:00:00` : today);
|
|
172
|
+
const floor = new Date(today);
|
|
173
|
+
floor.setDate(floor.getDate() - 370);
|
|
174
|
+
const shown = new Date(today);
|
|
175
|
+
shown.setDate(shown.getDate() - 125);
|
|
176
|
+
const from = new Date(Math.max(floor.getTime(), Math.min(first.getTime(), shown.getTime())));
|
|
177
|
+
from.setDate(from.getDate() - (from.getDay() + 6) % 7);
|
|
178
|
+
const cells = [];
|
|
179
|
+
const cursor = new Date(from);
|
|
180
|
+
let week = 0;
|
|
181
|
+
while (cursor <= today) {
|
|
182
|
+
const weekday = (cursor.getDay() + 6) % 7;
|
|
183
|
+
cells.push({
|
|
184
|
+
day: iso(cursor),
|
|
185
|
+
date: new Date(cursor),
|
|
186
|
+
week,
|
|
187
|
+
weekday
|
|
188
|
+
});
|
|
189
|
+
if (weekday === 6) week += 1;
|
|
190
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
cells,
|
|
194
|
+
weeks: week + 1
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
const cell = derived(() => Math.max(9, Math.min(30, Math.floor(648 / grid().weeks) - GAP)));
|
|
198
|
+
const step = derived(() => cell() + GAP);
|
|
199
|
+
/** Quantile buckets, or one loud day flattens every other square to nothing. */
|
|
200
|
+
const cuts = derived(() => {
|
|
201
|
+
const values = days.map((day) => value(day)).filter((amount) => amount > 0).sort((a, b) => a - b);
|
|
202
|
+
if (!values.length) return [];
|
|
203
|
+
return [
|
|
204
|
+
.2,
|
|
205
|
+
.4,
|
|
206
|
+
.6,
|
|
207
|
+
.8
|
|
208
|
+
].map((at) => values[Math.floor(values.length * at)]);
|
|
209
|
+
});
|
|
210
|
+
const level = (amount) => amount <= 0 ? 0 : cuts().filter((cut) => amount >= cut).length + 1;
|
|
211
|
+
const months = derived(() => {
|
|
212
|
+
const seen = [];
|
|
213
|
+
for (const entry of grid().cells) {
|
|
214
|
+
if (entry.weekday !== 0) continue;
|
|
215
|
+
const label = entry.date.toLocaleDateString("en-GB", { month: "short" });
|
|
216
|
+
if (!seen.length || entry.date.getDate() <= 7 && seen[seen.length - 1].label !== label) seen.push({
|
|
217
|
+
label,
|
|
218
|
+
week: entry.week
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return seen;
|
|
222
|
+
});
|
|
223
|
+
const dayCaption = (day) => {
|
|
224
|
+
if (start() && day < start()) return `${dayLabel(day)} - before your records start`;
|
|
225
|
+
const amount = value(byDay().get(day));
|
|
226
|
+
const shown = metric === "cost" ? money(amount) : `${compact(amount)} tokens`;
|
|
227
|
+
return `${dayLabel(day)} - ${amount ? shown : "nothing"}`;
|
|
228
|
+
};
|
|
229
|
+
const WEEKDAYS = [
|
|
230
|
+
"Mon",
|
|
231
|
+
"",
|
|
232
|
+
"Wed",
|
|
233
|
+
"",
|
|
234
|
+
"Fri",
|
|
235
|
+
"",
|
|
236
|
+
"Sun"
|
|
237
|
+
];
|
|
238
|
+
const busy = derived(() => days.filter((day) => value(day) > 0).length);
|
|
239
|
+
const caption = derived(() => start() ? `Since ${dayLabel(start())}, ${busy()} ${busy() === 1 ? "day" : "days"} with usage` : "");
|
|
240
|
+
$$renderer.push(`<div class="calendar svelte-1iu5iby"><svg${attr("width", grid().weeks * step() + LABELS)}${attr("height", 7 * step() + 18)} role="img" aria-label="Daily usage calendar" class="svelte-1iu5iby"><!--[-->`);
|
|
241
|
+
const each_array = ensure_array_like(months());
|
|
242
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
243
|
+
let month = each_array[$$index];
|
|
244
|
+
$$renderer.push(`<text class="axis svelte-1iu5iby"${attr("x", LABELS + month.week * step())} y="10">${escape_html(month.label)}</text>`);
|
|
245
|
+
}
|
|
246
|
+
$$renderer.push(`<!--]--><!--[-->`);
|
|
247
|
+
const each_array_1 = ensure_array_like(WEEKDAYS);
|
|
248
|
+
for (let row = 0, $$length = each_array_1.length; row < $$length; row++) {
|
|
249
|
+
let name = each_array_1[row];
|
|
250
|
+
if (name) $$renderer.push(`<!--[0--><text class="axis svelte-1iu5iby" x="0"${attr("y", 18 + row * step() + cell() / 2 + 3)}>${escape_html(name)}</text>`);
|
|
251
|
+
else $$renderer.push("<!--[-1-->");
|
|
252
|
+
$$renderer.push(`<!--]-->`);
|
|
253
|
+
}
|
|
254
|
+
$$renderer.push(`<!--]--><!--[-->`);
|
|
255
|
+
const each_array_2 = ensure_array_like(grid().cells);
|
|
256
|
+
for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
|
|
257
|
+
let entry = each_array_2[$$index_2];
|
|
258
|
+
const blank = !!start() && entry.day < start();
|
|
259
|
+
$$renderer.push(`<rect${attr_class("cell svelte-1iu5iby", void 0, { "blank": blank })}${attr("x", LABELS + entry.week * step())}${attr("y", 18 + entry.weekday * step())}${attr("width", cell())}${attr("height", cell())}${attr("rx", Math.max(2, cell() / 4))}${attr("fill", `var(--ramp-${stringify(blank ? 0 : level(value(byDay().get(entry.day))))})`)} role="img"${attr("aria-label", dayCaption(entry.day))}></rect>`);
|
|
260
|
+
}
|
|
261
|
+
$$renderer.push(`<!--]--></svg> <div class="legend svelte-1iu5iby"><span class="span svelte-1iu5iby">${escape_html(caption())}</span> <span>No records</span> <i class="blank svelte-1iu5iby"></i> <span class="spread svelte-1iu5iby">Quieter</span> <!--[-->`);
|
|
262
|
+
const each_array_3 = ensure_array_like([
|
|
263
|
+
0,
|
|
264
|
+
1,
|
|
265
|
+
2,
|
|
266
|
+
3,
|
|
267
|
+
4,
|
|
268
|
+
5
|
|
269
|
+
]);
|
|
270
|
+
for (let $$index_3 = 0, $$length = each_array_3.length; $$index_3 < $$length; $$index_3++) {
|
|
271
|
+
let tone = each_array_3[$$index_3];
|
|
272
|
+
$$renderer.push(`<i${attr_style(`background: var(--ramp-${stringify(tone)})`)} class="svelte-1iu5iby"></i>`);
|
|
273
|
+
}
|
|
274
|
+
$$renderer.push(`<!--]--> <span>Busier</span></div></div>`);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/lib/components/Trend.svelte
|
|
279
|
+
function Trend($$renderer, $$props) {
|
|
280
|
+
$$renderer.component(($$renderer) => {
|
|
281
|
+
let { series, keys, metric } = $$props;
|
|
282
|
+
const amount = (point) => metric === "cost" ? point.cost ?? 0 : point.total;
|
|
283
|
+
const columns = derived(() => {
|
|
284
|
+
const days = /* @__PURE__ */ new Map();
|
|
285
|
+
for (const point of series) {
|
|
286
|
+
const entry = days.get(point.day) ?? {
|
|
287
|
+
day: point.day,
|
|
288
|
+
total: 0,
|
|
289
|
+
parts: /* @__PURE__ */ new Map()
|
|
290
|
+
};
|
|
291
|
+
entry.total += amount(point);
|
|
292
|
+
entry.parts.set(point.key, (entry.parts.get(point.key) ?? 0) + amount(point));
|
|
293
|
+
days.set(point.day, entry);
|
|
294
|
+
}
|
|
295
|
+
return [...days.values()].sort((a, b) => a.day.localeCompare(b.day));
|
|
296
|
+
});
|
|
297
|
+
const peak = derived(() => Math.max(1, ...columns().map((column) => column.total)));
|
|
298
|
+
const tickLabel = (value) => metric === "cost" ? moneyCompact(value) : compact(value);
|
|
299
|
+
const ticks = derived(() => [
|
|
300
|
+
1,
|
|
301
|
+
.5,
|
|
302
|
+
0
|
|
303
|
+
].map((at) => ({
|
|
304
|
+
at,
|
|
305
|
+
label: tickLabel(peak() * at)
|
|
306
|
+
})));
|
|
307
|
+
$$renderer.push(`<div class="trend svelte-118k1un"><div class="plot svelte-118k1un"><!--[-->`);
|
|
308
|
+
const each_array = ensure_array_like(ticks());
|
|
309
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
310
|
+
let tick = each_array[$$index];
|
|
311
|
+
$$renderer.push(`<div class="tick svelte-118k1un"${attr_style(`bottom: ${stringify(tick.at * 100)}%`)}><span class="svelte-118k1un">${escape_html(tick.label)}</span></div>`);
|
|
312
|
+
}
|
|
313
|
+
$$renderer.push(`<!--]--> <div class="bars svelte-118k1un"><!--[-->`);
|
|
314
|
+
const each_array_1 = ensure_array_like(columns());
|
|
315
|
+
for (let $$index_2 = 0, $$length = each_array_1.length; $$index_2 < $$length; $$index_2++) {
|
|
316
|
+
let column = each_array_1[$$index_2];
|
|
317
|
+
$$renderer.push(`<div class="column svelte-118k1un"><div class="stack svelte-118k1un"${attr_style(`height: ${stringify(column.total / peak() * 100)}%`)}><!--[-->`);
|
|
318
|
+
const each_array_2 = ensure_array_like(keys);
|
|
319
|
+
for (let $$index_1 = 0, $$length = each_array_2.length; $$index_1 < $$length; $$index_1++) {
|
|
320
|
+
let key = each_array_2[$$index_1];
|
|
321
|
+
const part = column.parts.get(key.id) ?? 0;
|
|
322
|
+
if (part > 0) $$renderer.push(`<!--[0--><span${attr_style(`flex: ${stringify(part)}; background: ${stringify(key.colour)}`)} aria-hidden="true" class="svelte-118k1un"></span>`);
|
|
323
|
+
else $$renderer.push("<!--[-1-->");
|
|
324
|
+
$$renderer.push(`<!--]-->`);
|
|
325
|
+
}
|
|
326
|
+
$$renderer.push(`<!--]--></div></div>`);
|
|
327
|
+
}
|
|
328
|
+
$$renderer.push(`<!--]--></div></div> <div class="axis svelte-118k1un"><span>${escape_html(columns().length ? dayLabel(columns()[0].day) : "")}</span> <span>${escape_html(columns().length ? dayLabel(columns()[columns().length - 1].day) : "")}</span></div></div>`);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/lib/logos/marks.ts
|
|
333
|
+
/** Gemini's spark stands in for Google as a whole, since that's all we ever count. */
|
|
334
|
+
var GEMINI = "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";
|
|
335
|
+
/** 24x24 marks, drawn in currentColor. From Simple Icons (CC0) and LobeHub icons (MIT). */
|
|
336
|
+
var MARKS = {
|
|
337
|
+
anthropic: "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",
|
|
338
|
+
openai: "M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z",
|
|
339
|
+
google: GEMINI,
|
|
340
|
+
xai: "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815",
|
|
341
|
+
meta: "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",
|
|
342
|
+
mistral: "M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z",
|
|
343
|
+
deepseek: "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",
|
|
344
|
+
qwen: "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",
|
|
345
|
+
"claude-code": "M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z",
|
|
346
|
+
codex: "M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z",
|
|
347
|
+
opencode: "M22 24H2V0h20zM17 4.8H7v14.4h10z",
|
|
348
|
+
"gemini-cli": GEMINI
|
|
349
|
+
};
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/lib/logos/Mark.svelte
|
|
352
|
+
function Mark($$renderer, $$props) {
|
|
353
|
+
$$renderer.component(($$renderer) => {
|
|
354
|
+
let { id, label, colour, size = 16 } = $$props;
|
|
355
|
+
const path = derived(() => MARKS[id]);
|
|
356
|
+
const initials = derived(() => label.replace(/[^a-z ]/gi, "").split(" ").map((word) => word[0]).join("").slice(0, 2).toUpperCase());
|
|
357
|
+
$$renderer.push(`<svg class="mark svelte-jlywqn" viewBox="0 0 24 24"${attr("width", size)}${attr("height", size)} role="img"${attr("aria-label", label)}${attr_style("", { color: colour })}>`);
|
|
358
|
+
if (path()) $$renderer.push(`<!--[0--><path${attr("d", path())} fill="currentColor"></path>`);
|
|
359
|
+
else $$renderer.push(`<!--[-1--><rect x="1.5" y="1.5" width="21" height="21" rx="6" fill="none" stroke="currentColor" stroke-width="1.8"></rect><text x="12" y="12.5" text-anchor="middle" dominant-baseline="central" fill="currentColor"${attr("font-size", initials().length > 1 ? 9 : 12)} font-weight="600" font-family="var(--sans)">${escape_html(initials())}</text>`);
|
|
360
|
+
$$renderer.push(`<!--]--></svg>`);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/lib/components/BarList.svelte
|
|
365
|
+
function BarList($$renderer, $$props) {
|
|
366
|
+
$$renderer.component(($$renderer) => {
|
|
367
|
+
let { rows, active = [], onpick, empty = "Nothing in this range" } = $$props;
|
|
368
|
+
const peak = derived(() => Math.max(1, ...rows.map((row) => row.value)));
|
|
369
|
+
if (rows.length) {
|
|
370
|
+
$$renderer.push(`<!--[0--><ul class="bars svelte-eh60s9"><!--[-->`);
|
|
371
|
+
const each_array = ensure_array_like(rows);
|
|
372
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
373
|
+
let row = each_array[$$index];
|
|
374
|
+
$$renderer.push(`<li>`);
|
|
375
|
+
element($$renderer, onpick ? "button" : "div", () => {
|
|
376
|
+
$$renderer.push(`${attr_class("row svelte-eh60s9", void 0, { "on": active.includes(row.id) })}${attr("role", onpick ? "button" : void 0)}${attr("tabindex", onpick ? 0 : void 0)}`);
|
|
377
|
+
}, () => {
|
|
378
|
+
$$renderer.push(`<span class="fill svelte-eh60s9"${attr_style(`width: ${stringify(row.value / peak() * 100)}%; background: ${stringify(row.colour)}`)}></span> <span class="label svelte-eh60s9">`);
|
|
379
|
+
if (row.mark) {
|
|
380
|
+
$$renderer.push("<!--[0-->");
|
|
381
|
+
Mark($$renderer, {
|
|
382
|
+
id: row.mark,
|
|
383
|
+
label: row.label,
|
|
384
|
+
colour: row.colour,
|
|
385
|
+
size: 15
|
|
386
|
+
});
|
|
387
|
+
} else $$renderer.push(`<!--[-1--><i class="dot svelte-eh60s9"${attr_style(`background: ${stringify(row.colour)}`)}></i>`);
|
|
388
|
+
$$renderer.push(`<!--]--> <b class="svelte-eh60s9">${escape_html(row.label)}</b> `);
|
|
389
|
+
if (row.detail) $$renderer.push(`<!--[0--><em class="svelte-eh60s9">${escape_html(row.detail)}</em>`);
|
|
390
|
+
else $$renderer.push("<!--[-1-->");
|
|
391
|
+
$$renderer.push(`<!--]--></span> <span class="value num svelte-eh60s9">${escape_html(row.display)}</span>`);
|
|
392
|
+
});
|
|
393
|
+
$$renderer.push(`</li>`);
|
|
394
|
+
}
|
|
395
|
+
$$renderer.push(`<!--]--></ul>`);
|
|
396
|
+
} else $$renderer.push(`<!--[-1--><p class="empty svelte-eh60s9">${escape_html(empty)}</p>`);
|
|
397
|
+
$$renderer.push(`<!--]-->`);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/lib/components/Donut.svelte
|
|
402
|
+
function Donut($$renderer, $$props) {
|
|
403
|
+
$$renderer.component(($$renderer) => {
|
|
404
|
+
let { slices, centre, caption } = $$props;
|
|
405
|
+
const RADIUS = 52;
|
|
406
|
+
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
407
|
+
const arcs = derived(() => {
|
|
408
|
+
const total = slices.reduce((sum, slice) => sum + slice.value, 0) || 1;
|
|
409
|
+
let offset = 0;
|
|
410
|
+
return slices.map((slice) => {
|
|
411
|
+
const length = slice.value / total * CIRCUMFERENCE;
|
|
412
|
+
const arc = {
|
|
413
|
+
...slice,
|
|
414
|
+
length,
|
|
415
|
+
offset,
|
|
416
|
+
share: slice.value / total * 100
|
|
417
|
+
};
|
|
418
|
+
offset += length;
|
|
419
|
+
return arc;
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
$$renderer.push(`<div class="donut svelte-1o711x0"><svg viewBox="0 0 140 140" role="img"${attr("aria-label", caption)} class="svelte-1o711x0"><circle cx="70" cy="70"${attr("r", RADIUS)} fill="none" stroke="var(--surface-3)" stroke-width="18" class="svelte-1o711x0"></circle><!--[-->`);
|
|
423
|
+
const each_array = ensure_array_like(arcs());
|
|
424
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
425
|
+
let arc = each_array[$$index];
|
|
426
|
+
$$renderer.push(`<circle cx="70" cy="70"${attr("r", RADIUS)} fill="none"${attr("stroke", arc.colour)} stroke-width="18"${attr("stroke-dasharray", `${stringify(Math.max(0, arc.length - 2))} 326.7256359733385`)}${attr("stroke-dashoffset", -arc.offset)} transform="rotate(-90 70 70)" class="svelte-1o711x0"></circle>`);
|
|
427
|
+
}
|
|
428
|
+
$$renderer.push(`<!--]--><text class="centre num svelte-1o711x0" x="70" y="66" text-anchor="middle">${escape_html(centre)}</text><text class="sub svelte-1o711x0" x="70" y="84" text-anchor="middle">${escape_html(caption)}</text></svg></div>`);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/lib/components/Split.svelte
|
|
433
|
+
function Split($$renderer, $$props) {
|
|
434
|
+
$$renderer.component(($$renderer) => {
|
|
435
|
+
let { parts } = $$props;
|
|
436
|
+
const total = derived(() => parts.reduce((sum, part) => sum + part.value, 0) || 1);
|
|
437
|
+
$$renderer.push(`<div class="split svelte-1qwkpf0"><div class="bar svelte-1qwkpf0"><!--[-->`);
|
|
438
|
+
const each_array = ensure_array_like(parts);
|
|
439
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
440
|
+
let part = each_array[$$index];
|
|
441
|
+
if (part.value > 0) $$renderer.push(`<!--[0--><span${attr_style(`flex: ${stringify(part.value)}; background: ${stringify(part.colour)}`)} class="svelte-1qwkpf0"></span>`);
|
|
442
|
+
else $$renderer.push("<!--[-1-->");
|
|
443
|
+
$$renderer.push(`<!--]-->`);
|
|
444
|
+
}
|
|
445
|
+
$$renderer.push(`<!--]--></div> <ul class="svelte-1qwkpf0"><!--[-->`);
|
|
446
|
+
const each_array_1 = ensure_array_like(parts);
|
|
447
|
+
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
|
|
448
|
+
let part = each_array_1[$$index_1];
|
|
449
|
+
$$renderer.push(`<li class="svelte-1qwkpf0"><i${attr_style(`background: ${stringify(part.colour)}`)} class="svelte-1qwkpf0"></i> <span class="name svelte-1qwkpf0">${escape_html(part.label)}</span> <span class="value num svelte-1qwkpf0">${escape_html(part.display)}</span> <span class="share num svelte-1qwkpf0">${escape_html((part.value / total() * 100).toFixed(1))}%</span></li>`);
|
|
450
|
+
}
|
|
451
|
+
$$renderer.push(`<!--]--></ul></div>`);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/lib/components/Clock.svelte
|
|
456
|
+
function Clock($$renderer, $$props) {
|
|
457
|
+
$$renderer.component(($$renderer) => {
|
|
458
|
+
let { points } = $$props;
|
|
459
|
+
const DAYS = [
|
|
460
|
+
"Mon",
|
|
461
|
+
"Tue",
|
|
462
|
+
"Wed",
|
|
463
|
+
"Thu",
|
|
464
|
+
"Fri",
|
|
465
|
+
"Sat",
|
|
466
|
+
"Sun"
|
|
467
|
+
];
|
|
468
|
+
const grid = derived(() => {
|
|
469
|
+
const cells = /* @__PURE__ */ new Map();
|
|
470
|
+
for (const point of points) {
|
|
471
|
+
const row = (point.day + 6) % 7;
|
|
472
|
+
cells.set(`${row}-${point.hour}`, (cells.get(`${row}-${point.hour}`) ?? 0) + point.total);
|
|
473
|
+
}
|
|
474
|
+
return cells;
|
|
475
|
+
});
|
|
476
|
+
const peak = derived(() => Math.max(1, ...grid().values()));
|
|
477
|
+
/** Punch card: radius goes as the square root, off a floor so a quiet hour still shows. */
|
|
478
|
+
const dot = (value) => value ? .32 + .68 * Math.sqrt(value / peak()) : 0;
|
|
479
|
+
$$renderer.push(`<div class="clock svelte-13d36y"><div class="rows svelte-13d36y"><!--[-->`);
|
|
480
|
+
const each_array = ensure_array_like(DAYS);
|
|
481
|
+
for (let row = 0, $$length = each_array.length; row < $$length; row++) {
|
|
482
|
+
let day = each_array[row];
|
|
483
|
+
$$renderer.push(`<span class="day svelte-13d36y">${escape_html(day)}</span> <div class="hours svelte-13d36y"><!--[-->`);
|
|
484
|
+
const each_array_1 = ensure_array_like(Array(24));
|
|
485
|
+
for (let hour = 0, $$length = each_array_1.length; hour < $$length; hour++) {
|
|
486
|
+
each_array_1[hour];
|
|
487
|
+
const value = grid().get(`${row}-${hour}`) ?? 0;
|
|
488
|
+
$$renderer.push(`<i${attr_style(`--dot: ${stringify(dot(value))}`)} class="svelte-13d36y"></i>`);
|
|
489
|
+
}
|
|
490
|
+
$$renderer.push(`<!--]--></div>`);
|
|
491
|
+
}
|
|
492
|
+
$$renderer.push(`<!--]--></div> <div class="scale svelte-13d36y"><!--[-->`);
|
|
493
|
+
const each_array_2 = ensure_array_like([
|
|
494
|
+
0,
|
|
495
|
+
6,
|
|
496
|
+
12,
|
|
497
|
+
18
|
|
498
|
+
]);
|
|
499
|
+
for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
|
|
500
|
+
let hour = each_array_2[$$index_2];
|
|
501
|
+
$$renderer.push(`<span>${escape_html(String(hour).padStart(2, "0"))}</span>`);
|
|
502
|
+
}
|
|
503
|
+
$$renderer.push(`<!--]--> <span>23</span></div></div>`);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/lib/components/Meter.svelte
|
|
508
|
+
function Meter($$renderer, $$props) {
|
|
509
|
+
$$renderer.component(($$renderer) => {
|
|
510
|
+
let { window: meter } = $$props;
|
|
511
|
+
const stale = derived(() => !!meter.resets_at && meter.resets_at < Date.now());
|
|
512
|
+
const tone = derived(() => stale() ? "text-faint" : meter.used_percent >= 90 ? "bad" : meter.used_percent >= 70 ? "warn" : "good");
|
|
513
|
+
$$renderer.push(`<div${attr_class("meter svelte-tlhxud", void 0, { "stale": stale() })}><div class="top svelte-tlhxud"><span class="name svelte-tlhxud">${escape_html(meter.label)}</span> <span class="num pct svelte-tlhxud"${attr_style(`color: var(--${tone()})`)}>${escape_html(Math.round(meter.used_percent))}%</span></div> <div class="track svelte-tlhxud" role="meter"${attr("aria-valuenow", Math.round(meter.used_percent))} aria-valuemin="0" aria-valuemax="100"${attr("aria-label", meter.label)}><span${attr_style(`width: ${stringify(Math.min(100, meter.used_percent))}%; background: var(--${tone()})`)} class="svelte-tlhxud"></span></div> <span class="reset svelte-tlhxud">`);
|
|
514
|
+
if (stale()) $$renderer.push(`<!--[0-->window has turned over, last read ${escape_html(relative(meter.seen_at))}`);
|
|
515
|
+
else if (meter.resets_at) $$renderer.push(`<!--[1-->resets ${escape_html(relative(meter.resets_at))}`);
|
|
516
|
+
else $$renderer.push("<!--[-1-->");
|
|
517
|
+
$$renderer.push(`<!--]--></span></div>`);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/lib/providers.ts
|
|
522
|
+
var PROVIDERS = [
|
|
523
|
+
{
|
|
524
|
+
id: "anthropic",
|
|
525
|
+
label: "Anthropic",
|
|
526
|
+
colour: "#d97757"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: "openai",
|
|
530
|
+
label: "OpenAI",
|
|
531
|
+
colour: "#10a37f"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
id: "google",
|
|
535
|
+
label: "Google",
|
|
536
|
+
colour: "#4285f4"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
id: "meta",
|
|
540
|
+
label: "Meta",
|
|
541
|
+
colour: "#0668e1"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
id: "mistral",
|
|
545
|
+
label: "Mistral",
|
|
546
|
+
colour: "#fa520f"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
id: "xai",
|
|
550
|
+
label: "xAI",
|
|
551
|
+
colour: "#7c7c8a"
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: "deepseek",
|
|
555
|
+
label: "DeepSeek",
|
|
556
|
+
colour: "#4d6bfe"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: "qwen",
|
|
560
|
+
label: "Qwen",
|
|
561
|
+
colour: "#a855f7"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
id: "unknown",
|
|
565
|
+
label: "Other",
|
|
566
|
+
colour: "#8b93a7"
|
|
567
|
+
}
|
|
568
|
+
];
|
|
569
|
+
var MATCHES = [
|
|
570
|
+
[/^claude/, "anthropic"],
|
|
571
|
+
[/^(gpt|codex|o[1-4]-|chatgpt|text-embedding)/, "openai"],
|
|
572
|
+
[/^(gemini|gemma|palm)/, "google"],
|
|
573
|
+
[/^(llama|code-?llama)/, "meta"],
|
|
574
|
+
[/^(mistral|mixtral|codestral|magistral|devstral)/, "mistral"],
|
|
575
|
+
[/^grok/, "xai"],
|
|
576
|
+
[/^deepseek/, "deepseek"],
|
|
577
|
+
[/^(qwen|qwq)/, "qwen"]
|
|
578
|
+
];
|
|
579
|
+
/** Vendor-prefixed ids like `anthropic/claude-sonnet-5` come back from OpenRouter. */
|
|
580
|
+
function providerFor(model) {
|
|
581
|
+
const name = model.includes("/") ? model.slice(model.indexOf("/") + 1) : model;
|
|
582
|
+
const vendor = model.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : "";
|
|
583
|
+
if (PROVIDERS.some((provider) => provider.id === vendor)) return vendor;
|
|
584
|
+
return MATCHES.find(([pattern]) => pattern.test(name.toLowerCase()))?.[1] ?? "unknown";
|
|
585
|
+
}
|
|
586
|
+
var providerMeta = (id) => PROVIDERS.find((provider) => provider.id === id) ?? PROVIDERS[PROVIDERS.length - 1];
|
|
587
|
+
var TOOLS = [
|
|
588
|
+
{
|
|
589
|
+
id: "claude-code",
|
|
590
|
+
label: "Claude Code",
|
|
591
|
+
colour: "#d97757",
|
|
592
|
+
home: "https://claude.com/claude-code"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: "codex",
|
|
596
|
+
label: "Codex",
|
|
597
|
+
colour: "#10a37f",
|
|
598
|
+
home: "https://developers.openai.com/codex"
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: "opencode",
|
|
602
|
+
label: "OpenCode",
|
|
603
|
+
colour: "#f5a524",
|
|
604
|
+
home: "https://opencode.ai"
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "gemini-cli",
|
|
608
|
+
label: "Gemini CLI",
|
|
609
|
+
colour: "#4285f4",
|
|
610
|
+
home: "https://github.com/google-gemini/gemini-cli"
|
|
611
|
+
}
|
|
612
|
+
];
|
|
613
|
+
var toolMeta = (id) => TOOLS.find((tool) => tool.id === id) ?? {
|
|
614
|
+
id,
|
|
615
|
+
label: id,
|
|
616
|
+
colour: "#8b93a7",
|
|
617
|
+
home: ""
|
|
618
|
+
};
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/lib/components/Sessions.svelte
|
|
621
|
+
function Sessions($$renderer, $$props) {
|
|
622
|
+
$$renderer.component(($$renderer) => {
|
|
623
|
+
let { rows, metric} = $$props;
|
|
624
|
+
if (rows.length) {
|
|
625
|
+
$$renderer.push(`<!--[0--><div class="scroll svelte-1uqtl83"><table class="svelte-1uqtl83"><thead><tr><th scope="col" class="svelte-1uqtl83">Session</th><th scope="col" class="svelte-1uqtl83">Project</th><th scope="col" class="svelte-1uqtl83">Models</th><th scope="col" class="svelte-1uqtl83">Ran</th><th scope="col" class="right svelte-1uqtl83">${escape_html(metric === "cost" ? "Est. cost" : "Tokens")}</th></tr></thead><tbody class="svelte-1uqtl83"><!--[-->`);
|
|
626
|
+
const each_array = ensure_array_like(rows);
|
|
627
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
628
|
+
let row = each_array[$$index];
|
|
629
|
+
const tool = toolMeta(row.tool);
|
|
630
|
+
$$renderer.push(`<tr class="svelte-1uqtl83"><td class="svelte-1uqtl83"><span class="tool svelte-1uqtl83">`);
|
|
631
|
+
Mark($$renderer, {
|
|
632
|
+
id: row.tool,
|
|
633
|
+
label: tool.label,
|
|
634
|
+
colour: tool.colour,
|
|
635
|
+
size: 14
|
|
636
|
+
});
|
|
637
|
+
$$renderer.push(`<!----> <span class="id mono svelte-1uqtl83">${escape_html(row.id.slice(0, 8))}</span></span></td><td class="svelte-1uqtl83"><button type="button" class="link svelte-1uqtl83">${escape_html(projectName(row.project))}</button></td><td class="models svelte-1uqtl83">${escape_html(row.models.split(", ").map(modelName).join(", "))}</td><td class="svelte-1uqtl83">${escape_html(clockLabel(row.ended))}</td><td class="right num svelte-1uqtl83">${escape_html(metric === "cost" ? money(row.cost) : compact(row.total))}</td></tr>`);
|
|
638
|
+
}
|
|
639
|
+
$$renderer.push(`<!--]--></tbody></table></div>`);
|
|
640
|
+
} else $$renderer.push(`<!--[-1--><p class="empty svelte-1uqtl83">No sessions in this range</p>`);
|
|
641
|
+
$$renderer.push(`<!--]-->`);
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/lib/components/Problems.svelte
|
|
646
|
+
function Problems($$renderer, $$props) {
|
|
647
|
+
$$renderer.component(($$renderer) => {
|
|
648
|
+
let { problems } = $$props;
|
|
649
|
+
let dismissed = [];
|
|
650
|
+
let open = false;
|
|
651
|
+
const shown = derived(() => problems.filter((problem) => !dismissed.includes(problem.id)));
|
|
652
|
+
const worst = derived(() => shown().some((problem) => problem.level === "error") ? "error" : shown().some((problem) => problem.level === "warn") ? "warn" : "info");
|
|
653
|
+
if (shown().length) {
|
|
654
|
+
$$renderer.push(`<!--[0--><aside${attr_class(`problems ${worst()}`, "svelte-1vaf4tg")} aria-live="polite"><button type="button" class="head svelte-1vaf4tg"${attr("aria-expanded", open)}><svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="8" cy="8" r="6.6" fill="none" stroke="currentColor" stroke-width="1.5"></circle><path d="M8 4.6v4.2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path><circle cx="8" cy="11.4" r="0.9" fill="currentColor"></circle></svg> <span class="svelte-1vaf4tg">${escape_html(shown().length === 1 ? shown()[0].message : `${shown().length} things need a look`)}</span> <em class="svelte-1vaf4tg">${escape_html("Details")}</em></button> `);
|
|
655
|
+
$$renderer.push("<!--[-1-->");
|
|
656
|
+
$$renderer.push(`<!--]--></aside>`);
|
|
657
|
+
} else $$renderer.push("<!--[-1-->");
|
|
658
|
+
$$renderer.push(`<!--]-->`);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/lib/components/Settings.svelte
|
|
663
|
+
function Settings($$renderer, $$props) {
|
|
664
|
+
$$renderer.component(($$renderer) => {
|
|
665
|
+
let { open = void 0, saved, sources, dbPath, scannedAt, onsave, onrescan } = $$props;
|
|
666
|
+
let priceModel = "";
|
|
667
|
+
let priceIn = "";
|
|
668
|
+
let priceOut = "";
|
|
669
|
+
const overrides = derived(() => Object.entries(saved.prices));
|
|
670
|
+
const ENV = {
|
|
671
|
+
"claude-code": "AIUSAGE_CLAUDE_DIR",
|
|
672
|
+
codex: "AIUSAGE_CODEX_DIR",
|
|
673
|
+
opencode: "AIUSAGE_OPENCODE_DIR"
|
|
674
|
+
};
|
|
675
|
+
$$renderer.push(`<dialog class="svelte-anx9w7"><header class="svelte-anx9w7"><h2 class="svelte-anx9w7">Settings</h2> <button type="button" class="close svelte-anx9w7" aria-label="Close settings"><svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></button></header> <div class="body svelte-anx9w7"><section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Appearance</h3> <label class="field svelte-anx9w7"><span>Theme</span> `);
|
|
676
|
+
$$renderer.select({
|
|
677
|
+
value: saved.theme,
|
|
678
|
+
onchange: (event) => onsave({ theme: event.currentTarget.value }),
|
|
679
|
+
class: ""
|
|
680
|
+
}, ($$renderer) => {
|
|
681
|
+
$$renderer.option({ value: "auto" }, ($$renderer) => {
|
|
682
|
+
$$renderer.push(`Match the system`);
|
|
683
|
+
});
|
|
684
|
+
$$renderer.option({ value: "light" }, ($$renderer) => {
|
|
685
|
+
$$renderer.push(`Light`);
|
|
686
|
+
});
|
|
687
|
+
$$renderer.option({ value: "dark" }, ($$renderer) => {
|
|
688
|
+
$$renderer.push(`Dark`);
|
|
689
|
+
});
|
|
690
|
+
}, "svelte-anx9w7");
|
|
691
|
+
$$renderer.push(`</label> <label class="field svelte-anx9w7"><span>Range on open</span> `);
|
|
692
|
+
$$renderer.select({
|
|
693
|
+
value: saved.range,
|
|
694
|
+
onchange: (event) => onsave({ range: event.currentTarget.value }),
|
|
695
|
+
class: ""
|
|
696
|
+
}, ($$renderer) => {
|
|
697
|
+
$$renderer.push(`<!--[-->`);
|
|
698
|
+
const each_array = ensure_array_like(RANGES);
|
|
699
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
700
|
+
let range = each_array[$$index];
|
|
701
|
+
$$renderer.option({ value: range.id }, ($$renderer) => {
|
|
702
|
+
$$renderer.push(`${escape_html(range.label)}`);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
$$renderer.push(`<!--]-->`);
|
|
706
|
+
}, "svelte-anx9w7");
|
|
707
|
+
$$renderer.push(`</label> <label class="field svelte-anx9w7"><span>Headline measure</span> `);
|
|
708
|
+
$$renderer.select({
|
|
709
|
+
value: saved.metric,
|
|
710
|
+
onchange: (event) => onsave({ metric: event.currentTarget.value }),
|
|
711
|
+
class: ""
|
|
712
|
+
}, ($$renderer) => {
|
|
713
|
+
$$renderer.option({ value: "tokens" }, ($$renderer) => {
|
|
714
|
+
$$renderer.push(`Tokens`);
|
|
715
|
+
});
|
|
716
|
+
$$renderer.option({ value: "cost" }, ($$renderer) => {
|
|
717
|
+
$$renderer.push(`Estimated cost`);
|
|
718
|
+
});
|
|
719
|
+
}, "svelte-anx9w7");
|
|
720
|
+
$$renderer.push(`</label></section> <section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Data</h3> <ul class="sources svelte-anx9w7"><!--[-->`);
|
|
721
|
+
const each_array_1 = ensure_array_like(sources);
|
|
722
|
+
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
|
|
723
|
+
let source = each_array_1[$$index_1];
|
|
724
|
+
$$renderer.push(`<li class="svelte-anx9w7"><div><b class="svelte-anx9w7">${escape_html(source.label)}</b> <code class="svelte-anx9w7">${escape_html(source.root)}</code></div> <span${attr_class("badge svelte-anx9w7", void 0, { "on": source.present })}>${escape_html(source.present ? `${source.files} files` : "not found")}</span></li>`);
|
|
725
|
+
}
|
|
726
|
+
$$renderer.push(`<!--]--></ul> <p class="note svelte-anx9w7">Point these somewhere else with <!--[-->`);
|
|
727
|
+
const each_array_2 = ensure_array_like(sources);
|
|
728
|
+
for (let index = 0, $$length = each_array_2.length; index < $$length; index++) {
|
|
729
|
+
let source = each_array_2[index];
|
|
730
|
+
$$renderer.push(`<code class="svelte-anx9w7">${escape_html(ENV[source.id] ?? source.id)}</code>${escape_html(index < sources.length - 1 ? ", " : "")}`);
|
|
731
|
+
}
|
|
732
|
+
$$renderer.push(`<!--]-->. History lives in <code class="svelte-anx9w7">${escape_html(dbPath)}</code> and outlives the transcripts, which most tools prune.</p> <label class="field svelte-anx9w7"><span>Rescan every</span> `);
|
|
733
|
+
$$renderer.select({
|
|
734
|
+
value: String(saved.scanIntervalSec),
|
|
735
|
+
onchange: (event) => onsave({ scanIntervalSec: Number(event.currentTarget.value) }),
|
|
736
|
+
class: ""
|
|
737
|
+
}, ($$renderer) => {
|
|
738
|
+
$$renderer.option({ value: "30" }, ($$renderer) => {
|
|
739
|
+
$$renderer.push(`30 seconds`);
|
|
740
|
+
});
|
|
741
|
+
$$renderer.option({ value: "120" }, ($$renderer) => {
|
|
742
|
+
$$renderer.push(`2 minutes`);
|
|
743
|
+
});
|
|
744
|
+
$$renderer.option({ value: "600" }, ($$renderer) => {
|
|
745
|
+
$$renderer.push(`10 minutes`);
|
|
746
|
+
});
|
|
747
|
+
$$renderer.option({ value: "3600" }, ($$renderer) => {
|
|
748
|
+
$$renderer.push(`an hour`);
|
|
749
|
+
});
|
|
750
|
+
}, "svelte-anx9w7");
|
|
751
|
+
$$renderer.push(`</label> <label class="check svelte-anx9w7"><input type="checkbox"${attr("checked", saved.claudeWindows, true)} class="svelte-anx9w7"/> <span>Ask Anthropic for your 5-hour and 7-day windows <em class="svelte-anx9w7">Reads the Claude Code credentials on this machine. Nothing else leaves it.</em></span></label> <div class="row svelte-anx9w7"><button type="button" class="action svelte-anx9w7">Rescan now</button> <span class="note svelte-anx9w7">Last scan ${escape_html(relative(scannedAt))}</span></div></section> <section class="svelte-anx9w7"><h3 class="svelte-anx9w7">Prices</h3> <p class="note svelte-anx9w7">Dollars per million tokens. ${escape_html(Object.keys(RATES).length)} models are priced out of the box; anything
|
|
752
|
+
you add here wins, and an unknown model is reported rather than counted as free.</p> `);
|
|
753
|
+
if (overrides().length) {
|
|
754
|
+
$$renderer.push(`<!--[0--><ul class="prices svelte-anx9w7"><!--[-->`);
|
|
755
|
+
const each_array_3 = ensure_array_like(overrides());
|
|
756
|
+
for (let $$index_3 = 0, $$length = each_array_3.length; $$index_3 < $$length; $$index_3++) {
|
|
757
|
+
let [model, rate] = each_array_3[$$index_3];
|
|
758
|
+
$$renderer.push(`<li class="svelte-anx9w7"><code class="svelte-anx9w7">${escape_html(model)}</code> <span class="num">in ${escape_html(rate.input)} / out ${escape_html(rate.output)}</span> <button type="button" class="svelte-anx9w7">Remove</button></li>`);
|
|
759
|
+
}
|
|
760
|
+
$$renderer.push(`<!--]--></ul>`);
|
|
761
|
+
} else $$renderer.push("<!--[-1-->");
|
|
762
|
+
$$renderer.push(`<!--]--> <div class="add svelte-anx9w7"><input placeholder="model id"${attr("value", priceModel)} class="svelte-anx9w7"/> <input placeholder="in" inputmode="decimal"${attr("value", priceIn)} class="svelte-anx9w7"/> <input placeholder="out" inputmode="decimal"${attr("value", priceOut)} class="svelte-anx9w7"/> <button type="button" class="action svelte-anx9w7">Add</button></div></section></div></dialog>`);
|
|
763
|
+
bind_props($$props, { open });
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/lib/logos/Logo.svelte
|
|
768
|
+
function Logo($$renderer, $$props) {
|
|
769
|
+
$$renderer.component(($$renderer) => {
|
|
770
|
+
let { size = 28 } = $$props;
|
|
771
|
+
const cells = [
|
|
772
|
+
0,
|
|
773
|
+
1,
|
|
774
|
+
2,
|
|
775
|
+
3
|
|
776
|
+
].flatMap((row) => [
|
|
777
|
+
0,
|
|
778
|
+
1,
|
|
779
|
+
2,
|
|
780
|
+
3
|
|
781
|
+
].map((column) => ({
|
|
782
|
+
row,
|
|
783
|
+
column,
|
|
784
|
+
heat: (column + (3 - row)) / 6
|
|
785
|
+
})));
|
|
786
|
+
$$renderer.push(`<svg class="logo svelte-nck4wt" viewBox="0 0 32 32"${attr("width", size)}${attr("height", size)} role="img" aria-label="AI usage dashboard"><rect width="32" height="32" rx="8" fill="var(--accent)"></rect><!--[-->`);
|
|
787
|
+
const each_array = ensure_array_like(cells);
|
|
788
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
789
|
+
let cell = each_array[$$index];
|
|
790
|
+
$$renderer.push(`<rect${attr("x", 5 + cell.column * 6)}${attr("y", 5 + cell.row * 6)} width="4" height="4" rx="1.2" fill="var(--accent-text)"${attr("opacity", (.22 + cell.heat * .78).toFixed(2))}></rect>`);
|
|
791
|
+
}
|
|
792
|
+
$$renderer.push(`<!--]--></svg>`);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/routes/+page.svelte
|
|
797
|
+
function _page($$renderer, $$props) {
|
|
798
|
+
$$renderer.component(($$renderer) => {
|
|
799
|
+
let { data } = $$props;
|
|
800
|
+
const first = run(() => data);
|
|
801
|
+
let usage = first.usage;
|
|
802
|
+
let saved = first.settings;
|
|
803
|
+
let busy = false;
|
|
804
|
+
let settingsOpen = false;
|
|
805
|
+
let range = first.usage.filters.range;
|
|
806
|
+
let tools = first.usage.filters.tools;
|
|
807
|
+
let models = first.usage.filters.models;
|
|
808
|
+
let projects = first.usage.filters.projects;
|
|
809
|
+
let metric = first.settings.metric;
|
|
810
|
+
let trendBy = "tool";
|
|
811
|
+
let modelView = "share";
|
|
812
|
+
let projectBy = "measure";
|
|
813
|
+
const query = derived(() => new URLSearchParams({
|
|
814
|
+
range,
|
|
815
|
+
tools: tools.join(","),
|
|
816
|
+
models: models.join(","),
|
|
817
|
+
projects: projects.join(",")
|
|
818
|
+
}).toString());
|
|
819
|
+
let showing = run(() => query());
|
|
820
|
+
async function save(patch) {
|
|
821
|
+
const response = await fetch("/api/settings", {
|
|
822
|
+
method: "POST",
|
|
823
|
+
headers: { "content-type": "application/json" },
|
|
824
|
+
body: JSON.stringify(patch)
|
|
825
|
+
});
|
|
826
|
+
if (response.ok) saved = await response.json();
|
|
827
|
+
}
|
|
828
|
+
async function rescan() {
|
|
829
|
+
busy = true;
|
|
830
|
+
try {
|
|
831
|
+
const response = await fetch(`/api/refresh?${showing}`, { method: "POST" });
|
|
832
|
+
if (response.ok) usage = await response.json();
|
|
833
|
+
} finally {
|
|
834
|
+
busy = false;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const toggle = (list, value) => list.includes(value) ? list.filter((item) => item !== value) : [...list, value];
|
|
838
|
+
const measure = (slice) => metric === "cost" ? slice.cost ?? 0 : slice.total;
|
|
839
|
+
const show = (value) => metric === "cost" ? money(value) : compact(value);
|
|
840
|
+
const activeDays = derived(() => new Set(usage.series.filter((point) => point.total > 0).map((point) => point.day)).size);
|
|
841
|
+
const toolKeys = derived(() => usage.byTool.map((slice) => ({
|
|
842
|
+
id: slice.key,
|
|
843
|
+
label: toolMeta(slice.key).label,
|
|
844
|
+
colour: toolMeta(slice.key).colour
|
|
845
|
+
})));
|
|
846
|
+
const modelKeys = derived(() => usage.byModel.map((slice) => ({
|
|
847
|
+
id: slice.key,
|
|
848
|
+
label: modelName(slice.key),
|
|
849
|
+
colour: providerMeta(providerFor(slice.key)).colour
|
|
850
|
+
})));
|
|
851
|
+
const trendKeys = derived(() => trendBy === "tool" ? toolKeys() : modelKeys());
|
|
852
|
+
const modelRows = derived(() => usage.byModel.map((slice) => {
|
|
853
|
+
const provider = providerMeta(providerFor(slice.key));
|
|
854
|
+
return {
|
|
855
|
+
id: slice.key,
|
|
856
|
+
label: modelName(slice.key),
|
|
857
|
+
detail: provider.label,
|
|
858
|
+
value: measure(slice),
|
|
859
|
+
display: show(measure(slice)),
|
|
860
|
+
colour: provider.colour,
|
|
861
|
+
mark: provider.id,
|
|
862
|
+
hint: `${slice.key} - ${full(slice.total)} tokens over ${full(slice.events)} requests, ${money(slice.cost)} estimated`
|
|
863
|
+
};
|
|
864
|
+
}));
|
|
865
|
+
const projectRows = derived(() => usage.byProject.map((slice) => ({
|
|
866
|
+
id: slice.key,
|
|
867
|
+
label: projectName(slice.key),
|
|
868
|
+
detail: "",
|
|
869
|
+
value: projectBy === "sessions" ? slice.sessions : measure(slice),
|
|
870
|
+
display: projectBy === "sessions" ? full(slice.sessions) : show(measure(slice)),
|
|
871
|
+
colour: "var(--accent)",
|
|
872
|
+
hint: `${slice.key || "unknown"} - ${full(slice.total)} tokens, ${full(slice.sessions)} sessions`
|
|
873
|
+
})));
|
|
874
|
+
/** Tools with nothing to show still get a row, so it is obvious what else is supported. */
|
|
875
|
+
const toolRows = derived(() => usage.sources.map((source) => {
|
|
876
|
+
const slice = usage.byTool.find((entry) => entry.key === source.id);
|
|
877
|
+
const tool = toolMeta(source.id);
|
|
878
|
+
return {
|
|
879
|
+
id: source.id,
|
|
880
|
+
label: tool.label,
|
|
881
|
+
detail: slice ? `${full(slice.sessions)} sessions` : "not found",
|
|
882
|
+
value: slice ? measure(slice) : 0,
|
|
883
|
+
display: slice ? show(measure(slice)) : "--",
|
|
884
|
+
colour: tool.colour,
|
|
885
|
+
mark: source.id,
|
|
886
|
+
hint: source.present ? `${full(source.files)} files under ${source.root}` : `Nothing at ${source.root}`
|
|
887
|
+
};
|
|
888
|
+
}));
|
|
889
|
+
const mix = derived(() => [
|
|
890
|
+
{
|
|
891
|
+
id: "input",
|
|
892
|
+
label: "Input",
|
|
893
|
+
value: usage.totals.tokens.input,
|
|
894
|
+
display: compact(usage.totals.tokens.input),
|
|
895
|
+
colour: "var(--ramp-4)",
|
|
896
|
+
hint: "Fresh prompt tokens, billed at full rate"
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
id: "output",
|
|
900
|
+
label: "Output",
|
|
901
|
+
value: usage.totals.tokens.output,
|
|
902
|
+
display: compact(usage.totals.tokens.output),
|
|
903
|
+
colour: "var(--ramp-2)",
|
|
904
|
+
hint: `Everything the model wrote back, ${compact(usage.totals.reasoning)} of it reasoning`
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
id: "cache_read",
|
|
908
|
+
label: "Cache read",
|
|
909
|
+
value: usage.totals.tokens.cache_read,
|
|
910
|
+
display: compact(usage.totals.tokens.cache_read),
|
|
911
|
+
colour: "var(--ramp-1)",
|
|
912
|
+
hint: "Context served from cache, usually a tenth of the input price"
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
id: "cache_write",
|
|
916
|
+
label: "Cache write",
|
|
917
|
+
value: usage.totals.tokens.cache_write,
|
|
918
|
+
display: compact(usage.totals.tokens.cache_write),
|
|
919
|
+
colour: "var(--ramp-3)",
|
|
920
|
+
hint: "Putting context into the cache, a premium on the input price"
|
|
921
|
+
}
|
|
922
|
+
]);
|
|
923
|
+
const cacheShare = derived(() => usage.totals.total ? usage.totals.tokens.cache_read / usage.totals.total * 100 : 0);
|
|
924
|
+
let $$settled = true;
|
|
925
|
+
let $$inner_renderer;
|
|
926
|
+
function $$render_inner($$renderer) {
|
|
927
|
+
head("1uha8ag", $$renderer, ($$renderer) => {
|
|
928
|
+
$$renderer.title(($$renderer) => {
|
|
929
|
+
$$renderer.push(`<title>AI usage dashboard</title>`);
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
$$renderer.push(`<header class="bar svelte-1uha8ag"><a class="brand svelte-1uha8ag" href="/">`);
|
|
933
|
+
Logo($$renderer, {});
|
|
934
|
+
$$renderer.push(`<!----> <span>AI usage</span></a> <div class="filters svelte-1uha8ag">`);
|
|
935
|
+
Segmented($$renderer, {
|
|
936
|
+
options: RANGES.map((entry) => ({
|
|
937
|
+
id: entry.id,
|
|
938
|
+
label: entry.label
|
|
939
|
+
})),
|
|
940
|
+
label: "Date range",
|
|
941
|
+
size: "sm",
|
|
942
|
+
get value() {
|
|
943
|
+
return range;
|
|
944
|
+
},
|
|
945
|
+
set value($$value) {
|
|
946
|
+
range = $$value;
|
|
947
|
+
$$settled = false;
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
$$renderer.push(`<!----> `);
|
|
951
|
+
Segmented($$renderer, {
|
|
952
|
+
options: [{
|
|
953
|
+
id: "tokens",
|
|
954
|
+
label: "Tokens"
|
|
955
|
+
}, {
|
|
956
|
+
id: "cost",
|
|
957
|
+
label: "Est. cost",
|
|
958
|
+
hint: "At published API rates. Your subscription is billed separately."
|
|
959
|
+
}],
|
|
960
|
+
label: "Measure",
|
|
961
|
+
size: "sm",
|
|
962
|
+
get value() {
|
|
963
|
+
return metric;
|
|
964
|
+
},
|
|
965
|
+
set value($$value) {
|
|
966
|
+
metric = $$value;
|
|
967
|
+
$$settled = false;
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
$$renderer.push(`<!----> `);
|
|
971
|
+
MultiSelect($$renderer, {
|
|
972
|
+
label: "Tools",
|
|
973
|
+
options: usage.options.tools,
|
|
974
|
+
format: (id) => toolMeta(id).label,
|
|
975
|
+
get selected() {
|
|
976
|
+
return tools;
|
|
977
|
+
},
|
|
978
|
+
set selected($$value) {
|
|
979
|
+
tools = $$value;
|
|
980
|
+
$$settled = false;
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
$$renderer.push(`<!----> `);
|
|
984
|
+
MultiSelect($$renderer, {
|
|
985
|
+
label: "Models",
|
|
986
|
+
options: usage.options.models,
|
|
987
|
+
format: modelName,
|
|
988
|
+
get selected() {
|
|
989
|
+
return models;
|
|
990
|
+
},
|
|
991
|
+
set selected($$value) {
|
|
992
|
+
models = $$value;
|
|
993
|
+
$$settled = false;
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
$$renderer.push(`<!----> `);
|
|
997
|
+
MultiSelect($$renderer, {
|
|
998
|
+
label: "Projects",
|
|
999
|
+
options: usage.options.projects,
|
|
1000
|
+
format: projectName,
|
|
1001
|
+
get selected() {
|
|
1002
|
+
return projects;
|
|
1003
|
+
},
|
|
1004
|
+
set selected($$value) {
|
|
1005
|
+
projects = $$value;
|
|
1006
|
+
$$settled = false;
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
$$renderer.push(`<!----> `);
|
|
1010
|
+
if (tools.length || models.length || projects.length) $$renderer.push(`<!--[0--><button type="button" class="reset svelte-1uha8ag">Clear filters</button>`);
|
|
1011
|
+
else $$renderer.push("<!--[-1-->");
|
|
1012
|
+
$$renderer.push(`<!--]--></div> <div class="tools svelte-1uha8ag"><button type="button"${attr_class("icon svelte-1uha8ag", void 0, { "busy": busy })} aria-label="Rescan now"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" class="svelte-1uha8ag"><path d="M13.4 7A5.5 5.5 0 1 0 12.6 11" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M13.8 3.4v3.8h-3.8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg></button> <button type="button" class="icon svelte-1uha8ag" aria-label="Settings"><svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="2.3" fill="none" stroke="currentColor" stroke-width="1.5"></circle><path d="M8 1.6v1.8M8 12.6v1.8M14.4 8h-1.8M3.4 8H1.6M12.5 3.5l-1.3 1.3M4.8 11.2l-1.3 1.3M12.5 12.5l-1.3-1.3M4.8 4.8 3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg></button></div></header> <main class="svelte-1uha8ag">`);
|
|
1013
|
+
Figures($$renderer, {
|
|
1014
|
+
totals: usage.totals,
|
|
1015
|
+
previous: usage.previous,
|
|
1016
|
+
activeDays: activeDays(),
|
|
1017
|
+
rangeLabel: rangeLabel(usage.filters.range)
|
|
1018
|
+
});
|
|
1019
|
+
$$renderer.push(`<!----> <div class="grid svelte-1uha8ag">`);
|
|
1020
|
+
Panel($$renderer, {
|
|
1021
|
+
title: "Every day so far",
|
|
1022
|
+
span: 8,
|
|
1023
|
+
hint: "One square per day, whatever range is picked above. Outlined days are before your records start.",
|
|
1024
|
+
children: ($$renderer) => {
|
|
1025
|
+
Calendar($$renderer, {
|
|
1026
|
+
days: usage.calendar,
|
|
1027
|
+
metric,
|
|
1028
|
+
oldest: usage.oldest
|
|
1029
|
+
});
|
|
1030
|
+
}});
|
|
1031
|
+
$$renderer.push(`<!----> `);
|
|
1032
|
+
Panel($$renderer, {
|
|
1033
|
+
title: "Plan windows",
|
|
1034
|
+
span: 4,
|
|
1035
|
+
hint: "Straight from each account, not worked out from tokens",
|
|
1036
|
+
children: ($$renderer) => {
|
|
1037
|
+
if (usage.windows.length) {
|
|
1038
|
+
$$renderer.push(`<!--[0--><div class="meters svelte-1uha8ag"><!--[-->`);
|
|
1039
|
+
const each_array = ensure_array_like(usage.windows);
|
|
1040
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
1041
|
+
let window = each_array[$$index];
|
|
1042
|
+
Meter($$renderer, { window });
|
|
1043
|
+
}
|
|
1044
|
+
$$renderer.push(`<!--]--></div>`);
|
|
1045
|
+
} else $$renderer.push(`<!--[-1--><p class="empty svelte-1uha8ag">No plan windows yet. Sign in with Claude Code or run Codex once and they turn up here.</p>`);
|
|
1046
|
+
$$renderer.push(`<!--]-->`);
|
|
1047
|
+
}});
|
|
1048
|
+
$$renderer.push(`<!----> `);
|
|
1049
|
+
{
|
|
1050
|
+
function actions($$renderer) {
|
|
1051
|
+
Segmented($$renderer, {
|
|
1052
|
+
options: [{
|
|
1053
|
+
id: "tool",
|
|
1054
|
+
label: "By tool"
|
|
1055
|
+
}, {
|
|
1056
|
+
id: "model",
|
|
1057
|
+
label: "By model"
|
|
1058
|
+
}],
|
|
1059
|
+
label: "Trend grouping",
|
|
1060
|
+
size: "sm",
|
|
1061
|
+
get value() {
|
|
1062
|
+
return trendBy;
|
|
1063
|
+
},
|
|
1064
|
+
set value($$value) {
|
|
1065
|
+
trendBy = $$value;
|
|
1066
|
+
$$settled = false;
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
Panel($$renderer, {
|
|
1071
|
+
title: "Day by day",
|
|
1072
|
+
span: 12,
|
|
1073
|
+
hint: "Stacked over the range you picked",
|
|
1074
|
+
actions,
|
|
1075
|
+
children: ($$renderer) => {
|
|
1076
|
+
Trend($$renderer, {
|
|
1077
|
+
series: trendBy === "tool" ? usage.series : usage.seriesByModel,
|
|
1078
|
+
keys: trendKeys(),
|
|
1079
|
+
metric
|
|
1080
|
+
});
|
|
1081
|
+
}});
|
|
1082
|
+
}
|
|
1083
|
+
$$renderer.push(`<!----> `);
|
|
1084
|
+
{
|
|
1085
|
+
function actions($$renderer) {
|
|
1086
|
+
Segmented($$renderer, {
|
|
1087
|
+
options: [{
|
|
1088
|
+
id: "share",
|
|
1089
|
+
label: "Share"
|
|
1090
|
+
}, {
|
|
1091
|
+
id: "list",
|
|
1092
|
+
label: "Ranked"
|
|
1093
|
+
}],
|
|
1094
|
+
label: "Model view",
|
|
1095
|
+
size: "sm",
|
|
1096
|
+
get value() {
|
|
1097
|
+
return modelView;
|
|
1098
|
+
},
|
|
1099
|
+
set value($$value) {
|
|
1100
|
+
modelView = $$value;
|
|
1101
|
+
$$settled = false;
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
Panel($$renderer, {
|
|
1106
|
+
title: "Models",
|
|
1107
|
+
span: 5,
|
|
1108
|
+
hint: "Click one to filter the whole page by it",
|
|
1109
|
+
actions,
|
|
1110
|
+
children: ($$renderer) => {
|
|
1111
|
+
if (modelView === "share") {
|
|
1112
|
+
$$renderer.push(`<!--[0--><div class="share svelte-1uha8ag">`);
|
|
1113
|
+
Donut($$renderer, {
|
|
1114
|
+
slices: modelRows().map((row) => ({
|
|
1115
|
+
id: row.id,
|
|
1116
|
+
label: row.label,
|
|
1117
|
+
value: row.value,
|
|
1118
|
+
colour: row.colour,
|
|
1119
|
+
display: row.display
|
|
1120
|
+
})),
|
|
1121
|
+
centre: show(measure(usage.totals)),
|
|
1122
|
+
caption: metric === "cost" ? "estimated" : "tokens"
|
|
1123
|
+
});
|
|
1124
|
+
$$renderer.push(`<!----> <ul class="key svelte-1uha8ag"><!--[-->`);
|
|
1125
|
+
const each_array_1 = ensure_array_like(modelRows());
|
|
1126
|
+
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
|
|
1127
|
+
let row = each_array_1[$$index_1];
|
|
1128
|
+
$$renderer.push(`<li class="svelte-1uha8ag">`);
|
|
1129
|
+
Mark($$renderer, {
|
|
1130
|
+
id: row.mark,
|
|
1131
|
+
label: row.detail,
|
|
1132
|
+
colour: row.colour,
|
|
1133
|
+
size: 14
|
|
1134
|
+
});
|
|
1135
|
+
$$renderer.push(`<!----> <span class="svelte-1uha8ag">${escape_html(row.label)}</span> <b class="num svelte-1uha8ag">${escape_html(row.display)}</b></li>`);
|
|
1136
|
+
}
|
|
1137
|
+
$$renderer.push(`<!--]--></ul></div>`);
|
|
1138
|
+
} else {
|
|
1139
|
+
$$renderer.push("<!--[-1-->");
|
|
1140
|
+
BarList($$renderer, {
|
|
1141
|
+
rows: modelRows(),
|
|
1142
|
+
active: models,
|
|
1143
|
+
onpick: (id) => models = toggle(models, id)
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
$$renderer.push(`<!--]-->`);
|
|
1147
|
+
}});
|
|
1148
|
+
}
|
|
1149
|
+
$$renderer.push(`<!----> `);
|
|
1150
|
+
{
|
|
1151
|
+
function actions($$renderer) {
|
|
1152
|
+
Segmented($$renderer, {
|
|
1153
|
+
options: [{
|
|
1154
|
+
id: "measure",
|
|
1155
|
+
label: metric === "cost" ? "Est. cost" : "Tokens"
|
|
1156
|
+
}, {
|
|
1157
|
+
id: "sessions",
|
|
1158
|
+
label: "Sessions"
|
|
1159
|
+
}],
|
|
1160
|
+
label: "Project measure",
|
|
1161
|
+
size: "sm",
|
|
1162
|
+
get value() {
|
|
1163
|
+
return projectBy;
|
|
1164
|
+
},
|
|
1165
|
+
set value($$value) {
|
|
1166
|
+
projectBy = $$value;
|
|
1167
|
+
$$settled = false;
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
Panel($$renderer, {
|
|
1172
|
+
title: "Projects",
|
|
1173
|
+
span: 7,
|
|
1174
|
+
hint: "Where the work happened. Click one to filter.",
|
|
1175
|
+
actions,
|
|
1176
|
+
children: ($$renderer) => {
|
|
1177
|
+
BarList($$renderer, {
|
|
1178
|
+
rows: projectRows().slice(0, 8),
|
|
1179
|
+
active: projects,
|
|
1180
|
+
onpick: (id) => projects = toggle(projects, id)
|
|
1181
|
+
});
|
|
1182
|
+
}});
|
|
1183
|
+
}
|
|
1184
|
+
$$renderer.push(`<!----> `);
|
|
1185
|
+
Panel($$renderer, {
|
|
1186
|
+
title: "Where the tokens go",
|
|
1187
|
+
span: 4,
|
|
1188
|
+
hint: "Cache reads are cheap, cache writes are not. This is the split that decides the bill.",
|
|
1189
|
+
children: ($$renderer) => {
|
|
1190
|
+
Split($$renderer, { parts: mix() });
|
|
1191
|
+
$$renderer.push(`<!----> <p class="aside svelte-1uha8ag">${escape_html(cacheShare().toFixed(0))}% of everything counted was served from cache.</p>`);
|
|
1192
|
+
}});
|
|
1193
|
+
$$renderer.push(`<!----> `);
|
|
1194
|
+
Panel($$renderer, {
|
|
1195
|
+
title: "When you work",
|
|
1196
|
+
span: 5,
|
|
1197
|
+
hint: "Local time, over the range above",
|
|
1198
|
+
children: ($$renderer) => {
|
|
1199
|
+
Clock($$renderer, { points: usage.clock });
|
|
1200
|
+
}});
|
|
1201
|
+
$$renderer.push(`<!----> `);
|
|
1202
|
+
Panel($$renderer, {
|
|
1203
|
+
title: "Tools",
|
|
1204
|
+
span: 3,
|
|
1205
|
+
hint: "Click one to filter the whole page by it",
|
|
1206
|
+
children: ($$renderer) => {
|
|
1207
|
+
BarList($$renderer, {
|
|
1208
|
+
rows: toolRows(),
|
|
1209
|
+
active: tools,
|
|
1210
|
+
onpick: (id) => tools = toggle(tools, id)
|
|
1211
|
+
});
|
|
1212
|
+
}});
|
|
1213
|
+
$$renderer.push(`<!----> `);
|
|
1214
|
+
Panel($$renderer, {
|
|
1215
|
+
title: "Recent sessions",
|
|
1216
|
+
span: 12,
|
|
1217
|
+
children: ($$renderer) => {
|
|
1218
|
+
Sessions($$renderer, {
|
|
1219
|
+
rows: usage.sessions,
|
|
1220
|
+
metric});
|
|
1221
|
+
}});
|
|
1222
|
+
$$renderer.push(`<!----></div> <footer class="svelte-1uha8ag"><span>Reads the transcripts already on this machine. Nothing is sent anywhere.</span> <span>`);
|
|
1223
|
+
if (usage.oldest) $$renderer.push(`<!--[0-->Records from ${escape_html(new Date(usage.oldest).toLocaleDateString("en-GB"))} ·`);
|
|
1224
|
+
else $$renderer.push("<!--[-1-->");
|
|
1225
|
+
$$renderer.push(`<!--]--> Scanned ${escape_html(relative(usage.scannedAt))}</span></footer></main> `);
|
|
1226
|
+
Problems($$renderer, { problems: usage.problems });
|
|
1227
|
+
$$renderer.push(`<!----> `);
|
|
1228
|
+
Settings($$renderer, {
|
|
1229
|
+
saved,
|
|
1230
|
+
sources: usage.sources,
|
|
1231
|
+
dbPath: data.paths.db,
|
|
1232
|
+
scannedAt: usage.scannedAt,
|
|
1233
|
+
onsave: save,
|
|
1234
|
+
onrescan: rescan,
|
|
1235
|
+
get open() {
|
|
1236
|
+
return settingsOpen;
|
|
1237
|
+
},
|
|
1238
|
+
set open($$value) {
|
|
1239
|
+
settingsOpen = $$value;
|
|
1240
|
+
$$settled = false;
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
$$renderer.push(`<!---->`);
|
|
1244
|
+
}
|
|
1245
|
+
do {
|
|
1246
|
+
$$settled = true;
|
|
1247
|
+
$$inner_renderer = $$renderer.copy();
|
|
1248
|
+
$$render_inner($$inner_renderer);
|
|
1249
|
+
} while (!$$settled);
|
|
1250
|
+
$$renderer.subsume($$inner_renderer);
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
export { _page as default };
|
|
1255
|
+
//# sourceMappingURL=_page.svelte.js-D91hy9R4.js.map
|