@aixnn/aixautots 0.0.0-stage → 0.5.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.
Files changed (67) hide show
  1. package/README.md +239 -2
  2. package/dist/ScriptStudio-DaCqOF5H.d.ts +43 -0
  3. package/dist/agent-runner-V60B0is1.d.ts +57 -0
  4. package/dist/automation-queue-8HqRJV_K.d.ts +72 -0
  5. package/dist/chunk-536WVCTB.js +5744 -0
  6. package/dist/chunk-5BMFMDQR.js +18 -0
  7. package/dist/chunk-64Q7BBIQ.js +1347 -0
  8. package/dist/chunk-6YUAACW6.js +866 -0
  9. package/dist/chunk-AIUOLJZA.js +412 -0
  10. package/dist/chunk-CVV7FOMQ.js +175 -0
  11. package/dist/chunk-EHF2WQUR.js +2042 -0
  12. package/dist/chunk-EVHTAXQG.js +273 -0
  13. package/dist/chunk-FW23FLAF.js +56 -0
  14. package/dist/chunk-GKEPMDXO.js +410 -0
  15. package/dist/chunk-H7MHGYF3.js +341 -0
  16. package/dist/chunk-MRQ7VJFJ.js +1074 -0
  17. package/dist/chunk-NWFYTFMI.js +65 -0
  18. package/dist/chunk-OEZFYKL6.js +1 -0
  19. package/dist/chunk-PCYERBNZ.js +1564 -0
  20. package/dist/chunk-PUPVZRKZ.js +668 -0
  21. package/dist/chunk-SUKFJ73W.js +217 -0
  22. package/dist/config-Bm1hmXhD.d.ts +1380 -0
  23. package/dist/coords-LPR_MPyp.d.ts +22 -0
  24. package/dist/core/index.d.ts +135 -0
  25. package/dist/core/index.js +5 -0
  26. package/dist/device-normalize-BaThgnH7.d.ts +43 -0
  27. package/dist/device-session-KZVGLPOI.js +2 -0
  28. package/dist/i18n/index.d.ts +30 -0
  29. package/dist/i18n/index.js +1 -0
  30. package/dist/mirror-session-C_1vFkRV.d.ts +108 -0
  31. package/dist/nav/index.d.ts +19 -0
  32. package/dist/nav/index.js +1 -0
  33. package/dist/react/index.d.ts +487 -0
  34. package/dist/react/index.js +7 -0
  35. package/dist/script-debugger-Cw5jVRoz.d.ts +114 -0
  36. package/dist/task-agent-runner-eD9-SCJz.d.ts +160 -0
  37. package/dist/types-C2WXVcam.d.ts +1005 -0
  38. package/dist/ui/billing.d.ts +48 -0
  39. package/dist/ui/billing.js +425 -0
  40. package/dist/ui/device-create.d.ts +55 -0
  41. package/dist/ui/device-create.js +9 -0
  42. package/dist/ui/device-mirror.d.ts +42 -0
  43. package/dist/ui/device-mirror.js +10 -0
  44. package/dist/ui/devices.d.ts +223 -0
  45. package/dist/ui/devices.js +15 -0
  46. package/dist/ui/editor.d.ts +64 -0
  47. package/dist/ui/editor.js +344 -0
  48. package/dist/ui/index.d.ts +104 -0
  49. package/dist/ui/index.js +676 -0
  50. package/dist/ui/inspector.d.ts +5 -0
  51. package/dist/ui/inspector.js +1250 -0
  52. package/dist/ui/market.d.ts +10 -0
  53. package/dist/ui/market.js +60 -0
  54. package/dist/ui/mirror.d.ts +52 -0
  55. package/dist/ui/mirror.js +3 -0
  56. package/dist/ui/primitives/index.d.ts +101 -0
  57. package/dist/ui/primitives/index.js +1 -0
  58. package/dist/ui/script-studio.d.ts +85 -0
  59. package/dist/ui/script-studio.js +10 -0
  60. package/dist/ui/scripts.d.ts +18 -0
  61. package/dist/ui/scripts.js +210 -0
  62. package/dist/ui/tasks.d.ts +10 -0
  63. package/dist/ui/tasks.js +1039 -0
  64. package/dist/use-script-run-Diay7ibY.d.ts +50 -0
  65. package/package.json +76 -4
  66. package/styles.css +97 -0
  67. package/tailwind-preset.cjs +84 -0
@@ -0,0 +1,1039 @@
1
+ import { SharedParamsForm, resolveSharedRow } from '../chunk-536WVCTB.js';
2
+ import { useAixT } from '../chunk-64Q7BBIQ.js';
3
+ import '../chunk-FW23FLAF.js';
4
+ import '../chunk-AIUOLJZA.js';
5
+ import '../chunk-OEZFYKL6.js';
6
+ import '../chunk-CVV7FOMQ.js';
7
+ import '../chunk-MRQ7VJFJ.js';
8
+ import { Button, cn, Select, Input, Badge } from '../chunk-EVHTAXQG.js';
9
+ import { useDevicesView, useAutomationQueue, useApiTokens } from '../chunk-EHF2WQUR.js';
10
+ import { targetParam, variablesToSchema, schemaDefaults } from '../chunk-H7MHGYF3.js';
11
+ import '../chunk-PUPVZRKZ.js';
12
+ import { useClient, useAixConfig } from '../chunk-SUKFJ73W.js';
13
+ import '../chunk-GKEPMDXO.js';
14
+ import '../chunk-PCYERBNZ.js';
15
+ import '../chunk-NWFYTFMI.js';
16
+ import { useState, useEffect, useMemo } from 'react';
17
+ import { RefreshCw, Plug, Plus, X, Play, ChevronDown, ChevronRight, PlayCircle, Pause, Ban, Trash2, Webhook, MonitorSmartphone, Copy } from 'lucide-react';
18
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
19
+
20
+ // src/ui/tasks/snippets.ts
21
+ function header(name, value) {
22
+ return ` -H '${name}: ${value}'`;
23
+ }
24
+ function buildSnippets(opts) {
25
+ const { baseUrl, token, unpinned, exec } = opts;
26
+ const base = baseUrl.replace(/\/+$/, "");
27
+ const auth = header("Authorization", `Bearer ${token}`);
28
+ const projectHeader = unpinned ? header("X-Project-Id", "<PROJECT_ID>") : null;
29
+ const get = (path) => {
30
+ const lines = [`curl '${base}${path}'`, auth];
31
+ if (projectHeader) lines.push(projectHeader);
32
+ return lines.join(" \\\n");
33
+ };
34
+ const execBody = JSON.stringify({
35
+ script_id: exec?.script_id ?? "<SCRIPT_ID>",
36
+ ...exec?.flow ? { flow: exec.flow } : {},
37
+ params: exec?.params ?? {},
38
+ device_ids: exec?.device_ids ?? ["<DEVICE_ID>"]
39
+ });
40
+ const postExec = (() => {
41
+ const lines = [
42
+ `curl -X POST '${base}/api/webhook/tasks'`,
43
+ auth,
44
+ header("Content-Type", "application/json")
45
+ ];
46
+ if (projectHeader) lines.push(projectHeader);
47
+ lines.push(` -d '${execBody}'`);
48
+ return lines.join(" \\\n");
49
+ })();
50
+ return [
51
+ {
52
+ id: "listScripts",
53
+ i18nKey: "integration.snippet.listScripts",
54
+ method: "GET",
55
+ path: "/api/scripts",
56
+ curl: get("/api/scripts")
57
+ },
58
+ {
59
+ id: "listDevices",
60
+ i18nKey: "integration.snippet.listDevices",
61
+ method: "GET",
62
+ path: "/api/devices",
63
+ curl: get("/api/devices")
64
+ },
65
+ {
66
+ id: "execTask",
67
+ i18nKey: "integration.snippet.execTask",
68
+ method: "POST",
69
+ path: "/api/webhook/tasks",
70
+ curl: postExec
71
+ },
72
+ {
73
+ id: "listTasks",
74
+ i18nKey: "integration.snippet.listTasks",
75
+ method: "GET",
76
+ path: "/api/webhook/tasks",
77
+ curl: `${get("/api/webhook/tasks")}
78
+ # single task: GET ${base}/api/webhook/tasks/<TASK_ID>`
79
+ }
80
+ ];
81
+ }
82
+ var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "expired"]);
83
+ function displayStatus(t) {
84
+ if (TERMINAL.has(t.status)) return t.status;
85
+ if (t.status === "paused") return "paused";
86
+ if (t.finished > 0 && t.finished < t.total) return "running";
87
+ return "pending";
88
+ }
89
+ var STATUS_TAB_KEYS = [
90
+ "all",
91
+ "pending",
92
+ "running",
93
+ "paused",
94
+ "succeeded",
95
+ "failed",
96
+ "cancelled"
97
+ ];
98
+ var batchIdOf = (t) => {
99
+ const b = t.params?.__batch__;
100
+ return typeof b === "string" && b ? b : null;
101
+ };
102
+ var targetOf = (t) => {
103
+ const v = t.params?.__target__;
104
+ return typeof v === "string" ? v : "";
105
+ };
106
+ function TasksPage(_props = {}) {
107
+ const t = useAixT();
108
+ const { devices, fetchDevices } = useDevicesView();
109
+ const [tab, setTab] = useState("all");
110
+ const [drawerOpen, setDrawerOpen] = useState(false);
111
+ const [integrationOpen, setIntegrationOpen] = useState(false);
112
+ const [integrationExec, setIntegrationExec] = useState(void 0);
113
+ const queue = useAutomationQueue();
114
+ useEffect(() => {
115
+ void fetchDevices();
116
+ }, [fetchDevices]);
117
+ useEffect(() => {
118
+ const id = setInterval(() => {
119
+ if (typeof document !== "undefined" && document.hidden) return;
120
+ void queue.refetch();
121
+ }, 5e3);
122
+ return () => clearInterval(id);
123
+ }, []);
124
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
125
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-b border-border px-3 py-2 sm:px-6", children: [
126
+ /* @__PURE__ */ jsxs(
127
+ Button,
128
+ {
129
+ variant: "secondary",
130
+ size: "sm",
131
+ onClick: () => void queue.refetch(),
132
+ disabled: queue.loading,
133
+ children: [
134
+ /* @__PURE__ */ jsx(RefreshCw, { className: `w-3.5 h-3.5 ${queue.loading ? "animate-spin" : ""}` }),
135
+ t("common.refresh")
136
+ ]
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", onClick: () => {
140
+ setIntegrationExec(void 0);
141
+ setIntegrationOpen(true);
142
+ }, children: [
143
+ /* @__PURE__ */ jsx(Plug, { className: "w-3.5 h-3.5" }),
144
+ t("integration.open")
145
+ ] }),
146
+ /* @__PURE__ */ jsxs(Button, { variant: "primary", size: "sm", onClick: () => setDrawerOpen(true), children: [
147
+ /* @__PURE__ */ jsx(Plus, { className: "w-3.5 h-3.5" }),
148
+ t("tasks.newTask")
149
+ ] })
150
+ ] }),
151
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto p-3 sm:p-6", children: /* @__PURE__ */ jsx(
152
+ QueueList,
153
+ {
154
+ queue,
155
+ tab,
156
+ onTab: setTab,
157
+ t,
158
+ onIntegrate: (exec) => {
159
+ setIntegrationExec(exec);
160
+ setIntegrationOpen(true);
161
+ }
162
+ }
163
+ ) }),
164
+ /* @__PURE__ */ jsx(
165
+ EnqueueDrawer,
166
+ {
167
+ open: drawerOpen,
168
+ onClose: () => setDrawerOpen(false),
169
+ devices: devices.map((d) => ({ id: d.id, name: d.name, online: d.online })),
170
+ onEnqueued: () => void queue.refetch(),
171
+ submitBatch: queue.submitBatch,
172
+ t
173
+ }
174
+ ),
175
+ /* @__PURE__ */ jsx(IntegrationDrawer, { open: integrationOpen, onClose: () => setIntegrationOpen(false), t, exec: integrationExec })
176
+ ] });
177
+ }
178
+ function EnqueueDrawer({
179
+ open,
180
+ onClose,
181
+ devices,
182
+ onEnqueued,
183
+ submitBatch,
184
+ t
185
+ }) {
186
+ const client = useClient();
187
+ const [scripts, setScripts] = useState([]);
188
+ const [scriptId, setScriptId] = useState("");
189
+ const [flow, setFlow] = useState(null);
190
+ const [schema, setSchema] = useState([]);
191
+ const [targetText, setTargetText] = useState("");
192
+ const [values, setValues] = useState({});
193
+ const [randomOn, setRandomOn] = useState({});
194
+ const [checkedIds, setCheckedIds] = useState(/* @__PURE__ */ new Set());
195
+ const [mode, setMode] = useState("round-robin");
196
+ const [error, setError] = useState(null);
197
+ const [submitting, setSubmitting] = useState(false);
198
+ const MODE_OPTIONS = [
199
+ { value: "round-robin", label: t("tasks.mode.roundRobin") },
200
+ { value: "broadcast", label: t("tasks.mode.broadcast") }
201
+ ];
202
+ const target = useMemo(() => targetParam(schema), [schema]);
203
+ const sharedParams = useMemo(() => schema.filter((p) => !p.isTarget), [schema]);
204
+ useEffect(() => {
205
+ if (!open) return;
206
+ let cancelled = false;
207
+ (async () => {
208
+ try {
209
+ const list = await client.scripts.list();
210
+ if (!cancelled) setScripts(list.map((s) => ({ id: s.id, name: s.name })));
211
+ } catch (err) {
212
+ if (!cancelled) setError(String(err));
213
+ }
214
+ })();
215
+ return () => {
216
+ cancelled = true;
217
+ };
218
+ }, [open, client]);
219
+ useEffect(() => {
220
+ if (!scriptId) {
221
+ setFlow(null);
222
+ setSchema([]);
223
+ setValues({});
224
+ setRandomOn({});
225
+ setTargetText("");
226
+ return;
227
+ }
228
+ let cancelled = false;
229
+ (async () => {
230
+ try {
231
+ const f = await client.scripts.get(scriptId);
232
+ if (cancelled) return;
233
+ const sc = variablesToSchema(f.variables);
234
+ const defaults = schemaDefaults(sc);
235
+ const tp = targetParam(sc);
236
+ if (tp) delete defaults[tp.key];
237
+ setFlow(f);
238
+ setSchema(sc);
239
+ setValues(defaults);
240
+ setRandomOn({});
241
+ setTargetText("");
242
+ setError(null);
243
+ } catch (err) {
244
+ if (!cancelled) setError(String(err));
245
+ }
246
+ })();
247
+ return () => {
248
+ cancelled = true;
249
+ };
250
+ }, [scriptId, client]);
251
+ const deviceIds = useMemo(() => [...checkedIds], [checkedIds]);
252
+ const offlineSelected = useMemo(
253
+ () => devices.filter((d) => checkedIds.has(d.id) && !d.online).length,
254
+ [devices, checkedIds]
255
+ );
256
+ const onlineCount = useMemo(() => devices.filter((d) => d.online).length, [devices]);
257
+ const targetLines = useMemo(
258
+ () => targetText.split(/\r?\n/).map((l) => l.trim()).filter(Boolean),
259
+ [targetText]
260
+ );
261
+ const rowCount = target ? targetLines.length : 1;
262
+ const canSubmit = !!flow && !submitting && deviceIds.length > 0 && rowCount > 0;
263
+ const submit = async () => {
264
+ if (!flow || !scriptId) return;
265
+ setError(null);
266
+ setSubmitting(true);
267
+ try {
268
+ const batchId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : String(Date.now());
269
+ const lines = target ? targetLines : [""];
270
+ const rows = lines.map((line) => {
271
+ const shared = resolveSharedRow(sharedParams, values, randomOn);
272
+ const row = { ...shared, __batch__: batchId };
273
+ if (target) {
274
+ row[target.key] = line;
275
+ row.__target__ = line;
276
+ }
277
+ return row;
278
+ });
279
+ await submitBatch({ script: { id: scriptId }, rows, deviceIds, mode });
280
+ onEnqueued();
281
+ onClose();
282
+ } catch (err) {
283
+ setError(String(err));
284
+ } finally {
285
+ setSubmitting(false);
286
+ }
287
+ };
288
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
289
+ /* @__PURE__ */ jsx(
290
+ "div",
291
+ {
292
+ className: cn(
293
+ "fixed inset-0 z-30 bg-black/40 transition-opacity duration-300",
294
+ open ? "opacity-100" : "pointer-events-none opacity-0"
295
+ ),
296
+ onClick: onClose
297
+ }
298
+ ),
299
+ /* @__PURE__ */ jsxs(
300
+ "div",
301
+ {
302
+ className: cn(
303
+ "fixed right-0 top-0 z-40 flex h-full w-full max-w-md flex-col border-l border-border bg-card transition-transform duration-300 ease-in-out",
304
+ open ? "translate-x-0" : "translate-x-full"
305
+ ),
306
+ children: [
307
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
308
+ /* @__PURE__ */ jsx("h2", { className: "text-body-md font-semibold text-foreground", children: t("tasks.newTask") }),
309
+ /* @__PURE__ */ jsx(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ "aria-label": t("common.close"),
314
+ onClick: onClose,
315
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
316
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
317
+ }
318
+ )
319
+ ] }),
320
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto p-4", children: [
321
+ /* @__PURE__ */ jsxs("div", { children: [
322
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium", children: t("tasks.script") }),
323
+ /* @__PURE__ */ jsx(
324
+ Select,
325
+ {
326
+ value: scriptId,
327
+ onChange: setScriptId,
328
+ options: [
329
+ { value: "", label: t("tasks.selectScript") },
330
+ ...scripts.map((s) => ({ value: s.id, label: s.name }))
331
+ ]
332
+ }
333
+ )
334
+ ] }),
335
+ flow && target && /* @__PURE__ */ jsxs("div", { children: [
336
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium", children: t("tasks.targetLabel", {
337
+ label: target.label || target.key,
338
+ n: targetLines.length
339
+ }) }),
340
+ /* @__PURE__ */ jsx(
341
+ "textarea",
342
+ {
343
+ className: "w-full rounded-md border border-input bg-card px-2.5 py-1.5 text-xs text-foreground outline-none focus:border-primary",
344
+ rows: 6,
345
+ value: targetText,
346
+ placeholder: t("tasks.targetPlaceholder"),
347
+ onChange: (e) => setTargetText(e.target.value)
348
+ }
349
+ )
350
+ ] }),
351
+ flow && /* @__PURE__ */ jsxs("div", { children: [
352
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium", children: t("tasks.sharedParams") }),
353
+ /* @__PURE__ */ jsx(
354
+ SharedParamsForm,
355
+ {
356
+ params: sharedParams,
357
+ values,
358
+ randomOn,
359
+ onValue: (k, v) => setValues((p) => ({ ...p, [k]: v })),
360
+ onToggleRandom: (k, on) => setRandomOn((p) => ({ ...p, [k]: on }))
361
+ }
362
+ )
363
+ ] }),
364
+ flow && !target && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("tasks.noTargetHint") }),
365
+ /* @__PURE__ */ jsxs("div", { children: [
366
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium", children: t("tasks.targetDevices", { n: deviceIds.length }) }),
367
+ /* @__PURE__ */ jsx("div", { className: "max-h-48 space-y-1 overflow-y-auto rounded-md border border-border p-2", children: devices.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("tasks.noDevices") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
368
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border pb-1", children: [
369
+ /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-xs text-muted-foreground", children: [
370
+ /* @__PURE__ */ jsx(
371
+ "input",
372
+ {
373
+ type: "checkbox",
374
+ checked: devices.length > 0 && devices.every((d) => checkedIds.has(d.id)),
375
+ onChange: (e) => setCheckedIds(
376
+ e.target.checked ? new Set(devices.map((d) => d.id)) : /* @__PURE__ */ new Set()
377
+ ),
378
+ className: "h-3.5 w-3.5 rounded border-input accent-primary"
379
+ }
380
+ ),
381
+ t("tasks.selectAll")
382
+ ] }),
383
+ /* @__PURE__ */ jsx(
384
+ "button",
385
+ {
386
+ type: "button",
387
+ className: "text-[11px] text-primary hover:underline disabled:opacity-40",
388
+ disabled: onlineCount === 0,
389
+ onClick: () => setCheckedIds(new Set(devices.filter((d) => d.online).map((d) => d.id))),
390
+ children: t("tasks.onlyOnline", { n: onlineCount })
391
+ }
392
+ )
393
+ ] }),
394
+ devices.map((d) => /* @__PURE__ */ jsxs(
395
+ "label",
396
+ {
397
+ className: "flex cursor-pointer items-center gap-2 text-xs text-foreground",
398
+ children: [
399
+ /* @__PURE__ */ jsx(
400
+ "input",
401
+ {
402
+ type: "checkbox",
403
+ checked: checkedIds.has(d.id),
404
+ onChange: (e) => setCheckedIds((prev) => {
405
+ const next = new Set(prev);
406
+ if (e.target.checked) next.add(d.id);
407
+ else next.delete(d.id);
408
+ return next;
409
+ }),
410
+ className: "h-3.5 w-3.5 rounded border-input accent-primary"
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsx(
414
+ "span",
415
+ {
416
+ className: cn(
417
+ "h-1.5 w-1.5 shrink-0 rounded-full",
418
+ d.online ? "bg-success" : "bg-muted-foreground/40"
419
+ )
420
+ }
421
+ ),
422
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: d.name })
423
+ ]
424
+ },
425
+ d.id
426
+ ))
427
+ ] }) }),
428
+ offlineSelected > 0 && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-warning", children: t("tasks.offlineWarn", { n: offlineSelected }) })
429
+ ] }),
430
+ /* @__PURE__ */ jsxs("div", { children: [
431
+ /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium", children: t("tasks.dispatchMode") }),
432
+ /* @__PURE__ */ jsx(
433
+ Select,
434
+ {
435
+ value: mode,
436
+ onChange: (v) => setMode(v),
437
+ options: MODE_OPTIONS
438
+ }
439
+ )
440
+ ] }),
441
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
442
+ ] }),
443
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-border p-4", children: [
444
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: t("common.cancel") }),
445
+ /* @__PURE__ */ jsxs(Button, { variant: "primary", size: "sm", onClick: submit, disabled: !canSubmit, children: [
446
+ /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" }),
447
+ submitting ? t("tasks.enqueuing") : t("tasks.enqueueRun", { n: rowCount })
448
+ ] })
449
+ ] })
450
+ ]
451
+ }
452
+ )
453
+ ] });
454
+ }
455
+ function aggregate(tasks) {
456
+ const total = tasks.reduce((a, t) => a + t.total, 0);
457
+ const finished = tasks.reduce((a, t) => a + t.finished, 0);
458
+ const statuses = tasks.map((t) => displayStatus(t));
459
+ let status;
460
+ if (statuses.every((s) => TERMINAL.has(s))) {
461
+ status = statuses.some((s) => s === "failed" || s === "expired") ? "failed" : statuses.some((s) => s === "cancelled") ? "cancelled" : "succeeded";
462
+ } else if (statuses.some((s) => s === "paused")) {
463
+ status = "paused";
464
+ } else if (finished > 0 && finished < total) {
465
+ status = "running";
466
+ } else {
467
+ status = "pending";
468
+ }
469
+ return { total, finished, status };
470
+ }
471
+ function QueueList({
472
+ queue,
473
+ tab,
474
+ onTab,
475
+ t,
476
+ onIntegrate
477
+ }) {
478
+ const all = useMemo(() => queue.data ?? [], [queue.data]);
479
+ const groups = useMemo(() => {
480
+ const order = [];
481
+ const buckets = /* @__PURE__ */ new Map();
482
+ const singles = [];
483
+ for (const t2 of all) {
484
+ const b = batchIdOf(t2);
485
+ if (b) {
486
+ if (!buckets.has(b)) {
487
+ buckets.set(b, []);
488
+ order.push(b);
489
+ }
490
+ buckets.get(b).push(t2);
491
+ } else {
492
+ singles.push({ kind: "single", task: t2 });
493
+ }
494
+ }
495
+ const batchGroups = order.map((b) => ({
496
+ kind: "batch",
497
+ batchId: b,
498
+ tasks: buckets.get(b)
499
+ }));
500
+ return [...batchGroups, ...singles];
501
+ }, [all]);
502
+ const groupStatus = (g) => g.kind === "batch" ? aggregate(g.tasks).status : displayStatus(g.task);
503
+ const shown = tab === "all" ? groups : groups.filter((g) => groupStatus(g) === tab);
504
+ const countFor = (key) => key === "all" ? groups.length : groups.filter((g) => groupStatus(g) === key).length;
505
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card p-3 sm:p-4", children: [
506
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1 border-b border-border pb-2", children: STATUS_TAB_KEYS.map((key) => /* @__PURE__ */ jsxs(
507
+ "button",
508
+ {
509
+ type: "button",
510
+ onClick: () => onTab(key),
511
+ className: cn(
512
+ "rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
513
+ tab === key ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-secondary hover:text-foreground"
514
+ ),
515
+ children: [
516
+ t(`tasks.tab.${key}`),
517
+ /* @__PURE__ */ jsx("span", { className: "ml-1 opacity-60", children: countFor(key) })
518
+ ]
519
+ },
520
+ key
521
+ )) }),
522
+ queue.error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: queue.error.message }),
523
+ shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-8 text-center text-xs text-muted-foreground", children: queue.loading ? t("common.loading") : t("tasks.empty") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: shown.map(
524
+ (g) => g.kind === "batch" ? /* @__PURE__ */ jsx(BatchGroup, { tasks: g.tasks, queue, t }, g.batchId) : /* @__PURE__ */ jsx(
525
+ TaskRow,
526
+ {
527
+ task: g.task,
528
+ onCancel: () => void queue.cancel(g.task.task_id),
529
+ onPause: () => void queue.pause(g.task.task_id),
530
+ onResume: () => void queue.resume(g.task.task_id),
531
+ onIntegrate,
532
+ t
533
+ },
534
+ g.task.task_id
535
+ )
536
+ ) })
537
+ ] });
538
+ }
539
+ function BatchGroup({
540
+ tasks,
541
+ queue,
542
+ t
543
+ }) {
544
+ const [open, setOpen] = useState(false);
545
+ const agg = aggregate(tasks);
546
+ const progress = agg.total > 0 ? Math.round(agg.finished / agg.total * 100) : 0;
547
+ const terminal = TERMINAL.has(agg.status);
548
+ const paused = agg.status === "paused";
549
+ const createdAt = tasks.reduce(
550
+ (min, t2) => t2.created_at < min ? t2.created_at : min,
551
+ tasks[0]?.created_at ?? ""
552
+ );
553
+ const actOnAll = (fn) => tasks.forEach((t2) => fn(t2.task_id));
554
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border", children: [
555
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 p-2.5", children: [
556
+ /* @__PURE__ */ jsx(
557
+ "button",
558
+ {
559
+ type: "button",
560
+ "aria-label": open ? t("tasks.collapseTargets") : t("tasks.expandTargets"),
561
+ onClick: () => setOpen((v) => !v),
562
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
563
+ children: open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
564
+ }
565
+ ),
566
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
567
+ /* @__PURE__ */ jsxs("p", { className: "truncate text-xs font-medium text-foreground", children: [
568
+ t("tasks.batchLabel", { n: tasks.length }),
569
+ createdAt && /* @__PURE__ */ jsx("span", { className: "ml-2 text-muted-foreground", children: new Date(createdAt).toLocaleString() })
570
+ ] }),
571
+ /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: t("tasks.progress", { finished: agg.finished, total: agg.total, percent: progress }) })
572
+ ] }),
573
+ /* @__PURE__ */ jsx(TaskStatusBadge, { status: agg.status, t }),
574
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: !terminal && /* @__PURE__ */ jsxs(Fragment, { children: [
575
+ paused ? /* @__PURE__ */ jsx(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ title: t("tasks.resumeAll"),
580
+ onClick: () => actOnAll((id) => void queue.resume(id)),
581
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
582
+ children: /* @__PURE__ */ jsx(PlayCircle, { className: "h-3.5 w-3.5" })
583
+ }
584
+ ) : /* @__PURE__ */ jsx(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ title: t("tasks.pauseAll"),
589
+ onClick: () => actOnAll((id) => void queue.pause(id)),
590
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
591
+ children: /* @__PURE__ */ jsx(Pause, { className: "h-3.5 w-3.5" })
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx(
595
+ "button",
596
+ {
597
+ type: "button",
598
+ title: t("tasks.cancelAll"),
599
+ onClick: () => actOnAll((id) => void queue.cancel(id)),
600
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-destructive",
601
+ children: /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" })
602
+ }
603
+ )
604
+ ] }) })
605
+ ] }),
606
+ open && /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-secondary/30 p-2.5", children: /* @__PURE__ */ jsx("div", { className: "space-y-1", children: tasks.map((task) => /* @__PURE__ */ jsx(TargetRow, { task, t }, task.task_id)) }) })
607
+ ] });
608
+ }
609
+ function TargetRow({ task, t }) {
610
+ const client = useClient();
611
+ const [open, setOpen] = useState(false);
612
+ const [detail, setDetail] = useState(null);
613
+ const [loading, setLoading] = useState(false);
614
+ const ds = displayStatus(task);
615
+ const label = targetOf(task) || task.task_id.slice(0, 8);
616
+ useEffect(() => {
617
+ if (!open || detail) return;
618
+ let cancelled = false;
619
+ setLoading(true);
620
+ (async () => {
621
+ try {
622
+ const d = await client.tasks.get(task.task_id);
623
+ if (!cancelled) setDetail(d);
624
+ } catch {
625
+ } finally {
626
+ if (!cancelled) setLoading(false);
627
+ }
628
+ })();
629
+ return () => {
630
+ cancelled = true;
631
+ };
632
+ }, [open, detail, task.task_id, client]);
633
+ return /* @__PURE__ */ jsxs("div", { className: "rounded border border-border/60 bg-card/40", children: [
634
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 p-1.5", children: [
635
+ /* @__PURE__ */ jsxs(
636
+ "button",
637
+ {
638
+ type: "button",
639
+ onClick: () => setOpen((v) => !v),
640
+ className: "flex min-w-0 flex-1 items-center gap-1.5 text-left",
641
+ children: [
642
+ open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
643
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[11px]", children: label })
644
+ ]
645
+ }
646
+ ),
647
+ /* @__PURE__ */ jsx(TaskStatusBadge, { status: ds, t })
648
+ ] }),
649
+ open && /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 px-2 py-1.5", children: loading && !detail ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("tasks.loadingDetail") }) : detail && detail.targets.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-1", children: detail.targets.map((tg) => /* @__PURE__ */ jsxs(
650
+ "div",
651
+ {
652
+ className: "flex items-center justify-between gap-2 text-[11px]",
653
+ children: [
654
+ /* @__PURE__ */ jsx("span", { className: "truncate font-mono", children: tg.device_id }),
655
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
656
+ tg.error && /* @__PURE__ */ jsx("span", { className: "truncate text-destructive", title: tg.error, children: tg.error }),
657
+ /* @__PURE__ */ jsx(TaskStatusBadge, { status: tg.status, t })
658
+ ] })
659
+ ]
660
+ },
661
+ tg.device_id
662
+ )) }) : /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("tasks.noDeviceDetail") }) })
663
+ ] });
664
+ }
665
+ function TaskRow({
666
+ task,
667
+ onCancel,
668
+ onPause,
669
+ onResume,
670
+ onIntegrate,
671
+ t
672
+ }) {
673
+ const client = useClient();
674
+ const [open, setOpen] = useState(false);
675
+ const [detail, setDetail] = useState(null);
676
+ const [events, setEvents] = useState([]);
677
+ const [loadingDetail, setLoadingDetail] = useState(false);
678
+ const progress = task.total > 0 ? Math.round(task.finished / task.total * 100) : 0;
679
+ const ds = displayStatus(task);
680
+ const terminal = TERMINAL.has(ds);
681
+ const paused = ds === "paused";
682
+ useEffect(() => {
683
+ if (!open) return;
684
+ let cancelled = false;
685
+ setLoadingDetail(true);
686
+ (async () => {
687
+ try {
688
+ const [d, ev] = await Promise.all([
689
+ client.tasks.get(task.task_id),
690
+ client.tasks.events(task.task_id, { limit: 20 }).catch(() => ({ events: [] }))
691
+ ]);
692
+ if (!cancelled) {
693
+ setDetail(d);
694
+ setEvents(ev.events ?? []);
695
+ }
696
+ } catch {
697
+ } finally {
698
+ if (!cancelled) setLoadingDetail(false);
699
+ }
700
+ })();
701
+ return () => {
702
+ cancelled = true;
703
+ };
704
+ }, [open, task.task_id, client]);
705
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border", children: [
706
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 p-2.5", children: [
707
+ /* @__PURE__ */ jsx(
708
+ "button",
709
+ {
710
+ type: "button",
711
+ "aria-label": open ? t("tasks.collapseDetail") : t("tasks.expandDetail"),
712
+ onClick: () => setOpen((v) => !v),
713
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
714
+ children: open ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
718
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 truncate text-xs font-medium text-foreground", children: [
719
+ /* @__PURE__ */ jsx(TaskSourceIcon, { source: task.source, t }),
720
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: task.task_id.slice(0, 8) }),
721
+ /* @__PURE__ */ jsx("span", { className: "ml-1 text-muted-foreground", children: t("tasks.source", { source: task.source }) })
722
+ ] }),
723
+ /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: t("tasks.progress", { finished: task.finished, total: task.total, percent: progress }) })
724
+ ] }),
725
+ /* @__PURE__ */ jsx(TaskStatusBadge, { status: ds, t }),
726
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
727
+ /* @__PURE__ */ jsx(
728
+ "button",
729
+ {
730
+ type: "button",
731
+ title: t("integration.openForTask"),
732
+ onClick: () => onIntegrate({ params: task.params }),
733
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
734
+ children: /* @__PURE__ */ jsx(Plug, { className: "h-3.5 w-3.5" })
735
+ }
736
+ ),
737
+ !terminal && /* @__PURE__ */ jsxs(Fragment, { children: [
738
+ paused ? /* @__PURE__ */ jsx(
739
+ "button",
740
+ {
741
+ type: "button",
742
+ title: t("tasks.resume"),
743
+ onClick: onResume,
744
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
745
+ children: /* @__PURE__ */ jsx(PlayCircle, { className: "h-3.5 w-3.5" })
746
+ }
747
+ ) : /* @__PURE__ */ jsx(
748
+ "button",
749
+ {
750
+ type: "button",
751
+ title: t("tasks.pause"),
752
+ onClick: onPause,
753
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
754
+ children: /* @__PURE__ */ jsx(Pause, { className: "h-3.5 w-3.5" })
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsx(
758
+ "button",
759
+ {
760
+ type: "button",
761
+ title: t("tasks.cancel"),
762
+ onClick: onCancel,
763
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-destructive",
764
+ children: /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" })
765
+ }
766
+ )
767
+ ] })
768
+ ] })
769
+ ] }),
770
+ open && /* @__PURE__ */ jsxs("div", { className: "border-t border-border bg-secondary/30 p-2.5", children: [
771
+ loadingDetail && !detail ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("tasks.loadingDetail") }) : detail && detail.targets.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-1", children: detail.targets.map((tg) => /* @__PURE__ */ jsxs(
772
+ "div",
773
+ {
774
+ className: "flex items-center justify-between gap-2 text-[11px]",
775
+ children: [
776
+ /* @__PURE__ */ jsx("span", { className: "truncate font-mono", children: tg.device_id }),
777
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
778
+ tg.error && /* @__PURE__ */ jsx("span", { className: "truncate text-destructive", title: tg.error, children: tg.error }),
779
+ /* @__PURE__ */ jsx(TaskStatusBadge, { status: tg.status, t })
780
+ ] })
781
+ ]
782
+ },
783
+ tg.device_id
784
+ )) }) : /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("tasks.noTargetDetail") }),
785
+ events.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-2 border-t border-border/60 pt-2", children: [
786
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-medium text-muted-foreground", children: t("tasks.recentEvents") }),
787
+ /* @__PURE__ */ jsx("div", { className: "max-h-40 space-y-0.5 overflow-y-auto", children: events.map((ev) => /* @__PURE__ */ jsxs(
788
+ "div",
789
+ {
790
+ className: "flex items-center justify-between gap-2 text-[11px] text-muted-foreground",
791
+ children: [
792
+ /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
793
+ ev.kind,
794
+ ev.status ? ` \xB7 ${ev.status}` : "",
795
+ ev.external_id ? ` \xB7 ${ev.external_id}` : ""
796
+ ] }),
797
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 opacity-60", children: new Date(ev.created_at).toLocaleTimeString() })
798
+ ]
799
+ },
800
+ ev.id
801
+ )) })
802
+ ] })
803
+ ] })
804
+ ] });
805
+ }
806
+ function CopyButton({ text, t }) {
807
+ const [done, setDone] = useState(false);
808
+ const copy = async () => {
809
+ try {
810
+ await navigator.clipboard?.writeText(text);
811
+ setDone(true);
812
+ setTimeout(() => setDone(false), 1500);
813
+ } catch {
814
+ }
815
+ };
816
+ return /* @__PURE__ */ jsxs(
817
+ "button",
818
+ {
819
+ type: "button",
820
+ onClick: copy,
821
+ className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-primary hover:bg-secondary",
822
+ children: [
823
+ /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" }),
824
+ done ? t("integration.copied") : t("integration.copy")
825
+ ]
826
+ }
827
+ );
828
+ }
829
+ function IntegrationDrawer({
830
+ open,
831
+ onClose,
832
+ t,
833
+ exec
834
+ }) {
835
+ const cfg = useAixConfig();
836
+ const { data: tokens, loading, create, revoke } = useApiTokens();
837
+ const [name, setName] = useState("");
838
+ const [pin, setPin] = useState(false);
839
+ const [projectId, setProjectId] = useState("");
840
+ const [creating, setCreating] = useState(false);
841
+ const [error, setError] = useState(null);
842
+ const [selected, setSelected] = useState(null);
843
+ useEffect(() => {
844
+ if (!open) setSelected(null);
845
+ }, [open]);
846
+ const onCreate = async () => {
847
+ if (!name.trim()) return;
848
+ setCreating(true);
849
+ setError(null);
850
+ try {
851
+ const created = await create({
852
+ name: name.trim(),
853
+ project_id: pin && projectId.trim() ? projectId.trim() : void 0
854
+ });
855
+ setSelected({ token: created.token, unpinned: created.project_id == null });
856
+ setName("");
857
+ } catch (err) {
858
+ setError(String(err));
859
+ } finally {
860
+ setCreating(false);
861
+ }
862
+ };
863
+ const tokenList = tokens ?? [];
864
+ const snippetToken = selected?.token ?? `${tokenList[0]?.token_prefix ?? "aixpat_"}\u2026`;
865
+ const snippetUnpinned = selected?.unpinned ?? (tokenList[0] ? tokenList[0].project_id == null : true);
866
+ const snippets = buildSnippets({
867
+ baseUrl: cfg.baseUrl,
868
+ token: snippetToken,
869
+ unpinned: snippetUnpinned,
870
+ exec
871
+ });
872
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
873
+ /* @__PURE__ */ jsx(
874
+ "div",
875
+ {
876
+ className: cn(
877
+ "fixed inset-0 z-30 bg-black/40 transition-opacity duration-300",
878
+ open ? "opacity-100" : "pointer-events-none opacity-0"
879
+ ),
880
+ onClick: onClose
881
+ }
882
+ ),
883
+ /* @__PURE__ */ jsxs(
884
+ "div",
885
+ {
886
+ className: cn(
887
+ "fixed right-0 top-0 z-40 flex h-full w-full max-w-lg flex-col border-l border-border bg-card transition-transform duration-300 ease-in-out",
888
+ open ? "translate-x-0" : "translate-x-full"
889
+ ),
890
+ children: [
891
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 py-3", children: [
892
+ /* @__PURE__ */ jsxs("h2", { className: "flex items-center gap-2 text-body-md font-semibold text-foreground", children: [
893
+ /* @__PURE__ */ jsx(Plug, { className: "h-4 w-4" }),
894
+ t("integration.title")
895
+ ] }),
896
+ /* @__PURE__ */ jsx(
897
+ "button",
898
+ {
899
+ type: "button",
900
+ "aria-label": t("common.close"),
901
+ onClick: onClose,
902
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
903
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
904
+ }
905
+ )
906
+ ] }),
907
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-5 overflow-y-auto p-4", children: [
908
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
909
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-foreground", children: t("integration.tokens") }),
910
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border p-2.5", children: [
911
+ /* @__PURE__ */ jsx(
912
+ Input,
913
+ {
914
+ value: name,
915
+ placeholder: t("integration.tokenNamePlaceholder"),
916
+ onChange: (e) => setName(e.target.value)
917
+ }
918
+ ),
919
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
920
+ /* @__PURE__ */ jsx(
921
+ "input",
922
+ {
923
+ type: "checkbox",
924
+ checked: pin,
925
+ onChange: (e) => setPin(e.target.checked),
926
+ className: "h-3.5 w-3.5 rounded border-input accent-primary"
927
+ }
928
+ ),
929
+ pin ? t("integration.scope.pin") : t("integration.scope.all")
930
+ ] }),
931
+ pin && /* @__PURE__ */ jsx(
932
+ Input,
933
+ {
934
+ value: projectId,
935
+ placeholder: t("integration.projectIdPlaceholder"),
936
+ onChange: (e) => setProjectId(e.target.value)
937
+ }
938
+ ),
939
+ /* @__PURE__ */ jsxs(Button, { variant: "primary", size: "sm", onClick: () => void onCreate(), disabled: creating || !name.trim(), children: [
940
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
941
+ creating ? t("integration.creating") : t("integration.create")
942
+ ] }),
943
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
944
+ ] }),
945
+ selected?.token && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-primary/40 bg-primary/5 p-2.5", children: [
946
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] text-primary", children: t("integration.created.once") }),
947
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
948
+ /* @__PURE__ */ jsx("code", { className: "truncate font-mono text-xs text-foreground", children: selected.token }),
949
+ /* @__PURE__ */ jsx(CopyButton, { text: selected.token, t })
950
+ ] })
951
+ ] }),
952
+ loading ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("common.loading") }) : tokenList.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("integration.tokens.empty") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: tokenList.map((tk) => /* @__PURE__ */ jsxs(
953
+ "div",
954
+ {
955
+ className: "flex items-center justify-between gap-2 rounded border border-border/60 px-2 py-1.5 text-xs",
956
+ children: [
957
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
958
+ /* @__PURE__ */ jsxs("p", { className: "truncate font-medium text-foreground", children: [
959
+ tk.name,
960
+ /* @__PURE__ */ jsxs("span", { className: "ml-1.5 font-mono text-muted-foreground", children: [
961
+ tk.token_prefix,
962
+ "\u2026"
963
+ ] })
964
+ ] }),
965
+ /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: tk.revoked_at ? t("integration.revoked") : `${t("integration.lastUsed")}: ${tk.last_used_at ? new Date(tk.last_used_at).toLocaleDateString() : t("integration.never")}` })
966
+ ] }),
967
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
968
+ /* @__PURE__ */ jsx(
969
+ "button",
970
+ {
971
+ type: "button",
972
+ title: t("integration.selectToken"),
973
+ onClick: () => setSelected({ token: `${tk.token_prefix}\u2026`, unpinned: tk.project_id == null }),
974
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-foreground",
975
+ children: /* @__PURE__ */ jsx(Plug, { className: "h-3.5 w-3.5" })
976
+ }
977
+ ),
978
+ !tk.revoked_at && /* @__PURE__ */ jsx(
979
+ "button",
980
+ {
981
+ type: "button",
982
+ title: t("integration.revoke"),
983
+ onClick: () => {
984
+ if (typeof window !== "undefined" && !window.confirm(t("integration.revokeConfirm"))) return;
985
+ void revoke(tk.id);
986
+ },
987
+ className: "rounded p-1 text-muted-foreground hover:bg-secondary hover:text-destructive",
988
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
989
+ }
990
+ )
991
+ ] })
992
+ ]
993
+ },
994
+ tk.id
995
+ )) })
996
+ ] }),
997
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
998
+ /* @__PURE__ */ jsx("h3", { className: "text-xs font-semibold text-foreground", children: t("integration.snippets") }),
999
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("integration.selectTokenHint") }),
1000
+ snippetUnpinned && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-warning", children: t("integration.unpinnedHint") }),
1001
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: snippets.map((sn) => /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border", children: [
1002
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-2.5 py-1.5", children: [
1003
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
1004
+ /* @__PURE__ */ jsx("span", { className: "mr-1.5 font-mono text-[10px] text-muted-foreground", children: sn.method }),
1005
+ t(sn.i18nKey)
1006
+ ] }),
1007
+ /* @__PURE__ */ jsx(CopyButton, { text: sn.curl, t })
1008
+ ] }),
1009
+ /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all p-2.5 text-[11px] text-muted-foreground", children: sn.curl })
1010
+ ] }, sn.id)) })
1011
+ ] })
1012
+ ] })
1013
+ ]
1014
+ }
1015
+ )
1016
+ ] });
1017
+ }
1018
+ function TaskSourceIcon({ source, t }) {
1019
+ const isWebhook = source === "webhook";
1020
+ const Icon = isWebhook ? Webhook : MonitorSmartphone;
1021
+ const tip = isWebhook ? t("tasks.sourceTip.webhook") : t("tasks.sourceTip.local");
1022
+ return /* @__PURE__ */ jsx(
1023
+ "span",
1024
+ {
1025
+ title: tip,
1026
+ className: cn(
1027
+ "inline-flex h-4 w-4 shrink-0 items-center justify-center",
1028
+ isWebhook ? "text-primary" : "text-muted-foreground"
1029
+ ),
1030
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" })
1031
+ }
1032
+ );
1033
+ }
1034
+ function TaskStatusBadge({ status, t }) {
1035
+ const variant = status === "succeeded" ? "success" : status === "failed" || status === "expired" ? "destructive" : status === "running" || status === "notified" ? "info" : status === "paused" ? "warning" : "muted";
1036
+ return /* @__PURE__ */ jsx(Badge, { variant, children: t(`tasks.status.${status}`) });
1037
+ }
1038
+
1039
+ export { TasksPage };