opencami 1.4.0 → 1.5.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.
Files changed (77) hide show
  1. package/README.md +60 -0
  2. package/dist/client/assets/CSPContext-EgWK8bIJ.js +1 -0
  3. package/dist/client/assets/DirectionContext-DXtY05YF.js +1 -0
  4. package/dist/client/assets/_sessionKey-B89e7G3y.js +100 -0
  5. package/dist/client/assets/agents-screen-1BiEZ9od.js +1 -0
  6. package/dist/client/assets/agents-x54ocA9z.js +2 -0
  7. package/dist/client/assets/bots-screen-BNQciUeJ.js +1 -0
  8. package/dist/client/assets/bots-x86ZHG4b.js +2 -0
  9. package/dist/client/assets/button-nDcsaNPl.js +1 -0
  10. package/dist/client/assets/{connect-D3baVDFL.js → connect-w4lLOqiJ.js} +1 -1
  11. package/dist/client/assets/file-explorer-screen-CAsjd3w8.js +1 -0
  12. package/dist/client/assets/files-Bype5Mnb.js +2 -0
  13. package/dist/client/assets/{index-ByIsZcHh.js → index-36G0WCxU.js} +1 -1
  14. package/dist/client/assets/{index-CVV4XiZo.js → index-BXkRE220.js} +2 -2
  15. package/dist/client/assets/keyboard-shortcuts-dialog-BdCeXRjD.js +1 -0
  16. package/dist/client/assets/{main-CkIF0soY.js → main-B3N0eQFg.js} +129 -17
  17. package/dist/client/assets/{opencami-logo-CIxSO1oo.js → opencami-logo-DD0DPFRQ.js} +1 -1
  18. package/dist/client/assets/{react-BhVdgA5r.js → react-B16OrBeM.js} +1 -1
  19. package/dist/client/assets/search-dialog-BjTPceEl.js +1 -0
  20. package/dist/client/assets/session-export-dialog-DtHKG2zW.js +1 -0
  21. package/dist/client/assets/settings-dialog-hiqdk_UD.js +1 -0
  22. package/dist/client/assets/skills-DhwyFq3y.js +2 -0
  23. package/dist/client/assets/skills-panel-BLUjzfjJ.js +5 -0
  24. package/dist/client/assets/styles-CHP4l6vZ.css +1 -0
  25. package/dist/client/assets/switch-J6wLIVu2.js +1 -0
  26. package/dist/client/assets/tabs-DvPgTz5I.js +1 -0
  27. package/dist/client/assets/tooltip-C14vdXHK.js +1 -0
  28. package/dist/client/assets/use-file-explorer-state-BnaJEqRP.js +12 -0
  29. package/dist/client/assets/useButton-Bnnac1eR.js +1 -0
  30. package/dist/client/assets/useCompositeItem-BgiEMKAt.js +1 -0
  31. package/dist/client/assets/{useControlled-Y306krcC.js → useControlled-BhUuiHAm.js} +1 -1
  32. package/dist/client/assets/{useMutation-0WgW4xQJ.js → useMutation-CFmVaBag.js} +1 -1
  33. package/dist/client/assets/visuallyHidden-DCCICp6T.js +9 -0
  34. package/dist/server/assets/{_sessionKey-BhFH4uWY.js → _sessionKey-tRze5NLR.js} +178 -46
  35. package/dist/server/assets/_tanstack-start-manifest_v-CyfoMvUa.js +4 -0
  36. package/dist/server/assets/{agents-Dz_i76VW.js → agents-CmQ4vvXm.js} +1 -1
  37. package/dist/server/assets/{agents-screen-CqQPJndp.js → agents-screen-bmrIyFbk.js} +43 -35
  38. package/dist/server/assets/{bots-6ryCIgKh.js → bots-Byt6jv0a.js} +1 -1
  39. package/dist/server/assets/bots-screen-C2TGFv42.js +474 -0
  40. package/dist/server/assets/{button-DtQ3rV1m.js → button-CwY2OHFj.js} +2 -2
  41. package/dist/server/assets/{connect-B8jpGQGK.js → connect-d3AqjAqe.js} +2 -2
  42. package/dist/server/assets/{file-explorer-screen-DCfS_Ajx.js → file-explorer-screen-CVlFiAFu.js} +36 -36
  43. package/dist/server/assets/{files-D2GIrPF4.js → files-BIEcSPGp.js} +1 -1
  44. package/dist/server/assets/{index-BNSsDaLb.js → index-CNIATlJ9.js} +22 -3
  45. package/dist/server/assets/{index-B2JHn34C.js → index-CRfLKh30.js} +2 -1
  46. package/dist/server/assets/{keyboard-shortcuts-dialog-CqIm8aYF.js → keyboard-shortcuts-dialog-CsNP85q8.js} +2 -2
  47. package/dist/server/assets/{router-Dme7USeO.js → router-rn7pJO_D.js} +356 -64
  48. package/dist/server/assets/{search-dialog-DG0D9KRN.js → search-dialog-Bz4Cu0KW.js} +23 -6
  49. package/dist/server/assets/{session-export-dialog-DLPZVlQV.js → session-export-dialog-CwclV0Aj.js} +2 -2
  50. package/dist/server/assets/{settings-dialog-BaGT4e5l.js → settings-dialog-BBM7jCjE.js} +386 -95
  51. package/dist/server/assets/skills-Cy8xclXY.js +11 -0
  52. package/dist/server/assets/skills-panel-BnRNb7u9.js +762 -0
  53. package/dist/server/assets/{switch-DnX0MjGS.js → switch-BbkUeVDV.js} +1 -1
  54. package/dist/server/assets/tabs-DDFZob0m.js +67 -0
  55. package/dist/server/assets/{tooltip-gbV6rEVv.js → tooltip-DgsSPocE.js} +1 -1
  56. package/dist/server/assets/{use-file-explorer-state-DfAKF2gZ.js → use-file-explorer-state-Il1LlBAe.js} +1 -1
  57. package/dist/server/server.js +2 -2
  58. package/package.json +6 -2
  59. package/dist/client/assets/_sessionKey-DB95zj1L.js +0 -97
  60. package/dist/client/assets/agents-LFrWe-HX.js +0 -2
  61. package/dist/client/assets/agents-screen-CEBBk1yO.js +0 -1
  62. package/dist/client/assets/bots-CxDwf_WK.js +0 -2
  63. package/dist/client/assets/bots-screen-D6qma1wK.js +0 -1
  64. package/dist/client/assets/button-Il3CHIzX.js +0 -1
  65. package/dist/client/assets/file-explorer-screen-rtV6n-5_.js +0 -1
  66. package/dist/client/assets/files-DMemuq9D.js +0 -2
  67. package/dist/client/assets/keyboard-shortcuts-dialog-DXC0YHoy.js +0 -1
  68. package/dist/client/assets/search-dialog-I1jJplIh.js +0 -1
  69. package/dist/client/assets/session-export-dialog-CH5unryw.js +0 -1
  70. package/dist/client/assets/settings-dialog-B8v-GVJ8.js +0 -1
  71. package/dist/client/assets/styles-BaTVzdPa.css +0 -1
  72. package/dist/client/assets/switch-sQnv1YsK.js +0 -1
  73. package/dist/client/assets/tooltip-j_viC_EE.js +0 -1
  74. package/dist/client/assets/use-file-explorer-state-BUH-u7Jv.js +0 -12
  75. package/dist/client/assets/useButton-9VAzplAB.js +0 -9
  76. package/dist/server/assets/_tanstack-start-manifest_v-BaIrL1VQ.js +0 -4
  77. package/dist/server/assets/bots-screen-DS_ZF9Ec.js +0 -417
@@ -1,417 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback, useMemo } from "react";
3
- import { HugeiconsIcon } from "@hugeicons/react";
4
- import { Loading01Icon, Clock01Icon, PlayIcon, SmartPhone01Icon, ArrowLeft01Icon } from "@hugeicons/core-free-icons";
5
- import { Link } from "@tanstack/react-router";
6
- import { c as cn, B as Button, b as buttonVariants } from "./button-DtQ3rV1m.js";
7
- import { motion, AnimatePresence } from "motion/react";
8
- import { S as Switch } from "./switch-DnX0MjGS.js";
9
- import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
10
- import "@base-ui/react/merge-props";
11
- import "@base-ui/react/use-render";
12
- import "class-variance-authority";
13
- import "clsx";
14
- import "tailwind-merge";
15
- import "@base-ui/react/switch";
16
- const CRON_JOBS_KEY = ["cron-jobs"];
17
- function useCronJobs() {
18
- return useQuery({
19
- queryKey: CRON_JOBS_KEY,
20
- queryFn: async ({ signal }) => {
21
- const controller = new AbortController();
22
- const onAbort = () => controller.abort();
23
- signal.addEventListener("abort", onAbort);
24
- try {
25
- const res = await fetch("/api/cron", { signal: controller.signal });
26
- if (!res.ok) throw new Error("Failed to fetch cron jobs");
27
- const data = await res.json();
28
- return Array.isArray(data.jobs) ? data.jobs : [];
29
- } finally {
30
- signal.removeEventListener("abort", onAbort);
31
- }
32
- },
33
- refetchInterval: 3e4
34
- });
35
- }
36
- function useCronJobRuns(jobId) {
37
- return useQuery({
38
- queryKey: ["cron-job-runs", jobId],
39
- queryFn: async ({ signal }) => {
40
- if (!jobId) return [];
41
- const controller = new AbortController();
42
- const onAbort = () => controller.abort();
43
- signal.addEventListener("abort", onAbort);
44
- try {
45
- const res = await fetch(`/api/cron?jobId=${encodeURIComponent(jobId)}`, {
46
- signal: controller.signal
47
- });
48
- if (!res.ok) throw new Error("Failed to fetch cron job runs");
49
- const data = await res.json();
50
- return Array.isArray(data.runs) ? data.runs : [];
51
- } finally {
52
- signal.removeEventListener("abort", onAbort);
53
- }
54
- },
55
- enabled: !!jobId
56
- });
57
- }
58
- function useRunCronJob() {
59
- const queryClient = useQueryClient();
60
- return useMutation({
61
- mutationFn: async (jobId) => {
62
- const controller = new AbortController();
63
- const res = await fetch("/api/cron", {
64
- method: "POST",
65
- headers: { "Content-Type": "application/json" },
66
- body: JSON.stringify({ jobId }),
67
- signal: controller.signal
68
- });
69
- if (!res.ok) throw new Error("Failed to run cron job");
70
- return res.json();
71
- },
72
- onSuccess: () => {
73
- void queryClient.invalidateQueries({ queryKey: CRON_JOBS_KEY });
74
- }
75
- });
76
- }
77
- function useToggleCronJob() {
78
- const queryClient = useQueryClient();
79
- return useMutation({
80
- mutationFn: async ({ jobId, enabled }) => {
81
- const controller = new AbortController();
82
- const res = await fetch("/api/cron", {
83
- method: "PATCH",
84
- headers: { "Content-Type": "application/json" },
85
- body: JSON.stringify({ jobId, patch: { enabled } }),
86
- signal: controller.signal
87
- });
88
- if (!res.ok) throw new Error("Failed to update cron job");
89
- return res.json();
90
- },
91
- onSuccess: () => {
92
- void queryClient.invalidateQueries({ queryKey: CRON_JOBS_KEY });
93
- }
94
- });
95
- }
96
- function formatDuration(ms) {
97
- if (!ms || ms <= 0) return "—";
98
- if (ms < 1e3) return `${ms}ms`;
99
- if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
100
- return `${(ms / 6e4).toFixed(1)}m`;
101
- }
102
- function formatTime(ts) {
103
- if (!ts) return "—";
104
- return new Date(ts).toLocaleString();
105
- }
106
- function CronJobDetail({ job }) {
107
- const runsQuery = useCronJobRuns(job.id);
108
- const runs = runsQuery.data ?? [];
109
- return /* @__PURE__ */ jsx(
110
- motion.div,
111
- {
112
- initial: { height: 0, opacity: 0 },
113
- animate: { height: "auto", opacity: 1 },
114
- exit: { height: 0, opacity: 0 },
115
- transition: { duration: 0.15 },
116
- className: "overflow-hidden",
117
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-primary-200 bg-primary-50 px-4 py-3", children: [
118
- (job.payload.message || job.payload.prompt) && /* @__PURE__ */ jsxs("div", { children: [
119
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-primary-500", children: "Prompt / Message" }),
120
- /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words rounded bg-primary-100 p-2 text-sm text-primary-800", children: job.payload.message ?? job.payload.prompt })
121
- ] }),
122
- job.delivery && /* @__PURE__ */ jsxs("div", { children: [
123
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-primary-500", children: "Delivery" }),
124
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-3 text-sm text-primary-700", children: [
125
- job.delivery.mode && /* @__PURE__ */ jsxs("span", { children: [
126
- "Mode: ",
127
- /* @__PURE__ */ jsx("strong", { children: job.delivery.mode })
128
- ] }),
129
- job.delivery.channel && /* @__PURE__ */ jsxs("span", { children: [
130
- "Channel: ",
131
- /* @__PURE__ */ jsx("strong", { children: job.delivery.channel })
132
- ] }),
133
- job.delivery.to && /* @__PURE__ */ jsxs("span", { children: [
134
- "To: ",
135
- /* @__PURE__ */ jsx("strong", { children: job.delivery.to })
136
- ] })
137
- ] })
138
- ] }),
139
- job.payload.model && /* @__PURE__ */ jsxs("div", { children: [
140
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-primary-500", children: "Model" }),
141
- /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-700 font-mono", children: job.payload.model })
142
- ] }),
143
- job.state?.lastError && /* @__PURE__ */ jsxs("div", { children: [
144
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-red-500", children: "Last Error" }),
145
- /* @__PURE__ */ jsx("p", { className: "rounded bg-red-50 p-2 font-mono text-xs text-red-700", children: job.state.lastError })
146
- ] }),
147
- /* @__PURE__ */ jsxs("div", { children: [
148
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-primary-500", children: "Recent Runs" }),
149
- runsQuery.isLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-primary-500", children: [
150
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading01Icon, size: 14, className: "animate-spin" }),
151
- "Loading..."
152
- ] }) : runs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-400", children: "No run history available" }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: runs.slice(0, 5).map((run) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1 text-sm", children: [
153
- /* @__PURE__ */ jsx(
154
- "span",
155
- {
156
- className: cn(
157
- "inline-block h-2 w-2 shrink-0 rounded-full",
158
- run.status === "ok" ? "bg-green-500" : "bg-red-500"
159
- )
160
- }
161
- ),
162
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Clock01Icon, size: 12, className: "text-primary-500" }),
163
- /* @__PURE__ */ jsx("span", { className: "tabular-nums text-xs text-primary-600", children: formatTime(run.ranAt) }),
164
- /* @__PURE__ */ jsx("span", { className: "tabular-nums text-xs text-primary-500", children: formatDuration(run.durationMs) }),
165
- run.error && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-red-500", children: run.error })
166
- ] }, run.id)) })
167
- ] })
168
- ] })
169
- }
170
- );
171
- }
172
- function parseCronExpr(expr) {
173
- const parts = expr.trim().split(/\s+/);
174
- if (parts.length < 5) return expr;
175
- const [min, hour, dom, mon, dow] = parts;
176
- const timeStr = `${hour.padStart(2, "0")}:${min.padStart(2, "0")}`;
177
- if (dom === "*" && mon === "*" && dow === "*") return `Daily at ${timeStr}`;
178
- if (dom === "*" && mon === "*" && dow !== "*") {
179
- const days = {
180
- "0": "Sun",
181
- "1": "Mon",
182
- "2": "Tue",
183
- "3": "Wed",
184
- "4": "Thu",
185
- "5": "Fri",
186
- "6": "Sat",
187
- "7": "Sun"
188
- };
189
- const dayNames = dow.split(",").map((d) => days[d] ?? d).join(", ");
190
- return `${dayNames} at ${timeStr}`;
191
- }
192
- return expr;
193
- }
194
- function humanSchedule(job) {
195
- const schedule = job.schedule;
196
- if (schedule.kind === "every" && schedule.expr) return `Every ${schedule.expr}`;
197
- if (schedule.kind === "at" && schedule.expr) return `Once at ${schedule.expr}`;
198
- if (schedule.kind === "cron" && schedule.expr) return parseCronExpr(schedule.expr);
199
- return schedule.expr ?? "Unknown";
200
- }
201
- function formatRelativeMs$1(ms) {
202
- if (!ms) return "—";
203
- const diff = Date.now() - ms;
204
- if (diff < 6e4) return "just now";
205
- if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
206
- if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
207
- return new Date(ms).toLocaleDateString();
208
- }
209
- function formatFutureMs(ms) {
210
- if (!ms) return "—";
211
- const diff = ms - Date.now();
212
- if (diff < 0) return "overdue";
213
- if (diff < 6e4) return "in <1m";
214
- if (diff < 36e5) return `in ${Math.floor(diff / 6e4)}m`;
215
- if (diff < 864e5) return `in ${Math.floor(diff / 36e5)}h`;
216
- return new Date(ms).toLocaleDateString();
217
- }
218
- function CronJobTable({ jobs }) {
219
- const [expandedId, setExpandedId] = useState(null);
220
- const runMutation = useRunCronJob();
221
- const toggleMutation = useToggleCronJob();
222
- const handleToggle = useCallback(
223
- (job) => {
224
- toggleMutation.mutate({ jobId: job.id, enabled: !job.enabled });
225
- },
226
- [toggleMutation]
227
- );
228
- const handleRun = useCallback(
229
- (jobId) => {
230
- runMutation.mutate(jobId);
231
- },
232
- [runMutation]
233
- );
234
- if (jobs.length === 0) {
235
- return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-primary-500", children: "No cron jobs configured" });
236
- }
237
- return /* @__PURE__ */ jsxs("div", { className: "divide-y divide-primary-200", children: [
238
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_auto_auto_auto_auto_auto] gap-3 bg-primary-50 px-4 py-2 text-xs font-medium text-primary-500", children: [
239
- /* @__PURE__ */ jsx("div", { children: "Name" }),
240
- /* @__PURE__ */ jsx("div", { className: "w-44", children: "Schedule" }),
241
- /* @__PURE__ */ jsx("div", { className: "w-20 text-center", children: "Last Run" }),
242
- /* @__PURE__ */ jsx("div", { className: "w-16 text-center", children: "Status" }),
243
- /* @__PURE__ */ jsx("div", { className: "w-20 text-center", children: "Next Run" }),
244
- /* @__PURE__ */ jsx("div", { className: "w-24 text-right", children: "Actions" })
245
- ] }),
246
- jobs.map((job) => {
247
- const isExpanded = expandedId === job.id;
248
- const isRunning = runMutation.isPending && runMutation.variables === job.id;
249
- return /* @__PURE__ */ jsxs("div", { children: [
250
- /* @__PURE__ */ jsxs(
251
- "button",
252
- {
253
- type: "button",
254
- onClick: () => setExpandedId(isExpanded ? null : job.id),
255
- className: cn(
256
- "grid w-full grid-cols-[1fr_auto_auto_auto_auto_auto] gap-3 px-4 py-3 text-left text-sm",
257
- "transition-colors duration-100 hover:bg-primary-100",
258
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-300",
259
- isExpanded && "bg-primary-100"
260
- ),
261
- children: [
262
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx("span", { className: cn("block truncate font-medium", !job.enabled && "text-primary-400"), children: job.name ?? job.id }) }),
263
- /* @__PURE__ */ jsx("div", { className: "w-44 truncate text-primary-600", children: humanSchedule(job) }),
264
- /* @__PURE__ */ jsx("div", { className: "w-20 text-center tabular-nums text-primary-500", children: formatRelativeMs$1(job.state?.lastRunAtMs) }),
265
- /* @__PURE__ */ jsxs("div", { className: "flex w-16 justify-center", children: [
266
- job.state?.lastStatus === "ok" && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-green-100 px-1.5 py-0.5 text-xs font-medium text-green-700", children: "ok" }),
267
- job.state?.lastStatus === "error" && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-red-100 px-1.5 py-0.5 text-xs font-medium text-red-700", children: "error" }),
268
- !job.state?.lastStatus && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-primary-100 px-1.5 py-0.5 text-xs font-medium text-primary-400", children: "—" })
269
- ] }),
270
- /* @__PURE__ */ jsx("div", { className: "w-20 text-center tabular-nums text-primary-500", children: formatFutureMs(job.state?.nextRunAtMs) }),
271
- /* @__PURE__ */ jsxs(
272
- "div",
273
- {
274
- className: "flex w-24 items-center justify-end gap-2",
275
- onClick: (event) => event.stopPropagation(),
276
- children: [
277
- /* @__PURE__ */ jsx(
278
- Switch,
279
- {
280
- checked: job.enabled,
281
- onCheckedChange: () => handleToggle(job),
282
- "aria-label": `${job.enabled ? "Disable" : "Enable"} ${job.name ?? job.id}`,
283
- disabled: toggleMutation.isPending
284
- }
285
- ),
286
- /* @__PURE__ */ jsx(
287
- Button,
288
- {
289
- variant: "ghost",
290
- size: "icon-sm",
291
- onClick: () => handleRun(job.id),
292
- disabled: isRunning,
293
- "aria-label": `Run ${job.name ?? job.id} now`,
294
- children: /* @__PURE__ */ jsx(
295
- HugeiconsIcon,
296
- {
297
- icon: isRunning ? Loading01Icon : PlayIcon,
298
- size: 16,
299
- className: cn(isRunning && "animate-spin")
300
- }
301
- )
302
- }
303
- )
304
- ]
305
- }
306
- )
307
- ]
308
- }
309
- ),
310
- /* @__PURE__ */ jsx(AnimatePresence, { children: isExpanded && /* @__PURE__ */ jsx(CronJobDetail, { job }) })
311
- ] }, job.id);
312
- })
313
- ] });
314
- }
315
- function formatRelativeMs(ms) {
316
- if (!ms) return "never";
317
- const diff = Date.now() - ms;
318
- if (diff < 6e4) return "just now";
319
- if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
320
- if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
321
- return new Date(ms).toLocaleDateString();
322
- }
323
- function extractBotName(jobName) {
324
- for (const separator of [" - ", " | ", ": ", " / "]) {
325
- const index = jobName.indexOf(separator);
326
- if (index > 0) return jobName.substring(0, index).trim();
327
- }
328
- return jobName;
329
- }
330
- function groupJobsIntoBots(jobs) {
331
- const grouped = /* @__PURE__ */ new Map();
332
- for (const job of jobs) {
333
- const name = extractBotName(job.name ?? job.id);
334
- const existing = grouped.get(name);
335
- if (existing) {
336
- existing.push(job);
337
- } else {
338
- grouped.set(name, [job]);
339
- }
340
- }
341
- return Array.from(grouped.entries()).map(([name, groupedJobs]) => ({ name, jobs: groupedJobs })).sort((a, b) => a.name.localeCompare(b.name));
342
- }
343
- function BotCard({ bot }) {
344
- const lastActivity = bot.jobs.reduce((max, job) => Math.max(max, job.state?.lastRunAtMs ?? 0), 0);
345
- const hasErrors = bot.jobs.some((job) => job.state?.lastStatus === "error");
346
- return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-primary-200 bg-primary-50 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
347
- /* @__PURE__ */ jsx("div", { className: cn("rounded-lg p-2", hasErrors ? "bg-red-100" : "bg-primary-100"), children: /* @__PURE__ */ jsx(
348
- HugeiconsIcon,
349
- {
350
- icon: SmartPhone01Icon,
351
- size: 24,
352
- strokeWidth: 1.5,
353
- className: hasErrors ? "text-red-600" : "text-primary-600"
354
- }
355
- ) }),
356
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
357
- /* @__PURE__ */ jsx("h3", { className: "truncate font-medium text-primary-900", children: bot.name }),
358
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2 text-xs text-primary-500", children: [
359
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Clock01Icon, size: 12 }),
360
- /* @__PURE__ */ jsxs("span", { children: [
361
- "Last active: ",
362
- formatRelativeMs(lastActivity || void 0)
363
- ] })
364
- ] }),
365
- /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs("span", { className: "rounded-full bg-primary-100 px-2 py-0.5 text-xs text-primary-600", children: [
366
- bot.jobs.length,
367
- " job",
368
- bot.jobs.length !== 1 ? "s" : ""
369
- ] }) })
370
- ] })
371
- ] }) });
372
- }
373
- function BotsScreen() {
374
- const cronJobsQuery = useCronJobs();
375
- const jobs = cronJobsQuery.data ?? [];
376
- const bots = useMemo(() => groupJobsIntoBots(jobs), [jobs]);
377
- return /* @__PURE__ */ jsxs("div", { className: "flex h-screen flex-col bg-surface text-primary-900", children: [
378
- /* @__PURE__ */ jsx("header", { className: "border-b border-primary-200 bg-primary-100 px-6 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
379
- /* @__PURE__ */ jsx(Link, { to: "/new", className: cn(buttonVariants({ variant: "ghost", size: "icon-sm" })), children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArrowLeft01Icon, size: 20, strokeWidth: 1.5 }) }),
380
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: SmartPhone01Icon, size: 24, strokeWidth: 1.5, className: "text-primary-600" }),
381
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-primary-900", children: "Cron Jobs" })
382
- ] }) }),
383
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: cronJobsQuery.isLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Loading01Icon, size: 24, className: "animate-spin text-primary-400" }) }) : cronJobsQuery.isError ? /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
384
- /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: cronJobsQuery.error instanceof Error ? cronJobsQuery.error.message : "Failed to load cron jobs" }),
385
- /* @__PURE__ */ jsx(
386
- "button",
387
- {
388
- onClick: () => void cronJobsQuery.refetch(),
389
- className: "mt-2 text-sm text-primary-600 underline hover:text-primary-900",
390
- children: "Retry"
391
- }
392
- )
393
- ] }) : /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-6 p-6", children: [
394
- bots.length > 0 && /* @__PURE__ */ jsxs("section", { children: [
395
- /* @__PURE__ */ jsxs("h2", { className: "mb-3 flex items-center gap-2 text-sm font-medium text-primary-500", children: [
396
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: SmartPhone01Icon, size: 16 }),
397
- "Bots (",
398
- bots.length,
399
- ")"
400
- ] }),
401
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4", children: bots.map((bot) => /* @__PURE__ */ jsx(BotCard, { bot }, bot.name)) })
402
- ] }),
403
- /* @__PURE__ */ jsxs("section", { children: [
404
- /* @__PURE__ */ jsxs("h2", { className: "mb-3 flex items-center gap-2 text-sm font-medium text-primary-500", children: [
405
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Clock01Icon, size: 16 }),
406
- "All Cron Jobs (",
407
- jobs.length,
408
- ")"
409
- ] }),
410
- /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-primary-200 bg-white", children: /* @__PURE__ */ jsx(CronJobTable, { jobs }) })
411
- ] })
412
- ] }) })
413
- ] });
414
- }
415
- export {
416
- BotsScreen
417
- };