@chrysb/alphaclaw 0.6.2-beta.2 → 0.6.2-beta.3
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 +9 -1
- package/lib/public/js/components/cron-tab/cron-job-detail.js +161 -127
- package/lib/public/js/components/cron-tab/cron-job-usage.js +11 -18
- package/lib/public/js/components/cron-tab/cron-overview.js +13 -274
- package/lib/public/js/components/cron-tab/cron-run-history-panel.js +296 -0
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +155 -96
- package/lib/public/js/components/cron-tab/index.js +9 -4
- package/lib/public/js/components/cron-tab/use-cron-tab.js +118 -24
- package/lib/public/js/lib/api.js +25 -0
- package/lib/server/cron-service.js +57 -4
- package/lib/server/onboarding/import/import-applier.js +15 -3
- package/lib/server/routes/cron.js +21 -0
- package/package.json +1 -1
package/lib/public/css/cron.css
CHANGED
|
@@ -210,6 +210,9 @@
|
|
|
210
210
|
flex-direction: column;
|
|
211
211
|
gap: 12px;
|
|
212
212
|
min-height: 100%;
|
|
213
|
+
width: min(100%, 1024px);
|
|
214
|
+
margin-left: auto;
|
|
215
|
+
margin-right: auto;
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
.cron-prompt-editor-shell {
|
|
@@ -222,6 +225,11 @@
|
|
|
222
225
|
background: rgba(255, 255, 255, 0.01);
|
|
223
226
|
}
|
|
224
227
|
|
|
228
|
+
.cron-prompt-editor-shell .file-viewer-editor-line-num-col {
|
|
229
|
+
width: 44px;
|
|
230
|
+
padding: 16px 8px 112px 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
225
233
|
.cron-calendar-repeating-strip {
|
|
226
234
|
border: 1px solid var(--border);
|
|
227
235
|
border-radius: 10px;
|
|
@@ -284,7 +292,7 @@
|
|
|
284
292
|
.cron-calendar-grid-header,
|
|
285
293
|
.cron-calendar-grid-row {
|
|
286
294
|
display: grid;
|
|
287
|
-
grid-template-columns:
|
|
295
|
+
grid-template-columns: 80px repeat(7, minmax(80px, 1fr));
|
|
288
296
|
}
|
|
289
297
|
|
|
290
298
|
.cron-calendar-day-header {
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
kLargeFileSimpleEditorLineThreshold,
|
|
12
12
|
} from "../file-viewer/constants.js";
|
|
13
13
|
import { highlightEditorLines } from "../../lib/syntax-highlighters/index.js";
|
|
14
|
-
import { formatDurationCompactMs, formatLocaleDateTimeWithTodayTime } from "../../lib/format.js";
|
|
15
14
|
import {
|
|
16
15
|
formatCronScheduleLabel,
|
|
17
16
|
formatNextRunRelativeMs,
|
|
18
17
|
formatTokenCount,
|
|
19
18
|
} from "./cron-helpers.js";
|
|
20
19
|
import { CronJobUsage } from "./cron-job-usage.js";
|
|
20
|
+
import { CronRunHistoryPanel } from "./cron-run-history-panel.js";
|
|
21
21
|
import { readUiSettings, writeUiSettings } from "../../lib/ui-settings.js";
|
|
22
22
|
|
|
23
23
|
const html = htm.bind(h);
|
|
@@ -41,8 +41,7 @@ const PromptEditor = ({
|
|
|
41
41
|
promptValue = "",
|
|
42
42
|
savedPromptValue = "",
|
|
43
43
|
onChangePrompt = () => {},
|
|
44
|
-
|
|
45
|
-
savingPrompt = false,
|
|
44
|
+
onSaveChanges = () => {},
|
|
46
45
|
}) => {
|
|
47
46
|
const promptEditorShellRef = useRef(null);
|
|
48
47
|
const editorTextareaRef = useRef(null);
|
|
@@ -84,7 +83,7 @@ const PromptEditor = ({
|
|
|
84
83
|
const handleEditorKeyDown = (event) => {
|
|
85
84
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
|
|
86
85
|
event.preventDefault();
|
|
87
|
-
|
|
86
|
+
onSaveChanges();
|
|
88
87
|
}
|
|
89
88
|
if (event.key === "Tab") {
|
|
90
89
|
event.preventDefault();
|
|
@@ -133,17 +132,6 @@ const PromptEditor = ({
|
|
|
133
132
|
Prompt
|
|
134
133
|
${isDirty ? html`<span class="file-viewer-dirty-dot"></span>` : null}
|
|
135
134
|
</h3>
|
|
136
|
-
<div class="flex items-center gap-2">
|
|
137
|
-
<${ActionButton}
|
|
138
|
-
onClick=${onSavePrompt}
|
|
139
|
-
disabled=${!isDirty}
|
|
140
|
-
loading=${savingPrompt}
|
|
141
|
-
tone="primary"
|
|
142
|
-
size="sm"
|
|
143
|
-
idleLabel="Save"
|
|
144
|
-
loadingLabel="Saving..."
|
|
145
|
-
/>
|
|
146
|
-
</div>
|
|
147
135
|
</div>
|
|
148
136
|
<div
|
|
149
137
|
class="cron-prompt-editor-shell"
|
|
@@ -173,15 +161,6 @@ const PromptEditor = ({
|
|
|
173
161
|
`;
|
|
174
162
|
};
|
|
175
163
|
|
|
176
|
-
const runStatusClassName = (status) => {
|
|
177
|
-
const normalized = String(status || "").trim().toLowerCase();
|
|
178
|
-
if (normalized === "ok") return "text-green-300";
|
|
179
|
-
if (normalized === "error") return "text-red-300";
|
|
180
|
-
if (normalized === "skipped") return "text-yellow-300";
|
|
181
|
-
return "text-gray-400";
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const runDeliveryLabel = (run) => String(run?.deliveryStatus || "not-requested");
|
|
185
164
|
const kMetaCardClassName = "ac-surface-inset rounded-lg p-2.5 space-y-1.5";
|
|
186
165
|
const kRunStatusFilterOptions = [
|
|
187
166
|
{ label: "all", value: "all" },
|
|
@@ -189,11 +168,15 @@ const kRunStatusFilterOptions = [
|
|
|
189
168
|
{ label: "error", value: "error" },
|
|
190
169
|
{ label: "skipped", value: "skipped" },
|
|
191
170
|
];
|
|
192
|
-
const
|
|
193
|
-
{ label: "
|
|
194
|
-
{ label: "
|
|
195
|
-
|
|
171
|
+
const kSessionTargetOptions = [
|
|
172
|
+
{ label: "main", value: "main" },
|
|
173
|
+
{ label: "isolated", value: "isolated" },
|
|
174
|
+
];
|
|
175
|
+
const kWakeModeOptions = [
|
|
176
|
+
{ label: "now", value: "now" },
|
|
177
|
+
{ label: "next-heartbeat", value: "next-heartbeat" },
|
|
196
178
|
];
|
|
179
|
+
const kDeliveryNoneValue = "__none__";
|
|
197
180
|
const isSameCalendarDay = (leftDate, rightDate) =>
|
|
198
181
|
leftDate.getFullYear() === rightDate.getFullYear() &&
|
|
199
182
|
leftDate.getMonth() === rightDate.getMonth() &&
|
|
@@ -231,9 +214,7 @@ export const CronJobDetail = ({
|
|
|
231
214
|
runHasMore = false,
|
|
232
215
|
loadingMoreRuns = false,
|
|
233
216
|
runStatusFilter = "all",
|
|
234
|
-
runDeliveryFilter = "all",
|
|
235
217
|
onSetRunStatusFilter = () => {},
|
|
236
|
-
onSetRunDeliveryFilter = () => {},
|
|
237
218
|
onLoadMoreRuns = () => {},
|
|
238
219
|
onRunNow = () => {},
|
|
239
220
|
runningJob = false,
|
|
@@ -245,8 +226,15 @@ export const CronJobDetail = ({
|
|
|
245
226
|
promptValue = "",
|
|
246
227
|
savedPromptValue = "",
|
|
247
228
|
onChangePrompt = () => {},
|
|
248
|
-
|
|
249
|
-
|
|
229
|
+
onSaveChanges = () => {},
|
|
230
|
+
savingChanges = false,
|
|
231
|
+
routingDraft = null,
|
|
232
|
+
onChangeRoutingDraft = () => {},
|
|
233
|
+
deliverySessions = [],
|
|
234
|
+
loadingDeliverySessions = false,
|
|
235
|
+
deliverySessionsError = "",
|
|
236
|
+
destinationSessionKey = "",
|
|
237
|
+
onChangeDestinationSessionKey = () => {},
|
|
250
238
|
}) => {
|
|
251
239
|
if (!job) {
|
|
252
240
|
return html`
|
|
@@ -256,6 +244,50 @@ export const CronJobDetail = ({
|
|
|
256
244
|
`;
|
|
257
245
|
}
|
|
258
246
|
|
|
247
|
+
const sessionTarget = String(
|
|
248
|
+
routingDraft?.sessionTarget || job?.sessionTarget || "main",
|
|
249
|
+
);
|
|
250
|
+
const wakeMode = String(routingDraft?.wakeMode || job?.wakeMode || "now");
|
|
251
|
+
const deliveryMode = String(
|
|
252
|
+
routingDraft?.deliveryMode || job?.delivery?.mode || "none",
|
|
253
|
+
);
|
|
254
|
+
const currentSessionTarget = String(job?.sessionTarget || "main");
|
|
255
|
+
const currentWakeMode = String(job?.wakeMode || "now");
|
|
256
|
+
const currentDeliveryMode = String(job?.delivery?.mode || "none");
|
|
257
|
+
const deliverySessionOptions = useMemo(() => {
|
|
258
|
+
const seenLabels = new Set();
|
|
259
|
+
const deduped = [];
|
|
260
|
+
const selectedKey = String(destinationSessionKey || "").trim();
|
|
261
|
+
let selectedPresent = false;
|
|
262
|
+
(Array.isArray(deliverySessions) ? deliverySessions : []).forEach((sessionRow) => {
|
|
263
|
+
const key = String(sessionRow?.key || "").trim();
|
|
264
|
+
if (!key) return;
|
|
265
|
+
if (key === selectedKey) selectedPresent = true;
|
|
266
|
+
const label = String(sessionRow?.label || sessionRow?.key || "Session").trim();
|
|
267
|
+
const dedupeKey = label.toLowerCase();
|
|
268
|
+
if (seenLabels.has(dedupeKey)) return;
|
|
269
|
+
seenLabels.add(dedupeKey);
|
|
270
|
+
deduped.push(sessionRow);
|
|
271
|
+
});
|
|
272
|
+
if (!selectedPresent && selectedKey) {
|
|
273
|
+
const selectedRow = (Array.isArray(deliverySessions) ? deliverySessions : []).find(
|
|
274
|
+
(sessionRow) => String(sessionRow?.key || "").trim() === selectedKey,
|
|
275
|
+
);
|
|
276
|
+
if (selectedRow) deduped.unshift(selectedRow);
|
|
277
|
+
}
|
|
278
|
+
return deduped;
|
|
279
|
+
}, [deliverySessions, destinationSessionKey]);
|
|
280
|
+
const deliverySelectValue =
|
|
281
|
+
deliveryMode === "announce" && String(destinationSessionKey || "").trim()
|
|
282
|
+
? String(destinationSessionKey || "")
|
|
283
|
+
: kDeliveryNoneValue;
|
|
284
|
+
const isRoutingDirty =
|
|
285
|
+
sessionTarget !== currentSessionTarget ||
|
|
286
|
+
wakeMode !== currentWakeMode ||
|
|
287
|
+
deliveryMode !== currentDeliveryMode;
|
|
288
|
+
const isPromptDirty = promptValue !== savedPromptValue;
|
|
289
|
+
const hasUnsavedChanges = isRoutingDirty || isPromptDirty;
|
|
290
|
+
|
|
259
291
|
return html`
|
|
260
292
|
<div class="cron-detail-scroll">
|
|
261
293
|
<div class="cron-detail-content">
|
|
@@ -265,22 +297,15 @@ export const CronJobDetail = ({
|
|
|
265
297
|
<h2 class="font-semibold text-base text-gray-100">${job.name || job.id}</h2>
|
|
266
298
|
<div class="text-xs text-gray-500 mt-1">ID: <code>${job.id}</code></div>
|
|
267
299
|
</div>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
loading=${runningJob}
|
|
278
|
-
tone="secondary"
|
|
279
|
-
size="sm"
|
|
280
|
-
idleLabel="Run Now"
|
|
281
|
-
loadingLabel="Running..."
|
|
282
|
-
/>
|
|
283
|
-
</div>
|
|
300
|
+
<${ActionButton}
|
|
301
|
+
onClick=${onSaveChanges}
|
|
302
|
+
loading=${savingChanges}
|
|
303
|
+
disabled=${!hasUnsavedChanges}
|
|
304
|
+
tone="primary"
|
|
305
|
+
size="sm"
|
|
306
|
+
idleLabel="Save changes"
|
|
307
|
+
loadingLabel="Saving..."
|
|
308
|
+
/>
|
|
284
309
|
</div>
|
|
285
310
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
|
286
311
|
<div class=${kMetaCardClassName}>
|
|
@@ -304,32 +329,100 @@ export const CronJobDetail = ({
|
|
|
304
329
|
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
305
330
|
<div class=${kMetaCardClassName}>
|
|
306
331
|
<div class="text-gray-500">Session target</div>
|
|
307
|
-
<div class="
|
|
332
|
+
<div class="pt-1">
|
|
333
|
+
<${SegmentedControl}
|
|
334
|
+
options=${kSessionTargetOptions}
|
|
335
|
+
value=${sessionTarget}
|
|
336
|
+
onChange=${(value) =>
|
|
337
|
+
onChangeRoutingDraft((currentValue = {}) => ({
|
|
338
|
+
...currentValue,
|
|
339
|
+
sessionTarget: String(value || "main"),
|
|
340
|
+
}))}
|
|
341
|
+
/>
|
|
342
|
+
</div>
|
|
308
343
|
</div>
|
|
309
344
|
<div class=${kMetaCardClassName}>
|
|
310
345
|
<div class="text-gray-500">Wake mode</div>
|
|
311
|
-
<div class="
|
|
346
|
+
<div class="pt-1">
|
|
347
|
+
<${SegmentedControl}
|
|
348
|
+
options=${kWakeModeOptions}
|
|
349
|
+
value=${wakeMode}
|
|
350
|
+
onChange=${(value) =>
|
|
351
|
+
onChangeRoutingDraft((currentValue = {}) => ({
|
|
352
|
+
...currentValue,
|
|
353
|
+
wakeMode: String(value || "now"),
|
|
354
|
+
}))}
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
312
357
|
</div>
|
|
313
358
|
<div class=${kMetaCardClassName}>
|
|
314
359
|
<div class="text-gray-500">Delivery</div>
|
|
315
|
-
<div class="
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
360
|
+
<div class="pt-1">
|
|
361
|
+
<select
|
|
362
|
+
value=${deliverySelectValue}
|
|
363
|
+
onInput=${(event) => {
|
|
364
|
+
const nextValue = String(event.currentTarget?.value || "");
|
|
365
|
+
if (!nextValue || nextValue === kDeliveryNoneValue) {
|
|
366
|
+
onChangeRoutingDraft((currentValue = {}) => ({
|
|
367
|
+
...currentValue,
|
|
368
|
+
deliveryMode: "none",
|
|
369
|
+
deliveryChannel: "",
|
|
370
|
+
deliveryTo: "",
|
|
371
|
+
}));
|
|
372
|
+
onChangeDestinationSessionKey("");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
onChangeDestinationSessionKey(nextValue);
|
|
376
|
+
onChangeRoutingDraft((currentValue = {}) => ({
|
|
377
|
+
...currentValue,
|
|
378
|
+
deliveryMode: "announce",
|
|
379
|
+
}));
|
|
380
|
+
}}
|
|
381
|
+
disabled=${savingChanges}
|
|
382
|
+
class="w-full bg-black/30 border border-border rounded-lg px-2 py-1.5 text-[11px] text-gray-200 focus:border-gray-500"
|
|
383
|
+
>
|
|
384
|
+
<option value=${kDeliveryNoneValue}>None</option>
|
|
385
|
+
${deliverySessionOptions.map(
|
|
386
|
+
(sessionRow) => html`
|
|
387
|
+
<option value=${String(sessionRow?.key || "")}>
|
|
388
|
+
${String(sessionRow?.label || sessionRow?.key || "Session")}
|
|
389
|
+
</option>
|
|
390
|
+
`,
|
|
391
|
+
)}
|
|
392
|
+
</select>
|
|
322
393
|
</div>
|
|
394
|
+
${loadingDeliverySessions
|
|
395
|
+
? html`<div class="text-[11px] text-gray-500 pt-1">Loading delivery sessions...</div>`
|
|
396
|
+
: null}
|
|
397
|
+
${deliverySessionsError
|
|
398
|
+
? html`<div class="text-[11px] text-red-400 pt-1">${deliverySessionsError}</div>`
|
|
399
|
+
: null}
|
|
323
400
|
</div>
|
|
324
401
|
</div>
|
|
402
|
+
<div class="flex items-center justify-between gap-3">
|
|
403
|
+
<${ToggleSwitch}
|
|
404
|
+
checked=${job.enabled !== false}
|
|
405
|
+
disabled=${togglingJobEnabled || savingChanges}
|
|
406
|
+
onChange=${onToggleEnabled}
|
|
407
|
+
label=${job.enabled === false ? "Disabled" : "Enabled"}
|
|
408
|
+
/>
|
|
409
|
+
<${ActionButton}
|
|
410
|
+
onClick=${onRunNow}
|
|
411
|
+
loading=${runningJob}
|
|
412
|
+
disabled=${hasUnsavedChanges || savingChanges}
|
|
413
|
+
tone="secondary"
|
|
414
|
+
size="sm"
|
|
415
|
+
idleLabel="Run now"
|
|
416
|
+
loadingLabel="Running..."
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
325
419
|
</section>
|
|
326
420
|
|
|
327
421
|
<${PromptEditor}
|
|
328
422
|
promptValue=${promptValue}
|
|
329
423
|
savedPromptValue=${savedPromptValue}
|
|
330
424
|
onChangePrompt=${onChangePrompt}
|
|
331
|
-
|
|
332
|
-
savingPrompt=${savingPrompt}
|
|
425
|
+
onSaveChanges=${onSaveChanges}
|
|
333
426
|
/>
|
|
334
427
|
|
|
335
428
|
<${CronJobUsage}
|
|
@@ -338,73 +431,14 @@ export const CronJobDetail = ({
|
|
|
338
431
|
onSetUsageDays=${onSetUsageDays}
|
|
339
432
|
/>
|
|
340
433
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
value=${runStatusFilter}
|
|
350
|
-
onChange=${onSetRunStatusFilter}
|
|
351
|
-
/>
|
|
352
|
-
<${SegmentedControl}
|
|
353
|
-
options=${kRunDeliveryFilterOptions}
|
|
354
|
-
value=${runDeliveryFilter}
|
|
355
|
-
onChange=${onSetRunDeliveryFilter}
|
|
356
|
-
/>
|
|
357
|
-
</div>
|
|
358
|
-
|
|
359
|
-
${runEntries.length === 0
|
|
360
|
-
? html`<div class="text-sm text-gray-500">No runs found.</div>`
|
|
361
|
-
: html`
|
|
362
|
-
<div class="ac-history-list">
|
|
363
|
-
${runEntries.map(
|
|
364
|
-
(entry) => html`
|
|
365
|
-
<details key=${`${entry.ts}:${entry.sessionKey || ""}`} class="ac-history-item">
|
|
366
|
-
<summary class="ac-history-summary">
|
|
367
|
-
<div class="ac-history-summary-row">
|
|
368
|
-
<span class="inline-flex items-center gap-2 min-w-0">
|
|
369
|
-
<span class="ac-history-toggle shrink-0" aria-hidden="true">▸</span>
|
|
370
|
-
<span class="truncate text-xs text-gray-300">
|
|
371
|
-
${formatLocaleDateTimeWithTodayTime(entry.ts, {
|
|
372
|
-
fallback: "—",
|
|
373
|
-
valueIsEpochMs: true,
|
|
374
|
-
})}
|
|
375
|
-
</span>
|
|
376
|
-
</span>
|
|
377
|
-
<span class="inline-flex items-center gap-3 shrink-0 text-xs">
|
|
378
|
-
<span class=${runStatusClassName(entry.status)}>${entry.status || "unknown"}</span>
|
|
379
|
-
<span class="text-gray-400">${formatDurationCompactMs(entry.durationMs)}</span>
|
|
380
|
-
<span class="text-gray-400">
|
|
381
|
-
${formatTokenCount(entry?.usage?.total_tokens || 0)} tk
|
|
382
|
-
</span>
|
|
383
|
-
<span class="text-gray-500">${runDeliveryLabel(entry)}</span>
|
|
384
|
-
</span>
|
|
385
|
-
</div>
|
|
386
|
-
</summary>
|
|
387
|
-
<div class="ac-history-body space-y-2 text-xs">
|
|
388
|
-
${entry.summary
|
|
389
|
-
? html`<div><span class="text-gray-500">Summary:</span> ${entry.summary}</div>`
|
|
390
|
-
: null}
|
|
391
|
-
${entry.error
|
|
392
|
-
? html`<div class="text-red-300"><span class="text-gray-500">Error:</span> ${entry.error}</div>`
|
|
393
|
-
: null}
|
|
394
|
-
<div class="text-gray-500">
|
|
395
|
-
Model: <span class="text-gray-300 font-mono">${entry.model || "—"}</span>
|
|
396
|
-
${entry.sessionKey
|
|
397
|
-
? html` | Session:
|
|
398
|
-
<span class="text-gray-300 font-mono">${entry.sessionKey}</span>`
|
|
399
|
-
: null}
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
</details>
|
|
403
|
-
`,
|
|
404
|
-
)}
|
|
405
|
-
</div>
|
|
406
|
-
`}
|
|
407
|
-
${runHasMore
|
|
434
|
+
<${CronRunHistoryPanel}
|
|
435
|
+
entryCountLabel=${`${formatTokenCount(runTotal)} entries`}
|
|
436
|
+
primaryFilterOptions=${kRunStatusFilterOptions}
|
|
437
|
+
primaryFilterValue=${runStatusFilter}
|
|
438
|
+
onChangePrimaryFilter=${onSetRunStatusFilter}
|
|
439
|
+
rows=${runEntries}
|
|
440
|
+
variant="detail"
|
|
441
|
+
footer=${runHasMore
|
|
408
442
|
? html`
|
|
409
443
|
<div class="pt-2">
|
|
410
444
|
<${ActionButton}
|
|
@@ -418,7 +452,7 @@ export const CronJobDetail = ({
|
|
|
418
452
|
</div>
|
|
419
453
|
`
|
|
420
454
|
: null}
|
|
421
|
-
|
|
455
|
+
/>
|
|
422
456
|
</div>
|
|
423
457
|
</div>
|
|
424
458
|
`;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
2
|
import htm from "https://esm.sh/htm";
|
|
3
3
|
import { formatCost, formatTokenCount } from "./cron-helpers.js";
|
|
4
|
+
import { SegmentedControl } from "../segmented-control.js";
|
|
4
5
|
|
|
5
6
|
const html = htm.bind(h);
|
|
7
|
+
const kUsageRangeOptions = [
|
|
8
|
+
{ label: "7d", value: 7 },
|
|
9
|
+
{ label: "30d", value: 30 },
|
|
10
|
+
];
|
|
6
11
|
|
|
7
12
|
const resolveDominantModel = (usage = null) => {
|
|
8
13
|
const list = Array.isArray(usage?.modelBreakdown) ? usage.modelBreakdown : [];
|
|
@@ -25,23 +30,11 @@ export const CronJobUsage = ({ usage = null, usageDays = 30, onSetUsageDays = ()
|
|
|
25
30
|
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
26
31
|
<div class="flex items-center justify-between gap-2">
|
|
27
32
|
<h3 class="card-label">Usage</h3>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class=${`text-xs px-2 py-1 rounded border ${
|
|
34
|
-
usageDays === days
|
|
35
|
-
? "border-cyan-400 text-cyan-200 bg-cyan-400/10"
|
|
36
|
-
: "border-border text-gray-400 hover:text-gray-200"
|
|
37
|
-
}`}
|
|
38
|
-
onclick=${() => onSetUsageDays(days)}
|
|
39
|
-
>
|
|
40
|
-
${days}d
|
|
41
|
-
</button>
|
|
42
|
-
`,
|
|
43
|
-
)}
|
|
44
|
-
</div>
|
|
33
|
+
<${SegmentedControl}
|
|
34
|
+
options=${kUsageRangeOptions}
|
|
35
|
+
value=${usageDays}
|
|
36
|
+
onChange=${onSetUsageDays}
|
|
37
|
+
/>
|
|
45
38
|
</div>
|
|
46
39
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
|
47
40
|
<div class="ac-surface-inset rounded-lg p-2">
|
|
@@ -62,7 +55,7 @@ export const CronJobUsage = ({ usage = null, usageDays = 30, onSetUsageDays = ()
|
|
|
62
55
|
</div>
|
|
63
56
|
</div>
|
|
64
57
|
<div class="text-xs text-gray-500">
|
|
65
|
-
Dominant model:
|
|
58
|
+
Dominant model:
|
|
66
59
|
<span class="text-gray-300 font-mono">${resolveDominantModel(usage)}</span>
|
|
67
60
|
</div>
|
|
68
61
|
</section>
|