@chrysb/alphaclaw 0.6.0 → 0.6.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/public/css/agents.css +1 -1
- package/lib/public/css/cron.css +535 -0
- package/lib/public/css/theme.css +72 -0
- package/lib/public/js/app.js +45 -10
- package/lib/public/js/components/action-button.js +26 -20
- package/lib/public/js/components/agents-tab/agent-detail-panel.js +98 -17
- package/lib/public/js/components/agents-tab/agent-tools/index.js +105 -0
- package/lib/public/js/components/agents-tab/agent-tools/tool-catalog.js +289 -0
- package/lib/public/js/components/agents-tab/agent-tools/use-agent-tools.js +128 -0
- package/lib/public/js/components/agents-tab/index.js +4 -0
- package/lib/public/js/components/cron-tab/cron-calendar-helpers.js +385 -0
- package/lib/public/js/components/cron-tab/cron-calendar.js +441 -0
- package/lib/public/js/components/cron-tab/cron-helpers.js +326 -0
- package/lib/public/js/components/cron-tab/cron-job-detail.js +425 -0
- package/lib/public/js/components/cron-tab/cron-job-list.js +305 -0
- package/lib/public/js/components/cron-tab/cron-job-usage.js +70 -0
- package/lib/public/js/components/cron-tab/cron-overview.js +599 -0
- package/lib/public/js/components/cron-tab/cron-runs-trend-card.js +277 -0
- package/lib/public/js/components/cron-tab/index.js +100 -0
- package/lib/public/js/components/cron-tab/use-cron-tab.js +366 -0
- package/lib/public/js/components/doctor/summary-cards.js +5 -11
- package/lib/public/js/components/google/gmail-setup-wizard.js +30 -30
- package/lib/public/js/components/google/index.js +1 -1
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/pill-tabs.js +33 -0
- package/lib/public/js/components/pop-actions.js +58 -0
- package/lib/public/js/components/routes/agents-route.js +4 -0
- package/lib/public/js/components/routes/cron-route.js +9 -0
- package/lib/public/js/components/routes/index.js +1 -0
- package/lib/public/js/components/segmented-control.js +15 -9
- package/lib/public/js/components/summary-stat-card.js +17 -0
- package/lib/public/js/components/tooltip.js +50 -4
- package/lib/public/js/components/watchdog-tab.js +46 -1
- package/lib/public/js/lib/api.js +94 -0
- package/lib/public/js/lib/app-navigation.js +2 -0
- package/lib/public/js/lib/storage-keys.js +1 -0
- package/lib/public/setup.html +1 -0
- package/lib/server/agents/agents.js +15 -0
- package/lib/server/constants.js +1 -0
- package/lib/server/cost-utils.js +312 -0
- package/lib/server/cron-service.js +461 -0
- package/lib/server/db/usage/index.js +100 -1
- package/lib/server/db/usage/pricing.js +1 -83
- package/lib/server/db/usage/sessions.js +4 -1
- package/lib/server/db/usage/shared.js +2 -1
- package/lib/server/db/usage/summary.js +5 -1
- package/lib/server/gmail-watch.js +0 -1
- package/lib/server/onboarding/index.js +39 -5
- package/lib/server/onboarding/openclaw.js +25 -19
- package/lib/server/onboarding/validation.js +28 -0
- package/lib/server/routes/cron.js +148 -0
- package/lib/server.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
import { ActionButton } from "../action-button.js";
|
|
5
|
+
import { SegmentedControl } from "../segmented-control.js";
|
|
6
|
+
import { ToggleSwitch } from "../toggle-switch.js";
|
|
7
|
+
import { EditorSurface } from "../file-viewer/editor-surface.js";
|
|
8
|
+
import { countTextLines, shouldUseSimpleEditorMode } from "../file-viewer/utils.js";
|
|
9
|
+
import {
|
|
10
|
+
kLargeFileSimpleEditorCharThreshold,
|
|
11
|
+
kLargeFileSimpleEditorLineThreshold,
|
|
12
|
+
} from "../file-viewer/constants.js";
|
|
13
|
+
import { highlightEditorLines } from "../../lib/syntax-highlighters/index.js";
|
|
14
|
+
import { formatDurationCompactMs, formatLocaleDateTimeWithTodayTime } from "../../lib/format.js";
|
|
15
|
+
import {
|
|
16
|
+
formatCronScheduleLabel,
|
|
17
|
+
formatNextRunRelativeMs,
|
|
18
|
+
formatTokenCount,
|
|
19
|
+
} from "./cron-helpers.js";
|
|
20
|
+
import { CronJobUsage } from "./cron-job-usage.js";
|
|
21
|
+
import { readUiSettings, writeUiSettings } from "../../lib/ui-settings.js";
|
|
22
|
+
|
|
23
|
+
const html = htm.bind(h);
|
|
24
|
+
const kCronPromptEditorHeightUiSettingKey = "cronPromptEditorHeightPx";
|
|
25
|
+
const kCronPromptEditorDefaultHeightPx = 280;
|
|
26
|
+
const kCronPromptEditorMinHeightPx = 180;
|
|
27
|
+
const clampPromptEditorHeight = (value) => {
|
|
28
|
+
const parsed = Number(value);
|
|
29
|
+
const normalized = Number.isFinite(parsed)
|
|
30
|
+
? Math.round(parsed)
|
|
31
|
+
: kCronPromptEditorDefaultHeightPx;
|
|
32
|
+
return Math.max(kCronPromptEditorMinHeightPx, normalized);
|
|
33
|
+
};
|
|
34
|
+
const readCssHeightPx = (element) => {
|
|
35
|
+
if (!element) return 0;
|
|
36
|
+
const computedHeight = Number.parseFloat(window.getComputedStyle(element).height || "0");
|
|
37
|
+
return Number.isFinite(computedHeight) ? computedHeight : 0;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const PromptEditor = ({
|
|
41
|
+
promptValue = "",
|
|
42
|
+
savedPromptValue = "",
|
|
43
|
+
onChangePrompt = () => {},
|
|
44
|
+
onSavePrompt = () => {},
|
|
45
|
+
savingPrompt = false,
|
|
46
|
+
}) => {
|
|
47
|
+
const promptEditorShellRef = useRef(null);
|
|
48
|
+
const editorTextareaRef = useRef(null);
|
|
49
|
+
const editorLineNumbersRef = useRef(null);
|
|
50
|
+
const editorLineNumberRowRefs = useRef([]);
|
|
51
|
+
const editorHighlightRef = useRef(null);
|
|
52
|
+
const editorHighlightLineRefs = useRef([]);
|
|
53
|
+
const [promptEditorHeightPx, setPromptEditorHeightPx] = useState(() => {
|
|
54
|
+
const settings = readUiSettings();
|
|
55
|
+
return clampPromptEditorHeight(settings?.[kCronPromptEditorHeightUiSettingKey]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const lineCount = countTextLines(promptValue);
|
|
59
|
+
const editorLineNumbers = useMemo(
|
|
60
|
+
() => Array.from({ length: lineCount }, (_, index) => index + 1),
|
|
61
|
+
[lineCount],
|
|
62
|
+
);
|
|
63
|
+
const shouldUseHighlightedEditor = !shouldUseSimpleEditorMode({
|
|
64
|
+
contentLength: promptValue.length,
|
|
65
|
+
lineCount,
|
|
66
|
+
charThreshold: kLargeFileSimpleEditorCharThreshold,
|
|
67
|
+
lineThreshold: kLargeFileSimpleEditorLineThreshold,
|
|
68
|
+
});
|
|
69
|
+
const highlightedEditorLines = useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
shouldUseHighlightedEditor
|
|
72
|
+
? highlightEditorLines(promptValue, "markdown")
|
|
73
|
+
: [],
|
|
74
|
+
[promptValue, shouldUseHighlightedEditor],
|
|
75
|
+
);
|
|
76
|
+
const isDirty = promptValue !== savedPromptValue;
|
|
77
|
+
|
|
78
|
+
const handleEditorScroll = (event) => {
|
|
79
|
+
const scrollTop = event.currentTarget.scrollTop;
|
|
80
|
+
if (editorLineNumbersRef.current) editorLineNumbersRef.current.scrollTop = scrollTop;
|
|
81
|
+
if (editorHighlightRef.current) editorHighlightRef.current.scrollTop = scrollTop;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleEditorKeyDown = (event) => {
|
|
85
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
onSavePrompt();
|
|
88
|
+
}
|
|
89
|
+
if (event.key === "Tab") {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
const textarea = editorTextareaRef.current;
|
|
92
|
+
if (!textarea) return;
|
|
93
|
+
const start = textarea.selectionStart;
|
|
94
|
+
const end = textarea.selectionEnd;
|
|
95
|
+
const nextValue = `${promptValue.slice(0, start)} ${promptValue.slice(end)}`;
|
|
96
|
+
onChangePrompt(nextValue);
|
|
97
|
+
window.requestAnimationFrame(() => {
|
|
98
|
+
textarea.selectionStart = start + 2;
|
|
99
|
+
textarea.selectionEnd = start + 2;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const shellElement = promptEditorShellRef.current;
|
|
106
|
+
if (!shellElement || typeof ResizeObserver === "undefined") return () => {};
|
|
107
|
+
|
|
108
|
+
let saveTimer = null;
|
|
109
|
+
const observer = new ResizeObserver((entries) => {
|
|
110
|
+
const entry = entries?.[0];
|
|
111
|
+
const nextHeight = clampPromptEditorHeight(readCssHeightPx(entry?.target));
|
|
112
|
+
setPromptEditorHeightPx((currentValue) =>
|
|
113
|
+
Math.abs(currentValue - nextHeight) >= 1 ? nextHeight : currentValue
|
|
114
|
+
);
|
|
115
|
+
if (saveTimer) window.clearTimeout(saveTimer);
|
|
116
|
+
saveTimer = window.setTimeout(() => {
|
|
117
|
+
const settings = readUiSettings();
|
|
118
|
+
settings[kCronPromptEditorHeightUiSettingKey] = nextHeight;
|
|
119
|
+
writeUiSettings(settings);
|
|
120
|
+
}, 120);
|
|
121
|
+
});
|
|
122
|
+
observer.observe(shellElement);
|
|
123
|
+
return () => {
|
|
124
|
+
observer.disconnect();
|
|
125
|
+
if (saveTimer) window.clearTimeout(saveTimer);
|
|
126
|
+
};
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
return html`
|
|
130
|
+
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
131
|
+
<div class="flex items-center justify-between gap-2">
|
|
132
|
+
<h3 class="card-label inline-flex items-center gap-1.5">
|
|
133
|
+
Prompt
|
|
134
|
+
${isDirty ? html`<span class="file-viewer-dirty-dot"></span>` : null}
|
|
135
|
+
</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
|
+
</div>
|
|
148
|
+
<div
|
|
149
|
+
class="cron-prompt-editor-shell"
|
|
150
|
+
ref=${promptEditorShellRef}
|
|
151
|
+
style=${{ height: `${promptEditorHeightPx}px` }}
|
|
152
|
+
>
|
|
153
|
+
<${EditorSurface}
|
|
154
|
+
editorShellClassName="file-viewer-editor-shell"
|
|
155
|
+
editorLineNumbers=${editorLineNumbers}
|
|
156
|
+
editorLineNumbersRef=${editorLineNumbersRef}
|
|
157
|
+
editorLineNumberRowRefs=${editorLineNumberRowRefs}
|
|
158
|
+
shouldUseHighlightedEditor=${shouldUseHighlightedEditor}
|
|
159
|
+
highlightedEditorLines=${highlightedEditorLines}
|
|
160
|
+
editorHighlightRef=${editorHighlightRef}
|
|
161
|
+
editorHighlightLineRefs=${editorHighlightLineRefs}
|
|
162
|
+
editorTextareaRef=${editorTextareaRef}
|
|
163
|
+
renderContent=${promptValue}
|
|
164
|
+
handleContentInput=${(event) => onChangePrompt(event.target.value)}
|
|
165
|
+
handleEditorKeyDown=${handleEditorKeyDown}
|
|
166
|
+
handleEditorScroll=${handleEditorScroll}
|
|
167
|
+
handleEditorSelectionChange=${() => {}}
|
|
168
|
+
isEditBlocked=${false}
|
|
169
|
+
isPreviewOnly=${false}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
`;
|
|
174
|
+
};
|
|
175
|
+
|
|
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
|
+
const kMetaCardClassName = "ac-surface-inset rounded-lg p-2.5 space-y-1.5";
|
|
186
|
+
const kRunStatusFilterOptions = [
|
|
187
|
+
{ label: "all", value: "all" },
|
|
188
|
+
{ label: "ok", value: "ok" },
|
|
189
|
+
{ label: "error", value: "error" },
|
|
190
|
+
{ label: "skipped", value: "skipped" },
|
|
191
|
+
];
|
|
192
|
+
const kRunDeliveryFilterOptions = [
|
|
193
|
+
{ label: "all", value: "all" },
|
|
194
|
+
{ label: "delivered", value: "delivered" },
|
|
195
|
+
{ label: "not-delivered", value: "not-delivered" },
|
|
196
|
+
];
|
|
197
|
+
const isSameCalendarDay = (leftDate, rightDate) =>
|
|
198
|
+
leftDate.getFullYear() === rightDate.getFullYear() &&
|
|
199
|
+
leftDate.getMonth() === rightDate.getMonth() &&
|
|
200
|
+
leftDate.getDate() === rightDate.getDate();
|
|
201
|
+
|
|
202
|
+
const formatCompactMeridiemTime = (dateValue) =>
|
|
203
|
+
dateValue
|
|
204
|
+
.toLocaleTimeString([], {
|
|
205
|
+
hour: "numeric",
|
|
206
|
+
minute: "2-digit",
|
|
207
|
+
})
|
|
208
|
+
.replace(/\s*([AP])M$/i, (_, marker) => `${String(marker || "").toLowerCase()}m`)
|
|
209
|
+
.replace(/\s+/g, "");
|
|
210
|
+
|
|
211
|
+
const formatNextRunAbsolute = (value) => {
|
|
212
|
+
const timestamp = Number(value || 0);
|
|
213
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) return "—";
|
|
214
|
+
const dateValue = new Date(timestamp);
|
|
215
|
+
if (Number.isNaN(dateValue.getTime())) return "—";
|
|
216
|
+
const nowValue = new Date();
|
|
217
|
+
const tomorrowValue = new Date(nowValue);
|
|
218
|
+
tomorrowValue.setDate(nowValue.getDate() + 1);
|
|
219
|
+
const isToday = isSameCalendarDay(dateValue, nowValue);
|
|
220
|
+
const isTomorrow = isSameCalendarDay(dateValue, tomorrowValue);
|
|
221
|
+
const compactTime = formatCompactMeridiemTime(dateValue);
|
|
222
|
+
if (isToday) return compactTime;
|
|
223
|
+
if (isTomorrow) return `Tomorrow ${compactTime}`;
|
|
224
|
+
return `${dateValue.toLocaleDateString()} ${compactTime}`;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const CronJobDetail = ({
|
|
228
|
+
job = null,
|
|
229
|
+
runEntries = [],
|
|
230
|
+
runTotal = 0,
|
|
231
|
+
runHasMore = false,
|
|
232
|
+
loadingMoreRuns = false,
|
|
233
|
+
runStatusFilter = "all",
|
|
234
|
+
runDeliveryFilter = "all",
|
|
235
|
+
onSetRunStatusFilter = () => {},
|
|
236
|
+
onSetRunDeliveryFilter = () => {},
|
|
237
|
+
onLoadMoreRuns = () => {},
|
|
238
|
+
onRunNow = () => {},
|
|
239
|
+
runningJob = false,
|
|
240
|
+
onToggleEnabled = () => {},
|
|
241
|
+
togglingJobEnabled = false,
|
|
242
|
+
usage = null,
|
|
243
|
+
usageDays = 30,
|
|
244
|
+
onSetUsageDays = () => {},
|
|
245
|
+
promptValue = "",
|
|
246
|
+
savedPromptValue = "",
|
|
247
|
+
onChangePrompt = () => {},
|
|
248
|
+
onSavePrompt = () => {},
|
|
249
|
+
savingPrompt = false,
|
|
250
|
+
}) => {
|
|
251
|
+
if (!job) {
|
|
252
|
+
return html`
|
|
253
|
+
<div class="h-full flex items-center justify-center text-sm text-gray-500">
|
|
254
|
+
Select a cron job to view details.
|
|
255
|
+
</div>
|
|
256
|
+
`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return html`
|
|
260
|
+
<div class="cron-detail-scroll">
|
|
261
|
+
<div class="cron-detail-content">
|
|
262
|
+
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
263
|
+
<div class="flex items-center justify-between gap-3">
|
|
264
|
+
<div>
|
|
265
|
+
<h2 class="font-semibold text-base text-gray-100">${job.name || job.id}</h2>
|
|
266
|
+
<div class="text-xs text-gray-500 mt-1">ID: <code>${job.id}</code></div>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="flex items-center gap-2">
|
|
269
|
+
<${ToggleSwitch}
|
|
270
|
+
checked=${job.enabled !== false}
|
|
271
|
+
disabled=${togglingJobEnabled}
|
|
272
|
+
onChange=${onToggleEnabled}
|
|
273
|
+
label=${job.enabled === false ? "Disabled" : "Enabled"}
|
|
274
|
+
/>
|
|
275
|
+
<${ActionButton}
|
|
276
|
+
onClick=${onRunNow}
|
|
277
|
+
loading=${runningJob}
|
|
278
|
+
tone="secondary"
|
|
279
|
+
size="sm"
|
|
280
|
+
idleLabel="Run Now"
|
|
281
|
+
loadingLabel="Running..."
|
|
282
|
+
/>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="grid grid-cols-2 gap-2 text-xs">
|
|
286
|
+
<div class=${kMetaCardClassName}>
|
|
287
|
+
<div class="text-gray-500">Schedule</div>
|
|
288
|
+
<div class="text-gray-300 font-mono">
|
|
289
|
+
${formatCronScheduleLabel(job.schedule, {
|
|
290
|
+
includeTimeZoneWhenDifferent: true,
|
|
291
|
+
})}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
<div class=${kMetaCardClassName}>
|
|
295
|
+
<div class="text-gray-500">Next run</div>
|
|
296
|
+
<div class="text-gray-300 font-mono">
|
|
297
|
+
${formatNextRunAbsolute(job?.state?.nextRunAtMs)}
|
|
298
|
+
<span class="text-gray-500">
|
|
299
|
+
${` (${formatNextRunRelativeMs(job?.state?.nextRunAtMs)})`}
|
|
300
|
+
</span>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="grid grid-cols-3 gap-2 text-xs">
|
|
305
|
+
<div class=${kMetaCardClassName}>
|
|
306
|
+
<div class="text-gray-500">Session target</div>
|
|
307
|
+
<div class="text-gray-300 font-mono">${job.sessionTarget || "main"}</div>
|
|
308
|
+
</div>
|
|
309
|
+
<div class=${kMetaCardClassName}>
|
|
310
|
+
<div class="text-gray-500">Wake mode</div>
|
|
311
|
+
<div class="text-gray-300 font-mono">${job.wakeMode || "now"}</div>
|
|
312
|
+
</div>
|
|
313
|
+
<div class=${kMetaCardClassName}>
|
|
314
|
+
<div class="text-gray-500">Delivery</div>
|
|
315
|
+
<div class="text-gray-300 font-mono">
|
|
316
|
+
${String(job?.delivery?.mode || "none")}
|
|
317
|
+
${job?.delivery?.channel
|
|
318
|
+
? html`- ${job.delivery.channel}${job?.delivery?.to
|
|
319
|
+
? `:${job.delivery.to}`
|
|
320
|
+
: ""}`
|
|
321
|
+
: ""}
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</section>
|
|
326
|
+
|
|
327
|
+
<${PromptEditor}
|
|
328
|
+
promptValue=${promptValue}
|
|
329
|
+
savedPromptValue=${savedPromptValue}
|
|
330
|
+
onChangePrompt=${onChangePrompt}
|
|
331
|
+
onSavePrompt=${onSavePrompt}
|
|
332
|
+
savingPrompt=${savingPrompt}
|
|
333
|
+
/>
|
|
334
|
+
|
|
335
|
+
<${CronJobUsage}
|
|
336
|
+
usage=${usage}
|
|
337
|
+
usageDays=${usageDays}
|
|
338
|
+
onSetUsageDays=${onSetUsageDays}
|
|
339
|
+
/>
|
|
340
|
+
|
|
341
|
+
<section class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
342
|
+
<div class="flex items-center justify-between gap-2">
|
|
343
|
+
<h3 class="card-label card-label-bright">Run history</h3>
|
|
344
|
+
<div class="text-xs text-gray-500">${formatTokenCount(runTotal)} entries</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div class="flex items-center gap-2">
|
|
347
|
+
<${SegmentedControl}
|
|
348
|
+
options=${kRunStatusFilterOptions}
|
|
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
|
|
408
|
+
? html`
|
|
409
|
+
<div class="pt-2">
|
|
410
|
+
<${ActionButton}
|
|
411
|
+
onClick=${onLoadMoreRuns}
|
|
412
|
+
loading=${loadingMoreRuns}
|
|
413
|
+
tone="secondary"
|
|
414
|
+
size="sm"
|
|
415
|
+
idleLabel="Load More"
|
|
416
|
+
loadingLabel="Loading..."
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
`
|
|
420
|
+
: null}
|
|
421
|
+
</section>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
`;
|
|
425
|
+
};
|