@tangle-network/agent-app 0.46.2 → 0.46.4

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/assistant/index.js +3 -1
  3. package/dist/assistant/index.js.map +1 -1
  4. package/dist/chunk-KKBTPZIE.js +805 -0
  5. package/dist/chunk-KKBTPZIE.js.map +1 -0
  6. package/dist/{chunk-2RTYQU4W.js → chunk-LEGZX5MQ.js} +150 -4
  7. package/dist/chunk-LEGZX5MQ.js.map +1 -0
  8. package/dist/chunk-RLOHIX5Y.js +36 -0
  9. package/dist/chunk-RLOHIX5Y.js.map +1 -0
  10. package/dist/{chunk-MBKNKAN2.js → chunk-YTEUZXX2.js} +584 -1252
  11. package/dist/chunk-YTEUZXX2.js.map +1 -0
  12. package/dist/mention-editor-GVLG3W7C.js +521 -0
  13. package/dist/mention-editor-GVLG3W7C.js.map +1 -0
  14. package/dist/stories/studio/StudioProviders.d.ts +10 -0
  15. package/dist/stories/studio/fixtures.d.ts +10 -2
  16. package/dist/studio/audio-preview.d.ts +12 -0
  17. package/dist/studio/generation.d.ts +22 -1
  18. package/dist/studio/index.d.ts +2 -0
  19. package/dist/studio/index.js +29 -1
  20. package/dist/studio/ports.d.ts +54 -0
  21. package/dist/studio-react/composer-option-controls.d.ts +19 -8
  22. package/dist/studio-react/download-generations.d.ts +4 -0
  23. package/dist/studio-react/index.d.ts +38 -22
  24. package/dist/studio-react/index.js +2198 -545
  25. package/dist/studio-react/index.js.map +1 -1
  26. package/dist/studio-react/media-tile.d.ts +21 -0
  27. package/dist/studio-react/media-viewer.d.ts +12 -0
  28. package/dist/studio-react/studio-confirm.d.ts +8 -0
  29. package/dist/studio-react/studio-generation-screen.d.ts +42 -0
  30. package/dist/studio-react/studio-history-screen.d.ts +28 -0
  31. package/dist/studio-react/studio-home-screen.d.ts +20 -0
  32. package/dist/studio-react/studio-playback.d.ts +31 -0
  33. package/dist/studio-react/studio-toasts.d.ts +21 -0
  34. package/dist/studio-react/studio.css +142 -72
  35. package/dist/studio-react/use-batch-navigation.d.ts +20 -0
  36. package/dist/studio-react/use-deferred-delete.d.ts +19 -0
  37. package/dist/studio-react/use-generation-history.d.ts +22 -0
  38. package/dist/studio-react/vault-path-popover.d.ts +12 -0
  39. package/dist/teams/drizzle/invitations-schema.d.ts +1 -1
  40. package/dist/teams/drizzle/schema.d.ts +1 -1
  41. package/dist/web-react/chat-composer.d.ts +24 -4
  42. package/dist/web-react/index.d.ts +1 -0
  43. package/dist/web-react/index.js +12 -6
  44. package/dist/web-react/mention-boundaries.d.ts +33 -0
  45. package/dist/web-react/mention-editor.d.ts +84 -0
  46. package/dist/web-react/mention-list.d.ts +35 -0
  47. package/dist/web-react/mention-pill.d.ts +10 -0
  48. package/dist/web-react/mention-serialize.d.ts +44 -0
  49. package/dist/web-react/use-file-mentions.d.ts +28 -14
  50. package/package.json +33 -7
  51. package/dist/chunk-2RTYQU4W.js.map +0 -1
  52. package/dist/chunk-MBKNKAN2.js.map +0 -1
  53. package/dist/studio-react/generation-card.d.ts +0 -9
  54. package/dist/studio-react/generation-detail-modal.d.ts +0 -11
  55. package/dist/studio-react/generation-detail.d.ts +0 -6
  56. package/dist/studio-react/generation-grid.d.ts +0 -15
  57. package/dist/studio-react/library-drawer.d.ts +0 -12
  58. package/dist/studio-react/library-panel.d.ts +0 -15
  59. package/dist/studio-react/result-canvas.d.ts +0 -6
  60. package/dist/studio-react/studio-header.d.ts +0 -5
  61. package/dist/studio-react/studio-sheet.d.ts +0 -12
  62. package/dist/studio-react/studio-workspace.d.ts +0 -21
  63. package/dist/studio-react/type-config.d.ts +0 -11
@@ -0,0 +1,805 @@
1
+ import {
2
+ OVERLAY_SHADOW,
3
+ PopoverSurface,
4
+ usePopover
5
+ } from "./chunk-YDOQE47G.js";
6
+ import {
7
+ UNTITLED_SESSION_LABEL,
8
+ mergeSessionPages,
9
+ sessionLabel
10
+ } from "./chunk-EDTWGSQT.js";
11
+
12
+ // src/web-react/session-history.tsx
13
+ import {
14
+ useCallback,
15
+ useEffect,
16
+ useId,
17
+ useMemo,
18
+ useRef,
19
+ useState
20
+ } from "react";
21
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
+ function rethrowAsync(error) {
23
+ queueMicrotask(() => {
24
+ throw error;
25
+ });
26
+ }
27
+ function useInfiniteScroll(onLoadMore, { enabled, root, rootMargin = "300px" }) {
28
+ const [sentinel, setSentinel] = useState(null);
29
+ const sentinelRef = useCallback((node) => setSentinel(node), []);
30
+ const onLoadMoreRef = useRef(onLoadMore);
31
+ useEffect(() => {
32
+ onLoadMoreRef.current = onLoadMore;
33
+ }, [onLoadMore]);
34
+ useEffect(() => {
35
+ if (!sentinel || !enabled) return;
36
+ if (typeof IntersectionObserver === "undefined") return;
37
+ const observer = new IntersectionObserver(
38
+ (entries) => {
39
+ if (!entries.some((entry) => entry.isIntersecting)) return;
40
+ try {
41
+ onLoadMoreRef.current();
42
+ } catch (error) {
43
+ rethrowAsync(error);
44
+ }
45
+ },
46
+ { root: root?.current ?? null, rootMargin }
47
+ );
48
+ observer.observe(sentinel);
49
+ return () => observer.disconnect();
50
+ }, [sentinel, enabled, root, rootMargin]);
51
+ return sentinelRef;
52
+ }
53
+ function seedSignature(page) {
54
+ return `${page.nextCursor ?? ""}|${page.items.map((item) => item.id).join(",")}`;
55
+ }
56
+ function useSessionHistory({
57
+ fetchPage,
58
+ q,
59
+ sort,
60
+ initialPage,
61
+ defaultSort = "newest"
62
+ }) {
63
+ const [items, setItems] = useState(initialPage.items);
64
+ const [nextCursor, setNextCursor] = useState(initialPage.nextCursor ?? null);
65
+ const [phase, setPhase] = useState("idle");
66
+ const [reloadKey, setReloadKey] = useState(0);
67
+ const seqRef = useRef(0);
68
+ const resetAbortRef = useRef(null);
69
+ const loadMoreAbortRef = useRef(null);
70
+ const loadingMoreRef = useRef(false);
71
+ const lastOpRef = useRef("first");
72
+ const nextCursorRef = useRef(nextCursor);
73
+ nextCursorRef.current = nextCursor;
74
+ const viewRef = useRef({ q, sort, fetchPage });
75
+ viewRef.current = { q, sort, fetchPage };
76
+ const seedRef = useRef(initialPage);
77
+ seedRef.current = initialPage;
78
+ const isDefaultView = q === "" && sort === defaultSort;
79
+ const seedKey = useMemo(() => seedSignature(initialPage), [initialPage]);
80
+ useEffect(() => {
81
+ resetAbortRef.current?.abort();
82
+ loadMoreAbortRef.current?.abort();
83
+ loadingMoreRef.current = false;
84
+ const seq = ++seqRef.current;
85
+ if (isDefaultView && reloadKey === 0) {
86
+ setItems(seedRef.current.items);
87
+ setNextCursor(seedRef.current.nextCursor ?? null);
88
+ setPhase("idle");
89
+ return;
90
+ }
91
+ const controller = new AbortController();
92
+ resetAbortRef.current = controller;
93
+ lastOpRef.current = "first";
94
+ setItems([]);
95
+ setNextCursor(null);
96
+ setPhase("loadingFirst");
97
+ void (async () => {
98
+ try {
99
+ const page = await viewRef.current.fetchPage({ q, sort, cursor: null, signal: controller.signal });
100
+ if (seq !== seqRef.current) return;
101
+ setItems(page.items);
102
+ setNextCursor(page.nextCursor ?? null);
103
+ setPhase("idle");
104
+ } catch {
105
+ if (controller.signal.aborted || seq !== seqRef.current) return;
106
+ setPhase("error");
107
+ }
108
+ })();
109
+ return () => controller.abort();
110
+ }, [q, sort, seedKey, isDefaultView, reloadKey]);
111
+ const loadMore = useCallback(() => {
112
+ const cursor = nextCursorRef.current;
113
+ if (!cursor || loadingMoreRef.current) return;
114
+ const { q: currentQ, sort: currentSort, fetchPage: currentFetch } = viewRef.current;
115
+ const seq = seqRef.current;
116
+ loadingMoreRef.current = true;
117
+ lastOpRef.current = "more";
118
+ const controller = new AbortController();
119
+ loadMoreAbortRef.current = controller;
120
+ setPhase("loadingMore");
121
+ void (async () => {
122
+ try {
123
+ const page = await currentFetch({ q: currentQ, sort: currentSort, cursor, signal: controller.signal });
124
+ if (seq !== seqRef.current) return;
125
+ setItems((prev) => mergeSessionPages(prev, page.items));
126
+ setNextCursor(page.nextCursor ?? null);
127
+ setPhase("idle");
128
+ } catch {
129
+ if (controller.signal.aborted || seq !== seqRef.current) return;
130
+ setPhase("error");
131
+ } finally {
132
+ if (seq === seqRef.current) loadingMoreRef.current = false;
133
+ }
134
+ })();
135
+ }, []);
136
+ const retry = useCallback(() => {
137
+ if (lastOpRef.current === "more") loadMore();
138
+ else setReloadKey((key) => key + 1);
139
+ }, [loadMore]);
140
+ const reload = useCallback(() => {
141
+ setReloadKey((key) => key + 1);
142
+ }, []);
143
+ useEffect(
144
+ () => () => {
145
+ resetAbortRef.current?.abort();
146
+ loadMoreAbortRef.current?.abort();
147
+ },
148
+ []
149
+ );
150
+ return {
151
+ items,
152
+ hasMore: nextCursor !== null,
153
+ isLoadingFirst: phase === "loadingFirst",
154
+ isLoadingMore: phase === "loadingMore",
155
+ isError: phase === "error",
156
+ loadMore,
157
+ retry,
158
+ reload
159
+ };
160
+ }
161
+ var DEFAULT_LABELS = {
162
+ renameTitle: "Rename session",
163
+ renameField: "Title",
164
+ renameSubmit: "Save",
165
+ deleteTitle: "Delete session?",
166
+ deleteBody: (title) => `This will permanently delete \u201C${title}\u201D and its messages. This cannot be undone.`,
167
+ deleteSubmit: "Delete",
168
+ cancel: "Cancel",
169
+ renamed: "Session renamed",
170
+ deleted: "Session deleted",
171
+ renameFailed: "Failed to rename session",
172
+ deleteFailed: "Failed to delete session"
173
+ };
174
+ function useSessionActions({
175
+ renameSession,
176
+ deleteSession,
177
+ onChanged,
178
+ onDeletedCurrent,
179
+ currentSessionId,
180
+ notify,
181
+ labels
182
+ }) {
183
+ const text = { ...DEFAULT_LABELS, ...labels };
184
+ const [renameTarget, setRenameTarget] = useState(null);
185
+ const [renameValue, setRenameValue] = useState("");
186
+ const [deleteTarget, setDeleteTarget] = useState(null);
187
+ const [busy, setBusy] = useState(false);
188
+ const [error, setError] = useState(null);
189
+ const openRename = useCallback((session) => {
190
+ setError(null);
191
+ setRenameTarget(session);
192
+ setRenameValue(session.title ?? "");
193
+ }, []);
194
+ const openDelete = useCallback((session) => {
195
+ setError(null);
196
+ setDeleteTarget(session);
197
+ }, []);
198
+ const submitRename = useCallback(async () => {
199
+ if (!renameTarget) return;
200
+ const title = renameValue.trim();
201
+ if (!title || title === renameTarget.title) {
202
+ setRenameTarget(null);
203
+ return;
204
+ }
205
+ setBusy(true);
206
+ setError(null);
207
+ try {
208
+ await renameSession(renameTarget.id, title);
209
+ setRenameTarget(null);
210
+ notify?.("success", text.renamed);
211
+ onChanged?.();
212
+ } catch (e) {
213
+ const message = e instanceof Error ? e.message : text.renameFailed;
214
+ setError(message);
215
+ notify?.("error", message);
216
+ } finally {
217
+ setBusy(false);
218
+ }
219
+ }, [renameTarget, renameValue, renameSession, notify, onChanged, text.renamed, text.renameFailed]);
220
+ const confirmDelete = useCallback(async () => {
221
+ if (!deleteTarget) return;
222
+ const deletingCurrent = currentSessionId != null && deleteTarget.id === currentSessionId;
223
+ setBusy(true);
224
+ setError(null);
225
+ try {
226
+ await deleteSession(deleteTarget.id);
227
+ setDeleteTarget(null);
228
+ notify?.("success", text.deleted);
229
+ onChanged?.();
230
+ if (deletingCurrent) onDeletedCurrent?.();
231
+ } catch (e) {
232
+ const message = e instanceof Error ? e.message : text.deleteFailed;
233
+ setError(message);
234
+ notify?.("error", message);
235
+ } finally {
236
+ setBusy(false);
237
+ }
238
+ }, [deleteTarget, currentSessionId, deleteSession, notify, onChanged, onDeletedCurrent, text.deleted, text.deleteFailed]);
239
+ const dialogs = /* @__PURE__ */ jsxs(Fragment, { children: [
240
+ renameTarget && /* @__PURE__ */ jsxs(
241
+ SessionDialog,
242
+ {
243
+ title: text.renameTitle,
244
+ onClose: () => setRenameTarget(null),
245
+ busy,
246
+ error,
247
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
248
+ /* @__PURE__ */ jsx(DialogButton, { onClick: () => setRenameTarget(null), disabled: busy, variant: "ghost", children: text.cancel }),
249
+ /* @__PURE__ */ jsx(DialogButton, { onClick: () => void submitRename(), disabled: busy || !renameValue.trim(), children: text.renameSubmit })
250
+ ] }),
251
+ children: [
252
+ /* @__PURE__ */ jsx("label", { htmlFor: "agent-app-rename-session", className: "text-xs text-muted-foreground", children: text.renameField }),
253
+ /* @__PURE__ */ jsx(
254
+ "input",
255
+ {
256
+ id: "agent-app-rename-session",
257
+ value: renameValue,
258
+ autoFocus: true,
259
+ onChange: (e) => setRenameValue(e.target.value),
260
+ onKeyDown: (e) => {
261
+ if (e.key === "Enter" && !busy) {
262
+ e.preventDefault();
263
+ void submitRename();
264
+ }
265
+ },
266
+ className: "mt-1.5 h-9 w-full rounded-md border border-strong bg-background px-3 text-sm text-foreground"
267
+ }
268
+ )
269
+ ]
270
+ }
271
+ ),
272
+ deleteTarget && /* @__PURE__ */ jsx(
273
+ SessionDialog,
274
+ {
275
+ title: text.deleteTitle,
276
+ onClose: () => setDeleteTarget(null),
277
+ busy,
278
+ error,
279
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
280
+ /* @__PURE__ */ jsx(DialogButton, { onClick: () => setDeleteTarget(null), disabled: busy, variant: "ghost", children: text.cancel }),
281
+ /* @__PURE__ */ jsx(DialogButton, { onClick: () => void confirmDelete(), disabled: busy, variant: "destructive", children: text.deleteSubmit })
282
+ ] }),
283
+ children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: text.deleteBody(sessionLabel(deleteTarget)) })
284
+ }
285
+ )
286
+ ] });
287
+ return { openRename, openDelete, dialogs, busy };
288
+ }
289
+ function DialogButton({
290
+ children,
291
+ onClick,
292
+ disabled,
293
+ variant = "primary"
294
+ }) {
295
+ const tone = variant === "ghost" ? "text-muted-foreground hover:bg-accent hover:text-foreground" : variant === "destructive" ? "bg-destructive text-destructive-foreground hover:opacity-90" : "bg-primary text-primary-foreground hover:opacity-90";
296
+ return /* @__PURE__ */ jsx(
297
+ "button",
298
+ {
299
+ type: "button",
300
+ onClick,
301
+ disabled,
302
+ className: `h-9 rounded-md px-3 text-sm font-medium transition disabled:opacity-50 ${tone}`,
303
+ children
304
+ }
305
+ );
306
+ }
307
+ function SessionDialog({
308
+ title,
309
+ children,
310
+ footer,
311
+ onClose,
312
+ busy,
313
+ error
314
+ }) {
315
+ useEffect(() => {
316
+ const onKey = (e) => {
317
+ if (e.key === "Escape" && !busy) onClose();
318
+ };
319
+ document.addEventListener("keydown", onKey);
320
+ return () => document.removeEventListener("keydown", onKey);
321
+ }, [busy, onClose]);
322
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", children: [
323
+ /* @__PURE__ */ jsx(
324
+ "div",
325
+ {
326
+ className: "absolute inset-0 bg-black/50",
327
+ onClick: () => {
328
+ if (!busy) onClose();
329
+ },
330
+ "aria-hidden": true
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsxs(
334
+ "div",
335
+ {
336
+ role: "dialog",
337
+ "aria-modal": "true",
338
+ "aria-label": title,
339
+ className: `relative w-full max-w-sm rounded-xl border border-card-edge bg-popover p-5 ${OVERLAY_SHADOW}`,
340
+ children: [
341
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: title }),
342
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children }),
343
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-3 rounded-md bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error }),
344
+ /* @__PURE__ */ jsx("div", { className: "mt-5 flex justify-end gap-2", children: footer })
345
+ ]
346
+ }
347
+ )
348
+ ] });
349
+ }
350
+ function AnchorLink({ to, className, children }) {
351
+ return /* @__PURE__ */ jsx("a", { href: to, className, children });
352
+ }
353
+ var MINUTE = 6e4;
354
+ var HOUR = 60 * MINUTE;
355
+ var DAY = 24 * HOUR;
356
+ function formatSessionTimestamp(isoDate) {
357
+ if (!isoDate) return "";
358
+ const at = Date.parse(isoDate);
359
+ if (Number.isNaN(at)) return "";
360
+ const delta = Date.now() - at;
361
+ if (delta < MINUTE) return "just now";
362
+ if (delta < HOUR) return `${Math.floor(delta / MINUTE)}m ago`;
363
+ if (delta < DAY) return `${Math.floor(delta / HOUR)}h ago`;
364
+ if (delta < 7 * DAY) return `${Math.floor(delta / DAY)}d ago`;
365
+ return new Date(at).toLocaleDateString(void 0, { month: "short", day: "numeric" });
366
+ }
367
+ function SkeletonRows() {
368
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
369
+ /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", "aria-busy": true, className: "sr-only", children: "Loading sessions\u2026" }),
370
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "aria-hidden": true, children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2.5", children: [
371
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-4 animate-pulse rounded bg-muted" }),
372
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-1/2 animate-pulse rounded bg-muted" }),
373
+ /* @__PURE__ */ jsx("div", { className: "ml-auto h-3 w-12 animate-pulse rounded bg-muted" })
374
+ ] }, i)) })
375
+ ] });
376
+ }
377
+ function MessageIcon({ className }) {
378
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className, fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
379
+ }
380
+ function SessionHistoryPanel({
381
+ history,
382
+ hasAnySessions,
383
+ query,
384
+ onQueryChange,
385
+ sort,
386
+ onSortChange,
387
+ hrefForSession,
388
+ linkComponent: Link = AnchorLink,
389
+ respondingSessionIds,
390
+ onRename,
391
+ onDelete,
392
+ onBulkAction,
393
+ renameLabel = "Rename",
394
+ deleteLabel = "Delete",
395
+ extraActions,
396
+ newSessionHref,
397
+ title = "History",
398
+ untitledLabel = UNTITLED_SESSION_LABEL,
399
+ emptyTitle = "No sessions yet",
400
+ emptyDescription = "Your chat sessions will show up here once you start one.",
401
+ formatTimestamp = formatSessionTimestamp,
402
+ contentWidth = "reading",
403
+ className
404
+ }) {
405
+ const scrollRef = useRef(null);
406
+ const sentinelRef = useInfiniteScroll(history.loadMore, {
407
+ enabled: history.hasMore && !history.isLoadingMore && !history.isError,
408
+ root: scrollRef,
409
+ rootMargin: "300px"
410
+ });
411
+ const searchTerm = query.trim();
412
+ const isSearching = searchTerm.length > 0;
413
+ const column = contentWidth === "full" ? "w-full" : "mx-auto w-full max-w-4xl";
414
+ const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
415
+ const [ageDays, setAgeDays] = useState("30");
416
+ const [bulkTarget, setBulkTarget] = useState(null);
417
+ const [bulkBusy, setBulkBusy] = useState(false);
418
+ const [bulkError, setBulkError] = useState(null);
419
+ useEffect(() => {
420
+ setSelectedIds(/* @__PURE__ */ new Set());
421
+ }, [searchTerm, sort]);
422
+ useEffect(() => {
423
+ const visible = new Set(history.items.map((item) => item.id));
424
+ setSelectedIds((current) => {
425
+ const next = new Set([...current].filter((id) => visible.has(id)));
426
+ return next.size === current.size ? current : next;
427
+ });
428
+ }, [history.items]);
429
+ const selectedCount = selectedIds.size;
430
+ const allVisibleSelected = history.items.length > 0 && history.items.every((item) => selectedIds.has(item.id));
431
+ const parsedAgeDays = Number(ageDays);
432
+ const validAgeDays = Number.isInteger(parsedAgeDays) && parsedAgeDays >= 1 && parsedAgeDays <= 36500;
433
+ const openBulkAction = useCallback((action) => {
434
+ const verb = deleteLabel.toLowerCase();
435
+ if (action.kind === "selected") {
436
+ setBulkTarget({
437
+ action,
438
+ title: `${deleteLabel} selected sessions?`,
439
+ body: `${verb === "delete" ? "This permanently removes" : `This ${verb}s`} ${action.ids.length} selected session${action.ids.length === 1 ? "" : "s"} and its messages.`
440
+ });
441
+ return;
442
+ }
443
+ const range = action.kind === "older-than" ? `older than ${action.days} days` : `from the last ${action.days} days`;
444
+ setBulkTarget({
445
+ action,
446
+ title: `${deleteLabel} sessions ${range}?`,
447
+ body: "This applies to every matching session in this workspace, including sessions not currently loaded in this list."
448
+ });
449
+ }, [deleteLabel]);
450
+ const confirmBulkAction = useCallback(async () => {
451
+ if (!bulkTarget || !onBulkAction) return;
452
+ setBulkBusy(true);
453
+ setBulkError(null);
454
+ try {
455
+ await onBulkAction(bulkTarget.action);
456
+ setBulkTarget(null);
457
+ setSelectedIds(/* @__PURE__ */ new Set());
458
+ history.reload();
459
+ } catch (error) {
460
+ setBulkError(error instanceof Error ? error.message : `Could not ${deleteLabel.toLowerCase()} sessions`);
461
+ } finally {
462
+ setBulkBusy(false);
463
+ }
464
+ }, [bulkTarget, deleteLabel, history, onBulkAction]);
465
+ return /* @__PURE__ */ jsxs("div", { className: `flex min-h-0 min-w-0 flex-1 flex-col ${className ?? ""}`, children: [
466
+ /* @__PURE__ */ jsx("header", { className: "flex h-14 shrink-0 items-center border-b border-border px-4 sm:px-6", children: /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 px-3 ${column}`, children: [
467
+ /* @__PURE__ */ jsx("h1", { className: "flex-1 truncate text-sm font-semibold text-foreground", children: title }),
468
+ newSessionHref && /* @__PURE__ */ jsxs(
469
+ Link,
470
+ {
471
+ to: newSessionHref,
472
+ className: "inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground transition hover:opacity-90",
473
+ children: [
474
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-sm leading-none", children: "+" }),
475
+ "New chat"
476
+ ]
477
+ }
478
+ )
479
+ ] }) }),
480
+ /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "min-h-0 flex-1 overflow-y-auto", children: [
481
+ hasAnySessions && /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 bg-background px-4 sm:px-6", children: [
482
+ /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-2 px-3 pb-3 pt-4 sm:flex-row sm:items-center sm:gap-3 ${column}`, children: [
483
+ /* @__PURE__ */ jsx(
484
+ "input",
485
+ {
486
+ type: "search",
487
+ value: query,
488
+ onChange: (e) => onQueryChange(e.target.value),
489
+ placeholder: "Search your sessions\u2026",
490
+ "aria-label": "Search sessions",
491
+ className: "h-9 min-w-0 appearance-none rounded-md border border-strong bg-card px-3 text-sm text-foreground placeholder:text-muted-foreground sm:flex-1 [&::-webkit-search-cancel-button]:appearance-none"
492
+ }
493
+ ),
494
+ /* @__PURE__ */ jsxs(
495
+ "select",
496
+ {
497
+ value: sort,
498
+ onChange: (e) => onSortChange(e.target.value),
499
+ "aria-label": "Sort sessions",
500
+ className: "h-9 shrink-0 appearance-none rounded-md border border-strong bg-card px-2 text-sm text-foreground sm:w-[132px]",
501
+ children: [
502
+ /* @__PURE__ */ jsx("option", { value: "newest", children: "Newest" }),
503
+ /* @__PURE__ */ jsx("option", { value: "oldest", children: "Oldest" })
504
+ ]
505
+ }
506
+ )
507
+ ] }),
508
+ onBulkAction && /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-2 border-t border-border px-3 py-3 ${column}`, children: [
509
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
510
+ /* @__PURE__ */ jsx(
511
+ "button",
512
+ {
513
+ type: "button",
514
+ onClick: () => setSelectedIds(new Set(history.items.map((item) => item.id))),
515
+ disabled: allVisibleSelected || history.items.length === 0,
516
+ className: "h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50",
517
+ children: "Select all"
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsx(
521
+ "button",
522
+ {
523
+ type: "button",
524
+ onClick: () => setSelectedIds(/* @__PURE__ */ new Set()),
525
+ disabled: selectedCount === 0,
526
+ className: "h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50",
527
+ children: "Deselect all"
528
+ }
529
+ ),
530
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", "aria-live": "polite", children: [
531
+ selectedCount,
532
+ " selected"
533
+ ] }),
534
+ selectedCount > 0 && /* @__PURE__ */ jsxs(
535
+ "button",
536
+ {
537
+ type: "button",
538
+ onClick: () => openBulkAction({ kind: "selected", ids: [...selectedIds] }),
539
+ className: "h-8 rounded-md bg-destructive px-2.5 text-xs font-medium text-destructive-foreground transition hover:opacity-90",
540
+ children: [
541
+ deleteLabel,
542
+ " selected"
543
+ ]
544
+ }
545
+ )
546
+ ] }),
547
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
548
+ /* @__PURE__ */ jsx("label", { htmlFor: "agent-app-session-age", className: "text-xs text-muted-foreground", children: "Session age" }),
549
+ /* @__PURE__ */ jsx(
550
+ "input",
551
+ {
552
+ id: "agent-app-session-age",
553
+ type: "number",
554
+ min: 1,
555
+ max: 36500,
556
+ value: ageDays,
557
+ onChange: (event) => setAgeDays(event.target.value),
558
+ "aria-invalid": ageDays.length > 0 && !validAgeDays,
559
+ className: "h-8 w-20 rounded-md border border-strong bg-card px-2 text-xs tabular-nums text-foreground"
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "days" }),
563
+ /* @__PURE__ */ jsxs(
564
+ "button",
565
+ {
566
+ type: "button",
567
+ onClick: () => openBulkAction({ kind: "older-than", days: parsedAgeDays }),
568
+ disabled: !validAgeDays,
569
+ className: "h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50",
570
+ children: [
571
+ deleteLabel,
572
+ " older"
573
+ ]
574
+ }
575
+ ),
576
+ /* @__PURE__ */ jsxs(
577
+ "button",
578
+ {
579
+ type: "button",
580
+ onClick: () => openBulkAction({ kind: "newer-than", days: parsedAgeDays }),
581
+ disabled: !validAgeDays,
582
+ className: "h-8 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition hover:bg-accent disabled:opacity-50",
583
+ children: [
584
+ deleteLabel,
585
+ " recent"
586
+ ]
587
+ }
588
+ )
589
+ ] })
590
+ ] })
591
+ ] }),
592
+ /* @__PURE__ */ jsx("div", { className: `px-4 pb-8 pt-1 sm:px-6 ${column}`, children: !hasAnySessions ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-[50vh] flex-col items-center justify-center gap-2 text-center", children: [
593
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: emptyTitle }),
594
+ /* @__PURE__ */ jsx("p", { className: "max-w-xs text-xs text-muted-foreground", children: emptyDescription })
595
+ ] }) : history.isLoadingFirst ? /* @__PURE__ */ jsx(SkeletonRows, {}) : history.items.length === 0 ? history.isError ? /* @__PURE__ */ jsx(ErrorBlock, { onRetry: history.retry, message: "Couldn\u2019t load your sessions." }) : isSearching ? /* @__PURE__ */ jsxs("p", { className: "py-16 text-center text-sm text-muted-foreground", children: [
596
+ "No sessions match \u201C",
597
+ searchTerm,
598
+ "\u201D."
599
+ ] }) : /* @__PURE__ */ jsx("p", { className: "py-16 text-center text-sm text-muted-foreground", children: "No sessions remain." }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
600
+ history.items.map((session) => /* @__PURE__ */ jsx(
601
+ SessionRow,
602
+ {
603
+ session,
604
+ href: hrefForSession(session.id),
605
+ Link,
606
+ responding: respondingSessionIds?.has(session.id) ?? false,
607
+ untitledLabel,
608
+ timestamp: formatTimestamp(session.updatedAt),
609
+ onRename,
610
+ onDelete,
611
+ selectable: Boolean(onBulkAction),
612
+ selected: selectedIds.has(session.id),
613
+ onSelectedChange: (selected) => {
614
+ setSelectedIds((current) => {
615
+ const next = new Set(current);
616
+ if (selected) next.add(session.id);
617
+ else next.delete(session.id);
618
+ return next;
619
+ });
620
+ },
621
+ renameLabel,
622
+ deleteLabel,
623
+ extraActions
624
+ },
625
+ session.id
626
+ )),
627
+ history.isError ? /* @__PURE__ */ jsx(ErrorBlock, { onRetry: history.retry, message: "Couldn\u2019t load more sessions.", inline: true }) : history.hasMore ? /* @__PURE__ */ jsx("div", { ref: sentinelRef, className: "flex items-center justify-center py-6", children: history.isLoadingMore && /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", "aria-busy": true, className: "text-xs text-muted-foreground", children: "Loading\u2026" }) }) : null
628
+ ] }) })
629
+ ] }),
630
+ bulkTarget && /* @__PURE__ */ jsx(
631
+ SessionDialog,
632
+ {
633
+ title: bulkTarget.title,
634
+ onClose: () => {
635
+ if (!bulkBusy) {
636
+ setBulkTarget(null);
637
+ setBulkError(null);
638
+ }
639
+ },
640
+ busy: bulkBusy,
641
+ error: bulkError,
642
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
643
+ /* @__PURE__ */ jsx(
644
+ DialogButton,
645
+ {
646
+ onClick: () => {
647
+ setBulkTarget(null);
648
+ setBulkError(null);
649
+ },
650
+ disabled: bulkBusy,
651
+ variant: "ghost",
652
+ children: "Cancel"
653
+ }
654
+ ),
655
+ /* @__PURE__ */ jsx(DialogButton, { onClick: () => void confirmBulkAction(), disabled: bulkBusy, variant: "destructive", children: bulkBusy ? "Working\u2026" : deleteLabel })
656
+ ] }),
657
+ children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: bulkTarget.body })
658
+ }
659
+ )
660
+ ] });
661
+ }
662
+ function ErrorBlock({ message, onRetry, inline }) {
663
+ return /* @__PURE__ */ jsxs(
664
+ "div",
665
+ {
666
+ className: inline ? "flex items-center justify-center gap-3 py-6 text-sm text-muted-foreground" : "flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center",
667
+ children: [
668
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: message }),
669
+ /* @__PURE__ */ jsx(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ onClick: onRetry,
674
+ className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground transition hover:bg-accent",
675
+ children: "Retry"
676
+ }
677
+ )
678
+ ]
679
+ }
680
+ );
681
+ }
682
+ function SessionRow({
683
+ session,
684
+ href,
685
+ Link,
686
+ responding,
687
+ untitledLabel,
688
+ timestamp,
689
+ onRename,
690
+ onDelete,
691
+ renameLabel,
692
+ deleteLabel,
693
+ extraActions,
694
+ selectable,
695
+ selected,
696
+ onSelectedChange
697
+ }) {
698
+ const [menuOpen, setMenuOpen] = useState(false);
699
+ const panelId = useId();
700
+ const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(menuOpen, setMenuOpen);
701
+ const extras = extraActions?.(session) ?? [];
702
+ const hasMenu = Boolean(onRename) || Boolean(onDelete) || extras.length > 0;
703
+ const showUnread = Boolean(session.unread) && !responding;
704
+ return /* @__PURE__ */ jsxs("div", { className: `group relative flex items-center gap-2 rounded-lg px-3 py-2.5 transition-colors hover:bg-accent ${selected ? "bg-primary/10" : ""}`, children: [
705
+ selectable && /* @__PURE__ */ jsx(
706
+ "input",
707
+ {
708
+ type: "checkbox",
709
+ checked: selected,
710
+ onChange: (event) => onSelectedChange(event.target.checked),
711
+ "aria-label": `Select ${sessionLabel(session, untitledLabel)}`,
712
+ className: "h-4 w-4 shrink-0 rounded border-border accent-primary"
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsxs(Link, { to: href, className: "flex min-w-0 flex-1 items-center gap-3", children: [
716
+ showUnread && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary", "aria-hidden": true }),
717
+ /* @__PURE__ */ jsx(MessageIcon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
718
+ /* @__PURE__ */ jsx(
719
+ "span",
720
+ {
721
+ className: `truncate text-sm ${responding ? "text-muted-foreground" : "text-foreground"} ${showUnread ? "font-semibold" : ""}`,
722
+ ...responding ? { role: "status", "aria-label": "Agent responding" } : {},
723
+ children: sessionLabel(session, untitledLabel)
724
+ }
725
+ )
726
+ ] }),
727
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: timestamp }),
728
+ hasMenu && /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative shrink-0", children: [
729
+ /* @__PURE__ */ jsx(
730
+ "button",
731
+ {
732
+ type: "button",
733
+ ...triggerProps,
734
+ "aria-label": "Session actions",
735
+ "aria-controls": menuOpen ? panelId : void 0,
736
+ onClick: () => setMenuOpen((open) => !open),
737
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted hover:text-foreground focus-visible:opacity-100 sm:opacity-0 sm:group-hover:opacity-100 aria-expanded:opacity-100",
738
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-base leading-none", children: "\u22EF" })
739
+ }
740
+ ),
741
+ /* @__PURE__ */ jsxs(
742
+ PopoverSurface,
743
+ {
744
+ open: menuOpen,
745
+ id: panelId,
746
+ role: "menu",
747
+ triggerRef,
748
+ panelRef,
749
+ className: `w-36 overflow-hidden rounded-md border border-card-edge bg-popover py-1 ${OVERLAY_SHADOW}`,
750
+ children: [
751
+ onRename && /* @__PURE__ */ jsx(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ role: "menuitem",
756
+ onClick: () => {
757
+ setMenuOpen(false);
758
+ onRename(session);
759
+ },
760
+ className: "block w-full px-3 py-1.5 text-left text-xs text-foreground transition hover:bg-accent",
761
+ children: renameLabel
762
+ }
763
+ ),
764
+ extras.map((action) => /* @__PURE__ */ jsx(
765
+ "button",
766
+ {
767
+ type: "button",
768
+ role: "menuitem",
769
+ onClick: () => {
770
+ setMenuOpen(false);
771
+ action.onSelect();
772
+ },
773
+ className: `block w-full px-3 py-1.5 text-left text-xs transition ${action.destructive ? "text-destructive hover:bg-destructive/10" : "text-foreground hover:bg-accent"}`,
774
+ children: action.label
775
+ },
776
+ action.id
777
+ )),
778
+ onDelete && /* @__PURE__ */ jsx(
779
+ "button",
780
+ {
781
+ type: "button",
782
+ role: "menuitem",
783
+ onClick: () => {
784
+ setMenuOpen(false);
785
+ onDelete(session);
786
+ },
787
+ className: "block w-full px-3 py-1.5 text-left text-xs text-destructive transition hover:bg-destructive/10",
788
+ children: deleteLabel
789
+ }
790
+ )
791
+ ]
792
+ }
793
+ )
794
+ ] })
795
+ ] });
796
+ }
797
+
798
+ export {
799
+ useInfiniteScroll,
800
+ useSessionHistory,
801
+ useSessionActions,
802
+ formatSessionTimestamp,
803
+ SessionHistoryPanel
804
+ };
805
+ //# sourceMappingURL=chunk-KKBTPZIE.js.map