spiracha 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/AGENTS.md +28 -1
  2. package/README.md +47 -7
  3. package/apps/ui/AGENTS.md +70 -0
  4. package/apps/ui/README.md +72 -0
  5. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +1 -0
  6. package/apps/ui/dist/client/assets/analytics-BjYaHqXk.js +1 -0
  7. package/apps/ui/dist/client/assets/checkbox-wPoGG3of.js +1 -0
  8. package/apps/ui/dist/client/assets/data-table-6yDgAdtf.js +4 -0
  9. package/apps/ui/dist/client/assets/delete-confirm-dialog-DJUAk7ha.js +11 -0
  10. package/apps/ui/dist/client/assets/download-BhWd-Pm5.js +1 -0
  11. package/apps/ui/dist/client/assets/es2015-BlyMI4CF.js +41 -0
  12. package/apps/ui/dist/client/assets/formatters-BxjZwWSE.js +1 -0
  13. package/apps/ui/dist/client/assets/index-T01rPkb4.js +22 -0
  14. package/apps/ui/dist/client/assets/input-B3YN8gzg.js +1 -0
  15. package/apps/ui/dist/client/assets/metric-card-BWW7TWER.js +1 -0
  16. package/apps/ui/dist/client/assets/page-header-BZ8Gnxgs.js +1 -0
  17. package/apps/ui/dist/client/assets/projects._project-B7XcpoLt.js +1 -0
  18. package/apps/ui/dist/client/assets/projects._project-EfBhCHPY.js +1 -0
  19. package/apps/ui/dist/client/assets/projects.index-4vfIwLjw.js +1 -0
  20. package/apps/ui/dist/client/assets/projects.index-DzEZ4pAJ.js +1 -0
  21. package/apps/ui/dist/client/assets/routes-CWCCZykE.js +1 -0
  22. package/apps/ui/dist/client/assets/select-DLXGsyZ4.js +1 -0
  23. package/apps/ui/dist/client/assets/settings-b0Xthfae.js +1 -0
  24. package/apps/ui/dist/client/assets/styles-8Wtc8YJw.css +1 -0
  25. package/apps/ui/dist/client/assets/threads._threadId-CgtoCqTb.js +1 -0
  26. package/apps/ui/dist/client/assets/threads._threadId-DBiDb38K.js +7 -0
  27. package/apps/ui/dist/client/favicon.ico +0 -0
  28. package/apps/ui/dist/client/logo192.png +0 -0
  29. package/apps/ui/dist/client/logo512.png +0 -0
  30. package/apps/ui/dist/client/manifest.json +25 -0
  31. package/apps/ui/dist/client/robots.txt +3 -0
  32. package/apps/ui/dist/server/assets/__23tanstack-start-plugin-adapters-BzCA6dXo.js +5 -0
  33. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-BjsXNYgm.js +99 -0
  34. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +6 -0
  35. package/apps/ui/dist/server/assets/analytics-Br_fZB6a.js +139 -0
  36. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +46 -0
  37. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +19 -0
  38. package/apps/ui/dist/server/assets/codex-queries-CAF6HYiG.js +109 -0
  39. package/apps/ui/dist/server/assets/codex-server-Cqh0hb93.js +1995 -0
  40. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +189 -0
  41. package/apps/ui/dist/server/assets/delete-confirm-dialog-CWqcTXTF.js +139 -0
  42. package/apps/ui/dist/server/assets/download-CzHmFWGk.js +286 -0
  43. package/apps/ui/dist/server/assets/formatters-B6o5pTY9.js +72 -0
  44. package/apps/ui/dist/server/assets/input-B4tEzctc.js +46 -0
  45. package/apps/ui/dist/server/assets/loading-panel-DbLdvjtR.js +27 -0
  46. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +23 -0
  47. package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +25 -0
  48. package/apps/ui/dist/server/assets/path-transforms-DD1e7rhY.js +31 -0
  49. package/apps/ui/dist/server/assets/projects._project-Bwf6iJC-.js +335 -0
  50. package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +26 -0
  51. package/apps/ui/dist/server/assets/projects._project-DdVSdfPe.js +18 -0
  52. package/apps/ui/dist/server/assets/projects.index-CaplpeMy.js +26 -0
  53. package/apps/ui/dist/server/assets/projects.index-DKeVeqUZ.js +171 -0
  54. package/apps/ui/dist/server/assets/router-ve2Hrl2Y.js +307 -0
  55. package/apps/ui/dist/server/assets/routes-BJyx5OmO.js +34 -0
  56. package/apps/ui/dist/server/assets/routes-pkOwjjYc.js +168 -0
  57. package/apps/ui/dist/server/assets/select-GW76p-ld.js +76 -0
  58. package/apps/ui/dist/server/assets/settings-MvWDgc1u.js +100 -0
  59. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +52 -0
  60. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +13 -0
  61. package/apps/ui/dist/server/assets/start-BAvbjjfs.js +4 -0
  62. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +26 -0
  63. package/apps/ui/dist/server/assets/threads._threadId-D3PYZIwl.js +18 -0
  64. package/apps/ui/dist/server/assets/threads._threadId-D3xaWM86.js +1037 -0
  65. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +8 -0
  66. package/apps/ui/dist/server/server.js +5678 -0
  67. package/package.json +47 -7
  68. package/src/export-chats.ts +1 -14
  69. package/src/lib/codex-analytics.ts +100 -0
  70. package/src/lib/codex-browser-db.ts +518 -0
  71. package/src/lib/codex-browser-export.ts +418 -0
  72. package/src/lib/codex-browser-types.ts +224 -0
  73. package/src/lib/codex-exporter-cli.ts +5 -0
  74. package/src/lib/codex-exporter-transcript.ts +143 -32
  75. package/src/lib/codex-exporter-types.ts +8 -0
  76. package/src/lib/codex-thread-cache.ts +58 -0
  77. package/src/lib/codex-thread-parser.ts +604 -0
  78. package/src/lib/interactive-cli.ts +5 -13
  79. package/src/lib/native-open.ts +54 -0
  80. package/src/lib/path-transforms.ts +45 -0
  81. package/src/lib/shared.ts +37 -1
  82. package/src/lib/sqlite-error.ts +14 -0
  83. package/src/lib/sqlite-retry.ts +39 -0
  84. package/src/lib/ui-cache.ts +96 -0
  85. package/src/lib/ui-export-files.ts +77 -0
  86. package/src/mcp-server.ts +1 -0
  87. package/src/spiracha.ts +14 -1
  88. package/src/ui-cli.ts +310 -0
@@ -0,0 +1,189 @@
1
+ import { t as cn } from "./utils-C_uf36nf.js";
2
+ import { t as Checkbox } from "./checkbox-C0hovF41.js";
3
+ import { useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { ArrowDownUp } from "lucide-react";
6
+ import { flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
7
+ //#region src/components/ui/table.tsx
8
+ function Table({ className, ...props }) {
9
+ return /* @__PURE__ */ jsx("div", {
10
+ "data-slot": "table-container",
11
+ className: "relative w-full overflow-x-auto",
12
+ children: /* @__PURE__ */ jsx("table", {
13
+ "data-slot": "table",
14
+ className: cn("w-full caption-bottom text-sm", className),
15
+ ...props
16
+ })
17
+ });
18
+ }
19
+ function TableHeader({ className, ...props }) {
20
+ return /* @__PURE__ */ jsx("thead", {
21
+ "data-slot": "table-header",
22
+ className: cn("[&_tr]:border-b", className),
23
+ ...props
24
+ });
25
+ }
26
+ function TableBody({ className, ...props }) {
27
+ return /* @__PURE__ */ jsx("tbody", {
28
+ "data-slot": "table-body",
29
+ className: cn("[&_tr:last-child]:border-0", className),
30
+ ...props
31
+ });
32
+ }
33
+ function TableRow({ className, ...props }) {
34
+ return /* @__PURE__ */ jsx("tr", {
35
+ "data-slot": "table-row",
36
+ className: cn("border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted", className),
37
+ ...props
38
+ });
39
+ }
40
+ function TableHead({ className, ...props }) {
41
+ return /* @__PURE__ */ jsx("th", {
42
+ "data-slot": "table-head",
43
+ className: cn("h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className),
44
+ ...props
45
+ });
46
+ }
47
+ function TableCell({ className, ...props }) {
48
+ return /* @__PURE__ */ jsx("td", {
49
+ "data-slot": "table-cell",
50
+ className: cn("whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className),
51
+ ...props
52
+ });
53
+ }
54
+ //#endregion
55
+ //#region src/components/data-table.tsx
56
+ var getSortIndicator = (value) => {
57
+ if (value === "asc") return "↑";
58
+ if (value === "desc") return "↓";
59
+ return /* @__PURE__ */ jsx(ArrowDownUp, { className: "size-3" });
60
+ };
61
+ var getRangeRowIds = (visibleRowIds, anchorRowId, targetRowId) => {
62
+ const anchorIndex = visibleRowIds.indexOf(anchorRowId);
63
+ const targetIndex = visibleRowIds.indexOf(targetRowId);
64
+ if (anchorIndex === -1 || targetIndex === -1) return null;
65
+ const [startIndex, endIndex] = anchorIndex <= targetIndex ? [anchorIndex, targetIndex] : [targetIndex, anchorIndex];
66
+ return visibleRowIds.slice(startIndex, endIndex + 1);
67
+ };
68
+ var applySelectionState = (selection, rowIds, checked) => {
69
+ const nextSelection = { ...selection };
70
+ for (const rowId of rowIds) {
71
+ if (checked) {
72
+ nextSelection[rowId] = true;
73
+ continue;
74
+ }
75
+ delete nextSelection[rowId];
76
+ }
77
+ return nextSelection;
78
+ };
79
+ function DataTable({ className, columns, data, emptyMessage, enableRowSelection = false, getRowId, initialSorting = [], onRowClick, renderToolbar }) {
80
+ const [sorting, setSorting] = useState(initialSorting);
81
+ const [rowSelection, setRowSelection] = useState({});
82
+ const lastSelectedRowIdRef = useRef(null);
83
+ const updateSelectionForRow = (rowId, checked, shiftKey) => {
84
+ const visibleRowIds = table.getRowModel().rows.map((row) => row.id);
85
+ if (shiftKey && lastSelectedRowIdRef.current) {
86
+ const rangeRowIds = getRangeRowIds(visibleRowIds, lastSelectedRowIdRef.current, rowId);
87
+ if (rangeRowIds) {
88
+ setRowSelection(applySelectionState(rowSelection, rangeRowIds, checked));
89
+ lastSelectedRowIdRef.current = rowId;
90
+ return;
91
+ }
92
+ }
93
+ setRowSelection(applySelectionState(rowSelection, [rowId], checked));
94
+ lastSelectedRowIdRef.current = rowId;
95
+ };
96
+ const tableColumns = enableRowSelection ? [{
97
+ cell: ({ row }) => /* @__PURE__ */ jsx(Checkbox, {
98
+ "aria-label": `Select row ${row.id}`,
99
+ checked: row.getIsSelected(),
100
+ onClick: (event) => {
101
+ event.stopPropagation();
102
+ event.preventDefault();
103
+ updateSelectionForRow(row.id, !row.getIsSelected(), event.shiftKey);
104
+ },
105
+ onCheckedChange: (checked) => {
106
+ if (typeof checked !== "boolean") return;
107
+ updateSelectionForRow(row.id, checked, false);
108
+ }
109
+ }),
110
+ enableSorting: false,
111
+ header: ({ table }) => /* @__PURE__ */ jsx(Checkbox, {
112
+ "aria-label": "Select all rows",
113
+ checked: table.getIsAllPageRowsSelected() ? true : table.getIsSomePageRowsSelected() ? "indeterminate" : false,
114
+ onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked === true)
115
+ }),
116
+ id: "select"
117
+ }, ...columns] : [...columns];
118
+ const table = useReactTable({
119
+ autoResetPageIndex: false,
120
+ columns: tableColumns,
121
+ data,
122
+ enableRowSelection,
123
+ enableSortingRemoval: false,
124
+ getCoreRowModel: getCoreRowModel(),
125
+ getFilteredRowModel: getFilteredRowModel(),
126
+ getRowId,
127
+ getSortedRowModel: getSortedRowModel(),
128
+ onRowSelectionChange: setRowSelection,
129
+ onSortingChange: setSorting,
130
+ sortDescFirst: false,
131
+ state: {
132
+ rowSelection,
133
+ sorting
134
+ }
135
+ });
136
+ const selectedRows = table.getSelectedRowModel().rows.map((row) => row.original);
137
+ return /* @__PURE__ */ jsxs("div", {
138
+ className: cn("w-full overflow-x-auto rounded-[1.5rem] border border-[var(--border)] bg-[var(--panel)]", className),
139
+ children: [renderToolbar ? /* @__PURE__ */ jsx("div", {
140
+ className: "border-[var(--border)] border-b px-4 py-3",
141
+ children: renderToolbar({
142
+ clearSelection: () => setRowSelection({}),
143
+ selectedRows
144
+ })
145
+ }) : null, /* @__PURE__ */ jsxs(Table, {
146
+ className: "min-w-full",
147
+ children: [/* @__PURE__ */ jsx(TableHeader, {
148
+ className: "bg-[var(--panel-secondary)]",
149
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
150
+ className: "border-[var(--border)] hover:bg-transparent",
151
+ children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(TableHead, {
152
+ className: "h-10 whitespace-nowrap px-4 font-semibold text-[11px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]",
153
+ children: header.isPlaceholder ? null : header.column.getCanSort() ? /* @__PURE__ */ jsxs("button", {
154
+ className: "inline-flex items-center gap-1.5 text-left",
155
+ type: "button",
156
+ onClick: header.column.getToggleSortingHandler(),
157
+ children: [/* @__PURE__ */ jsx("span", { children: flexRender(header.column.columnDef.header, header.getContext()) }), /* @__PURE__ */ jsx("span", {
158
+ "aria-hidden": "true",
159
+ className: "text-[10px]",
160
+ children: getSortIndicator(header.column.getIsSorted())
161
+ })]
162
+ }) : flexRender(header.column.columnDef.header, header.getContext())
163
+ }, header.id))
164
+ }, headerGroup.id))
165
+ }), /* @__PURE__ */ jsx(TableBody, { children: table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx(TableRow, {
166
+ className: "border-[var(--border)]",
167
+ children: /* @__PURE__ */ jsx(TableCell, {
168
+ className: "px-4 py-10 text-center text-[var(--muted-foreground)] text-sm",
169
+ colSpan: tableColumns.length,
170
+ children: emptyMessage
171
+ })
172
+ }) : table.getRowModel().rows.map((row) => {
173
+ return /* @__PURE__ */ jsx(TableRow, {
174
+ className: cn("border-[var(--border)] hover:bg-[var(--panel-secondary)]/75", Boolean(onRowClick) ? "cursor-pointer" : ""),
175
+ onClick: () => {
176
+ if (!onRowClick) return;
177
+ onRowClick(row.original);
178
+ },
179
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
180
+ className: "px-4 py-2.5 align-top",
181
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
182
+ }, cell.id))
183
+ }, row.id);
184
+ }) })]
185
+ })]
186
+ });
187
+ }
188
+ //#endregion
189
+ export { DataTable as t };
@@ -0,0 +1,139 @@
1
+ import { t as cn } from "./utils-C_uf36nf.js";
2
+ import { t as Button } from "./button-CmTDnzOn.js";
3
+ import { t as Checkbox$1 } from "./checkbox-C0hovF41.js";
4
+ import { useEffect, useId, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { AlertDialog } from "radix-ui";
7
+ //#region src/components/ui/alert-dialog.tsx
8
+ function AlertDialog$1({ ...props }) {
9
+ return /* @__PURE__ */ jsx(AlertDialog.Root, {
10
+ "data-slot": "alert-dialog",
11
+ ...props
12
+ });
13
+ }
14
+ function AlertDialogPortal({ ...props }) {
15
+ return /* @__PURE__ */ jsx(AlertDialog.Portal, {
16
+ "data-slot": "alert-dialog-portal",
17
+ ...props
18
+ });
19
+ }
20
+ function AlertDialogOverlay({ className, ...props }) {
21
+ return /* @__PURE__ */ jsx(AlertDialog.Overlay, {
22
+ "data-slot": "alert-dialog-overlay",
23
+ className: cn("data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in", className),
24
+ ...props
25
+ });
26
+ }
27
+ function AlertDialogContent({ className, size = "default", ...props }) {
28
+ return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [/* @__PURE__ */ jsx(AlertDialogOverlay, {}), /* @__PURE__ */ jsx(AlertDialog.Content, {
29
+ "data-slot": "alert-dialog-content",
30
+ "data-size": size,
31
+ className: cn("group/alert-dialog-content data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=open]:animate-in data-[size=default]:sm:max-w-lg", className),
32
+ ...props
33
+ })] });
34
+ }
35
+ function AlertDialogHeader({ className, ...props }) {
36
+ return /* @__PURE__ */ jsx("div", {
37
+ "data-slot": "alert-dialog-header",
38
+ className: cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className),
39
+ ...props
40
+ });
41
+ }
42
+ function AlertDialogFooter({ className, ...props }) {
43
+ return /* @__PURE__ */ jsx("div", {
44
+ "data-slot": "alert-dialog-footer",
45
+ className: cn("flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end", className),
46
+ ...props
47
+ });
48
+ }
49
+ function AlertDialogTitle({ className, ...props }) {
50
+ return /* @__PURE__ */ jsx(AlertDialog.Title, {
51
+ "data-slot": "alert-dialog-title",
52
+ className: cn("font-semibold text-lg sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className),
53
+ ...props
54
+ });
55
+ }
56
+ function AlertDialogDescription({ className, ...props }) {
57
+ return /* @__PURE__ */ jsx(AlertDialog.Description, {
58
+ "data-slot": "alert-dialog-description",
59
+ className: cn("text-muted-foreground text-sm", className),
60
+ ...props
61
+ });
62
+ }
63
+ function AlertDialogAction({ className, variant = "default", size = "default", ...props }) {
64
+ return /* @__PURE__ */ jsx(Button, {
65
+ variant,
66
+ size,
67
+ asChild: true,
68
+ children: /* @__PURE__ */ jsx(AlertDialog.Action, {
69
+ "data-slot": "alert-dialog-action",
70
+ className: cn(className),
71
+ ...props
72
+ })
73
+ });
74
+ }
75
+ function AlertDialogCancel({ className, variant = "outline", size = "default", ...props }) {
76
+ return /* @__PURE__ */ jsx(Button, {
77
+ variant,
78
+ size,
79
+ asChild: true,
80
+ children: /* @__PURE__ */ jsx(AlertDialog.Cancel, {
81
+ "data-slot": "alert-dialog-cancel",
82
+ className: cn(className),
83
+ ...props
84
+ })
85
+ });
86
+ }
87
+ //#endregion
88
+ //#region src/components/delete-confirm-dialog.tsx
89
+ function DeleteConfirmDialog({ confirmLabel = "Delete", description, open, showDeleteSessionFilesOption = false, title, onConfirm, onOpenChange }) {
90
+ const checkboxId = useId();
91
+ const checkboxDescriptionId = useId();
92
+ const [deleteSessionFiles, setDeleteSessionFiles] = useState(false);
93
+ useEffect(() => {
94
+ if (!open) setDeleteSessionFiles(false);
95
+ }, [open]);
96
+ return /* @__PURE__ */ jsx(AlertDialog$1, {
97
+ open,
98
+ onOpenChange,
99
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, {
100
+ className: "border-[var(--border)] bg-[var(--panel)] text-[var(--foreground)]",
101
+ children: [
102
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [/* @__PURE__ */ jsx(AlertDialogTitle, { children: title }), /* @__PURE__ */ jsx(AlertDialogDescription, {
103
+ className: "text-[var(--muted-foreground)]",
104
+ children: description
105
+ })] }),
106
+ showDeleteSessionFilesOption ? /* @__PURE__ */ jsxs("div", {
107
+ className: "flex items-start gap-3 rounded-xl border border-[var(--border)] bg-[var(--background)]/70 px-4 py-3 text-sm",
108
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
109
+ "aria-describedby": checkboxDescriptionId,
110
+ checked: deleteSessionFiles,
111
+ id: checkboxId,
112
+ onCheckedChange: (checked) => setDeleteSessionFiles(checked === true)
113
+ }), /* @__PURE__ */ jsxs("span", {
114
+ className: "space-y-1",
115
+ children: [/* @__PURE__ */ jsx("label", {
116
+ className: "block font-medium",
117
+ htmlFor: checkboxId,
118
+ children: "Delete Session files"
119
+ }), /* @__PURE__ */ jsx("span", {
120
+ className: "block text-[var(--muted-foreground)] text-xs",
121
+ id: checkboxDescriptionId,
122
+ children: "Remove the rollout JSONL from disk as well, so Codex cannot backfill this thread later."
123
+ })]
124
+ })]
125
+ }) : null,
126
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, {
127
+ className: "border-[var(--border)]",
128
+ children: "Cancel"
129
+ }), /* @__PURE__ */ jsx(AlertDialogAction, {
130
+ className: "bg-[var(--destructive)] text-[var(--destructive-foreground)] hover:bg-[var(--destructive)]/90",
131
+ onClick: () => onConfirm({ deleteSessionFiles }),
132
+ children: confirmLabel
133
+ })] })
134
+ ]
135
+ })
136
+ });
137
+ }
138
+ //#endregion
139
+ export { DeleteConfirmDialog as t };
@@ -0,0 +1,286 @@
1
+ import { t as cn } from "./utils-C_uf36nf.js";
2
+ import { t as Button } from "./button-CmTDnzOn.js";
3
+ import { t as Checkbox$1 } from "./checkbox-C0hovF41.js";
4
+ import { a as SelectValue, i as SelectTrigger, n as SelectContent, r as SelectItem, t as Select$1 } from "./select-GW76p-ld.js";
5
+ import { useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { XIcon } from "lucide-react";
8
+ import { Dialog } from "radix-ui";
9
+ //#region src/components/ui/dialog.tsx
10
+ function Dialog$1({ ...props }) {
11
+ return /* @__PURE__ */ jsx(Dialog.Root, {
12
+ "data-slot": "dialog",
13
+ ...props
14
+ });
15
+ }
16
+ function DialogPortal({ ...props }) {
17
+ return /* @__PURE__ */ jsx(Dialog.Portal, {
18
+ "data-slot": "dialog-portal",
19
+ ...props
20
+ });
21
+ }
22
+ function DialogOverlay({ className, ...props }) {
23
+ return /* @__PURE__ */ jsx(Dialog.Overlay, {
24
+ "data-slot": "dialog-overlay",
25
+ className: cn("data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=open]:animate-in", className),
26
+ ...props
27
+ });
28
+ }
29
+ function DialogContent({ className, children, showCloseButton = true, ...props }) {
30
+ return /* @__PURE__ */ jsxs(DialogPortal, {
31
+ "data-slot": "dialog-portal",
32
+ children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog.Content, {
33
+ "data-slot": "dialog-content",
34
+ className: cn("data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg outline-none duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg", className),
35
+ ...props,
36
+ children: [children, showCloseButton && /* @__PURE__ */ jsxs(Dialog.Close, {
37
+ "data-slot": "dialog-close",
38
+ className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
39
+ children: [/* @__PURE__ */ jsx(XIcon, {}), /* @__PURE__ */ jsx("span", {
40
+ className: "sr-only",
41
+ children: "Close"
42
+ })]
43
+ })]
44
+ })]
45
+ });
46
+ }
47
+ function DialogHeader({ className, ...props }) {
48
+ return /* @__PURE__ */ jsx("div", {
49
+ "data-slot": "dialog-header",
50
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
51
+ ...props
52
+ });
53
+ }
54
+ function DialogFooter({ className, showCloseButton = false, children, ...props }) {
55
+ return /* @__PURE__ */ jsxs("div", {
56
+ "data-slot": "dialog-footer",
57
+ className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
58
+ ...props,
59
+ children: [children, showCloseButton && /* @__PURE__ */ jsx(Dialog.Close, {
60
+ asChild: true,
61
+ children: /* @__PURE__ */ jsx(Button, {
62
+ variant: "outline",
63
+ children: "Close"
64
+ })
65
+ })]
66
+ });
67
+ }
68
+ function DialogTitle({ className, ...props }) {
69
+ return /* @__PURE__ */ jsx(Dialog.Title, {
70
+ "data-slot": "dialog-title",
71
+ className: cn("font-semibold text-lg leading-none", className),
72
+ ...props
73
+ });
74
+ }
75
+ function DialogDescription({ className, ...props }) {
76
+ return /* @__PURE__ */ jsx(Dialog.Description, {
77
+ "data-slot": "dialog-description",
78
+ className: cn("text-muted-foreground text-sm", className),
79
+ ...props
80
+ });
81
+ }
82
+ //#endregion
83
+ //#region src/components/export-dialog.tsx
84
+ function ExportDialog({ open, pending = false, title = "Export thread", onExport, onOpenChange }) {
85
+ const [outputFormat, setOutputFormat] = useState("md");
86
+ const [optimized, setOptimized] = useState(false);
87
+ const [includeCommentary, setIncludeCommentary] = useState(false);
88
+ const [includeTools, setIncludeTools] = useState(true);
89
+ return /* @__PURE__ */ jsx(Dialog$1, {
90
+ open,
91
+ onOpenChange,
92
+ children: /* @__PURE__ */ jsxs(DialogContent, {
93
+ className: "border-[var(--border)] bg-[var(--panel)] text-[var(--foreground)]",
94
+ children: [
95
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: title }), /* @__PURE__ */ jsx(DialogDescription, {
96
+ className: "text-[var(--muted-foreground)]",
97
+ children: "Choose the transcript format and whether the export includes tool calls."
98
+ })] }),
99
+ /* @__PURE__ */ jsxs("div", {
100
+ className: "space-y-5 py-2",
101
+ children: [
102
+ /* @__PURE__ */ jsxs("div", {
103
+ className: "space-y-2",
104
+ children: [/* @__PURE__ */ jsx("label", {
105
+ className: "font-medium text-sm",
106
+ htmlFor: "output-format",
107
+ children: "Output format"
108
+ }), /* @__PURE__ */ jsxs(Select$1, {
109
+ value: outputFormat,
110
+ onValueChange: (value) => setOutputFormat(value),
111
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
112
+ id: "output-format",
113
+ className: "border-[var(--border)] bg-[var(--panel-secondary)]",
114
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Choose a format" })
115
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
116
+ value: "md",
117
+ children: "Markdown (.md)"
118
+ }), /* @__PURE__ */ jsx(SelectItem, {
119
+ value: "txt",
120
+ children: "Plain text (.txt)"
121
+ })] })]
122
+ })]
123
+ }),
124
+ /* @__PURE__ */ jsxs("div", {
125
+ className: "flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3",
126
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
127
+ checked: optimized,
128
+ onCheckedChange: (checked) => setOptimized(checked === true)
129
+ }), /* @__PURE__ */ jsxs("span", {
130
+ className: "space-y-1",
131
+ children: [/* @__PURE__ */ jsx("span", {
132
+ className: "block font-medium text-sm",
133
+ children: "Optimized transcript"
134
+ }), /* @__PURE__ */ jsx("span", {
135
+ className: "block text-[var(--muted-foreground)] text-sm",
136
+ children: "Removes metadata and condenses the transcript for readability and token efficiency."
137
+ })]
138
+ })]
139
+ }),
140
+ /* @__PURE__ */ jsxs("div", {
141
+ className: "flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3",
142
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
143
+ checked: includeCommentary,
144
+ onCheckedChange: (checked) => setIncludeCommentary(checked === true)
145
+ }), /* @__PURE__ */ jsxs("span", {
146
+ className: "space-y-1",
147
+ children: [/* @__PURE__ */ jsx("span", {
148
+ className: "block font-medium text-sm",
149
+ children: "Include commentary"
150
+ }), /* @__PURE__ */ jsx("span", {
151
+ className: "block text-[var(--muted-foreground)] text-sm",
152
+ children: "Includes assistant commentary-phase updates in the exported transcript."
153
+ })]
154
+ })]
155
+ }),
156
+ /* @__PURE__ */ jsxs("div", {
157
+ className: "flex items-center gap-3 rounded-2xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3",
158
+ children: [/* @__PURE__ */ jsx(Checkbox$1, {
159
+ checked: includeTools,
160
+ onCheckedChange: (checked) => setIncludeTools(checked === true)
161
+ }), /* @__PURE__ */ jsxs("span", {
162
+ className: "space-y-1",
163
+ children: [/* @__PURE__ */ jsx("span", {
164
+ className: "block font-medium text-sm",
165
+ children: "Include tool calls"
166
+ }), /* @__PURE__ */ jsx("span", {
167
+ className: "block text-[var(--muted-foreground)] text-sm",
168
+ children: "Includes tool-call summaries and tool-output summaries in the export."
169
+ })]
170
+ })]
171
+ })
172
+ ]
173
+ }),
174
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
175
+ className: "rounded-full",
176
+ variant: "outline",
177
+ onClick: () => onOpenChange(false),
178
+ children: "Cancel"
179
+ }), /* @__PURE__ */ jsx(Button, {
180
+ className: "rounded-full",
181
+ disabled: pending,
182
+ onClick: () => onExport({
183
+ includeCommentary,
184
+ includeTools,
185
+ optimized,
186
+ outputFormat
187
+ }),
188
+ children: pending ? "Exporting..." : "Download export"
189
+ })] })
190
+ ]
191
+ })
192
+ });
193
+ }
194
+ //#endregion
195
+ //#region src/lib/download.ts
196
+ var DEFAULT_DOWNLOAD_ATTEMPTS = 6;
197
+ var DEFAULT_DOWNLOAD_RETRY_DELAY_MS = 250;
198
+ var DEFAULT_INLINE_REVOKE_DELAY_MS = 3e4;
199
+ var logDownloadEvent = (logger, level, event, details) => {
200
+ logger[level](`[spiracha:download] ${event}`, details);
201
+ };
202
+ var delay = (delayMs) => new Promise((resolve) => {
203
+ window.setTimeout(resolve, delayMs);
204
+ });
205
+ var triggerAnchorDownload = (documentRef, href, fileName) => {
206
+ const link = documentRef.createElement("a");
207
+ link.href = href;
208
+ link.download = fileName;
209
+ documentRef.body.append(link);
210
+ link.click();
211
+ link.remove();
212
+ };
213
+ var isReadyStatus = (status) => {
214
+ return status >= 200 && status < 400 || status === 405;
215
+ };
216
+ var waitForDownloadUrlAvailability = async (downloadUrl, fileName, { fetchImpl = fetch, logger = console, maxAttempts = DEFAULT_DOWNLOAD_ATTEMPTS, retryDelayMs = DEFAULT_DOWNLOAD_RETRY_DELAY_MS, sleep = delay } = {}) => {
217
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
218
+ try {
219
+ const response = await fetchImpl(downloadUrl, {
220
+ cache: "no-store",
221
+ method: "HEAD"
222
+ });
223
+ if (isReadyStatus(response.status)) {
224
+ logDownloadEvent(logger, "info", "url_ready", {
225
+ attempt,
226
+ downloadUrl,
227
+ fileName,
228
+ status: response.status
229
+ });
230
+ return;
231
+ }
232
+ logDownloadEvent(logger, "warn", "url_not_ready", {
233
+ attempt,
234
+ downloadUrl,
235
+ fileName,
236
+ status: response.status
237
+ });
238
+ } catch (error) {
239
+ logDownloadEvent(logger, "warn", "url_probe_failed", {
240
+ attempt,
241
+ downloadUrl,
242
+ error: error instanceof Error ? error.message : String(error),
243
+ fileName
244
+ });
245
+ }
246
+ if (attempt < maxAttempts) await sleep(retryDelayMs);
247
+ }
248
+ throw new Error(`Download file was not available after ${maxAttempts} attempts: ${fileName}`);
249
+ };
250
+ var downloadUrlFile = async (fileName, downloadUrl, { documentRef = document, fetchImpl = fetch, logger = console, maxAttempts = DEFAULT_DOWNLOAD_ATTEMPTS, retryDelayMs = DEFAULT_DOWNLOAD_RETRY_DELAY_MS, sleep = delay } = {}) => {
251
+ logDownloadEvent(logger, "info", "start", {
252
+ downloadUrl,
253
+ fileName
254
+ });
255
+ await waitForDownloadUrlAvailability(downloadUrl, fileName, {
256
+ fetchImpl,
257
+ logger,
258
+ maxAttempts,
259
+ retryDelayMs,
260
+ sleep
261
+ });
262
+ triggerAnchorDownload(documentRef, downloadUrl, fileName);
263
+ logDownloadEvent(logger, "info", "triggered", {
264
+ downloadUrl,
265
+ fileName
266
+ });
267
+ };
268
+ var downloadTextFile = (fileName, content, mimeType, { createObjectUrl = (blob) => URL.createObjectURL(blob), documentRef = document, logger = console, revokeDelayMs = DEFAULT_INLINE_REVOKE_DELAY_MS, revokeObjectUrl = (url) => URL.revokeObjectURL(url), schedule = (callback, delayMs) => {
269
+ window.setTimeout(callback, delayMs);
270
+ } } = {}) => {
271
+ logDownloadEvent(logger, "info", "inline_start", {
272
+ fileName,
273
+ mimeType,
274
+ sizeBytes: content.length
275
+ });
276
+ const url = createObjectUrl(new Blob([content], { type: mimeType }));
277
+ triggerAnchorDownload(documentRef, url, fileName);
278
+ schedule(() => revokeObjectUrl(url), revokeDelayMs);
279
+ logDownloadEvent(logger, "info", "inline_triggered", {
280
+ fileName,
281
+ mimeType,
282
+ sizeBytes: content.length
283
+ });
284
+ };
285
+ //#endregion
286
+ export { downloadUrlFile as n, ExportDialog as r, downloadTextFile as t };
@@ -0,0 +1,72 @@
1
+ //#region src/lib/formatters.ts
2
+ var MONTHS = [
3
+ "Jan",
4
+ "Feb",
5
+ "Mar",
6
+ "Apr",
7
+ "May",
8
+ "Jun",
9
+ "Jul",
10
+ "Aug",
11
+ "Sep",
12
+ "Oct",
13
+ "Nov",
14
+ "Dec"
15
+ ];
16
+ var formatNumber = (value) => {
17
+ return new Intl.NumberFormat("en-US").format(value);
18
+ };
19
+ var formatTokens = (value) => {
20
+ return `${formatNumber(value)} tokens`;
21
+ };
22
+ var formatBytes = (value) => {
23
+ if (!value || value <= 0) return "0 B";
24
+ const units = [
25
+ "B",
26
+ "KB",
27
+ "MB",
28
+ "GB",
29
+ "TB"
30
+ ];
31
+ let size = value;
32
+ let unitIndex = 0;
33
+ while (size >= 1024 && unitIndex < units.length - 1) {
34
+ size /= 1024;
35
+ unitIndex += 1;
36
+ }
37
+ const fractionDigits = size >= 100 || unitIndex === 0 ? 0 : 1;
38
+ return `${size.toFixed(fractionDigits)} ${units[unitIndex]}`;
39
+ };
40
+ var formatDateTime = (value) => {
41
+ if (value === null || value === void 0 || value === "") return "n/a";
42
+ const date = typeof value === "number" ? new Date(value) : new Date(value);
43
+ if (Number.isNaN(date.getTime())) return "n/a";
44
+ const today = /* @__PURE__ */ new Date();
45
+ const isToday = date.getUTCDate() === today.getUTCDate() && date.getUTCMonth() === today.getUTCMonth() && date.getUTCFullYear() === today.getUTCFullYear();
46
+ const month = MONTHS[date.getUTCMonth()];
47
+ const day = date.getUTCDate();
48
+ const hours12 = date.getUTCHours() % 12 || 12;
49
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
50
+ const ampm = date.getUTCHours() >= 12 ? "PM" : "AM";
51
+ if (isToday) return `${hours12}:${minutes} ${ampm}`;
52
+ return `${month} ${day} · ${hours12}:${minutes} ${ampm}`;
53
+ };
54
+ var formatList = (values) => {
55
+ if (values.length === 0) return "n/a";
56
+ return values.join(", ");
57
+ };
58
+ var formatBooleanLabel = (value) => {
59
+ return value ? "Yes" : "No";
60
+ };
61
+ var formatModelLabel = (value) => {
62
+ if (!value) return "Assistant";
63
+ return value.split(/[-_\s]+/u).filter(Boolean).map((part) => {
64
+ const lower = part.toLowerCase();
65
+ if (lower === "gpt") return "GPT";
66
+ if (/^[a-z]\d$/u.test(lower)) return lower.toUpperCase();
67
+ if (/^\d+(\.\d+)*$/u.test(part)) return part;
68
+ return `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`;
69
+ }).join(" ");
70
+ };
71
+ //#endregion
72
+ export { formatModelLabel as a, formatList as i, formatBytes as n, formatNumber as o, formatDateTime as r, formatTokens as s, formatBooleanLabel as t };