@datum-cloud/datum-ui 0.2.0-alpha.3 → 0.2.0-alpha.5

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 (145) hide show
  1. package/README.md +66 -32
  2. package/dist/alert/index.mjs +3 -0
  3. package/dist/alert-BC2Mccfo.mjs +95 -0
  4. package/dist/autocomplete/index.mjs +7 -0
  5. package/dist/autocomplete-DZtI97HP.mjs +295 -0
  6. package/dist/avatar-stack/index.mjs +5 -0
  7. package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
  8. package/dist/badge/index.mjs +3 -0
  9. package/dist/badge-bFgeYceE.mjs +185 -0
  10. package/dist/breadcrumb/index.mjs +4 -0
  11. package/dist/breadcrumb-BGYJgom_.mjs +71 -0
  12. package/dist/button/index.mjs +4 -0
  13. package/dist/button-AzpnV-WB.mjs +49 -0
  14. package/dist/button-C1wRfGtT.mjs +230 -0
  15. package/dist/button-group/index.mjs +5 -0
  16. package/dist/button-group-C1IB2K5s.mjs +40 -0
  17. package/dist/calendar/index.mjs +5 -0
  18. package/dist/calendar-DlIHeWb0.mjs +113 -0
  19. package/dist/card/index.mjs +4 -0
  20. package/dist/card-3Kd0VdNf.mjs +63 -0
  21. package/dist/chart/index.mjs +4 -0
  22. package/dist/chart-BZqUKpkh.mjs +143 -0
  23. package/dist/checkbox/index.mjs +4 -0
  24. package/dist/checkbox-LG1OKTpG.mjs +34 -0
  25. package/dist/col-lrLMZaTJ.mjs +184 -0
  26. package/dist/collapsible/index.mjs +3 -0
  27. package/dist/collapsible-Bt9UYfv3.mjs +9 -0
  28. package/dist/command/index.mjs +5 -0
  29. package/dist/command-s0Yv3abE.mjs +86 -0
  30. package/dist/components/features/date-picker/index.d.ts +3 -0
  31. package/dist/components/features/date-picker/index.d.ts.map +1 -0
  32. package/dist/components/features/dropzone/index.d.ts +1 -0
  33. package/dist/components/features/dropzone/index.d.ts.map +1 -1
  34. package/dist/components/themes/index.d.ts +1 -1
  35. package/dist/components/themes/index.d.ts.map +1 -1
  36. package/dist/components/themes/types.d.ts +0 -2
  37. package/dist/components/themes/types.d.ts.map +1 -1
  38. package/dist/date-picker/index.mjs +9 -0
  39. package/dist/dialog/index.mjs +5 -0
  40. package/dist/dialog-DXBaT9gA.mjs +86 -0
  41. package/dist/dialog-bnMMf9GD.mjs +73 -0
  42. package/dist/dropdown/index.mjs +3 -0
  43. package/dist/dropdown-DtSa_lqc.mjs +112 -0
  44. package/dist/dropzone/index.mjs +5 -0
  45. package/dist/dropzone-BkOnwrS4.mjs +221 -0
  46. package/dist/empty-content/index.mjs +3 -0
  47. package/dist/empty-content-BM9rzI13.mjs +196 -0
  48. package/dist/exports/map.d.ts +3 -0
  49. package/dist/exports/map.d.ts.map +1 -0
  50. package/dist/fonts/AllianceNo1-Medium.ttf +0 -0
  51. package/dist/fonts/AllianceNo1-Regular.ttf +0 -0
  52. package/dist/fonts/AllianceNo1-SemiBold.ttf +0 -0
  53. package/dist/form/index.mjs +146 -0
  54. package/dist/grid/index.mjs +3 -0
  55. package/dist/hooks/index.mjs +2 -3
  56. package/dist/hover-card/index.mjs +4 -0
  57. package/dist/hover-card-CUPfFUqE.mjs +33 -0
  58. package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
  59. package/dist/icons/index.mjs +3 -3
  60. package/dist/index.d.ts +0 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.mjs +65 -9
  63. package/dist/input/index.mjs +5 -0
  64. package/dist/input-DuyjEKEW.mjs +17 -0
  65. package/dist/input-fzXBheCN.mjs +17 -0
  66. package/dist/input-group/index.mjs +7 -0
  67. package/dist/input-group-CPaFSTEV.mjs +80 -0
  68. package/dist/input-number/index.mjs +6 -0
  69. package/dist/input-number-9o62JHRl.mjs +106 -0
  70. package/dist/input-with-addons/index.mjs +3 -0
  71. package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
  72. package/dist/label/index.mjs +4 -0
  73. package/dist/label-_ste_Re3.mjs +44 -0
  74. package/dist/link-button-TIF2Zdrk.mjs +36 -0
  75. package/dist/loader-overlay/index.mjs +3 -0
  76. package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
  77. package/dist/map/index.mjs +13 -0
  78. package/dist/map-WL6jhkSM.mjs +1094 -0
  79. package/dist/more-actions/index.mjs +5 -0
  80. package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
  81. package/dist/nprogress/index.mjs +32 -0
  82. package/dist/page-title/index.mjs +3 -0
  83. package/dist/page-title-BJuo81rT.mjs +26 -0
  84. package/dist/popover/index.mjs +4 -0
  85. package/dist/popover-SQlKSz6L.mjs +36 -0
  86. package/dist/radio-group/index.mjs +4 -0
  87. package/dist/radio-group-Oshv0b-U.mjs +49 -0
  88. package/dist/select/index.mjs +4 -0
  89. package/dist/select-DVlEzD2W.mjs +166 -0
  90. package/dist/separator/index.mjs +4 -0
  91. package/dist/separator-T2ppyD-8.mjs +18 -0
  92. package/dist/sheet/index.mjs +5 -0
  93. package/dist/sheet-BKiCwtNO.mjs +45 -0
  94. package/dist/sheet-CtnP6gTD.mjs +77 -0
  95. package/dist/sidebar/index.mjs +11 -0
  96. package/dist/sidebar-DfqezV8t.mjs +945 -0
  97. package/dist/skeleton/index.mjs +4 -0
  98. package/dist/skeleton-vzbxA-DQ.mjs +13 -0
  99. package/dist/spinner/index.mjs +4 -0
  100. package/dist/spinner-BE7k2bAD.mjs +16 -0
  101. package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
  102. package/dist/stepper/index.mjs +5 -0
  103. package/dist/stepper-SWB-u_nM.mjs +323 -0
  104. package/dist/{style.css → styles.css} +317 -575
  105. package/dist/styles.mjs +1 -0
  106. package/dist/switch/index.mjs +4 -0
  107. package/dist/switch-Calk7Gyw.mjs +32 -0
  108. package/dist/table/index.mjs +4 -0
  109. package/dist/table-CsXBcQLI.mjs +68 -0
  110. package/dist/tabs/index.mjs +3 -0
  111. package/dist/tabs-D8n-dqnw.mjs +52 -0
  112. package/dist/tag-input/index.mjs +5 -0
  113. package/dist/tag-input-Di7SDNbK.mjs +284 -0
  114. package/dist/task-queue/index.mjs +7 -0
  115. package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
  116. package/dist/textarea/index.mjs +5 -0
  117. package/dist/textarea-CxE3YbC7.mjs +17 -0
  118. package/dist/textarea-QYRcDEpK.mjs +15 -0
  119. package/dist/theme/index.mjs +3 -0
  120. package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-CzCxEFFh.mjs} +63 -1
  121. package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
  122. package/dist/toast/index.mjs +3 -0
  123. package/dist/tooltip/index.mjs +4 -0
  124. package/dist/tooltip-Dd3ActSS.mjs +74 -0
  125. package/dist/typography/index.mjs +3 -0
  126. package/dist/typography-UA7ZZvgJ.mjs +200 -0
  127. package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
  128. package/dist/use-stepper-BaToCYMs.mjs +2017 -0
  129. package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
  130. package/dist/utils/index.mjs +0 -1
  131. package/dist/utils-Bfgoe-Gm.mjs +20 -0
  132. package/dist/visually-hidden/index.mjs +3 -0
  133. package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
  134. package/package.json +223 -24
  135. package/dist/components/index.mjs +0 -8
  136. package/dist/datum.provider-D6VMjSV0.mjs +0 -37
  137. package/dist/providers/datum.provider.d.ts +0 -20
  138. package/dist/providers/datum.provider.d.ts.map +0 -1
  139. package/dist/providers/index.d.ts +0 -3
  140. package/dist/providers/index.d.ts.map +0 -1
  141. package/dist/providers/index.mjs +0 -4
  142. package/dist/theme-script-DHyLk25i.mjs +0 -11128
  143. /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
  144. /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-C4JYls8q.mjs} +0 -0
  145. /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-B6wPrZV8.mjs} +0 -0
@@ -0,0 +1,1356 @@
1
+ import { t as cn } from "./cn-DWCc1QRE.mjs";
2
+ import { t as Badge } from "./badge-bFgeYceE.mjs";
3
+ import { t as SpinnerIcon } from "./spinner.icon-Bg8zgGh0.mjs";
4
+ import { t as Button } from "./button-C1wRfGtT.mjs";
5
+ import { t as Icon } from "./icon-wrapper-9ticVbRL.mjs";
6
+ import { t as Dialog } from "./dialog-bnMMf9GD.mjs";
7
+ import { c as TableRow, i as TableCell, n as TableBody, o as TableHead, s as TableHeader, t as Table } from "./table-CsXBcQLI.mjs";
8
+ import { a as TooltipTrigger, n as Tooltip, r as TooltipContent, t as Tooltip$1 } from "./tooltip-Dd3ActSS.mjs";
9
+ import { h as DropdownMenuTrigger, r as DropdownMenuContent, t as DropdownMenu } from "./dropdown-DtSa_lqc.mjs";
10
+ import { Ban, CheckCircle2, CircleAlert, CircleCheck, CornerDownRightIcon, FileIcon, ListTodo, X, XCircle } from "lucide-react";
11
+ import { createContext, use, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
12
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
+ import { formatDistanceToNowStrict } from "date-fns";
14
+
15
+ //#region src/components/features/task-queue/engine/executor.ts
16
+ function createTaskContext(task, callbacks) {
17
+ let cancelled = false;
18
+ let shouldStop = false;
19
+ const cleanupCallbacks = [];
20
+ const updateTask = () => {
21
+ callbacks.onUpdate({
22
+ ...task,
23
+ succeededItems: [...task.succeededItems],
24
+ failedItems: [...task.failedItems]
25
+ });
26
+ };
27
+ return {
28
+ ctx: {
29
+ get items() {
30
+ return task.items ?? [];
31
+ },
32
+ get cancelled() {
33
+ return cancelled || shouldStop;
34
+ },
35
+ get failedItems() {
36
+ return [...task.failedItems];
37
+ },
38
+ succeed(itemId) {
39
+ task.completed += 1;
40
+ if (itemId) task.succeededItems = [...task.succeededItems, itemId];
41
+ updateTask();
42
+ },
43
+ fail(itemId, message) {
44
+ task.failed += 1;
45
+ if (itemId || message) task.failedItems = [...task.failedItems, {
46
+ id: itemId,
47
+ message: message ?? "Unknown error"
48
+ }];
49
+ if (task.errorStrategy === "stop") shouldStop = true;
50
+ updateTask();
51
+ },
52
+ setTitle(title) {
53
+ task.title = title;
54
+ updateTask();
55
+ },
56
+ setResult(result) {
57
+ task.result = result;
58
+ },
59
+ onCancel(cleanup) {
60
+ cleanupCallbacks.push(cleanup);
61
+ }
62
+ },
63
+ getCancelled: () => cancelled,
64
+ setCancelled: (v) => {
65
+ cancelled = v;
66
+ if (v) cleanupCallbacks.forEach((cleanup) => {
67
+ try {
68
+ cleanup();
69
+ } catch (error) {
70
+ console.error("[TaskQueue] Cleanup callback error:", error);
71
+ }
72
+ });
73
+ },
74
+ getShouldStop: () => shouldStop
75
+ };
76
+ }
77
+ async function executeTask(task, callbacks) {
78
+ const processor = task._processor;
79
+ if (!processor) return {
80
+ status: "failed",
81
+ completed: 0,
82
+ failed: 0,
83
+ failedItems: [{ message: "No processor attached" }]
84
+ };
85
+ task.status = "running";
86
+ task.startedAt = Date.now();
87
+ if (!task.succeededItems) task.succeededItems = [];
88
+ callbacks.onUpdate({ ...task });
89
+ const { ctx, getCancelled, setCancelled, getShouldStop } = createTaskContext(task, callbacks);
90
+ callbacks.onCancelReady?.(setCancelled);
91
+ try {
92
+ await processor(ctx);
93
+ if (getCancelled()) task.status = "cancelled";
94
+ else if (getShouldStop() || task.failed > 0) task.status = "failed";
95
+ else task.status = "completed";
96
+ } catch (error) {
97
+ task.status = "failed";
98
+ const message = error instanceof Error ? error.message : "Unknown error";
99
+ task.failedItems = [...task.failedItems, { message }];
100
+ task.failed += 1;
101
+ }
102
+ task.completedAt = Date.now();
103
+ callbacks.onUpdate({ ...task });
104
+ callbacks.onTaskComplete?.();
105
+ return {
106
+ status: task.status,
107
+ completed: task.completed,
108
+ failed: task.failed,
109
+ failedItems: [...task.failedItems],
110
+ result: task.result
111
+ };
112
+ }
113
+
114
+ //#endregion
115
+ //#region src/components/features/task-queue/constants.ts
116
+ const TASK_QUEUE_DEFAULTS = {
117
+ concurrency: 3,
118
+ errorStrategy: "continue",
119
+ cancelable: true,
120
+ retryable: true
121
+ };
122
+ const TASK_STORAGE_KEY = "datum-task-queue";
123
+
124
+ //#endregion
125
+ //#region src/components/features/task-queue/utils/index.ts
126
+ /**
127
+ * Check if code is running in a browser environment.
128
+ * Used for SSR safety in storage and other browser-dependent code.
129
+ */
130
+ function isBrowser() {
131
+ return typeof window !== "undefined" && typeof localStorage !== "undefined";
132
+ }
133
+ function generateTaskId() {
134
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `task_${crypto.randomUUID()}`;
135
+ return `task_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
136
+ }
137
+ /**
138
+ * Extract an ID from an item using common patterns.
139
+ * Checks: primitives (string/number) → obj.id → obj.name → obj.key → obj.uuid
140
+ */
141
+ function extractItemId(item) {
142
+ if (typeof item === "string" || typeof item === "number") return String(item);
143
+ if (item && typeof item === "object") {
144
+ const obj = item;
145
+ if (typeof obj.id === "string" || typeof obj.id === "number") return String(obj.id);
146
+ if (typeof obj.name === "string") return obj.name;
147
+ if (typeof obj.key === "string") return obj.key;
148
+ if (typeof obj.uuid === "string") return obj.uuid;
149
+ }
150
+ }
151
+ /**
152
+ * Create metadata for project-scoped tasks.
153
+ * Use this when enqueueing tasks that operate on project resources.
154
+ */
155
+ function createProjectMetadata(project, org, extra) {
156
+ return {
157
+ scope: "project",
158
+ projectId: project.id,
159
+ projectName: project.name,
160
+ orgId: org.id,
161
+ orgName: org.name,
162
+ ...extra
163
+ };
164
+ }
165
+ /**
166
+ * Create metadata for organization-scoped tasks.
167
+ * Use this when enqueueing tasks that operate on org-level resources.
168
+ */
169
+ function createOrgMetadata(org, extra) {
170
+ return {
171
+ scope: "org",
172
+ orgId: org.id,
173
+ orgName: org.name,
174
+ ...extra
175
+ };
176
+ }
177
+ /**
178
+ * Create metadata for user-scoped tasks.
179
+ * Use this when enqueueing tasks that operate on user-level resources.
180
+ */
181
+ function createUserMetadata(extra) {
182
+ return {
183
+ scope: "user",
184
+ ...extra
185
+ };
186
+ }
187
+
188
+ //#endregion
189
+ //#region src/components/features/task-queue/engine/storage/local-storage.ts
190
+ var LocalTaskStorage = class {
191
+ key;
192
+ constructor(key = TASK_STORAGE_KEY) {
193
+ this.key = key;
194
+ }
195
+ getAll() {
196
+ if (!isBrowser()) return [];
197
+ try {
198
+ const raw = localStorage.getItem(this.key);
199
+ return raw ? JSON.parse(raw) : [];
200
+ } catch {
201
+ return [];
202
+ }
203
+ }
204
+ get(id) {
205
+ if (!isBrowser()) return void 0;
206
+ return this.getAll().find((t) => t.id === id);
207
+ }
208
+ set(id, task) {
209
+ if (!isBrowser()) return;
210
+ const tasks = this.getAll();
211
+ const index = tasks.findIndex((t) => t.id === id);
212
+ if (index >= 0) tasks[index] = task;
213
+ else tasks.push(task);
214
+ this.persist(tasks);
215
+ }
216
+ remove(id) {
217
+ if (!isBrowser()) return;
218
+ const tasks = this.getAll().filter((t) => t.id !== id);
219
+ this.persist(tasks);
220
+ }
221
+ clear() {
222
+ if (!isBrowser()) return;
223
+ try {
224
+ localStorage.removeItem(this.key);
225
+ } catch {}
226
+ }
227
+ persist(tasks) {
228
+ try {
229
+ const serializable = tasks.map((task) => {
230
+ const { _processor, _icon, _completionActions, ...rest } = task;
231
+ return rest;
232
+ });
233
+ localStorage.setItem(this.key, JSON.stringify(serializable));
234
+ } catch (error) {
235
+ if (error instanceof Error && error.name === "QuotaExceededError") console.warn("[TaskQueue] Storage quota exceeded. Consider dismissing old tasks.");
236
+ }
237
+ }
238
+ };
239
+
240
+ //#endregion
241
+ //#region src/components/features/task-queue/engine/storage/memory-storage.ts
242
+ /**
243
+ * In-memory task storage.
244
+ *
245
+ * Tasks are stored in a Map and lost on page reload.
246
+ * Use with beforeunload warning to alert users of active tasks.
247
+ *
248
+ * SSR-safe: works on both server and client (just empty on server).
249
+ */
250
+ var MemoryTaskStorage = class {
251
+ tasks = /* @__PURE__ */ new Map();
252
+ getAll() {
253
+ return Array.from(this.tasks.values());
254
+ }
255
+ get(id) {
256
+ return this.tasks.get(id);
257
+ }
258
+ set(id, task) {
259
+ this.tasks.set(id, task);
260
+ }
261
+ remove(id) {
262
+ this.tasks.delete(id);
263
+ }
264
+ clear() {
265
+ this.tasks.clear();
266
+ }
267
+ };
268
+
269
+ //#endregion
270
+ //#region src/components/features/task-queue/engine/storage/redis-storage.ts
271
+ const DEFAULT_TTL = 3600 * 24 * 7;
272
+ var RedisTaskStorage = class {
273
+ client;
274
+ key;
275
+ cache = /* @__PURE__ */ new Map();
276
+ initialized = false;
277
+ initPromise = null;
278
+ ttl;
279
+ constructor(client, key = TASK_STORAGE_KEY, ttl = DEFAULT_TTL) {
280
+ this.client = client;
281
+ this.key = key;
282
+ this.ttl = ttl;
283
+ if (isBrowser()) this.initPromise = this.initialize();
284
+ }
285
+ /**
286
+ * Wait for initialization to complete.
287
+ * Call this before accessing data if you need guaranteed consistency.
288
+ */
289
+ async waitForInit() {
290
+ if (this.initPromise) await this.initPromise;
291
+ }
292
+ async initialize() {
293
+ if (this.initialized) return;
294
+ if (!isBrowser()) {
295
+ this.initialized = true;
296
+ return;
297
+ }
298
+ try {
299
+ const raw = await this.client.get(this.key);
300
+ if (raw) JSON.parse(raw).forEach((t) => this.cache.set(t.id, t));
301
+ this.initialized = true;
302
+ } catch {
303
+ this.initialized = true;
304
+ }
305
+ }
306
+ getAll() {
307
+ return Array.from(this.cache.values());
308
+ }
309
+ get(id) {
310
+ return this.cache.get(id);
311
+ }
312
+ set(id, task) {
313
+ if (!isBrowser()) return;
314
+ this.cache.set(id, task);
315
+ this.syncToRedis();
316
+ }
317
+ remove(id) {
318
+ if (!isBrowser()) return;
319
+ this.cache.delete(id);
320
+ this.syncToRedis();
321
+ }
322
+ clear() {
323
+ if (!isBrowser()) return;
324
+ this.cache.clear();
325
+ this.client.del(this.key).catch(() => {});
326
+ }
327
+ syncToRedis() {
328
+ const tasks = this.getAll().map((task) => {
329
+ const { _processor, _icon, _completionActions, ...rest } = task;
330
+ return rest;
331
+ });
332
+ const value = JSON.stringify(tasks);
333
+ if ("setex" in this.client && typeof this.client.setex === "function") this.client.setex(this.key, this.ttl, value).catch(() => {});
334
+ else this.client.set(this.key, value).catch(() => {});
335
+ }
336
+ };
337
+
338
+ //#endregion
339
+ //#region src/components/features/task-queue/engine/storage/detect-storage.ts
340
+ /**
341
+ * Auto-detects and returns the appropriate storage backend.
342
+ *
343
+ * Storage types:
344
+ * - `memory` (default): In-memory storage, tasks lost on reload
345
+ * - `local`: Browser localStorage, tasks persist across reloads
346
+ * - `auto`: Uses Redis if available, otherwise localStorage
347
+ *
348
+ * All storage backends are SSR-safe and will return empty data on the server.
349
+ */
350
+ function detectStorage(options = {}) {
351
+ const { redisClient, storageKey, storageType = "memory" } = options;
352
+ if (storageType === "memory") return new MemoryTaskStorage();
353
+ if (storageType === "local") return new LocalTaskStorage(storageKey);
354
+ if (redisClient && redisClient.status === "ready") return new RedisTaskStorage(redisClient, storageKey);
355
+ return new LocalTaskStorage(storageKey);
356
+ }
357
+
358
+ //#endregion
359
+ //#region src/components/features/task-queue/engine/queue.ts
360
+ var TaskQueue = class {
361
+ storage;
362
+ concurrency;
363
+ runningCount = 0;
364
+ listeners = /* @__PURE__ */ new Set();
365
+ taskResolvers = /* @__PURE__ */ new Map();
366
+ snapshot = [];
367
+ notifyScheduled = false;
368
+ processors = /* @__PURE__ */ new Map();
369
+ cancelFunctions = /* @__PURE__ */ new Map();
370
+ onCompleteCallbacks = /* @__PURE__ */ new Map();
371
+ taskTimeouts = /* @__PURE__ */ new Map();
372
+ _activeSummary = null;
373
+ _summaryRenderContent;
374
+ constructor(config = {}) {
375
+ this.concurrency = config.concurrency ?? TASK_QUEUE_DEFAULTS.concurrency;
376
+ this.storage = config.storage ?? detectStorage({
377
+ redisClient: config.redisClient,
378
+ storageKey: config.storageKey,
379
+ storageType: config.storageType
380
+ });
381
+ this._summaryRenderContent = config.summaryRenderContent;
382
+ this.updateSnapshot();
383
+ }
384
+ subscribe = (listener) => {
385
+ this.listeners.add(listener);
386
+ return () => {
387
+ this.listeners.delete(listener);
388
+ };
389
+ };
390
+ getSnapshot = () => {
391
+ return this.snapshot;
392
+ };
393
+ notify() {
394
+ if (this.notifyScheduled) return;
395
+ this.notifyScheduled = true;
396
+ queueMicrotask(() => {
397
+ this.notifyScheduled = false;
398
+ this.updateSnapshot();
399
+ this.listeners.forEach((listener) => listener());
400
+ });
401
+ }
402
+ updateSnapshot() {
403
+ this.snapshot = this.storage.getAll();
404
+ }
405
+ enqueue = (options) => {
406
+ const id = generateTaskId();
407
+ const items = options.items;
408
+ let processor;
409
+ if ("processItem" in options && options.processItem) processor = this.buildProcessor(options);
410
+ else if ("processor" in options && options.processor) processor = options.processor;
411
+ else throw new Error("[TaskQueue] enqueue: must provide either processor or processItem");
412
+ const cancelable = options.cancelable ?? TASK_QUEUE_DEFAULTS.cancelable;
413
+ const confirmBeforeUnload = options.confirmBeforeUnload ?? true;
414
+ const task = {
415
+ id,
416
+ title: options.title,
417
+ status: "pending",
418
+ icon: options.icon,
419
+ category: options.category,
420
+ metadata: options.metadata,
421
+ items,
422
+ total: items?.length,
423
+ completed: 0,
424
+ failed: 0,
425
+ succeededItems: [],
426
+ failedItems: [],
427
+ errorStrategy: options.errorStrategy ?? TASK_QUEUE_DEFAULTS.errorStrategy,
428
+ cancelable,
429
+ retryable: options.retryable ?? TASK_QUEUE_DEFAULTS.retryable,
430
+ confirmBeforeUnload,
431
+ completionActions: options.completionActions,
432
+ retryCount: 0,
433
+ createdAt: Date.now(),
434
+ _processor: processor,
435
+ _originalItems: items ? [...items] : void 0
436
+ };
437
+ this.processors.set(id, processor);
438
+ if (options.onComplete) this.onCompleteCallbacks.set(id, options.onComplete);
439
+ this.storage.set(id, task);
440
+ this.notify();
441
+ const timeout = options.timeout ?? 3e5;
442
+ const timeoutId = setTimeout(() => {
443
+ this.handleTaskTimeout(id);
444
+ }, timeout);
445
+ this.taskTimeouts.set(id, timeoutId);
446
+ const promise = new Promise((resolve) => {
447
+ this.taskResolvers.set(id, resolve);
448
+ });
449
+ this.drain();
450
+ return {
451
+ id,
452
+ cancel: () => this.cancel(id),
453
+ promise
454
+ };
455
+ };
456
+ cancel = (taskId) => {
457
+ const task = this.storage.get(taskId);
458
+ if (!task || task.status !== "running") return;
459
+ const setCancelled = this.cancelFunctions.get(taskId);
460
+ if (setCancelled) setCancelled(true);
461
+ };
462
+ retry = (taskId) => {
463
+ const task = this.storage.get(taskId);
464
+ if (!task) return;
465
+ if (task.status !== "failed" && task.status !== "cancelled") return;
466
+ const processor = this.processors.get(taskId);
467
+ if (!processor) {
468
+ console.error("[TaskQueue] retry: no processor found for task", taskId);
469
+ return;
470
+ }
471
+ if (task.items && task.items.length > 0) {
472
+ const remainingItems = this.getRetryItems(task);
473
+ if (!remainingItems || remainingItems.length === 0) {
474
+ const succeededItems = task.succeededItems ?? [];
475
+ const originalItems = task._originalItems ?? task.items ?? [];
476
+ if (succeededItems.length >= originalItems.length || task.completed >= (task.total ?? 0)) {
477
+ task.status = "completed";
478
+ task.completedAt = Date.now();
479
+ this.storage.set(taskId, task);
480
+ this.notify();
481
+ return;
482
+ }
483
+ console.warn("[TaskQueue] retry: no remaining items for task", taskId);
484
+ return;
485
+ }
486
+ task.status = "pending";
487
+ task.items = remainingItems;
488
+ task.failedItems = [];
489
+ task.failed = 0;
490
+ task.retryCount += 1;
491
+ } else {
492
+ task.status = "pending";
493
+ task.completed = 0;
494
+ task.failed = 0;
495
+ task.succeededItems = [];
496
+ task.failedItems = [];
497
+ task.result = void 0;
498
+ task.retryCount += 1;
499
+ }
500
+ task.startedAt = void 0;
501
+ task.completedAt = void 0;
502
+ this.processors.set(taskId, processor);
503
+ this.storage.set(taskId, task);
504
+ this.notify();
505
+ this.drain();
506
+ };
507
+ getRetryItems(task) {
508
+ const originalItems = task._originalItems ?? task.items;
509
+ if (!originalItems || originalItems.length === 0) return void 0;
510
+ const succeededItems = task.succeededItems ?? [];
511
+ const failedItems = task.failedItems ?? [];
512
+ if (task.status === "cancelled" && succeededItems.length > 0) {
513
+ const remaining = originalItems.filter((item) => {
514
+ const itemId = this.resolveItemId(item);
515
+ if (!itemId) return true;
516
+ return !succeededItems.includes(itemId);
517
+ });
518
+ return remaining.length > 0 ? remaining : void 0;
519
+ }
520
+ if (task.status === "failed" && failedItems.length > 0) {
521
+ const failed = originalItems.filter((item) => {
522
+ const itemId = this.resolveItemId(item);
523
+ if (!itemId) return false;
524
+ return failedItems.some((fi) => fi.id === itemId);
525
+ });
526
+ return failed.length > 0 ? failed : void 0;
527
+ }
528
+ return originalItems;
529
+ }
530
+ /** Extract ID from an item using custom extractor or shared utility */
531
+ resolveItemId(item, getItemId) {
532
+ if (getItemId) return getItemId(item);
533
+ const id = extractItemId(item);
534
+ if (id !== void 0) return id;
535
+ try {
536
+ return JSON.stringify(item);
537
+ } catch {
538
+ return;
539
+ }
540
+ }
541
+ dismiss = (taskId) => {
542
+ const task = this.storage.get(taskId);
543
+ if (!task) return;
544
+ if (task.status === "running" || task.status === "pending") return;
545
+ this.storage.remove(taskId);
546
+ this.processors.delete(taskId);
547
+ this.onCompleteCallbacks.delete(taskId);
548
+ this.notify();
549
+ };
550
+ dismissAll = () => {
551
+ this.storage.getAll().forEach((task) => {
552
+ if (task.status !== "running" && task.status !== "pending") {
553
+ this.storage.remove(task.id);
554
+ this.processors.delete(task.id);
555
+ this.onCompleteCallbacks.delete(task.id);
556
+ }
557
+ });
558
+ this.notify();
559
+ };
560
+ showSummary = (title, items, options) => {
561
+ this._activeSummary = {
562
+ title,
563
+ items,
564
+ renderContent: options?.renderContent
565
+ };
566
+ this.notify();
567
+ };
568
+ closeSummary = () => {
569
+ this._activeSummary = null;
570
+ this.notify();
571
+ };
572
+ getActiveSummary = () => {
573
+ return this._activeSummary;
574
+ };
575
+ getSummaryRenderContent = () => {
576
+ return this._summaryRenderContent;
577
+ };
578
+ buildProcessor(options) {
579
+ const { processItem, itemConcurrency = 1, getItemId, errorStrategy = "continue" } = options;
580
+ return async (ctx) => {
581
+ const pending = [...ctx.items];
582
+ const inFlight = [];
583
+ while (pending.length > 0 || inFlight.length > 0) {
584
+ if (ctx.cancelled) break;
585
+ if (errorStrategy === "stop" && ctx.failedItems.length > 0) break;
586
+ while (inFlight.length < itemConcurrency && pending.length > 0) {
587
+ const item = pending.shift();
588
+ const itemId = this.resolveItemId(item, getItemId) ?? String(item);
589
+ const promise = this.processOneItem(item, itemId, processItem, ctx).finally(() => {
590
+ const idx = inFlight.indexOf(promise);
591
+ if (idx !== -1) inFlight.splice(idx, 1);
592
+ });
593
+ inFlight.push(promise);
594
+ }
595
+ if (inFlight.length > 0) await Promise.race(inFlight);
596
+ }
597
+ };
598
+ }
599
+ async processOneItem(item, itemId, processItem, ctx) {
600
+ let manuallyHandled = false;
601
+ const itemCtx = {
602
+ get cancelled() {
603
+ return ctx.cancelled;
604
+ },
605
+ succeed: (id) => {
606
+ manuallyHandled = true;
607
+ ctx.succeed(id ?? itemId);
608
+ },
609
+ fail: (id, message) => {
610
+ manuallyHandled = true;
611
+ ctx.fail(id ?? itemId, message);
612
+ }
613
+ };
614
+ try {
615
+ await processItem(item, itemCtx);
616
+ if (!manuallyHandled) ctx.succeed(itemId);
617
+ } catch (error) {
618
+ if (!manuallyHandled) {
619
+ const message = error instanceof Error ? error.message : "Unknown error";
620
+ ctx.fail(itemId, message);
621
+ }
622
+ }
623
+ }
624
+ drain() {
625
+ const pending = this.storage.getAll().filter((t) => t.status === "pending");
626
+ while (this.runningCount < this.concurrency && pending.length > 0) {
627
+ const next = pending.shift();
628
+ if (!next) break;
629
+ this.runTask(next);
630
+ }
631
+ }
632
+ async runTask(task) {
633
+ this.runningCount += 1;
634
+ const processor = this.processors.get(task.id);
635
+ if (!processor) {
636
+ console.error("[TaskQueue] No processor found for task", task.id);
637
+ this.runningCount -= 1;
638
+ return;
639
+ }
640
+ task._processor = processor;
641
+ try {
642
+ const outcome = await executeTask(task, {
643
+ onUpdate: (updated) => {
644
+ this.storage.set(updated.id, updated);
645
+ this.notify();
646
+ },
647
+ onCancelReady: (setCancelled) => {
648
+ this.cancelFunctions.set(task.id, setCancelled);
649
+ },
650
+ onTaskComplete: () => {
651
+ this.clearTaskTimeout(task.id);
652
+ }
653
+ });
654
+ const onComplete = this.onCompleteCallbacks.get(task.id);
655
+ if (onComplete) try {
656
+ await onComplete(outcome);
657
+ } catch (error) {
658
+ console.error("[TaskQueue] onComplete callback error:", error);
659
+ }
660
+ const resolver = this.taskResolvers.get(task.id);
661
+ if (resolver) {
662
+ resolver(outcome);
663
+ this.taskResolvers.delete(task.id);
664
+ }
665
+ if (outcome.status === "completed") {
666
+ this.processors.delete(task.id);
667
+ this.onCompleteCallbacks.delete(task.id);
668
+ }
669
+ } finally {
670
+ this.runningCount -= 1;
671
+ this.cancelFunctions.delete(task.id);
672
+ this.drain();
673
+ }
674
+ }
675
+ handleTaskTimeout(taskId) {
676
+ const task = this.storage.get(taskId);
677
+ if (!task || task.status !== "running") return;
678
+ const setCancelled = this.cancelFunctions.get(taskId);
679
+ if (setCancelled) setCancelled(true);
680
+ task.status = "failed";
681
+ task.completedAt = Date.now();
682
+ task.failedItems = [...task.failedItems, { message: "Task timeout: exceeded maximum execution time" }];
683
+ task.failed += 1;
684
+ this.storage.set(taskId, task);
685
+ this.notify();
686
+ const resolver = this.taskResolvers.get(taskId);
687
+ if (resolver) {
688
+ resolver({
689
+ status: "failed",
690
+ completed: task.completed,
691
+ failed: task.failed,
692
+ failedItems: [...task.failedItems],
693
+ result: task.result
694
+ });
695
+ this.taskResolvers.delete(taskId);
696
+ }
697
+ this.processors.delete(taskId);
698
+ this.onCompleteCallbacks.delete(taskId);
699
+ this.cancelFunctions.delete(taskId);
700
+ this.taskTimeouts.delete(taskId);
701
+ }
702
+ clearTaskTimeout(taskId) {
703
+ const timeoutId = this.taskTimeouts.get(taskId);
704
+ if (timeoutId) {
705
+ clearTimeout(timeoutId);
706
+ this.taskTimeouts.delete(taskId);
707
+ }
708
+ }
709
+ };
710
+
711
+ //#endregion
712
+ //#region src/components/features/task-queue/provider/task-queue-provider.tsx
713
+ const TaskQueueContext = createContext(null);
714
+ function TaskQueueProvider({ children, config }) {
715
+ const queueRef = useRef(null);
716
+ if (!queueRef.current) queueRef.current = new TaskQueue(config);
717
+ useEffect(() => {
718
+ const queue = queueRef.current;
719
+ return () => {
720
+ if (!queue) return;
721
+ queue.getSnapshot().forEach((task) => {
722
+ if (task.status === "running") queue.cancel(task.id);
723
+ });
724
+ };
725
+ }, []);
726
+ useEffect(() => {
727
+ const handleBeforeUnload = (e) => {
728
+ if ((queueRef.current?.getSnapshot() ?? []).some((t) => (t.status === "running" || t.status === "pending") && t.confirmBeforeUnload !== false)) {
729
+ e.preventDefault();
730
+ e.returnValue = "Tasks in progress will be lost.";
731
+ return e.returnValue;
732
+ }
733
+ };
734
+ window.addEventListener("beforeunload", handleBeforeUnload);
735
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
736
+ }, []);
737
+ return /* @__PURE__ */ jsx(TaskQueueContext, {
738
+ value: useMemo(() => ({ queue: queueRef.current }), []),
739
+ children
740
+ });
741
+ }
742
+
743
+ //#endregion
744
+ //#region src/components/features/task-queue/hooks/use-task-queue.ts
745
+ const EMPTY_TASKS = [];
746
+ function useTaskQueue(options) {
747
+ const context = use(TaskQueueContext);
748
+ if (!context) throw new Error("useTaskQueue must be used within a TaskQueueProvider");
749
+ const { queue } = context;
750
+ const allTasks = useSyncExternalStore(queue.subscribe, queue.getSnapshot, () => EMPTY_TASKS);
751
+ const tasks = useMemo(() => {
752
+ if (!options?.status) return allTasks;
753
+ return allTasks.filter((t) => t.status === options.status);
754
+ }, [allTasks, options?.status]);
755
+ const activeSummary = useSyncExternalStore(queue.subscribe, queue.getActiveSummary, () => null);
756
+ const summaryRenderContent = queue.getSummaryRenderContent();
757
+ return useMemo(() => ({
758
+ enqueue: queue.enqueue,
759
+ cancel: queue.cancel,
760
+ retry: queue.retry,
761
+ dismiss: queue.dismiss,
762
+ dismissAll: queue.dismissAll,
763
+ showSummary: queue.showSummary,
764
+ closeSummary: queue.closeSummary,
765
+ activeSummary,
766
+ summaryRenderContent,
767
+ tasks
768
+ }), [
769
+ tasks,
770
+ activeSummary,
771
+ summaryRenderContent
772
+ ]);
773
+ }
774
+
775
+ //#endregion
776
+ //#region src/components/features/task-queue/hooks/use-task-scope.ts
777
+ /**
778
+ * Detect the current scope from URL params.
779
+ * Returns project scope if projectId is present, org scope if only orgId, otherwise global.
780
+ *
781
+ * @param params - Route params (e.g. from useParams() in react-router)
782
+ */
783
+ function useCurrentScope(params) {
784
+ return useMemo(() => {
785
+ if (params.projectId) return {
786
+ type: "project",
787
+ projectId: params.projectId,
788
+ orgId: params.orgId
789
+ };
790
+ if (params.orgId) return {
791
+ type: "org",
792
+ orgId: params.orgId
793
+ };
794
+ return { type: "global" };
795
+ }, [params.projectId, params.orgId]);
796
+ }
797
+ /**
798
+ * Check if task metadata matches the current scope.
799
+ */
800
+ function matchesCurrentScope(metadata, scope) {
801
+ if (!metadata) return false;
802
+ if (scope.type === "project") return metadata.projectId === scope.projectId;
803
+ if (scope.type === "org") return metadata.orgId === scope.orgId && !metadata.projectId;
804
+ return false;
805
+ }
806
+ /**
807
+ * Get the context label to display for a task.
808
+ * Returns formatted label with scope type: "Project: Name", "Org: Name", etc.
809
+ */
810
+ function getContextLabel(metadata) {
811
+ if (!metadata) return void 0;
812
+ const scope = metadata.scope;
813
+ if (scope === "project" && metadata.projectName) return `Project: ${metadata.projectName}`;
814
+ if (scope === "org" && metadata.orgName) return `Org: ${metadata.orgName}`;
815
+ if (scope === "user") return "User";
816
+ if (scope === "edge" && metadata.projectName) return `AI Edge: ${metadata.projectName}`;
817
+ if (metadata.projectName) return `Project: ${metadata.projectName}`;
818
+ if (metadata.orgName) return `Org: ${metadata.orgName}`;
819
+ if (scope) return scope.charAt(0).toUpperCase() + scope.slice(1);
820
+ }
821
+ /**
822
+ * Hook that returns tasks with a flag indicating whether to show context labels.
823
+ * Labels are shown when:
824
+ * - User is on a global page (account, etc.)
825
+ * - Tasks are from mixed scopes (not all match current scope)
826
+ *
827
+ * @param tasks - Array of tasks to evaluate
828
+ * @param params - Route params (e.g. from useParams() in react-router)
829
+ */
830
+ function useTasksWithLabels(tasks, params) {
831
+ const currentScope = useCurrentScope(params);
832
+ return {
833
+ tasks,
834
+ showLabels: useMemo(() => {
835
+ if (tasks.length === 0) return false;
836
+ if (currentScope.type === "global") return true;
837
+ return !tasks.every((task) => matchesCurrentScope(task.metadata, currentScope));
838
+ }, [tasks, currentScope])
839
+ };
840
+ }
841
+
842
+ //#endregion
843
+ //#region src/components/features/task-queue/core/task-panel-header.tsx
844
+ function TaskPanelHeader() {
845
+ return /* @__PURE__ */ jsx("div", {
846
+ className: "border-border flex items-center border-b px-4 py-3",
847
+ children: /* @__PURE__ */ jsx("span", {
848
+ className: "text-sm font-medium",
849
+ children: "Tasks"
850
+ })
851
+ });
852
+ }
853
+
854
+ //#endregion
855
+ //#region src/components/features/task-queue/core/task-panel-actions.tsx
856
+ function TaskPanelActions({ task }) {
857
+ const { showSummary } = useTaskQueue();
858
+ const actions = resolveActions(task);
859
+ const hasBatch = task.total != null;
860
+ const hasFailedMessages = task.failedItems.length > 0 && task.failedItems.some((f) => f.message);
861
+ if (!hasBatch && task.status === "failed" && hasFailedMessages && actions.length === 0) return /* @__PURE__ */ jsx("div", {
862
+ className: "flex items-center justify-end gap-1.5",
863
+ children: /* @__PURE__ */ jsx(Button, {
864
+ htmlType: "button",
865
+ type: "quaternary",
866
+ theme: "outline",
867
+ size: "xs",
868
+ onClick: () => showSummary(task.title, task.failedItems.map((item, i) => ({
869
+ id: item.id ?? `error-${i}`,
870
+ label: item.id ?? "Error",
871
+ status: "failed",
872
+ message: item.message
873
+ }))),
874
+ children: "Details"
875
+ })
876
+ });
877
+ if (actions.length === 0) return null;
878
+ return /* @__PURE__ */ jsx("div", {
879
+ className: "flex items-center justify-end gap-1.5",
880
+ children: actions.map((action, i) => /* @__PURE__ */ jsx(Button, {
881
+ htmlType: "button",
882
+ ...action
883
+ }, i))
884
+ });
885
+ }
886
+ const TERMINAL_STATUSES = [
887
+ "completed",
888
+ "failed",
889
+ "cancelled"
890
+ ];
891
+ function resolveActions(task) {
892
+ if (!task.completionActions) return [];
893
+ if (!TERMINAL_STATUSES.includes(task.status)) return [];
894
+ if (typeof task.completionActions === "function") {
895
+ const originalItems = task._originalItems ?? task.items ?? [];
896
+ const failedMap = new Map(task.failedItems.filter((f) => f.id).map((f) => [f.id, f]));
897
+ const succeededSet = new Set(task.succeededItems);
898
+ const items = originalItems.map((item) => {
899
+ const id = extractItemId(item);
900
+ if (!id) return null;
901
+ const failed = failedMap.get(id);
902
+ if (failed) return {
903
+ id,
904
+ status: "failed",
905
+ message: failed.message,
906
+ data: item
907
+ };
908
+ if (succeededSet.has(id)) return {
909
+ id,
910
+ status: "succeeded",
911
+ data: item
912
+ };
913
+ return null;
914
+ }).filter((item) => item !== null);
915
+ return task.completionActions(task.result, {
916
+ status: task.status,
917
+ completed: task.completed,
918
+ failed: task.failed,
919
+ items
920
+ });
921
+ }
922
+ return task.completionActions;
923
+ }
924
+
925
+ //#endregion
926
+ //#region src/components/features/task-queue/core/task-panel-counter.tsx
927
+ function TaskPanelCounter({ total, completed, failed = 0, status }) {
928
+ const hasBatch = total != null;
929
+ const parts = [];
930
+ if (status === "completed") parts.push({ text: "Completed" });
931
+ else if (status === "failed") if (hasBatch) {
932
+ if (completed > 0) parts.push({ text: `${completed} completed` });
933
+ parts.push({
934
+ text: `${failed} failed`,
935
+ destructive: true
936
+ });
937
+ } else parts.push({
938
+ text: "Failed",
939
+ destructive: true
940
+ });
941
+ else if (status === "cancelled") {
942
+ parts.push({ text: "Cancelled" });
943
+ if (hasBatch && completed > 0) parts.push({ text: `${completed} completed` });
944
+ } else if (hasBatch) {
945
+ parts.push({ text: `${completed + failed}/${total}` });
946
+ if (failed > 0) parts.push({
947
+ text: `${failed} failed`,
948
+ destructive: true
949
+ });
950
+ }
951
+ if (parts.length === 0) return null;
952
+ return /* @__PURE__ */ jsx("span", {
953
+ className: "text-muted-foreground text-xs",
954
+ children: parts.map((part, i) => /* @__PURE__ */ jsxs("span", { children: [i > 0 && ", ", /* @__PURE__ */ jsx("span", {
955
+ className: cn(part.destructive && "text-destructive"),
956
+ children: part.text
957
+ })] }, i))
958
+ });
959
+ }
960
+
961
+ //#endregion
962
+ //#region src/components/features/task-queue/core/task-panel-item.tsx
963
+ function TaskPanelItem({ task, contextLabel, onCancel }) {
964
+ const hasBatch = task.total != null;
965
+ const isTerminal = task.status === "completed" || task.status === "failed" || task.status === "cancelled";
966
+ return /* @__PURE__ */ jsxs("div", {
967
+ className: "group border-border flex flex-col gap-1 px-4 py-3 not-first:border-t",
968
+ children: [/* @__PURE__ */ jsxs("div", {
969
+ className: "flex items-start gap-3",
970
+ children: [
971
+ /* @__PURE__ */ jsx("div", {
972
+ className: "mt-0.5 flex shrink-0 items-center",
973
+ children: /* @__PURE__ */ jsx(TaskIcon, { task })
974
+ }),
975
+ /* @__PURE__ */ jsxs("div", {
976
+ className: "flex min-w-0 flex-1 flex-col gap-0.5",
977
+ children: [
978
+ /* @__PURE__ */ jsx("span", {
979
+ className: "truncate text-sm font-medium",
980
+ children: task.title
981
+ }),
982
+ task.status === "pending" && /* @__PURE__ */ jsx("span", {
983
+ className: "text-muted-foreground text-xs",
984
+ children: "Waiting..."
985
+ }),
986
+ hasBatch && task.status === "running" && /* @__PURE__ */ jsx(TaskPanelCounter, {
987
+ total: task.total,
988
+ completed: task.completed,
989
+ failed: task.failed
990
+ }),
991
+ isTerminal && /* @__PURE__ */ jsx(TaskPanelCounter, {
992
+ total: task.total,
993
+ completed: task.completed,
994
+ failed: task.failed,
995
+ status: task.status
996
+ }),
997
+ contextLabel && /* @__PURE__ */ jsxs("span", {
998
+ className: "text-muted-foreground flex items-center gap-1 text-xs",
999
+ children: [/* @__PURE__ */ jsx(Icon, {
1000
+ icon: CornerDownRightIcon,
1001
+ className: "size-3 shrink-0 opacity-60"
1002
+ }), /* @__PURE__ */ jsx("span", {
1003
+ className: "truncate",
1004
+ children: contextLabel
1005
+ })]
1006
+ })
1007
+ ]
1008
+ }),
1009
+ /* @__PURE__ */ jsx("div", {
1010
+ className: "flex shrink-0 items-center",
1011
+ children: /* @__PURE__ */ jsx(TaskStatusAction, {
1012
+ task,
1013
+ onCancel
1014
+ })
1015
+ })
1016
+ ]
1017
+ }), /* @__PURE__ */ jsx(TaskPanelActions, { task })]
1018
+ });
1019
+ }
1020
+ /** Left-side task icon - shows task icon when running, status icon when complete */
1021
+ function TaskIcon({ task }) {
1022
+ if (task.status === "running" || task.status === "pending") {
1023
+ if (task.icon) return /* @__PURE__ */ jsx("span", {
1024
+ className: "text-muted-foreground [&>svg]:size-4",
1025
+ children: task.icon
1026
+ });
1027
+ return /* @__PURE__ */ jsx(Icon, {
1028
+ icon: FileIcon,
1029
+ className: "text-muted-foreground size-4"
1030
+ });
1031
+ }
1032
+ if (task.status === "completed" && task.failed > 0) return /* @__PURE__ */ jsx(Icon, {
1033
+ icon: CircleAlert,
1034
+ className: "size-4 text-amber-500"
1035
+ });
1036
+ if (task.status === "completed") return /* @__PURE__ */ jsx(Icon, {
1037
+ icon: CircleCheck,
1038
+ className: "size-4 text-green-600 dark:text-green-400"
1039
+ });
1040
+ if (task.status === "failed") return /* @__PURE__ */ jsx(Icon, {
1041
+ icon: XCircle,
1042
+ className: "text-destructive size-4"
1043
+ });
1044
+ if (task.status === "cancelled") return /* @__PURE__ */ jsx(Icon, {
1045
+ icon: Ban,
1046
+ className: "text-muted-foreground size-4"
1047
+ });
1048
+ return /* @__PURE__ */ jsx(Icon, {
1049
+ icon: FileIcon,
1050
+ className: "text-muted-foreground size-4"
1051
+ });
1052
+ }
1053
+ /** Format completedAt timestamp as human-readable relative time */
1054
+ function formatCompletedAt(completedAt) {
1055
+ if (!completedAt) return "";
1056
+ if (Date.now() - completedAt < 1e3) return "just now";
1057
+ return `${formatDistanceToNowStrict(new Date(completedAt), { addSuffix: false })} ago`;
1058
+ }
1059
+ /** Right-side: spinner/cancel for active tasks, timestamp for terminal tasks */
1060
+ function TaskStatusAction({ task, onCancel }) {
1061
+ if (task.status === "running") return /* @__PURE__ */ jsxs("div", {
1062
+ className: "relative size-5",
1063
+ children: [/* @__PURE__ */ jsx("div", {
1064
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center transition-opacity group-hover:opacity-0",
1065
+ children: /* @__PURE__ */ jsx(SpinnerIcon, { size: "sm" })
1066
+ }), task.cancelable && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1067
+ asChild: true,
1068
+ children: /* @__PURE__ */ jsx("button", {
1069
+ type: "button",
1070
+ onClick: onCancel,
1071
+ className: cn("flex size-5 items-center justify-center rounded-md transition-colors", "text-muted-foreground hover:bg-accent hover:text-foreground", "absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100"),
1072
+ "aria-label": "Cancel task",
1073
+ children: /* @__PURE__ */ jsx(Icon, {
1074
+ icon: X,
1075
+ className: "size-4"
1076
+ })
1077
+ })
1078
+ }), /* @__PURE__ */ jsx(TooltipContent, {
1079
+ side: "left",
1080
+ children: "Cancel"
1081
+ })] })]
1082
+ });
1083
+ if (task.status === "pending") return /* @__PURE__ */ jsx("div", {
1084
+ className: "flex size-5 items-center justify-center",
1085
+ children: /* @__PURE__ */ jsx(SpinnerIcon, {
1086
+ size: "sm",
1087
+ className: "opacity-50"
1088
+ })
1089
+ });
1090
+ return /* @__PURE__ */ jsx("span", {
1091
+ className: "text-muted-foreground/60 text-xs whitespace-nowrap",
1092
+ children: formatCompletedAt(task.completedAt)
1093
+ });
1094
+ }
1095
+
1096
+ //#endregion
1097
+ //#region src/components/features/task-queue/core/task-panel.tsx
1098
+ /**
1099
+ * @deprecated Use TaskQueueDropdown instead — renders as a header-anchored dropdown.
1100
+ * TaskPanel is kept for backward compatibility but is no longer used in the main layout.
1101
+ */
1102
+ function TaskPanel() {
1103
+ const { tasks, cancel } = useTaskQueue();
1104
+ if (tasks.length === 0) return null;
1105
+ return /* @__PURE__ */ jsxs("div", {
1106
+ className: cn("bg-background fixed right-4 bottom-4 z-50 w-96 overflow-hidden", "border-border/50 rounded-xl border shadow-xl shadow-black/10", "animate-in slide-in-from-bottom-4 fade-in duration-200"),
1107
+ children: [/* @__PURE__ */ jsx(TaskPanelHeader, {}), /* @__PURE__ */ jsx("div", {
1108
+ className: "max-h-80 overflow-y-auto",
1109
+ children: tasks.map((task) => /* @__PURE__ */ jsx(TaskPanelItem, {
1110
+ task,
1111
+ contextLabel: getContextLabel(task.metadata),
1112
+ onCancel: () => cancel(task.id)
1113
+ }, task.id))
1114
+ })]
1115
+ });
1116
+ }
1117
+
1118
+ //#endregion
1119
+ //#region src/components/features/task-queue/core/task-queue-trigger.tsx
1120
+ function TaskQueueTrigger({ ref, tasks, ...props }) {
1121
+ const runningCount = tasks.filter((t) => t.status === "running").length;
1122
+ const activeCount = runningCount + tasks.filter((t) => t.status === "pending").length;
1123
+ const hasRunning = runningCount > 0;
1124
+ const isAllComplete = tasks.length > 0 && activeCount === 0 && tasks.every((t) => t.status === "completed" || t.status === "failed" || t.status === "cancelled");
1125
+ const [flash, setFlash] = useState(false);
1126
+ const prevAllComplete = useRef(false);
1127
+ useEffect(() => {
1128
+ if (isAllComplete && !prevAllComplete.current) {
1129
+ prevAllComplete.current = true;
1130
+ setFlash(true);
1131
+ const timer = setTimeout(() => setFlash(false), 5e3);
1132
+ return () => clearTimeout(timer);
1133
+ }
1134
+ if (!isAllComplete) prevAllComplete.current = false;
1135
+ }, [isAllComplete]);
1136
+ useEffect(() => {
1137
+ if (tasks.length === 0) {
1138
+ setFlash(false);
1139
+ prevAllComplete.current = false;
1140
+ }
1141
+ }, [tasks.length]);
1142
+ return /* @__PURE__ */ jsx(Tooltip$1, {
1143
+ message: "Tasks",
1144
+ children: /* @__PURE__ */ jsxs(Button, {
1145
+ ref,
1146
+ type: "quaternary",
1147
+ theme: "borderless",
1148
+ size: "small",
1149
+ className: cn("hover:bg-sidebar-accent relative h-7 w-7 rounded-lg p-0 transition-colors duration-300", flash && "bg-primary/10"),
1150
+ "aria-label": `Tasks${activeCount > 0 ? ` (${activeCount} active)` : ""}`,
1151
+ ...props,
1152
+ children: [
1153
+ /* @__PURE__ */ jsx(Icon, {
1154
+ icon: ListTodo,
1155
+ className: cn("text-icon-header size-4", flash ? "text-primary" : "text-icon-header")
1156
+ }),
1157
+ hasRunning && /* @__PURE__ */ jsx("span", { className: "border-t-primary pointer-events-none absolute inset-[-3px] animate-spin rounded-full border-2 border-transparent" }),
1158
+ activeCount > 0 && /* @__PURE__ */ jsx(Badge, {
1159
+ type: "tertiary",
1160
+ theme: "solid",
1161
+ className: "bg-primary text-primary-foreground text-2xs absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full p-0 leading-0",
1162
+ children: activeCount > 99 ? "99+" : activeCount
1163
+ })
1164
+ ]
1165
+ })
1166
+ });
1167
+ }
1168
+
1169
+ //#endregion
1170
+ //#region src/components/features/task-queue/core/task-summary-dialog.tsx
1171
+ function getStatusConfig(status) {
1172
+ switch (status) {
1173
+ case "success": return {
1174
+ icon: CircleCheck,
1175
+ label: "Success",
1176
+ className: "text-green-600"
1177
+ };
1178
+ case "failed": return {
1179
+ icon: XCircle,
1180
+ label: "Failed",
1181
+ className: "text-destructive"
1182
+ };
1183
+ }
1184
+ }
1185
+ function StatusCell({ item }) {
1186
+ const config = getStatusConfig(item.status);
1187
+ return /* @__PURE__ */ jsxs("div", {
1188
+ className: "flex flex-col gap-0.5",
1189
+ children: [/* @__PURE__ */ jsxs("div", {
1190
+ className: "flex items-center gap-1.5",
1191
+ children: [/* @__PURE__ */ jsx(Icon, {
1192
+ icon: config.icon,
1193
+ className: cn("size-4", config.className)
1194
+ }), /* @__PURE__ */ jsx("span", {
1195
+ className: cn("text-xs font-medium", config.className),
1196
+ children: config.label
1197
+ })]
1198
+ }), item.message && item.status !== "success" && /* @__PURE__ */ jsx("span", {
1199
+ className: "text-muted-foreground pl-5.5 text-xs text-wrap",
1200
+ children: item.message
1201
+ })]
1202
+ });
1203
+ }
1204
+ function DefaultTableContent({ items }) {
1205
+ const sorted = useMemo(() => [...items].sort((a, b) => {
1206
+ if (a.status === "failed" && b.status !== "failed") return -1;
1207
+ if (a.status !== "failed" && b.status === "failed") return 1;
1208
+ return 0;
1209
+ }), [items]);
1210
+ return /* @__PURE__ */ jsx("div", {
1211
+ className: "max-h-[400px] overflow-auto rounded-xl border",
1212
+ children: /* @__PURE__ */ jsxs(Table, { children: [/* @__PURE__ */ jsx(TableHeader, {
1213
+ className: "bg-muted/50 sticky top-0",
1214
+ children: /* @__PURE__ */ jsxs(TableRow, { children: [/* @__PURE__ */ jsx(TableHead, { children: "Item" }), /* @__PURE__ */ jsx(TableHead, {
1215
+ className: "max-w-80",
1216
+ children: "Status"
1217
+ })] })
1218
+ }), /* @__PURE__ */ jsx(TableBody, { children: sorted.length === 0 ? /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
1219
+ colSpan: 2,
1220
+ className: "text-muted-foreground py-6 text-center",
1221
+ children: "No items"
1222
+ }) }) : sorted.map((item) => /* @__PURE__ */ jsxs(TableRow, { children: [/* @__PURE__ */ jsx(TableCell, {
1223
+ className: "font-medium",
1224
+ children: item.label
1225
+ }), /* @__PURE__ */ jsx(TableCell, {
1226
+ className: "max-w-80 break-all text-wrap whitespace-normal",
1227
+ children: /* @__PURE__ */ jsx(StatusCell, { item })
1228
+ })] }, item.id)) })] })
1229
+ });
1230
+ }
1231
+ function useTaskSummaryItems(taskId, getItemLabel) {
1232
+ const { tasks } = useTaskQueue();
1233
+ return useMemo(() => {
1234
+ if (!taskId || !getItemLabel) return [];
1235
+ const task = tasks.find((t) => t.id === taskId);
1236
+ if (!task) return [];
1237
+ const succeeded = task.succeededItems.map((id) => ({
1238
+ id,
1239
+ label: getItemLabel(id),
1240
+ status: "success"
1241
+ }));
1242
+ return [...task.failedItems.map((item) => ({
1243
+ id: item.id ?? "",
1244
+ label: getItemLabel(item.id ?? ""),
1245
+ status: "failed",
1246
+ message: item.message
1247
+ })), ...succeeded];
1248
+ }, [
1249
+ taskId,
1250
+ getItemLabel,
1251
+ tasks
1252
+ ]);
1253
+ }
1254
+ function TaskSummaryDialog(props) {
1255
+ const { open, onOpenChange, title, description, actions, renderContent } = props;
1256
+ const taskIdItems = useTaskSummaryItems("taskId" in props ? props.taskId : void 0, "getItemLabel" in props ? props.getItemLabel : void 0);
1257
+ const resolvedItems = props.items ?? taskIdItems;
1258
+ const successCount = resolvedItems.filter((i) => i.status === "success").length;
1259
+ const failedCount = resolvedItems.filter((i) => i.status === "failed").length;
1260
+ return /* @__PURE__ */ jsx(Dialog, {
1261
+ open,
1262
+ onOpenChange,
1263
+ children: /* @__PURE__ */ jsxs(Dialog.Content, {
1264
+ className: "w-full sm:max-w-[774px]",
1265
+ children: [
1266
+ /* @__PURE__ */ jsx(Dialog.Header, {
1267
+ title,
1268
+ description: description ?? `${successCount} succeeded, ${failedCount} failed`,
1269
+ onClose: () => onOpenChange(false),
1270
+ className: "border-b-0"
1271
+ }),
1272
+ /* @__PURE__ */ jsx(Dialog.Body, {
1273
+ className: "px-5 py-0",
1274
+ children: renderContent ? renderContent(resolvedItems) : /* @__PURE__ */ jsx(DefaultTableContent, { items: resolvedItems })
1275
+ }),
1276
+ /* @__PURE__ */ jsxs(Dialog.Footer, {
1277
+ className: "border-t-0",
1278
+ children: [actions, /* @__PURE__ */ jsx(Button, {
1279
+ type: "primary",
1280
+ theme: "solid",
1281
+ onClick: () => onOpenChange(false),
1282
+ children: "Done"
1283
+ })]
1284
+ })
1285
+ ]
1286
+ })
1287
+ });
1288
+ }
1289
+
1290
+ //#endregion
1291
+ //#region src/components/features/task-queue/core/task-queue-dropdown.tsx
1292
+ const autoOpenedForIds = /* @__PURE__ */ new Set();
1293
+ function TaskQueueDropdown() {
1294
+ const { tasks, cancel, dismissAll, activeSummary, closeSummary, summaryRenderContent } = useTaskQueue();
1295
+ const [open, setOpen] = useState(() => tasks.some((t) => !autoOpenedForIds.has(t.id)));
1296
+ useEffect(() => {
1297
+ if (tasks.some((t) => !autoOpenedForIds.has(t.id))) setOpen(true);
1298
+ for (const t of tasks) autoOpenedForIds.add(t.id);
1299
+ if (autoOpenedForIds.size > tasks.length) {
1300
+ const currentIds = new Set(tasks.map((t) => t.id));
1301
+ for (const id of autoOpenedForIds) if (!currentIds.has(id)) autoOpenedForIds.delete(id);
1302
+ }
1303
+ }, [tasks]);
1304
+ const hasDismissable = tasks.some((t) => t.status !== "running" && t.status !== "pending");
1305
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(DropdownMenu, {
1306
+ open,
1307
+ onOpenChange: setOpen,
1308
+ children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1309
+ asChild: true,
1310
+ children: /* @__PURE__ */ jsx(TaskQueueTrigger, { tasks })
1311
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
1312
+ align: "end",
1313
+ className: "w-96 rounded-lg p-0",
1314
+ onCloseAutoFocus: (e) => e.preventDefault(),
1315
+ children: [
1316
+ /* @__PURE__ */ jsx(TaskPanelHeader, {}),
1317
+ /* @__PURE__ */ jsx("div", {
1318
+ className: "max-h-[350px] overflow-y-auto",
1319
+ children: tasks.length === 0 && !activeSummary ? /* @__PURE__ */ jsxs("div", {
1320
+ className: "flex flex-col items-center justify-center px-4 py-12 text-center",
1321
+ children: [/* @__PURE__ */ jsx(CheckCircle2, { className: "text-muted-foreground/30 mb-3 h-12 w-12" }), /* @__PURE__ */ jsx("p", {
1322
+ className: "text-muted-foreground text-sm",
1323
+ children: "No tasks currently scheduled"
1324
+ })]
1325
+ }) : tasks.map((task) => /* @__PURE__ */ jsx(TaskPanelItem, {
1326
+ task,
1327
+ contextLabel: getContextLabel(task.metadata),
1328
+ onCancel: () => cancel(task.id)
1329
+ }, task.id))
1330
+ }),
1331
+ hasDismissable && /* @__PURE__ */ jsx("button", {
1332
+ type: "button",
1333
+ onClick: () => {
1334
+ dismissAll();
1335
+ },
1336
+ className: "border-border hover:bg-accent flex w-full cursor-pointer items-center justify-center gap-2 border-t px-3 py-2 transition-colors",
1337
+ children: /* @__PURE__ */ jsx("span", {
1338
+ className: "text-destructive text-xs",
1339
+ children: "Clear tasks"
1340
+ })
1341
+ })
1342
+ ]
1343
+ })]
1344
+ }), activeSummary && /* @__PURE__ */ jsx(TaskSummaryDialog, {
1345
+ open: true,
1346
+ onOpenChange: (isOpen) => {
1347
+ if (!isOpen) closeSummary();
1348
+ },
1349
+ title: activeSummary.title,
1350
+ items: activeSummary.items,
1351
+ renderContent: activeSummary.renderContent ?? summaryRenderContent
1352
+ })] });
1353
+ }
1354
+
1355
+ //#endregion
1356
+ export { RedisTaskStorage as _, TaskPanelItem as a, createProjectMetadata as b, TaskPanelHeader as c, useCurrentScope as d, useTasksWithLabels as f, detectStorage as g, TaskQueue as h, TaskPanel as i, getContextLabel as l, TaskQueueProvider as m, TaskSummaryDialog as n, TaskPanelCounter as o, useTaskQueue as p, TaskQueueTrigger as r, TaskPanelActions as s, TaskQueueDropdown as t, matchesCurrentScope as u, LocalTaskStorage as v, createUserMetadata as x, createOrgMetadata as y };