@chrysb/alphaclaw 0.6.2-beta.0 → 0.6.2-beta.2
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/lib/public/css/cron.css
CHANGED
|
@@ -484,15 +484,18 @@
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
.cron-runs-trend-segment-ok {
|
|
487
|
-
background: rgba(34,
|
|
487
|
+
background: rgba(34, 255, 170, 0.82);
|
|
488
|
+
box-shadow: inset 0 0 0 1px rgba(34, 255, 170, 0.28), 0 0 8px rgba(34, 255, 170, 0.24);
|
|
488
489
|
}
|
|
489
490
|
|
|
490
491
|
.cron-runs-trend-segment-error {
|
|
491
|
-
background: rgba(
|
|
492
|
+
background: rgba(255, 74, 138, 0.84);
|
|
493
|
+
box-shadow: inset 0 0 0 1px rgba(255, 74, 138, 0.32), 0 0 8px rgba(255, 74, 138, 0.24);
|
|
492
494
|
}
|
|
493
495
|
|
|
494
496
|
.cron-runs-trend-segment-skipped {
|
|
495
|
-
background: rgba(
|
|
497
|
+
background: rgba(255, 214, 64, 0.82);
|
|
498
|
+
box-shadow: inset 0 0 0 1px rgba(255, 214, 64, 0.28), 0 0 8px rgba(255, 214, 64, 0.22);
|
|
496
499
|
}
|
|
497
500
|
|
|
498
501
|
.cron-runs-trend-label {
|
|
@@ -523,13 +526,16 @@
|
|
|
523
526
|
}
|
|
524
527
|
|
|
525
528
|
.cron-runs-trend-legend-dot.is-ok {
|
|
526
|
-
background: rgba(34,
|
|
529
|
+
background: rgba(34, 255, 170, 0.98);
|
|
530
|
+
box-shadow: 0 0 8px rgba(34, 255, 170, 0.6);
|
|
527
531
|
}
|
|
528
532
|
|
|
529
533
|
.cron-runs-trend-legend-dot.is-error {
|
|
530
|
-
background: rgba(
|
|
534
|
+
background: rgba(255, 74, 138, 0.98);
|
|
535
|
+
box-shadow: 0 0 8px rgba(255, 74, 138, 0.58);
|
|
531
536
|
}
|
|
532
537
|
|
|
533
538
|
.cron-runs-trend-legend-dot.is-skipped {
|
|
534
|
-
background: rgba(
|
|
539
|
+
background: rgba(255, 214, 64, 0.98);
|
|
540
|
+
box-shadow: 0 0 8px rgba(255, 214, 64, 0.5);
|
|
535
541
|
}
|
|
@@ -7,7 +7,6 @@ import { formatCost, formatTokenCount } from "./cron-helpers.js";
|
|
|
7
7
|
import { formatCronScheduleLabel } from "./cron-helpers.js";
|
|
8
8
|
import { readUiSettings, updateUiSettings } from "../../lib/ui-settings.js";
|
|
9
9
|
import {
|
|
10
|
-
buildTokenTierByJobId,
|
|
11
10
|
classifyRepeatingJobs,
|
|
12
11
|
expandJobsToRollingSlots,
|
|
13
12
|
getUpcomingSlots,
|
|
@@ -68,47 +67,195 @@ const renderLegend = () => html`
|
|
|
68
67
|
|
|
69
68
|
const kNowRefreshMs = 60 * 1000;
|
|
70
69
|
const kCalendarExpandedUiSettingKey = "cronCalendarExpanded";
|
|
70
|
+
const kRunWindow7dMs = 7 * 24 * 60 * 60 * 1000;
|
|
71
|
+
const kSlotRunToleranceMs = 45 * 60 * 1000;
|
|
72
|
+
const kUnknownTier = "unknown";
|
|
71
73
|
|
|
72
74
|
const formatUpcomingTime = (timestampMs) => {
|
|
73
75
|
const dateValue = new Date(timestampMs);
|
|
74
76
|
return dateValue.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
75
77
|
};
|
|
76
78
|
|
|
79
|
+
const getRunTotalTokens = (entry = {}) => {
|
|
80
|
+
const usage = entry?.usage || {};
|
|
81
|
+
const candidates = [
|
|
82
|
+
usage?.total_tokens,
|
|
83
|
+
usage?.totalTokens,
|
|
84
|
+
entry?.total_tokens,
|
|
85
|
+
entry?.totalTokens,
|
|
86
|
+
];
|
|
87
|
+
for (const candidate of candidates) {
|
|
88
|
+
const numericValue = Number(candidate);
|
|
89
|
+
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
90
|
+
}
|
|
91
|
+
return 0;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getRunEstimatedCost = (entry = {}) => {
|
|
95
|
+
const usage = entry?.usage || {};
|
|
96
|
+
const candidates = [
|
|
97
|
+
entry?.estimatedCost,
|
|
98
|
+
entry?.estimated_cost,
|
|
99
|
+
usage?.estimatedCost,
|
|
100
|
+
usage?.estimated_cost,
|
|
101
|
+
usage?.totalCost,
|
|
102
|
+
usage?.total_cost,
|
|
103
|
+
usage?.costUsd,
|
|
104
|
+
usage?.cost,
|
|
105
|
+
];
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
const numericValue = Number(candidate);
|
|
108
|
+
if (Number.isFinite(numericValue) && numericValue >= 0) return numericValue;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const buildRunSummaryByJobId = ({ runsByJobId = {}, nowMs = Date.now() } = {}) => {
|
|
114
|
+
const cutoffMs = Number(nowMs || Date.now()) - kRunWindow7dMs;
|
|
115
|
+
return Object.entries(runsByJobId || {}).reduce((accumulator, [jobId, runResult]) => {
|
|
116
|
+
const entries = Array.isArray(runResult?.entries) ? runResult.entries : [];
|
|
117
|
+
const recentEntries = entries.filter((entry) => {
|
|
118
|
+
const timestampMs = Number(entry?.ts || 0);
|
|
119
|
+
return Number.isFinite(timestampMs) && timestampMs >= cutoffMs && timestampMs <= nowMs;
|
|
120
|
+
});
|
|
121
|
+
const runCount = recentEntries.length;
|
|
122
|
+
const totalTokens = recentEntries.reduce(
|
|
123
|
+
(sum, entry) => sum + Number(getRunTotalTokens(entry) || 0),
|
|
124
|
+
0,
|
|
125
|
+
);
|
|
126
|
+
const totalCost = recentEntries.reduce((sum, entry) => {
|
|
127
|
+
const cost = getRunEstimatedCost(entry);
|
|
128
|
+
return sum + Number(cost == null ? 0 : cost);
|
|
129
|
+
}, 0);
|
|
130
|
+
accumulator[String(jobId || "")] = {
|
|
131
|
+
runCount,
|
|
132
|
+
totalTokens,
|
|
133
|
+
totalCost,
|
|
134
|
+
avgTokensPerRun: runCount > 0 ? Math.round(totalTokens / runCount) : 0,
|
|
135
|
+
avgCostPerRun: runCount > 0 ? totalCost / runCount : 0,
|
|
136
|
+
};
|
|
137
|
+
return accumulator;
|
|
138
|
+
}, {});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const mapRunsToSlots = ({ slots = [], runsByJobId = {}, nowMs = Date.now() } = {}) => {
|
|
142
|
+
const runsBySlotKey = {};
|
|
143
|
+
const consumedRunTimestampsByJobId = {};
|
|
144
|
+
const runEntriesByJobId = Object.entries(runsByJobId || {}).reduce(
|
|
145
|
+
(accumulator, [jobId, runResult]) => {
|
|
146
|
+
const entries = Array.isArray(runResult?.entries) ? runResult.entries : [];
|
|
147
|
+
const normalizedEntries = entries
|
|
148
|
+
.map((entry) => ({ ...entry, ts: Number(entry?.ts || 0) }))
|
|
149
|
+
.filter((entry) => Number.isFinite(entry.ts) && entry.ts > 0)
|
|
150
|
+
.sort((left, right) => left.ts - right.ts);
|
|
151
|
+
accumulator[String(jobId || "")] = normalizedEntries;
|
|
152
|
+
return accumulator;
|
|
153
|
+
},
|
|
154
|
+
{},
|
|
155
|
+
);
|
|
156
|
+
slots.forEach((slot) => {
|
|
157
|
+
if (Number(slot?.scheduledAtMs || 0) > nowMs) return;
|
|
158
|
+
const jobId = String(slot?.jobId || "");
|
|
159
|
+
const runEntries = runEntriesByJobId[jobId] || [];
|
|
160
|
+
if (runEntries.length === 0) return;
|
|
161
|
+
const consumedSet = consumedRunTimestampsByJobId[jobId] || new Set();
|
|
162
|
+
consumedRunTimestampsByJobId[jobId] = consumedSet;
|
|
163
|
+
let nearestEntry = null;
|
|
164
|
+
let nearestDeltaMs = Number.MAX_SAFE_INTEGER;
|
|
165
|
+
runEntries.forEach((entry) => {
|
|
166
|
+
if (consumedSet.has(entry.ts)) return;
|
|
167
|
+
const deltaMs = Math.abs(entry.ts - Number(slot?.scheduledAtMs || 0));
|
|
168
|
+
if (deltaMs > kSlotRunToleranceMs) return;
|
|
169
|
+
if (deltaMs < nearestDeltaMs) {
|
|
170
|
+
nearestDeltaMs = deltaMs;
|
|
171
|
+
nearestEntry = entry;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
if (!nearestEntry) return;
|
|
175
|
+
consumedSet.add(nearestEntry.ts);
|
|
176
|
+
runsBySlotKey[String(slot?.key || "")] = nearestEntry;
|
|
177
|
+
});
|
|
178
|
+
return runsBySlotKey;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const buildTierThresholds = (values = []) => {
|
|
182
|
+
const sortedValues = values
|
|
183
|
+
.map((value) => Number(value || 0))
|
|
184
|
+
.filter((value) => Number.isFinite(value) && value > 0)
|
|
185
|
+
.sort((left, right) => left - right);
|
|
186
|
+
if (sortedValues.length === 0) return null;
|
|
187
|
+
const percentileAt = (indexRatio = 0) => {
|
|
188
|
+
const index = Math.min(
|
|
189
|
+
sortedValues.length - 1,
|
|
190
|
+
Math.floor((sortedValues.length - 1) * indexRatio),
|
|
191
|
+
);
|
|
192
|
+
return sortedValues[Math.max(0, index)];
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
q1: percentileAt(0.25),
|
|
196
|
+
q2: percentileAt(0.5),
|
|
197
|
+
p90: percentileAt(0.9),
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const classifyTokenTier = ({
|
|
202
|
+
enabled = true,
|
|
203
|
+
tokenValue = 0,
|
|
204
|
+
thresholds = null,
|
|
205
|
+
} = {}) => {
|
|
206
|
+
if (!enabled) return "disabled";
|
|
207
|
+
const safeValue = Number(tokenValue || 0);
|
|
208
|
+
if (!Number.isFinite(safeValue) || safeValue <= 0 || !thresholds) return kUnknownTier;
|
|
209
|
+
if (safeValue <= thresholds.q1) return "low";
|
|
210
|
+
if (safeValue <= thresholds.q2) return "medium";
|
|
211
|
+
if (safeValue <= thresholds.p90) return "high";
|
|
212
|
+
return "very-high";
|
|
213
|
+
};
|
|
214
|
+
|
|
77
215
|
|
|
78
216
|
const buildJobTooltipText = ({
|
|
79
217
|
jobName = "",
|
|
80
218
|
job = null,
|
|
81
|
-
|
|
219
|
+
runSummary7d = {},
|
|
220
|
+
slotRun = null,
|
|
82
221
|
latestRun = null,
|
|
83
222
|
scheduledAtMs = 0,
|
|
84
223
|
scheduledStatus = "",
|
|
224
|
+
nowMs = Date.now(),
|
|
85
225
|
} = {}) => {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
226
|
+
const isPastSlot = Number(scheduledAtMs || 0) > 0 && Number(scheduledAtMs || 0) <= nowMs;
|
|
227
|
+
const runCount7d = Number(runSummary7d?.runCount || 0);
|
|
228
|
+
const avgTokensPerRun7d = Number(runSummary7d?.avgTokensPerRun || 0);
|
|
229
|
+
const avgCostPerRun7d = Number(runSummary7d?.avgCostPerRun || 0);
|
|
230
|
+
const slotRunTokens = getRunTotalTokens(slotRun || {});
|
|
231
|
+
const slotRunCost = getRunEstimatedCost(slotRun || {});
|
|
232
|
+
const slotRunStatus = String(slotRun?.status || "").trim().toLowerCase();
|
|
93
233
|
|
|
94
|
-
const lines = [
|
|
95
|
-
|
|
96
|
-
`
|
|
97
|
-
`
|
|
98
|
-
`
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
234
|
+
const lines = [String(jobName || "Job")];
|
|
235
|
+
if (isPastSlot) {
|
|
236
|
+
lines.push(`Run tokens: ${slotRun ? formatTokenCount(slotRunTokens) : "—"}`);
|
|
237
|
+
lines.push(`Run cost: ${slotRunCost == null ? "—" : formatCost(slotRunCost)}`);
|
|
238
|
+
lines.push(`Run status: ${slotRunStatus || scheduledStatus || "unknown"}`);
|
|
239
|
+
if (slotRun?.ts) {
|
|
240
|
+
lines.push(
|
|
241
|
+
`Run time: ${new Date(Number(slotRun.ts || 0)).toLocaleString()}`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
103
244
|
} else {
|
|
104
|
-
lines.push(
|
|
245
|
+
lines.push(
|
|
246
|
+
`Avg tokens/run (last 7d): ${runCount7d > 0 ? formatTokenCount(avgTokensPerRun7d) : "—"}`,
|
|
247
|
+
);
|
|
248
|
+
lines.push(
|
|
249
|
+
`Avg cost/run (last 7d): ${runCount7d > 0 ? formatCost(avgCostPerRun7d) : "—"}`,
|
|
250
|
+
);
|
|
251
|
+
lines.push(`Runs (last 7d): ${runCount7d > 0 ? formatTokenCount(runCount7d) : "none"}`);
|
|
105
252
|
}
|
|
106
253
|
|
|
107
|
-
if (latestRun?.status) {
|
|
254
|
+
if (!isPastSlot && latestRun?.status) {
|
|
108
255
|
lines.push(
|
|
109
256
|
`Latest run: ${latestRun.status} (${new Date(Number(latestRun.ts || 0)).toLocaleString()})`,
|
|
110
257
|
);
|
|
111
|
-
} else {
|
|
258
|
+
} else if (!isPastSlot) {
|
|
112
259
|
lines.push("Latest run: none");
|
|
113
260
|
}
|
|
114
261
|
if (Number(job?.state?.runningAtMs || 0) > 0) {
|
|
@@ -125,7 +272,6 @@ const buildJobTooltipText = ({
|
|
|
125
272
|
|
|
126
273
|
export const CronCalendar = ({
|
|
127
274
|
jobs = [],
|
|
128
|
-
usageByJobId = {},
|
|
129
275
|
runsByJobId = {},
|
|
130
276
|
onSelectJob = () => {},
|
|
131
277
|
}) => {
|
|
@@ -163,10 +309,6 @@ export const CronCalendar = ({
|
|
|
163
309
|
() => mapRunStatusesToSlots({ slots: timeline.slots, bulkRunsByJobId: runsByJobId, nowMs }),
|
|
164
310
|
[timeline.slots, runsByJobId, nowMs],
|
|
165
311
|
);
|
|
166
|
-
const tokenTierByJobId = useMemo(
|
|
167
|
-
() => buildTokenTierByJobId({ jobs, usageByJobId }),
|
|
168
|
-
[jobs, usageByJobId],
|
|
169
|
-
);
|
|
170
312
|
const jobById = useMemo(
|
|
171
313
|
() =>
|
|
172
314
|
jobs.reduce((accumulator, job) => {
|
|
@@ -188,6 +330,63 @@ export const CronCalendar = ({
|
|
|
188
330
|
}, {}),
|
|
189
331
|
[runsByJobId],
|
|
190
332
|
);
|
|
333
|
+
const runSummary7dByJobId = useMemo(
|
|
334
|
+
() => buildRunSummaryByJobId({ runsByJobId, nowMs }),
|
|
335
|
+
[runsByJobId, nowMs],
|
|
336
|
+
);
|
|
337
|
+
const runBySlotKey = useMemo(
|
|
338
|
+
() => mapRunsToSlots({ slots: timeline.slots, runsByJobId, nowMs }),
|
|
339
|
+
[timeline.slots, runsByJobId, nowMs],
|
|
340
|
+
);
|
|
341
|
+
const slotTierThresholds = useMemo(() => {
|
|
342
|
+
const values = [];
|
|
343
|
+
timeline.slots.forEach((slot) => {
|
|
344
|
+
const job = jobById[slot.jobId] || null;
|
|
345
|
+
if (!job || job.enabled === false) return;
|
|
346
|
+
const isPastSlot = Number(slot?.scheduledAtMs || 0) <= nowMs;
|
|
347
|
+
if (isPastSlot) {
|
|
348
|
+
const slotRunTokens = getRunTotalTokens(runBySlotKey[slot.key] || {});
|
|
349
|
+
if (slotRunTokens > 0) values.push(slotRunTokens);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const projectedAvgTokens = Number(runSummary7dByJobId[slot.jobId]?.avgTokensPerRun || 0);
|
|
353
|
+
if (projectedAvgTokens > 0) values.push(projectedAvgTokens);
|
|
354
|
+
});
|
|
355
|
+
repeatingJobs.forEach((job) => {
|
|
356
|
+
const jobId = String(job?.id || "");
|
|
357
|
+
const projectedAvgTokens = Number(runSummary7dByJobId[jobId]?.avgTokensPerRun || 0);
|
|
358
|
+
if (projectedAvgTokens > 0) values.push(projectedAvgTokens);
|
|
359
|
+
});
|
|
360
|
+
return buildTierThresholds(values);
|
|
361
|
+
}, [jobById, nowMs, repeatingJobs, runBySlotKey, runSummary7dByJobId, timeline.slots]);
|
|
362
|
+
const getSlotTokenTier = useCallback((slot = null) => {
|
|
363
|
+
const jobId = String(slot?.jobId || "");
|
|
364
|
+
const job = jobById[jobId] || null;
|
|
365
|
+
const enabled = job?.enabled !== false;
|
|
366
|
+
const isPastSlot = Number(slot?.scheduledAtMs || 0) <= nowMs;
|
|
367
|
+
if (isPastSlot) {
|
|
368
|
+
const slotRunTokens = getRunTotalTokens(runBySlotKey[String(slot?.key || "")] || {});
|
|
369
|
+
return classifyTokenTier({
|
|
370
|
+
enabled,
|
|
371
|
+
tokenValue: slotRunTokens,
|
|
372
|
+
thresholds: slotTierThresholds,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const projectedAvgTokens = Number(runSummary7dByJobId[jobId]?.avgTokensPerRun || 0);
|
|
376
|
+
return classifyTokenTier({
|
|
377
|
+
enabled,
|
|
378
|
+
tokenValue: projectedAvgTokens,
|
|
379
|
+
thresholds: slotTierThresholds,
|
|
380
|
+
});
|
|
381
|
+
}, [jobById, nowMs, runBySlotKey, runSummary7dByJobId, slotTierThresholds]);
|
|
382
|
+
const getJobProjectedTier = useCallback((jobId = "") => {
|
|
383
|
+
const job = jobById[jobId] || null;
|
|
384
|
+
return classifyTokenTier({
|
|
385
|
+
enabled: job?.enabled !== false,
|
|
386
|
+
tokenValue: Number(runSummary7dByJobId[jobId]?.avgTokensPerRun || 0),
|
|
387
|
+
thresholds: slotTierThresholds,
|
|
388
|
+
});
|
|
389
|
+
}, [jobById, runSummary7dByJobId, slotTierThresholds]);
|
|
191
390
|
|
|
192
391
|
const upcomingSlots = useMemo(
|
|
193
392
|
() => getUpcomingSlots({ slots: timeline.slots, nowMs }),
|
|
@@ -223,13 +422,14 @@ export const CronCalendar = ({
|
|
|
223
422
|
: html`
|
|
224
423
|
<div class="cron-calendar-compact-strip">
|
|
225
424
|
${upcomingSlots.map((slot) => {
|
|
226
|
-
const usage = usageByJobId[slot.jobId] || {};
|
|
227
425
|
const tooltipText = buildJobTooltipText({
|
|
228
426
|
jobName: slot.jobName,
|
|
229
427
|
job: jobById[slot.jobId] || null,
|
|
230
|
-
|
|
428
|
+
runSummary7d: runSummary7dByJobId[slot.jobId] || {},
|
|
429
|
+
slotRun: runBySlotKey[slot.key] || null,
|
|
231
430
|
latestRun: latestRunByJobId[slot.jobId],
|
|
232
431
|
scheduledAtMs: slot.scheduledAtMs,
|
|
432
|
+
nowMs,
|
|
233
433
|
});
|
|
234
434
|
return html`
|
|
235
435
|
<${Tooltip}
|
|
@@ -309,15 +509,16 @@ export const CronCalendar = ({
|
|
|
309
509
|
${visibleSlots.map((slot) => {
|
|
310
510
|
const status = statusBySlotKey[slot.key] || "";
|
|
311
511
|
const isPast = slot.scheduledAtMs <= nowMs;
|
|
312
|
-
const tokenTier =
|
|
313
|
-
const usage = usageByJobId[slot.jobId] || {};
|
|
512
|
+
const tokenTier = getSlotTokenTier(slot);
|
|
314
513
|
const tooltipText = buildJobTooltipText({
|
|
315
514
|
jobName: slot.jobName,
|
|
316
515
|
job: jobById[slot.jobId] || null,
|
|
317
|
-
|
|
516
|
+
runSummary7d: runSummary7dByJobId[slot.jobId] || {},
|
|
517
|
+
slotRun: runBySlotKey[slot.key] || null,
|
|
318
518
|
latestRun: latestRunByJobId[slot.jobId],
|
|
319
519
|
scheduledAtMs: slot.scheduledAtMs,
|
|
320
520
|
scheduledStatus: status,
|
|
521
|
+
nowMs,
|
|
321
522
|
});
|
|
322
523
|
return html`
|
|
323
524
|
<${Tooltip}
|
|
@@ -366,13 +567,16 @@ export const CronCalendar = ({
|
|
|
366
567
|
<div class="cron-calendar-repeating-list">
|
|
367
568
|
${repeatingJobs.map((job) => {
|
|
368
569
|
const jobId = String(job?.id || "");
|
|
369
|
-
const
|
|
370
|
-
|
|
570
|
+
const avgTokensPerRun = Number(
|
|
571
|
+
runSummary7dByJobId[jobId]?.avgTokensPerRun || 0,
|
|
572
|
+
);
|
|
371
573
|
const tooltipText = buildJobTooltipText({
|
|
372
574
|
jobName: job.name || job.id,
|
|
373
575
|
job,
|
|
374
|
-
|
|
576
|
+
runSummary7d: runSummary7dByJobId[jobId] || {},
|
|
577
|
+
slotRun: null,
|
|
375
578
|
latestRun: latestRunByJobId[jobId],
|
|
579
|
+
nowMs,
|
|
376
580
|
});
|
|
377
581
|
return html`
|
|
378
582
|
<${Tooltip}
|
|
@@ -385,7 +589,7 @@ export const CronCalendar = ({
|
|
|
385
589
|
class=${`cron-calendar-repeating-pill ${slotStateClassName({
|
|
386
590
|
isPast: false,
|
|
387
591
|
mappedStatus: "",
|
|
388
|
-
tokenTier:
|
|
592
|
+
tokenTier: getJobProjectedTier(jobId),
|
|
389
593
|
})}`}
|
|
390
594
|
role="button"
|
|
391
595
|
tabindex="0"
|
|
@@ -50,7 +50,14 @@ const getSessionUsageByKeyPattern = ({ keyPattern = "", sinceMs = 0 } = {}) => {
|
|
|
50
50
|
COALESCE(model, '') AS model,
|
|
51
51
|
COALESCE(provider, '') AS provider,
|
|
52
52
|
COUNT(*) AS event_count,
|
|
53
|
-
COUNT(
|
|
53
|
+
COUNT(
|
|
54
|
+
DISTINCT COALESCE(
|
|
55
|
+
NULLIF(run_id, ''),
|
|
56
|
+
NULLIF(session_key, ''),
|
|
57
|
+
NULLIF(session_id, ''),
|
|
58
|
+
printf('event:%d', id)
|
|
59
|
+
)
|
|
60
|
+
) AS run_count,
|
|
54
61
|
SUM(COALESCE(input_tokens, 0)) AS input_tokens,
|
|
55
62
|
SUM(COALESCE(output_tokens, 0)) AS output_tokens,
|
|
56
63
|
SUM(COALESCE(cache_read_tokens, 0)) AS cache_read_tokens,
|
|
@@ -67,6 +74,28 @@ const getSessionUsageByKeyPattern = ({ keyPattern = "", sinceMs = 0 } = {}) => {
|
|
|
67
74
|
$keyPattern: normalizedPattern,
|
|
68
75
|
$sinceMs: Number.isFinite(Number(sinceMs)) ? Number(sinceMs) : 0,
|
|
69
76
|
});
|
|
77
|
+
const totalsRow = database
|
|
78
|
+
.prepare(
|
|
79
|
+
`
|
|
80
|
+
SELECT
|
|
81
|
+
COUNT(*) AS event_count,
|
|
82
|
+
COUNT(
|
|
83
|
+
DISTINCT COALESCE(
|
|
84
|
+
NULLIF(run_id, ''),
|
|
85
|
+
NULLIF(session_key, ''),
|
|
86
|
+
NULLIF(session_id, ''),
|
|
87
|
+
printf('event:%d', id)
|
|
88
|
+
)
|
|
89
|
+
) AS run_count
|
|
90
|
+
FROM usage_events
|
|
91
|
+
WHERE session_key LIKE $keyPattern
|
|
92
|
+
AND ($sinceMs <= 0 OR timestamp >= $sinceMs)
|
|
93
|
+
`,
|
|
94
|
+
)
|
|
95
|
+
.get({
|
|
96
|
+
$keyPattern: normalizedPattern,
|
|
97
|
+
$sinceMs: Number.isFinite(Number(sinceMs)) ? Number(sinceMs) : 0,
|
|
98
|
+
}) || {};
|
|
70
99
|
const modelBreakdown = rows.map((row) => {
|
|
71
100
|
const inputTokens = Number(row.input_tokens || 0);
|
|
72
101
|
const outputTokens = Number(row.output_tokens || 0);
|
|
@@ -118,6 +147,8 @@ const getSessionUsageByKeyPattern = ({ keyPattern = "", sinceMs = 0 } = {}) => {
|
|
|
118
147
|
runCount: 0,
|
|
119
148
|
},
|
|
120
149
|
);
|
|
150
|
+
totals.eventCount = Number(totalsRow.event_count || totals.eventCount || 0);
|
|
151
|
+
totals.runCount = Number(totalsRow.run_count || 0);
|
|
121
152
|
|
|
122
153
|
return { totals, modelBreakdown };
|
|
123
154
|
};
|