spiracha 1.0.0 → 1.1.1
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.
- package/AGENTS.md +31 -1
- package/README.md +61 -7
- package/apps/ui/AGENTS.md +70 -0
- package/apps/ui/README.md +72 -0
- package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +1 -0
- package/apps/ui/dist/client/assets/analytics-CqWZmyV6.js +1 -0
- package/apps/ui/dist/client/assets/checkbox-DXM4lkJq.js +1 -0
- package/apps/ui/dist/client/assets/data-table-DnPYMPCD.js +4 -0
- package/apps/ui/dist/client/assets/delete-confirm-dialog-CcZaRX33.js +11 -0
- package/apps/ui/dist/client/assets/download-DOwxk-cG.js +1 -0
- package/apps/ui/dist/client/assets/es2015-Bm0kEzx2.js +41 -0
- package/apps/ui/dist/client/assets/formatters-C12LmYaa.js +1 -0
- package/apps/ui/dist/client/assets/index-DdJ7ahIt.js +22 -0
- package/apps/ui/dist/client/assets/input-CEsI7EpI.js +1 -0
- package/apps/ui/dist/client/assets/metric-card-9jwBF7rG.js +1 -0
- package/apps/ui/dist/client/assets/page-header-Dr_h1CVv.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-uyNGnpjH.js +1 -0
- package/apps/ui/dist/client/assets/projects._project-zoM8d2nH.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-D1CWVN-O.js +1 -0
- package/apps/ui/dist/client/assets/projects.index-DukMuny6.js +1 -0
- package/apps/ui/dist/client/assets/routes-Gr2Wwh83.js +1 -0
- package/apps/ui/dist/client/assets/select-CFim44gT.js +1 -0
- package/apps/ui/dist/client/assets/settings-DqhyDxo2.js +1 -0
- package/apps/ui/dist/client/assets/styles-CMrP9Jb4.css +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-DT75NiBa.js +1 -0
- package/apps/ui/dist/client/assets/threads._threadId-Df5VXIuZ.js +7 -0
- package/apps/ui/dist/client/favicon.ico +0 -0
- package/apps/ui/dist/client/logo192.png +0 -0
- package/apps/ui/dist/client/logo512.png +0 -0
- package/apps/ui/dist/client/manifest.json +25 -0
- package/apps/ui/dist/client/robots.txt +3 -0
- package/apps/ui/dist/server/assets/__23tanstack-start-plugin-adapters-BzCA6dXo.js +5 -0
- package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-C0V305Nt.js +99 -0
- package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +6 -0
- package/apps/ui/dist/server/assets/analytics-BMxW_bZL.js +139 -0
- package/apps/ui/dist/server/assets/button-CmTDnzOn.js +46 -0
- package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +19 -0
- package/apps/ui/dist/server/assets/codex-queries-CAF6HYiG.js +109 -0
- package/apps/ui/dist/server/assets/codex-server-BFZq2Y2O.js +2062 -0
- package/apps/ui/dist/server/assets/data-table-Cdct823O.js +189 -0
- package/apps/ui/dist/server/assets/delete-confirm-dialog-CWqcTXTF.js +139 -0
- package/apps/ui/dist/server/assets/download-C5rkk_Bo.js +289 -0
- package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +91 -0
- package/apps/ui/dist/server/assets/input-B4tEzctc.js +46 -0
- package/apps/ui/dist/server/assets/loading-panel-DbLdvjtR.js +27 -0
- package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +23 -0
- package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +13 -0
- package/apps/ui/dist/server/assets/page-header-CxdZM86z.js +25 -0
- package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +31 -0
- package/apps/ui/dist/server/assets/projects._project-CJ7l0ynC.js +18 -0
- package/apps/ui/dist/server/assets/projects._project-CLSohrBp.js +26 -0
- package/apps/ui/dist/server/assets/projects._project-CcJLp_A8.js +337 -0
- package/apps/ui/dist/server/assets/projects.index-CaplpeMy.js +26 -0
- package/apps/ui/dist/server/assets/projects.index-srtogpuF.js +172 -0
- package/apps/ui/dist/server/assets/router-C_w-haH6.js +307 -0
- package/apps/ui/dist/server/assets/routes-BhbxvJE7.js +34 -0
- package/apps/ui/dist/server/assets/routes-CPe-ppmC.js +169 -0
- package/apps/ui/dist/server/assets/select-GW76p-ld.js +76 -0
- package/apps/ui/dist/server/assets/settings-MvWDgc1u.js +100 -0
- package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +52 -0
- package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +13 -0
- package/apps/ui/dist/server/assets/start-HeKLHD9b.js +4 -0
- package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +26 -0
- package/apps/ui/dist/server/assets/threads._threadId-Ba7vv6-K.js +18 -0
- package/apps/ui/dist/server/assets/threads._threadId-euyNckhj.js +1059 -0
- package/apps/ui/dist/server/assets/utils-C_uf36nf.js +8 -0
- package/apps/ui/dist/server/server.js +5678 -0
- package/package.json +53 -7
- package/src/export-chats.ts +4 -18
- package/src/lib/claude-exporter.ts +1 -1
- package/src/lib/codex-analytics.ts +100 -0
- package/src/lib/codex-browser-db.ts +605 -0
- package/src/lib/codex-browser-export.ts +429 -0
- package/src/lib/codex-browser-types.ts +224 -0
- package/src/lib/codex-exporter-cli.ts +6 -1
- package/src/lib/codex-exporter-db.ts +19 -20
- package/src/lib/codex-exporter-transcript.ts +158 -34
- package/src/lib/codex-exporter-types.ts +8 -0
- package/src/lib/codex-thread-cache.ts +58 -0
- package/src/lib/codex-thread-parser.ts +604 -0
- package/src/lib/interactive-cli.ts +10 -25
- package/src/lib/model-label.ts +24 -0
- package/src/lib/native-open.ts +54 -0
- package/src/lib/path-transforms.ts +46 -0
- package/src/lib/shared.ts +15 -1
- package/src/lib/sqlite-error.ts +14 -0
- package/src/lib/sqlite-retry.ts +53 -0
- package/src/lib/ui-cache.ts +96 -0
- package/src/lib/ui-export-files.ts +77 -0
- package/src/mcp-server.ts +1 -0
- package/src/spiracha.ts +16 -4
- package/src/ui-cli.ts +310 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { t as Button } from "./button-CmTDnzOn.js";
|
|
2
|
+
import { i as projectsQueryOptions, s as deleteProjectFn } from "./codex-queries-CAF6HYiG.js";
|
|
3
|
+
import { t as DataTable } from "./data-table-Cdct823O.js";
|
|
4
|
+
import { t as PageHeader } from "./page-header-CxdZM86z.js";
|
|
5
|
+
import { i as formatList, o as formatNumber, r as formatDateTime, s as formatTokens } from "./formatters-FJaGZgJk.js";
|
|
6
|
+
import { t as DeleteConfirmDialog } from "./delete-confirm-dialog-CWqcTXTF.js";
|
|
7
|
+
import { a as DropdownMenuTrigger, i as DropdownMenuItem, n as DropdownMenu, r as DropdownMenuContent, t as Input } from "./input-B4tEzctc.js";
|
|
8
|
+
import { startTransition, useDeferredValue, useState } from "react";
|
|
9
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
|
|
12
|
+
import { MoreHorizontal, Trash2 } from "lucide-react";
|
|
13
|
+
import { createColumnHelper } from "@tanstack/react-table";
|
|
14
|
+
//#region src/components/projects-table.tsx
|
|
15
|
+
var columnHelper = createColumnHelper();
|
|
16
|
+
var columns = (onDeleteProject) => [
|
|
17
|
+
columnHelper.accessor("name", {
|
|
18
|
+
cell: (info) => /* @__PURE__ */ jsxs("div", {
|
|
19
|
+
className: "space-y-1",
|
|
20
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
21
|
+
className: "font-medium",
|
|
22
|
+
children: info.getValue()
|
|
23
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
24
|
+
className: "text-[var(--muted-foreground)] text-xs",
|
|
25
|
+
children: [
|
|
26
|
+
formatNumber(info.row.original.cwdPaths.length),
|
|
27
|
+
" cwd path",
|
|
28
|
+
info.row.original.cwdPaths.length === 1 ? "" : "s"
|
|
29
|
+
]
|
|
30
|
+
})]
|
|
31
|
+
}),
|
|
32
|
+
header: "Project"
|
|
33
|
+
}),
|
|
34
|
+
columnHelper.accessor("threadCount", {
|
|
35
|
+
cell: (info) => /* @__PURE__ */ jsx("span", {
|
|
36
|
+
className: "font-mono text-sm",
|
|
37
|
+
children: formatNumber(info.getValue())
|
|
38
|
+
}),
|
|
39
|
+
header: "Threads"
|
|
40
|
+
}),
|
|
41
|
+
columnHelper.accessor("totalTokens", {
|
|
42
|
+
cell: (info) => /* @__PURE__ */ jsx("span", {
|
|
43
|
+
className: "font-mono text-sm",
|
|
44
|
+
children: formatTokens(info.getValue())
|
|
45
|
+
}),
|
|
46
|
+
header: "Tokens"
|
|
47
|
+
}),
|
|
48
|
+
columnHelper.accessor("lastUpdatedAtMs", {
|
|
49
|
+
cell: (info) => /* @__PURE__ */ jsx("span", {
|
|
50
|
+
className: "text-sm",
|
|
51
|
+
suppressHydrationWarning: true,
|
|
52
|
+
children: formatDateTime(info.getValue())
|
|
53
|
+
}),
|
|
54
|
+
header: "Last updated"
|
|
55
|
+
}),
|
|
56
|
+
columnHelper.display({
|
|
57
|
+
cell: (info) => /* @__PURE__ */ jsx("span", {
|
|
58
|
+
className: "text-[var(--muted-foreground)] text-sm",
|
|
59
|
+
children: formatList(info.row.original.modelNames)
|
|
60
|
+
}),
|
|
61
|
+
header: "Models",
|
|
62
|
+
id: "models"
|
|
63
|
+
}),
|
|
64
|
+
columnHelper.accessor("archivedThreadCount", {
|
|
65
|
+
cell: (info) => /* @__PURE__ */ jsx("span", {
|
|
66
|
+
className: "text-sm",
|
|
67
|
+
children: formatNumber(info.getValue())
|
|
68
|
+
}),
|
|
69
|
+
header: "Archived"
|
|
70
|
+
}),
|
|
71
|
+
columnHelper.display({
|
|
72
|
+
cell: (info) => /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
73
|
+
asChild: true,
|
|
74
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
75
|
+
className: "rounded-full",
|
|
76
|
+
size: "icon",
|
|
77
|
+
type: "button",
|
|
78
|
+
variant: "ghost",
|
|
79
|
+
onClick: (event) => event.stopPropagation(),
|
|
80
|
+
children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
|
|
81
|
+
})
|
|
82
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
83
|
+
align: "end",
|
|
84
|
+
children: /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
85
|
+
className: "text-[var(--destructive)]",
|
|
86
|
+
onClick: () => onDeleteProject(info.row.original),
|
|
87
|
+
children: [/* @__PURE__ */ jsx(Trash2, { className: "mr-2 size-4" }), "Delete project"]
|
|
88
|
+
})
|
|
89
|
+
})] }),
|
|
90
|
+
header: "",
|
|
91
|
+
id: "actions"
|
|
92
|
+
})
|
|
93
|
+
];
|
|
94
|
+
function ProjectsTable({ projects, onDeleteProject }) {
|
|
95
|
+
const navigate = useNavigate();
|
|
96
|
+
return /* @__PURE__ */ jsx(DataTable, {
|
|
97
|
+
columns: columns(onDeleteProject),
|
|
98
|
+
data: projects,
|
|
99
|
+
emptyMessage: "No projects match the current search.",
|
|
100
|
+
onRowClick: (project) => navigate({
|
|
101
|
+
params: { project: project.name },
|
|
102
|
+
to: "/projects/$project"
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/routes/projects.index.tsx?tsr-split=component
|
|
108
|
+
function ProjectsPage() {
|
|
109
|
+
const queryClient = useQueryClient();
|
|
110
|
+
const projects = useSuspenseQuery(projectsQueryOptions()).data;
|
|
111
|
+
const [searchInput, setSearchInput] = useState("");
|
|
112
|
+
const [pendingDelete, setPendingDelete] = useState(null);
|
|
113
|
+
const deferredSearch = useDeferredValue(searchInput.trim().toLowerCase());
|
|
114
|
+
const deleteProjectMutation = useMutation({
|
|
115
|
+
mutationFn: (input) => deleteProjectFn({ data: input }),
|
|
116
|
+
onSuccess: async () => {
|
|
117
|
+
await Promise.all([
|
|
118
|
+
queryClient.invalidateQueries({ queryKey: ["analytics"] }),
|
|
119
|
+
queryClient.invalidateQueries({ queryKey: ["dashboard"] }),
|
|
120
|
+
queryClient.invalidateQueries({ queryKey: ["projects"] })
|
|
121
|
+
]);
|
|
122
|
+
setPendingDelete(null);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const visibleProjects = projects.filter((project) => {
|
|
126
|
+
if (!deferredSearch) return true;
|
|
127
|
+
return project.name.toLowerCase().includes(deferredSearch);
|
|
128
|
+
});
|
|
129
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
130
|
+
className: "space-y-6",
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ jsx(PageHeader, {
|
|
133
|
+
actions: /* @__PURE__ */ jsx(Input, {
|
|
134
|
+
className: "h-10 w-full rounded-full border-[var(--border)] bg-[var(--panel)] px-4 sm:w-[20rem]",
|
|
135
|
+
placeholder: "Search projects by name",
|
|
136
|
+
value: searchInput,
|
|
137
|
+
onChange: (event) => {
|
|
138
|
+
startTransition(() => {
|
|
139
|
+
setSearchInput(event.target.value);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}),
|
|
143
|
+
eyebrow: "Inventory",
|
|
144
|
+
subtitle: "Derived projects are grouped from the final basename of each thread cwd, matching the existing CLI behavior.",
|
|
145
|
+
title: "Projects"
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ jsx(ProjectsTable, {
|
|
148
|
+
projects: visibleProjects,
|
|
149
|
+
onDeleteProject: setPendingDelete
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ jsx(DeleteConfirmDialog, {
|
|
152
|
+
confirmLabel: deleteProjectMutation.isPending ? "Deleting..." : "Delete project",
|
|
153
|
+
description: pendingDelete ? `Delete ${pendingDelete.threadCount} thread records for the derived project "${pendingDelete.name}" from the Codex database. Enable Delete Session files to remove the rollout JSONL files too.` : "",
|
|
154
|
+
open: pendingDelete !== null,
|
|
155
|
+
showDeleteSessionFilesOption: true,
|
|
156
|
+
title: "Delete project from Codex DB?",
|
|
157
|
+
onConfirm: ({ deleteSessionFiles }) => {
|
|
158
|
+
if (!pendingDelete) return;
|
|
159
|
+
deleteProjectMutation.mutate({
|
|
160
|
+
deleteSessionFiles,
|
|
161
|
+
project: pendingDelete.name
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
onOpenChange: (open) => {
|
|
165
|
+
if (!open) setPendingDelete(null);
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { ProjectsPage as component };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { t as cn } from "./utils-C_uf36nf.js";
|
|
2
|
+
import { t as Button } from "./button-CmTDnzOn.js";
|
|
3
|
+
import { t as SettingsProvider } from "./settings-store-DpEJEQ7M.js";
|
|
4
|
+
import { i as projectsQueryOptions, t as analyticsQueryOptions } from "./codex-queries-CAF6HYiG.js";
|
|
5
|
+
import { t as Route$5 } from "./routes-BhbxvJE7.js";
|
|
6
|
+
import { t as Route$6 } from "./threads._threadId-Ba7vv6-K.js";
|
|
7
|
+
import { t as Route$7 } from "./projects._project-CJ7l0ynC.js";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import { HeadContent, Link, Outlet, Scripts, createFileRoute, createRootRouteWithContext, createRouter, lazyRouteComponent, notFound, redirect, useRouterState } from "@tanstack/react-router";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
|
|
12
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
13
|
+
import { BarChart3, FolderOpen, LayoutDashboard, Moon, Settings2, Sun } from "lucide-react";
|
|
14
|
+
import { Tooltip } from "radix-ui";
|
|
15
|
+
//#region src/integrations/tanstack-query/root-provider.tsx
|
|
16
|
+
function getContext() {
|
|
17
|
+
return { queryClient: new QueryClient() };
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/components/theme-toggle.tsx
|
|
21
|
+
var getPreferredTheme = () => {
|
|
22
|
+
if (typeof window === "undefined") return "light";
|
|
23
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
24
|
+
};
|
|
25
|
+
var applyTheme = (mode) => {
|
|
26
|
+
document.documentElement.classList.toggle("dark", mode === "dark");
|
|
27
|
+
document.documentElement.classList.toggle("light", mode === "light");
|
|
28
|
+
document.documentElement.style.colorScheme = mode;
|
|
29
|
+
window.localStorage.setItem("spiracha-theme", mode);
|
|
30
|
+
};
|
|
31
|
+
function ThemeToggle() {
|
|
32
|
+
const [theme, setTheme] = useState("light");
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const storedTheme = window.localStorage.getItem("spiracha-theme");
|
|
35
|
+
const nextTheme = storedTheme === "dark" || storedTheme === "light" ? storedTheme : getPreferredTheme();
|
|
36
|
+
setTheme(nextTheme);
|
|
37
|
+
applyTheme(nextTheme);
|
|
38
|
+
}, []);
|
|
39
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
40
|
+
"aria-label": "Toggle color theme",
|
|
41
|
+
className: "h-9 rounded-full border border-[var(--border)] bg-[var(--panel)] px-3 text-[var(--panel-foreground)] shadow-none",
|
|
42
|
+
size: "sm",
|
|
43
|
+
type: "button",
|
|
44
|
+
variant: "ghost",
|
|
45
|
+
onClick: () => {
|
|
46
|
+
const nextTheme = theme === "light" ? "dark" : "light";
|
|
47
|
+
setTheme(nextTheme);
|
|
48
|
+
applyTheme(nextTheme);
|
|
49
|
+
},
|
|
50
|
+
children: theme === "light" ? /* @__PURE__ */ jsx(Moon, { className: "size-4" }) : /* @__PURE__ */ jsx(Sun, { className: "size-4" })
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/components/app-shell.tsx
|
|
55
|
+
var navItems = [
|
|
56
|
+
{
|
|
57
|
+
icon: LayoutDashboard,
|
|
58
|
+
label: "Dashboard",
|
|
59
|
+
to: "/"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
icon: FolderOpen,
|
|
63
|
+
label: "Projects",
|
|
64
|
+
to: "/projects"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
icon: BarChart3,
|
|
68
|
+
label: "Analytics",
|
|
69
|
+
to: "/analytics"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
icon: Settings2,
|
|
73
|
+
label: "Settings",
|
|
74
|
+
to: "/settings"
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
function AppShell({ children }) {
|
|
78
|
+
const pathname = useRouterState({ select: (state) => state.location.pathname });
|
|
79
|
+
return /* @__PURE__ */ jsx("div", {
|
|
80
|
+
className: "min-h-screen bg-[var(--background)] text-[var(--foreground)]",
|
|
81
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
82
|
+
className: "flex min-h-screen w-full flex-col lg:flex-row",
|
|
83
|
+
children: [/* @__PURE__ */ jsxs("aside", {
|
|
84
|
+
className: "border-[var(--border)] border-b bg-[var(--panel)]/90 px-5 py-5 backdrop-blur lg:sticky lg:top-0 lg:h-screen lg:w-[240px] lg:border-r lg:border-b-0 lg:px-5",
|
|
85
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
86
|
+
className: "flex items-start justify-between gap-4 lg:flex-col lg:items-stretch",
|
|
87
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
88
|
+
className: "space-y-1.5",
|
|
89
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
90
|
+
className: "font-semibold text-[10px] text-[var(--muted-foreground)] uppercase tracking-[0.18em]",
|
|
91
|
+
children: "Spiracha"
|
|
92
|
+
}), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("h1", {
|
|
93
|
+
className: "font-['IBM_Plex_Sans'] font-semibold text-lg tracking-[-0.02em]",
|
|
94
|
+
children: "Codex Console"
|
|
95
|
+
}) })]
|
|
96
|
+
}), /* @__PURE__ */ jsx(ThemeToggle, {})]
|
|
97
|
+
}), /* @__PURE__ */ jsx("nav", {
|
|
98
|
+
className: "mt-5 grid gap-1",
|
|
99
|
+
children: navItems.map((item) => {
|
|
100
|
+
const active = pathname === item.to || pathname.startsWith(`${item.to}/`);
|
|
101
|
+
const Icon = item.icon;
|
|
102
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
103
|
+
className: cn("flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors", active ? "bg-[var(--accent-muted)] font-medium text-[var(--accent-foreground)]" : "text-[var(--muted-foreground)] hover:bg-[var(--panel-secondary)] hover:text-[var(--foreground)]"),
|
|
104
|
+
to: item.to,
|
|
105
|
+
children: [/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0" }), /* @__PURE__ */ jsx("span", { children: item.label })]
|
|
106
|
+
}, item.to);
|
|
107
|
+
})
|
|
108
|
+
})]
|
|
109
|
+
}), /* @__PURE__ */ jsx("main", {
|
|
110
|
+
className: "min-w-0 flex-1 px-4 py-4 sm:px-5 sm:py-5 lg:px-6",
|
|
111
|
+
children
|
|
112
|
+
})]
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/components/ui/tooltip.tsx
|
|
118
|
+
function TooltipProvider({ delayDuration = 0, ...props }) {
|
|
119
|
+
return /* @__PURE__ */ jsx(Tooltip.Provider, {
|
|
120
|
+
"data-slot": "tooltip-provider",
|
|
121
|
+
delayDuration,
|
|
122
|
+
...props
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/styles.css?url
|
|
127
|
+
var styles_default = "/assets/styles-CMrP9Jb4.css";
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/routes/__root.tsx
|
|
130
|
+
var themeInitScript = `
|
|
131
|
+
(() => {
|
|
132
|
+
try {
|
|
133
|
+
const stored = window.localStorage.getItem('spiracha-theme')
|
|
134
|
+
const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
135
|
+
const mode = stored === 'dark' || stored === 'light' ? stored : preferred
|
|
136
|
+
document.documentElement.classList.add(mode)
|
|
137
|
+
document.documentElement.style.colorScheme = mode
|
|
138
|
+
} catch {}
|
|
139
|
+
})()
|
|
140
|
+
`;
|
|
141
|
+
function RootErrorComponent({ error }) {
|
|
142
|
+
const isSqliteError = error.message.includes("unable to open database") || error.message.includes("database is locked") || error.message.includes("SQLITE_");
|
|
143
|
+
return /* @__PURE__ */ jsx("div", {
|
|
144
|
+
className: "flex min-h-screen items-center justify-center bg-[#101418] px-4 text-[#eef3f7]",
|
|
145
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
146
|
+
className: "max-w-[30rem] text-center",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ jsx("h1", {
|
|
149
|
+
className: "mb-3 font-semibold text-base",
|
|
150
|
+
children: isSqliteError ? "Database unavailable" : "Something went wrong"
|
|
151
|
+
}),
|
|
152
|
+
isSqliteError ? /* @__PURE__ */ jsx("p", {
|
|
153
|
+
className: "my-2 text-[#99a3af] text-[0.875rem] leading-6",
|
|
154
|
+
children: "Spiracha could not open the Codex SQLite database. Codex may have an exclusive lock on the file, or the database does not exist yet. Close Codex or wait a moment, then reload."
|
|
155
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
156
|
+
className: "my-2 text-[#99a3af] text-[0.875rem] leading-6",
|
|
157
|
+
children: /* @__PURE__ */ jsx("code", {
|
|
158
|
+
className: "rounded border border-white/10 bg-[#12181e] px-1.5 py-1 text-[0.8em]",
|
|
159
|
+
children: error.message
|
|
160
|
+
})
|
|
161
|
+
}),
|
|
162
|
+
/* @__PURE__ */ jsx("button", {
|
|
163
|
+
className: "mt-6 rounded-full border border-white/15 bg-[#1a222b] px-5 py-2 text-sm hover:bg-[#222e3a]",
|
|
164
|
+
type: "button",
|
|
165
|
+
onClick: () => window.location.reload(),
|
|
166
|
+
children: "Reload"
|
|
167
|
+
})
|
|
168
|
+
]
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
var Route$4 = createRootRouteWithContext()({
|
|
173
|
+
component: RootComponent,
|
|
174
|
+
errorComponent: RootErrorComponent,
|
|
175
|
+
head: () => ({
|
|
176
|
+
links: [{
|
|
177
|
+
href: styles_default,
|
|
178
|
+
rel: "stylesheet"
|
|
179
|
+
}],
|
|
180
|
+
meta: [
|
|
181
|
+
{ charSet: "utf-8" },
|
|
182
|
+
{
|
|
183
|
+
content: "width=device-width, initial-scale=1",
|
|
184
|
+
name: "viewport"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
content: "Browse local Codex threads, projects, tool calls, and analytics through a compact workspace UI.",
|
|
188
|
+
name: "description"
|
|
189
|
+
},
|
|
190
|
+
{ title: "Spiracha UI" }
|
|
191
|
+
]
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
function RootComponent() {
|
|
195
|
+
return /* @__PURE__ */ jsx(RootDocument, { children: /* @__PURE__ */ jsx(SettingsProvider, { children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(AppShell, { children: /* @__PURE__ */ jsx(Outlet, {}) }) }) }) });
|
|
196
|
+
}
|
|
197
|
+
function RootDocument({ children }) {
|
|
198
|
+
return /* @__PURE__ */ jsxs("html", {
|
|
199
|
+
lang: "en",
|
|
200
|
+
suppressHydrationWarning: true,
|
|
201
|
+
children: [/* @__PURE__ */ jsxs("head", { children: [/* @__PURE__ */ jsx("script", { children: themeInitScript }), /* @__PURE__ */ jsx(HeadContent, {})] }), /* @__PURE__ */ jsxs("body", { children: [children, /* @__PURE__ */ jsx(Scripts, {})] })]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/routes/settings.tsx
|
|
206
|
+
var $$splitComponentImporter$3 = () => import("./settings-MvWDgc1u.js");
|
|
207
|
+
var Route$3 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$3, "component") });
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/routes/analytics.tsx
|
|
210
|
+
var $$splitComponentImporter$2 = () => import("./analytics-BMxW_bZL.js");
|
|
211
|
+
var Route$2 = createFileRoute("/analytics")({
|
|
212
|
+
component: lazyRouteComponent($$splitComponentImporter$2, "component"),
|
|
213
|
+
loader: ({ context }) => {
|
|
214
|
+
return Promise.all([context.queryClient.ensureQueryData(projectsQueryOptions()), context.queryClient.ensureQueryData(analyticsQueryOptions(null))]);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/lib/thread-id.ts
|
|
219
|
+
var THREAD_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
220
|
+
var isCodexThreadId = (value) => THREAD_ID_PATTERN.test(value);
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/routes/$threadId.tsx
|
|
223
|
+
var $$splitComponentImporter$1 = () => import("./_threadId-B6SrBR9E.js");
|
|
224
|
+
var Route$1 = createFileRoute("/$threadId")({
|
|
225
|
+
beforeLoad: ({ params }) => {
|
|
226
|
+
if (!isCodexThreadId(params.threadId)) throw notFound();
|
|
227
|
+
throw redirect({
|
|
228
|
+
params: { threadId: params.threadId },
|
|
229
|
+
to: "/threads/$threadId"
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
component: lazyRouteComponent($$splitComponentImporter$1, "component")
|
|
233
|
+
});
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/routes/projects.index.tsx
|
|
236
|
+
var $$splitErrorComponentImporter = () => import("./projects.index-CaplpeMy.js");
|
|
237
|
+
var $$splitComponentImporter = () => import("./projects.index-srtogpuF.js");
|
|
238
|
+
var Route = createFileRoute("/projects/")({
|
|
239
|
+
component: lazyRouteComponent($$splitComponentImporter, "component"),
|
|
240
|
+
errorComponent: lazyRouteComponent($$splitErrorComponentImporter, "errorComponent"),
|
|
241
|
+
loader: ({ context }) => context.queryClient.ensureQueryData(projectsQueryOptions())
|
|
242
|
+
});
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/routeTree.gen.ts
|
|
245
|
+
var SettingsRoute = Route$3.update({
|
|
246
|
+
id: "/settings",
|
|
247
|
+
path: "/settings",
|
|
248
|
+
getParentRoute: () => Route$4
|
|
249
|
+
});
|
|
250
|
+
var AnalyticsRoute = Route$2.update({
|
|
251
|
+
id: "/analytics",
|
|
252
|
+
path: "/analytics",
|
|
253
|
+
getParentRoute: () => Route$4
|
|
254
|
+
});
|
|
255
|
+
var ThreadIdRoute = Route$1.update({
|
|
256
|
+
id: "/$threadId",
|
|
257
|
+
path: "/$threadId",
|
|
258
|
+
getParentRoute: () => Route$4
|
|
259
|
+
});
|
|
260
|
+
var IndexRoute = Route$5.update({
|
|
261
|
+
id: "/",
|
|
262
|
+
path: "/",
|
|
263
|
+
getParentRoute: () => Route$4
|
|
264
|
+
});
|
|
265
|
+
var ProjectsIndexRoute = Route.update({
|
|
266
|
+
id: "/projects/",
|
|
267
|
+
path: "/projects/",
|
|
268
|
+
getParentRoute: () => Route$4
|
|
269
|
+
});
|
|
270
|
+
var ThreadsThreadIdRoute = Route$6.update({
|
|
271
|
+
id: "/threads/$threadId",
|
|
272
|
+
path: "/threads/$threadId",
|
|
273
|
+
getParentRoute: () => Route$4
|
|
274
|
+
});
|
|
275
|
+
var rootRouteChildren = {
|
|
276
|
+
IndexRoute,
|
|
277
|
+
ThreadIdRoute,
|
|
278
|
+
AnalyticsRoute,
|
|
279
|
+
SettingsRoute,
|
|
280
|
+
ProjectsProjectRoute: Route$7.update({
|
|
281
|
+
id: "/projects/$project",
|
|
282
|
+
path: "/projects/$project",
|
|
283
|
+
getParentRoute: () => Route$4
|
|
284
|
+
}),
|
|
285
|
+
ThreadsThreadIdRoute,
|
|
286
|
+
ProjectsIndexRoute
|
|
287
|
+
};
|
|
288
|
+
var routeTree = Route$4._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/router.tsx
|
|
291
|
+
function getRouter() {
|
|
292
|
+
const context = getContext();
|
|
293
|
+
const router = createRouter({
|
|
294
|
+
context,
|
|
295
|
+
defaultPreload: "intent",
|
|
296
|
+
defaultPreloadStaleTime: 0,
|
|
297
|
+
routeTree,
|
|
298
|
+
scrollRestoration: true
|
|
299
|
+
});
|
|
300
|
+
setupRouterSsrQueryIntegration({
|
|
301
|
+
queryClient: context.queryClient,
|
|
302
|
+
router
|
|
303
|
+
});
|
|
304
|
+
return router;
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
export { getRouter };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { n as dashboardQueryOptions } from "./codex-queries-CAF6HYiG.js";
|
|
2
|
+
import { createFileRoute, lazyRouteComponent } from "@tanstack/react-router";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/routes/index.tsx
|
|
5
|
+
var $$splitComponentImporter = () => import("./routes-CPe-ppmC.js");
|
|
6
|
+
var Route = createFileRoute("/")({
|
|
7
|
+
component: lazyRouteComponent($$splitComponentImporter, "component"),
|
|
8
|
+
loader: ({ context }) => context.queryClient.ensureQueryData(dashboardQueryOptions())
|
|
9
|
+
});
|
|
10
|
+
function DashboardErrorComponent({ error }) {
|
|
11
|
+
const isSqlite = error.message.includes("unable to open database") || error.message.includes("database is locked");
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
+
className: "rounded-xl border border-[var(--border)] bg-[var(--panel)] px-6 py-10 text-center",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx("p", {
|
|
16
|
+
className: "font-medium text-[var(--destructive)] text-sm",
|
|
17
|
+
children: isSqlite ? "Database unavailable" : "Failed to load dashboard"
|
|
18
|
+
}),
|
|
19
|
+
/* @__PURE__ */ jsx("p", {
|
|
20
|
+
className: "mt-2 text-[var(--muted-foreground)] text-sm",
|
|
21
|
+
children: isSqlite ? "Codex may have an exclusive lock on the database. Reload to retry." : error.message
|
|
22
|
+
}),
|
|
23
|
+
/* @__PURE__ */ jsx("button", {
|
|
24
|
+
className: "mt-4 text-[var(--accent)] text-sm underline-offset-2 hover:underline",
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: () => window.location.reload(),
|
|
27
|
+
children: "Reload"
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
Route.update({ errorComponent: DashboardErrorComponent });
|
|
33
|
+
//#endregion
|
|
34
|
+
export { Route as t };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { n as dashboardQueryOptions } from "./codex-queries-CAF6HYiG.js";
|
|
2
|
+
import { t as Route } from "./routes-BhbxvJE7.js";
|
|
3
|
+
import { t as MetricCard } from "./metric-card-ByEeLu0r.js";
|
|
4
|
+
import { t as PageHeader } from "./page-header-CxdZM86z.js";
|
|
5
|
+
import { o as formatNumber, r as formatDateTime, s as formatTokens } from "./formatters-FJaGZgJk.js";
|
|
6
|
+
import { Link } from "@tanstack/react-router";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
9
|
+
//#region src/routes/index.tsx?tsr-split=component
|
|
10
|
+
function DashboardErrorComponent({ error }) {
|
|
11
|
+
const isSqlite = error.message.includes("unable to open database") || error.message.includes("database is locked");
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
+
className: "rounded-xl border border-[var(--border)] bg-[var(--panel)] px-6 py-10 text-center",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx("p", {
|
|
16
|
+
className: "font-medium text-[var(--destructive)] text-sm",
|
|
17
|
+
children: isSqlite ? "Database unavailable" : "Failed to load dashboard"
|
|
18
|
+
}),
|
|
19
|
+
/* @__PURE__ */ jsx("p", {
|
|
20
|
+
className: "mt-2 text-[var(--muted-foreground)] text-sm",
|
|
21
|
+
children: isSqlite ? "Codex may have an exclusive lock on the database. Reload to retry." : error.message
|
|
22
|
+
}),
|
|
23
|
+
/* @__PURE__ */ jsx("button", {
|
|
24
|
+
className: "mt-4 text-[var(--accent)] text-sm underline-offset-2 hover:underline",
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: () => window.location.reload(),
|
|
27
|
+
children: "Reload"
|
|
28
|
+
})
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
Route.update({ errorComponent: DashboardErrorComponent });
|
|
33
|
+
function DashboardPage() {
|
|
34
|
+
const dashboard = useSuspenseQuery(dashboardQueryOptions()).data;
|
|
35
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
36
|
+
className: "space-y-6",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(PageHeader, {
|
|
39
|
+
eyebrow: "Overview",
|
|
40
|
+
subtitle: "A compact readout of local Codex activity, project distribution, and recent thread movement.",
|
|
41
|
+
title: "Dashboard"
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsxs("div", {
|
|
44
|
+
className: "grid gap-3 md:grid-cols-2 xl:grid-cols-4",
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx(MetricCard, {
|
|
47
|
+
label: "Threads",
|
|
48
|
+
value: formatNumber(dashboard.totalThreads)
|
|
49
|
+
}),
|
|
50
|
+
/* @__PURE__ */ jsx(MetricCard, {
|
|
51
|
+
label: "Projects",
|
|
52
|
+
value: formatNumber(dashboard.totalProjects)
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ jsx(MetricCard, {
|
|
55
|
+
label: "Tokens",
|
|
56
|
+
value: formatTokens(dashboard.totalTokens)
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ jsx(MetricCard, {
|
|
59
|
+
helper: `${formatNumber(dashboard.archivedThreads)} archived`,
|
|
60
|
+
label: "Active",
|
|
61
|
+
value: formatNumber(dashboard.activeThreads)
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ jsxs("div", {
|
|
66
|
+
className: "grid min-w-0 gap-4 xl:grid-cols-[1.4fr_1fr]",
|
|
67
|
+
children: [/* @__PURE__ */ jsxs("section", {
|
|
68
|
+
className: "min-w-0 overflow-hidden rounded-[1.8rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]",
|
|
69
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
70
|
+
className: "flex items-center justify-between gap-3",
|
|
71
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: "min-w-0",
|
|
73
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
74
|
+
className: "font-semibold text-sm",
|
|
75
|
+
children: "Recent threads"
|
|
76
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
77
|
+
className: "mt-0.5 text-[var(--muted-foreground)] text-xs",
|
|
78
|
+
children: "Most recently updated threads across the local Codex database."
|
|
79
|
+
})]
|
|
80
|
+
}), /* @__PURE__ */ jsx(Link, {
|
|
81
|
+
className: "shrink-0 font-medium text-[var(--accent)] text-sm",
|
|
82
|
+
to: "/projects",
|
|
83
|
+
children: "View projects"
|
|
84
|
+
})]
|
|
85
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
86
|
+
className: "mt-4 space-y-2",
|
|
87
|
+
children: dashboard.recentThreads.map((thread) => /* @__PURE__ */ jsxs(Link, {
|
|
88
|
+
className: "block overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3.5 transition-colors hover:border-[var(--accent)]/30 hover:bg-[var(--accent-muted)]",
|
|
89
|
+
params: { threadId: thread.id },
|
|
90
|
+
to: "/threads/$threadId",
|
|
91
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
92
|
+
className: "flex min-w-0 flex-wrap items-start justify-between gap-2",
|
|
93
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
94
|
+
className: "min-w-0 shrink",
|
|
95
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
96
|
+
className: "truncate font-medium text-sm",
|
|
97
|
+
children: thread.title
|
|
98
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
99
|
+
className: "mt-0.5 line-clamp-1 text-[var(--muted-foreground)] text-xs",
|
|
100
|
+
children: thread.preview
|
|
101
|
+
})]
|
|
102
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
103
|
+
className: "shrink-0 whitespace-nowrap font-mono text-[var(--muted-foreground)] text-xs",
|
|
104
|
+
suppressHydrationWarning: true,
|
|
105
|
+
children: formatDateTime(thread.updated_at_ms ?? thread.updated_at * 1e3)
|
|
106
|
+
})]
|
|
107
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
108
|
+
className: "mt-2 flex flex-wrap gap-3 text-[var(--muted-foreground)] text-xs",
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ jsx("span", {
|
|
111
|
+
className: "font-mono",
|
|
112
|
+
children: thread.model ?? "unknown model"
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ jsx("span", { children: formatTokens(thread.tokens_used) }),
|
|
115
|
+
/* @__PURE__ */ jsx("span", { children: thread.thread_source ?? "n/a" })
|
|
116
|
+
]
|
|
117
|
+
})]
|
|
118
|
+
}, thread.id))
|
|
119
|
+
})]
|
|
120
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
121
|
+
className: "grid min-w-0 gap-4",
|
|
122
|
+
children: [/* @__PURE__ */ jsxs("section", {
|
|
123
|
+
className: "min-w-0 overflow-hidden rounded-[1.8rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]",
|
|
124
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
125
|
+
className: "font-semibold text-sm",
|
|
126
|
+
children: "Top projects by thread count"
|
|
127
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
128
|
+
className: "mt-3 space-y-2",
|
|
129
|
+
children: dashboard.topProjectsByThreadCount.map((project) => /* @__PURE__ */ jsxs(Link, {
|
|
130
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-[var(--border)] bg-[var(--panel-secondary)] px-3.5 py-2.5 text-sm",
|
|
131
|
+
params: { project: project.name },
|
|
132
|
+
to: "/projects/$project",
|
|
133
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
134
|
+
className: "truncate",
|
|
135
|
+
children: project.name
|
|
136
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
137
|
+
className: "shrink-0 font-mono text-[var(--muted-foreground)]",
|
|
138
|
+
children: formatNumber(project.threadCount)
|
|
139
|
+
})]
|
|
140
|
+
}, project.name))
|
|
141
|
+
})]
|
|
142
|
+
}), /* @__PURE__ */ jsxs("section", {
|
|
143
|
+
className: "min-w-0 overflow-hidden rounded-[1.8rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]",
|
|
144
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
145
|
+
className: "font-semibold text-sm",
|
|
146
|
+
children: "Top projects by tokens"
|
|
147
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
148
|
+
className: "mt-3 space-y-2",
|
|
149
|
+
children: dashboard.topProjectsByTokens.map((project) => /* @__PURE__ */ jsxs(Link, {
|
|
150
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-[var(--border)] bg-[var(--panel-secondary)] px-3.5 py-2.5 text-sm",
|
|
151
|
+
params: { project: project.name },
|
|
152
|
+
to: "/projects/$project",
|
|
153
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
154
|
+
className: "truncate",
|
|
155
|
+
children: project.name
|
|
156
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
157
|
+
className: "shrink-0 font-mono text-[var(--muted-foreground)]",
|
|
158
|
+
children: formatTokens(project.totalTokens)
|
|
159
|
+
})]
|
|
160
|
+
}, project.name))
|
|
161
|
+
})]
|
|
162
|
+
})]
|
|
163
|
+
})]
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { DashboardPage as component };
|