From 0d89b3b9f370f9a7f5cc1b07fa21d552c3ecffcf Mon Sep 17 00:00:00 2001
From: James Pine
Date: Fri, 3 Apr 2026 20:44:10 -0700
Subject: [PATCH 01/52] spaceui
---
interface/bun.lock | 281 +++++++-----------
interface/package.json | 25 +-
interface/postcss.config.js | 6 -
interface/src/components/ChannelCard.tsx | 6 +-
interface/src/components/ChannelEditModal.tsx | 14 +-
.../src/components/ChannelSettingCard.tsx | 16 +-
interface/src/components/ConnectionBanner.tsx | 2 +-
interface/src/components/ConnectionScreen.tsx | 3 +-
.../components/ConversationSettingsPanel.tsx | 34 +--
.../src/components/ConversationsSidebar.tsx | 18 +-
interface/src/components/CortexChatPanel.tsx | 18 +-
.../src/components/CreateAgentDialog.tsx | 6 +-
.../src/components/DeleteAgentDialog.tsx | 6 +-
interface/src/components/MemoryGraph.tsx | 93 +++---
interface/src/components/ModelSelect.tsx | 2 +-
.../src/components/PromptInspectModal.tsx | 7 +-
interface/src/components/SetupBanner.tsx | 2 +-
interface/src/components/Sidebar.tsx | 9 +-
interface/src/components/TaskBoard.tsx | 18 +-
interface/src/components/ToolCall.tsx | 2 +-
interface/src/components/TopologyGraph.tsx | 11 +-
interface/src/components/WebChatPanel.tsx | 12 +-
interface/src/hooks/useTheme.ts | 31 +-
interface/src/main.tsx | 2 +-
interface/src/routes/AgentChannels.tsx | 7 +-
interface/src/routes/AgentConfig.tsx | 17 +-
interface/src/routes/AgentCortex.tsx | 7 +-
interface/src/routes/AgentCron.tsx | 39 ++-
interface/src/routes/AgentIngest.tsx | 2 +-
interface/src/routes/AgentMemories.tsx | 64 ++--
interface/src/routes/AgentProjects.tsx | 23 +-
interface/src/routes/AgentSkills.tsx | 10 +-
interface/src/routes/AgentWorkers.tsx | 7 +-
interface/src/routes/ChannelDetail.tsx | 11 +-
interface/src/routes/Orchestrate.tsx | 4 +-
interface/src/routes/Overlay.tsx | 2 +-
interface/src/routes/Overview.tsx | 7 +-
interface/src/routes/Settings.tsx | 53 ++--
interface/src/styles.css | 185 ++++++++++++
interface/src/ui/Badge.tsx | 80 -----
interface/src/ui/Banner.tsx | 63 ----
interface/src/ui/Button.tsx | 92 ------
interface/src/ui/Card.tsx | 110 -------
interface/src/ui/Checkbox.tsx | 110 -------
interface/src/ui/Dialog.tsx | 117 --------
interface/src/ui/Divider.tsx | 5 -
interface/src/ui/Dropdown.tsx | 210 -------------
interface/src/ui/FilterButton.tsx | 30 --
interface/src/ui/Input.tsx | 153 ----------
interface/src/ui/Loader.tsx | 54 ----
interface/src/ui/NumberStepper.tsx | 118 --------
interface/src/ui/Popover.tsx | 83 ------
interface/src/ui/ProgressBar.tsx | 258 ----------------
interface/src/ui/RadioGroup.tsx | 121 --------
interface/src/ui/Select.tsx | 158 ----------
interface/src/ui/SettingSidebarButton.tsx | 2 +-
interface/src/ui/Slider.tsx | 144 ---------
interface/src/ui/Tabs.tsx | 52 ----
interface/src/ui/Toggle.tsx | 3 +-
interface/src/ui/ToggleGroup.tsx | 51 ----
interface/src/ui/Tooltip.tsx | 27 --
interface/src/ui/Typography.tsx | 15 -
interface/src/ui/forms/CheckBoxField.tsx | 48 ---
interface/src/ui/forms/Form.tsx | 20 --
interface/src/ui/forms/FormField.tsx | 183 ------------
interface/src/ui/forms/InputField.tsx | 46 ---
interface/src/ui/forms/SelectField.tsx | 81 -----
interface/src/ui/forms/SwitchField.tsx | 54 ----
interface/src/ui/forms/TextAreaField.tsx | 47 ---
interface/src/ui/forms/index.ts | 9 -
interface/src/ui/index.ts | 26 --
interface/src/ui/style/colors.scss | 198 ------------
interface/src/ui/style/style.scss | 134 ---------
interface/src/ui/utils.tsx | 73 -----
interface/tailwind.config.ts | 109 -------
interface/vite.config.ts | 107 ++++++-
packages/api-client/src/client.ts | 51 ++++
packages/api-client/src/events.ts | 11 +-
78 files changed, 722 insertions(+), 3593 deletions(-)
delete mode 100644 interface/postcss.config.js
create mode 100644 interface/src/styles.css
delete mode 100644 interface/src/ui/Badge.tsx
delete mode 100644 interface/src/ui/Banner.tsx
delete mode 100644 interface/src/ui/Button.tsx
delete mode 100644 interface/src/ui/Card.tsx
delete mode 100644 interface/src/ui/Checkbox.tsx
delete mode 100644 interface/src/ui/Dialog.tsx
delete mode 100644 interface/src/ui/Divider.tsx
delete mode 100644 interface/src/ui/Dropdown.tsx
delete mode 100644 interface/src/ui/FilterButton.tsx
delete mode 100644 interface/src/ui/Input.tsx
delete mode 100644 interface/src/ui/Loader.tsx
delete mode 100644 interface/src/ui/NumberStepper.tsx
delete mode 100644 interface/src/ui/Popover.tsx
delete mode 100644 interface/src/ui/ProgressBar.tsx
delete mode 100644 interface/src/ui/RadioGroup.tsx
delete mode 100644 interface/src/ui/Select.tsx
delete mode 100644 interface/src/ui/Slider.tsx
delete mode 100644 interface/src/ui/Tabs.tsx
delete mode 100644 interface/src/ui/ToggleGroup.tsx
delete mode 100644 interface/src/ui/Tooltip.tsx
delete mode 100644 interface/src/ui/Typography.tsx
delete mode 100644 interface/src/ui/forms/CheckBoxField.tsx
delete mode 100644 interface/src/ui/forms/Form.tsx
delete mode 100644 interface/src/ui/forms/FormField.tsx
delete mode 100644 interface/src/ui/forms/InputField.tsx
delete mode 100644 interface/src/ui/forms/SelectField.tsx
delete mode 100644 interface/src/ui/forms/SwitchField.tsx
delete mode 100644 interface/src/ui/forms/TextAreaField.tsx
delete mode 100644 interface/src/ui/forms/index.ts
delete mode 100644 interface/src/ui/index.ts
delete mode 100644 interface/src/ui/style/colors.scss
delete mode 100644 interface/src/ui/style/style.scss
delete mode 100644 interface/src/ui/utils.tsx
delete mode 100644 interface/tailwind.config.ts
diff --git a/interface/bun.lock b/interface/bun.lock
index 4fc28204e..3bfecf5a3 100644
--- a/interface/bun.lock
+++ b/interface/bun.lock
@@ -19,20 +19,14 @@
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^3.0.0",
"@hookform/resolvers": "^5.2.2",
- "@hugeicons/core-free-icons": "^3.1.1",
- "@hugeicons/react": "^1.1.5",
"@lobehub/icons": "^4.6.0",
- "@radix-ui/react-checkbox": "^1.3.3",
- "@radix-ui/react-dialog": "^1.1.15",
- "@radix-ui/react-dropdown-menu": "^2.1.16",
- "@radix-ui/react-popover": "^1.1.15",
- "@radix-ui/react-radio-group": "^1.3.8",
- "@radix-ui/react-select": "^2.2.6",
- "@radix-ui/react-slider": "^1.3.6",
- "@radix-ui/react-switch": "^1.2.6",
- "@radix-ui/react-tabs": "^1.1.13",
- "@radix-ui/react-tooltip": "^1.2.8",
+ "@phosphor-icons/react": "^2.1.10",
"@react-sigma/core": "^5.0.6",
+ "@spaceui/ai": "link:@spaceui/ai",
+ "@spaceui/explorer": "link:@spaceui/explorer",
+ "@spaceui/forms": "link:@spaceui/forms",
+ "@spaceui/primitives": "link:@spaceui/primitives",
+ "@spaceui/tokens": "link:@spaceui/tokens",
"@tanstack/react-query": "^5.62.0",
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-router": "^1.159.5",
@@ -61,15 +55,12 @@
"zod": "^4.3.6",
},
"devDependencies": {
+ "@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
- "autoprefixer": "^10.4.18",
"openapi-typescript": "^7",
- "postcss": "^8.4.36",
- "sass": "^1.72.0",
- "tailwindcss": "^3.4.1",
- "tailwindcss-animate": "^1.0.7",
+ "tailwindcss": "^4.2.2",
"typescript": "^5.6.2",
"vite": "^6.0.0",
},
@@ -80,8 +71,6 @@
},
},
"packages": {
- "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, ""],
-
"@ant-design/colors": ["@ant-design/colors@8.0.1", "", { "dependencies": { "@ant-design/fast-color": "^3.0.0" } }, "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ=="],
"@ant-design/cssinjs": ["@ant-design/cssinjs@2.1.0", "", { "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", "@emotion/unitless": "^0.7.5", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "csstype": "^3.1.3", "stylis": "^4.3.4" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, "sha512-eZFrPCnrYrF3XtL7qA4L75P0qA3TtZta8H3Yggy7UYFh8gZgu5bSMNF+v4UVCzGxzYmx8ZvPdgOce0BJ6PsW9g=="],
@@ -240,10 +229,6 @@
"@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],
- "@hugeicons/core-free-icons": ["@hugeicons/core-free-icons@3.1.1", "", {}, "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg=="],
-
- "@hugeicons/react": ["@hugeicons/react@1.1.5", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-JX/iDz3oO7hWdVqbjwFwRrAjHk8h2vI+mBkNzp4JcXG3t4idoupfjon73nLOA7cr27m0M8hrRC1Q2h6nEBGKVA=="],
-
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
@@ -284,52 +269,28 @@
"@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="],
- "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, ""],
-
- "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, ""],
-
- "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, ""],
-
"@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-darwin-arm64": "2.5.6" } }, ""],
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.6", "", { "os": "darwin", "cpu": "arm64" }, ""],
+ "@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="],
+
"@pierre/diffs": ["@pierre/diffs@1.0.11", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-j6zIEoyImQy1HfcJqbrDwP0O5I7V2VNXAaw53FqQ+SykRfaNwABeZHs9uibXO4supaXPmTx6LEH9Lffr03e1Tw=="],
"@primer/octicons": ["@primer/octicons@19.22.0", "", { "dependencies": { "object-assign": "^4.1.1" } }, "sha512-nWoh9PlE6u7xbiZF3KcUm3ktLpN2rQPt11trwp/t4EsKuYRNVWVbBp1LkCBsvZq7ScckNKUURLigIU0wS1FQdw=="],
- "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
-
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
- "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="],
-
- "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
-
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
- "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
-
- "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
-
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
- "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="],
-
- "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
-
- "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
-
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
- "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="],
-
- "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="],
-
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
@@ -338,19 +299,7 @@
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
- "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="],
-
- "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
-
- "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="],
-
- "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="],
-
- "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
-
- "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="],
-
- "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
+ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="],
@@ -364,8 +313,6 @@
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
- "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
-
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
@@ -490,6 +437,16 @@
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
+ "@spaceui/ai": ["@spaceui/ai@link:@spaceui/ai", {}],
+
+ "@spaceui/explorer": ["@spaceui/explorer@link:@spaceui/explorer", {}],
+
+ "@spaceui/forms": ["@spaceui/forms@link:@spaceui/forms", {}],
+
+ "@spaceui/primitives": ["@spaceui/primitives@link:@spaceui/primitives", {}],
+
+ "@spaceui/tokens": ["@spaceui/tokens@link:@spaceui/tokens", {}],
+
"@splinetool/runtime": ["@splinetool/runtime@0.9.526", "", { "dependencies": { "on-change": "^4.0.0", "semver-compare": "^1.0.0" } }, "sha512-qznHbXA5aKwDbCgESAothCNm1IeEZcmNWG145p5aXj4w5uoqR1TZ9qkTHTKLTsUbHeitCwdhzmRqan1kxboLgQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, ""],
@@ -498,6 +455,36 @@
"@stitches/react": ["@stitches/react@1.2.8", "", { "peerDependencies": { "react": ">= 16.3.0" } }, "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA=="],
+ "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
+
+ "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
+
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
+
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
+
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
+
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
+
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
+
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
+
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
+
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
+
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
+
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
+
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
+
+ "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
+
"@tanstack/history": ["@tanstack/history@1.154.14", "", {}, ""],
"@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, ""],
@@ -658,24 +645,14 @@
"antd-style": ["antd-style@4.1.0", "", { "dependencies": { "@ant-design/cssinjs": "^2.0.0", "@babel/runtime": "^7.24.1", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.3", "@emotion/utils": "^1.2.1", "use-merge-value": "^1.2.0" }, "peerDependencies": { "antd": ">=6.0.0", "react": ">=18" } }, "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ=="],
- "any-promise": ["any-promise@1.3.0", "", {}, ""],
-
- "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, ""],
-
- "arg": ["arg@5.0.2", "", {}, ""],
-
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
- "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
-
"assign-symbols": ["assign-symbols@1.0.0", "", {}, "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw=="],
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
- "autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": "bin/autoprefixer" }, ""],
-
"babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
"bail": ["bail@2.0.2", "", {}, ""],
@@ -684,18 +661,12 @@
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": "dist/cli.js" }, ""],
- "binary-extensions": ["binary-extensions@2.3.0", "", {}, ""],
-
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
- "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, ""],
-
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, ""],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
- "camelcase-css": ["camelcase-css@2.0.1", "", {}, ""],
-
"caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, ""],
"ccount": ["ccount@2.0.1", "", {}, ""],
@@ -736,7 +707,7 @@
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, ""],
- "commander": ["commander@4.1.1", "", {}, ""],
+ "commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
@@ -752,8 +723,6 @@
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
- "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, ""],
-
"csstype": ["csstype@3.2.3", "", {}, ""],
"cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="],
@@ -844,16 +813,10 @@
"detect-libc": ["detect-libc@2.1.2", "", {}, ""],
- "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
-
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, ""],
- "didyoumean": ["didyoumean@1.2.2", "", {}, ""],
-
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
- "dlv": ["dlv@1.1.3", "", {}, ""],
-
"dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, ""],
@@ -862,6 +825,8 @@
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
+ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
+
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
@@ -902,24 +867,16 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
- "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, ""],
-
- "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, ""],
-
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""],
"file-selector": ["file-selector@0.5.0", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="],
- "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""],
-
"filter-obj": ["filter-obj@5.1.0", "", {}, "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng=="],
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
"for-in": ["for-in@1.0.2", "", {}, "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="],
- "fraction.js": ["fraction.js@5.3.4", "", {}, ""],
-
"framer-motion": ["framer-motion@12.34.0", "", { "dependencies": { "motion-dom": "^12.34.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, ""],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, ""],
@@ -930,16 +887,14 @@
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
- "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
-
"get-value": ["get-value@2.0.6", "", {}, "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="],
"giscus": ["giscus@1.6.0", "", { "dependencies": { "lit": "^3.2.1" } }, "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ=="],
- "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, ""],
-
"goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, ""],
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
"graphology": ["graphology@0.26.0", "", { "dependencies": { "events": "^3.3.0" }, "peerDependencies": { "graphology-types": ">=0.24.0" } }, "sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg=="],
"graphology-layout-forceatlas2": ["graphology-layout-forceatlas2@0.10.1", "", { "dependencies": { "graphology-utils": "^2.1.0" }, "peerDependencies": { "graphology-types": ">=0.19.0" } }, "sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ=="],
@@ -1010,8 +965,6 @@
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
- "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, ""],
-
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, ""],
"is-decimal": ["is-decimal@2.0.1", "", {}, ""],
@@ -1026,8 +979,6 @@
"is-mobile": ["is-mobile@5.0.0", "", {}, "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ=="],
- "is-number": ["is-number@7.0.0", "", {}, ""],
-
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, ""],
"is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
@@ -1066,7 +1017,29 @@
"leva": ["leva@0.10.1", "", { "dependencies": { "@radix-ui/react-portal": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.8", "@stitches/react": "^1.2.8", "@use-gesture/react": "^10.2.5", "colord": "^2.9.2", "dequal": "^2.0.2", "merge-value": "^1.0.0", "react-colorful": "^5.5.1", "react-dropzone": "^12.0.0", "v8n": "^1.3.3", "zustand": "^3.6.9" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA=="],
- "lilconfig": ["lilconfig@3.1.3", "", {}, ""],
+ "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
+
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
+
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
+
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
+
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
+
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
+
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
+
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
+
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
+
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
+
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, ""],
@@ -1090,6 +1063,8 @@
"lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="],
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, ""],
@@ -1134,8 +1109,6 @@
"merge-value": ["merge-value@1.0.0", "", { "dependencies": { "get-value": "^2.0.6", "is-extendable": "^1.0.0", "mixin-deep": "^1.2.0", "set-value": "^2.0.0" } }, "sha512-fJMmvat4NeKz63Uv9iHWcPDjCWcCkoiRoajRTEO8hlhUC6rwaHg0QCF9hBOTjZmm4JuglPckPSTtcuJL5kp0TQ=="],
- "merge2": ["merge2@1.4.1", "", {}, ""],
-
"mermaid": ["mermaid@11.12.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, ""],
@@ -1214,8 +1187,6 @@
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, ""],
- "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""],
-
"minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
"mixin-deep": ["mixin-deep@1.3.2", "", { "dependencies": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" } }, "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA=="],
@@ -1230,22 +1201,16 @@
"ms": ["ms@2.1.3", "", {}, ""],
- "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, ""],
-
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, ""],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, ""],
"node-releases": ["node-releases@2.0.27", "", {}, ""],
- "normalize-path": ["normalize-path@3.0.0", "", {}, ""],
-
"numeral": ["numeral@2.0.6", "", {}, "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA=="],
"object-assign": ["object-assign@4.1.1", "", {}, ""],
- "object-hash": ["object-hash@3.0.0", "", {}, ""],
-
"ogl": ["ogl@1.0.11", "", {}, "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA=="],
"on-change": ["on-change@4.0.2", "", {}, "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA=="],
@@ -1282,10 +1247,6 @@
"picomatch": ["picomatch@4.0.3", "", {}, ""],
- "pify": ["pify@2.3.0", "", {}, ""],
-
- "pirates": ["pirates@4.0.7", "", {}, ""],
-
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="],
@@ -1298,26 +1259,12 @@
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, ""],
- "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, ""],
-
- "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, ""],
-
- "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["tsx", "yaml"] }, ""],
-
- "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, ""],
-
- "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, ""],
-
- "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, ""],
-
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"property-information": ["property-information@7.1.0", "", {}, ""],
"query-string": ["query-string@9.3.1", "", { "dependencies": { "decode-uri-component": "^0.4.1", "filter-obj": "^5.1.0", "split-on-first": "^3.0.0" } }, "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw=="],
- "queue-microtask": ["queue-microtask@1.2.3", "", {}, ""],
-
"rc-collapse": ["rc-collapse@4.0.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", "rc-motion": "^2.3.4", "rc-util": "^5.27.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-SwoOByE39/3oIokDs/BnkqI+ltwirZbP8HZdq1/3SkPSBi7xDdvWHTp7cpNI9ullozkR6mwTWQi6/E/9huQVrA=="],
"rc-dialog": ["rc-dialog@9.6.0", "", { "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/portal": "^1.0.0-8", "classnames": "^2.2.6", "rc-motion": "^2.3.0", "rc-util": "^5.21.0" }, "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" } }, "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg=="],
@@ -1372,18 +1319,10 @@
"react-refresh": ["react-refresh@0.17.0", "", {}, ""],
- "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
-
- "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
-
"react-rnd": ["react-rnd@10.5.2", "", { "dependencies": { "re-resizable": "6.11.2", "react-draggable": "4.4.6", "tslib": "2.6.2" }, "peerDependencies": { "react": ">=16.3.0", "react-dom": ">=16.3.0" } }, "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw=="],
- "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
-
"react-zoom-pan-pinch": ["react-zoom-pan-pinch@3.7.0", "", { "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA=="],
- "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, ""],
-
"readdirp": ["readdirp@4.1.2", "", {}, ""],
"recharts": ["recharts@3.7.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, ""],
@@ -1444,16 +1383,12 @@
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
- "reusify": ["reusify@1.1.0", "", {}, ""],
-
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "4.57.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, ""],
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
- "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, ""],
-
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
@@ -1508,8 +1443,6 @@
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
- "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, ""],
-
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, ""],
@@ -1518,13 +1451,9 @@
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
- "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, ""],
-
- "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, ""],
+ "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
- "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, ""],
-
- "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, ""],
+ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
"throttle-debounce": ["throttle-debounce@5.0.2", "", {}, "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="],
@@ -1536,8 +1465,6 @@
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, ""],
- "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, ""],
-
"to-vfile": ["to-vfile@8.0.0", "", { "dependencies": { "vfile": "^6.0.0" } }, "sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, ""],
@@ -1546,8 +1473,6 @@
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
- "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, ""],
-
"ts-md5": ["ts-md5@2.0.1", "", {}, "sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w=="],
"tslib": ["tslib@2.8.1", "", {}, ""],
@@ -1582,16 +1507,10 @@
"url-join": ["url-join@5.0.0", "", {}, "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="],
- "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
-
"use-merge-value": ["use-merge-value@1.2.0", "", { "peerDependencies": { "react": ">= 16.x" } }, "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw=="],
- "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
-
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, ""],
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, ""],
-
"uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
"v8n": ["v8n@1.5.1", "", {}, "sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A=="],
@@ -1658,12 +1577,14 @@
"@lobehub/fluent-emoji/lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
- "@lobehub/ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
-
"@lobehub/ui/immer": ["immer@11.1.4", "", {}, ""],
"@lobehub/ui/lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="],
+ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
+ "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
+
"@rc-component/dialog/@rc-component/portal": ["@rc-component/portal@2.2.0", "", { "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ=="],
"@rc-component/drawer/@rc-component/portal": ["@rc-component/portal@2.2.0", "", { "dependencies": { "@rc-component/util": "^1.2.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ=="],
@@ -1678,7 +1599,19 @@
"@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, ""],
- "anymatch/picomatch": ["picomatch@2.3.1", "", {}, ""],
+ "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, ""],
"chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
@@ -1694,12 +1627,8 @@
"extend-shallow/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
- "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""],
-
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
- "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
-
"leva/zustand": ["zustand@3.7.2", "", { "peerDependencies": { "react": ">=16.8" }, "optionalPeers": ["react"] }, "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="],
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, ""],
@@ -1708,8 +1637,6 @@
"mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
- "micromatch/picomatch": ["picomatch@2.3.1", "", {}, ""],
-
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, ""],
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
@@ -1726,18 +1653,10 @@
"split-string/extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="],
- "tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, ""],
-
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
"d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
-
- "tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, ""],
-
- "tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, ""],
-
- "tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, ""],
}
}
diff --git a/interface/package.json b/interface/package.json
index 80cb3661f..53f275991 100644
--- a/interface/package.json
+++ b/interface/package.json
@@ -23,20 +23,14 @@
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^3.0.0",
"@hookform/resolvers": "^5.2.2",
- "@hugeicons/core-free-icons": "^3.1.1",
- "@hugeicons/react": "^1.1.5",
"@lobehub/icons": "^4.6.0",
- "@radix-ui/react-checkbox": "^1.3.3",
- "@radix-ui/react-dialog": "^1.1.15",
- "@radix-ui/react-dropdown-menu": "^2.1.16",
- "@radix-ui/react-popover": "^1.1.15",
- "@radix-ui/react-radio-group": "^1.3.8",
- "@radix-ui/react-select": "^2.2.6",
- "@radix-ui/react-slider": "^1.3.6",
- "@radix-ui/react-switch": "^1.2.6",
- "@radix-ui/react-tabs": "^1.1.13",
- "@radix-ui/react-tooltip": "^1.2.8",
+ "@phosphor-icons/react": "^2.1.10",
"@react-sigma/core": "^5.0.6",
+ "@spaceui/ai": "link:@spaceui/ai",
+ "@spaceui/explorer": "link:@spaceui/explorer",
+ "@spaceui/forms": "link:@spaceui/forms",
+ "@spaceui/primitives": "link:@spaceui/primitives",
+ "@spaceui/tokens": "link:@spaceui/tokens",
"@tanstack/react-query": "^5.62.0",
"@tanstack/react-query-devtools": "^5.91.3",
"@tanstack/react-router": "^1.159.5",
@@ -65,15 +59,12 @@
"zod": "^4.3.6"
},
"devDependencies": {
+ "@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
- "autoprefixer": "^10.4.18",
"openapi-typescript": "^7",
- "postcss": "^8.4.36",
- "sass": "^1.72.0",
- "tailwindcss": "^3.4.1",
- "tailwindcss-animate": "^1.0.7",
+ "tailwindcss": "^4.2.2",
"typescript": "^5.6.2",
"vite": "^6.0.0"
},
diff --git a/interface/postcss.config.js b/interface/postcss.config.js
deleted file mode 100644
index 7b75c83af..000000000
--- a/interface/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
diff --git a/interface/src/components/ChannelCard.tsx b/interface/src/components/ChannelCard.tsx
index 0105b4591..dcba08b03 100644
--- a/interface/src/components/ChannelCard.tsx
+++ b/interface/src/components/ChannelCard.tsx
@@ -8,7 +8,7 @@ import type { ConversationSettings, ConversationDefaultsResponse } from "@/api/t
import { isOpenCodeWorker, type ActiveBranch, type ActiveWorker, type ChannelLiveState } from "@/hooks/useChannelLiveState";
import { LiveDuration } from "@/components/LiveDuration";
import { ConversationSettingsPanel } from "@/components/ConversationSettingsPanel";
-import { Popover, PopoverTrigger, PopoverContent } from "@/ui/Popover";
+import { PopoverRoot, PopoverTrigger, PopoverContent } from "@spaceui/primitives";
import { formatTimeAgo, formatTimestamp, platformIcon, platformColor } from "@/lib/format";
const VISIBLE_MESSAGES = 6;
@@ -180,7 +180,7 @@ export function ChannelCard({
-
+
+
{/* Status message */}
@@ -1110,7 +1110,7 @@ function BindingForm({
-
+
{platform === "discord" && (
diff --git a/interface/src/components/ConnectionBanner.tsx b/interface/src/components/ConnectionBanner.tsx
index cc3763b8f..1d4f9a520 100644
--- a/interface/src/components/ConnectionBanner.tsx
+++ b/interface/src/components/ConnectionBanner.tsx
@@ -1,4 +1,4 @@
-import { Banner } from "@/ui";
+import { Banner } from "@spaceui/primitives";
import type { ConnectionState } from "@/hooks/useEventSource";
const stateConfig: Record
, { label: string; variant: "info" | "warning" | "error" }> = {
diff --git a/interface/src/components/ConnectionScreen.tsx b/interface/src/components/ConnectionScreen.tsx
index 783316e27..bb3c3f6ee 100644
--- a/interface/src/components/ConnectionScreen.tsx
+++ b/interface/src/components/ConnectionScreen.tsx
@@ -1,6 +1,5 @@
import { useState, useCallback, useEffect, lazy, Suspense } from "react";
-import { Button } from "@/ui/Button";
-import { Input } from "@/ui/Input";
+import { Button, Input } from "@spaceui/primitives";
import { useServer } from "@/hooks/useServer";
import { IS_TAURI } from "@/platform";
diff --git a/interface/src/components/ConversationSettingsPanel.tsx b/interface/src/components/ConversationSettingsPanel.tsx
index be511dd6d..23aed0b6c 100644
--- a/interface/src/components/ConversationSettingsPanel.tsx
+++ b/interface/src/components/ConversationSettingsPanel.tsx
@@ -1,14 +1,14 @@
import { useState } from "react";
-import { Button } from "@/ui/Button";
import {
- Select,
+ Button,
+ SelectRoot,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
-} from "@/ui/Select";
+} from "@spaceui/primitives";
import type { ConversationSettings, ConversationDefaultsResponse } from "@/api/types";
interface ConversationSettingsPanelProps {
@@ -207,7 +207,7 @@ export function ConversationSettingsPanel({
{/* Core settings */}
-
+
-
+
-
+
-
+
@@ -356,7 +356,7 @@ export function ConversationSettingsPanel({
process.slice(1)
}
>
-
+
),
)}
@@ -418,7 +418,7 @@ export function ConversationSettingsPanel({
-
+
-
+
)}
diff --git a/interface/src/components/ConversationsSidebar.tsx b/interface/src/components/ConversationsSidebar.tsx
index 7a0ff651b..2d5fdf959 100644
--- a/interface/src/components/ConversationsSidebar.tsx
+++ b/interface/src/components/ConversationsSidebar.tsx
@@ -1,13 +1,5 @@
import {useState} from "react";
-import {Button} from "@/ui/Button";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogFooter,
-} from "@/ui/Dialog";
-import {Input} from "@/ui/Input";
+import {Button, Input, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogFooter} from "@spaceui/primitives";
import type {PortalConversationSummary} from "@/api/types";
interface ConversationsSidebarProps {
@@ -237,7 +229,7 @@ export function ConversationsSidebar({
{/* Rename Dialog */}
-
+
{/* Delete Dialog */}
-
+
);
}
diff --git a/interface/src/components/CortexChatPanel.tsx b/interface/src/components/CortexChatPanel.tsx
index 5ec03b031..563d40bd4 100644
--- a/interface/src/components/CortexChatPanel.tsx
+++ b/interface/src/components/CortexChatPanel.tsx
@@ -3,10 +3,8 @@ import {useCortexChat, type ToolActivity} from "@/hooks/useCortexChat";
import {Markdown} from "@/components/Markdown";
import {ToolCall, type ToolCallPair} from "@/components/ToolCall";
import {api, type CortexChatToolCall, type CortexChatThread} from "@/api/client";
-import {Button} from "@/ui";
-import {Popover, PopoverContent, PopoverTrigger} from "@/ui/Popover";
-import {PlusSignIcon, Cancel01Icon, Clock01Icon, Delete02Icon} from "@hugeicons/core-free-icons";
-import {HugeiconsIcon} from "@hugeicons/react";
+import {Button, PopoverRoot, PopoverContent, PopoverTrigger} from "@spaceui/primitives";
+import {Plus, X, Clock, Trash} from "@phosphor-icons/react";
interface CortexChatPanelProps {
agentId: string;
@@ -344,7 +342,7 @@ function ThreadList({
className="mt-0.5 shrink-0 rounded p-0.5 text-ink-faint opacity-0 transition-all hover:bg-red-500/10 hover:text-red-400 group-hover:opacity-100"
title="Delete thread"
>
-
+
)}
@@ -422,7 +420,7 @@ export function CortexChatPanel({
)}
-
+
setThreadListOpen(false)}
/>
-
+
{onClose && (
)}
diff --git a/interface/src/components/CreateAgentDialog.tsx b/interface/src/components/CreateAgentDialog.tsx
index 4a4c9aa89..3db904a1d 100644
--- a/interface/src/components/CreateAgentDialog.tsx
+++ b/interface/src/components/CreateAgentDialog.tsx
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { api, type PresetMeta } from "@/api/client";
-import { Dialog, DialogContent } from "@/ui";
+import { DialogRoot, DialogContent } from "@spaceui/primitives";
import { CortexChatPanel } from "@/components/CortexChatPanel";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
@@ -119,7 +119,7 @@ export function CreateAgentDialog({ open, onOpenChange, agentId }: CreateAgentDi
}
return (
-
+
);
}
diff --git a/interface/src/components/DeleteAgentDialog.tsx b/interface/src/components/DeleteAgentDialog.tsx
index cc34ed4d7..ca3bd88d7 100644
--- a/interface/src/components/DeleteAgentDialog.tsx
+++ b/interface/src/components/DeleteAgentDialog.tsx
@@ -2,7 +2,7 @@ import {useState} from "react";
import {useMutation, useQueryClient} from "@tanstack/react-query";
import {useNavigate} from "@tanstack/react-router";
import {api} from "@/api/client";
-import {Button, Input, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter} from "@/ui";
+import {Button, Input, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogFooter} from "@spaceui/primitives";
interface DeleteAgentDialogProps {
open: boolean;
@@ -42,7 +42,7 @@ export function DeleteAgentDialog({open, onOpenChange, agentId}: DeleteAgentDial
}
return (
-
+
);
}
diff --git a/interface/src/components/MemoryGraph.tsx b/interface/src/components/MemoryGraph.tsx
index 14082d5f8..7f4771daf 100644
--- a/interface/src/components/MemoryGraph.tsx
+++ b/interface/src/components/MemoryGraph.tsx
@@ -13,9 +13,8 @@ import {
type RelationType,
} from "@/api/client";
import { formatTimeAgo } from "@/lib/format";
-import { Button } from "@/ui";
-import { Target02Icon, PlusSignIcon, MinusSignIcon, RefreshIcon, Cancel01Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
+import { Button, CircleButton } from "@spaceui/primitives";
+import { Crosshair, Plus, Minus, ArrowsClockwise, X } from "@phosphor-icons/react";
// -- Constants --
@@ -386,58 +385,42 @@ export function MemoryGraph({ agentId, sort, typeFilter }: MemoryGraphProps) {
{/* Controls */}
-
-
-
-
+ }}
+ title="Re-run layout"
+ />
{/* Node detail panel */}
@@ -466,7 +449,7 @@ export function MemoryGraph({ agentId, sort, typeFilter }: MemoryGraphProps) {
size="icon"
className="h-7 w-7"
>
-
+
diff --git a/interface/src/components/ModelSelect.tsx b/interface/src/components/ModelSelect.tsx
index c44b5173c..f824b5315 100644
--- a/interface/src/components/ModelSelect.tsx
+++ b/interface/src/components/ModelSelect.tsx
@@ -1,5 +1,5 @@
import { api, type ModelInfo } from "@/api/client";
-import { Input } from "@/ui";
+import { Input } from "@spaceui/primitives";
import { useQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useRef, useState } from "react";
diff --git a/interface/src/components/PromptInspectModal.tsx b/interface/src/components/PromptInspectModal.tsx
index a1f6ceced..71817c7e5 100644
--- a/interface/src/components/PromptInspectModal.tsx
+++ b/interface/src/components/PromptInspectModal.tsx
@@ -5,7 +5,8 @@ import {
type PromptInspectResponse,
type PromptSnapshotSummary,
} from "@/api/client";
-import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Toggle } from "@/ui";
+import { Button, DialogRoot, DialogContent, DialogHeader, DialogTitle } from "@spaceui/primitives";
+import { Toggle } from "@/ui/Toggle";
interface PromptInspectModalProps {
open: boolean;
@@ -64,7 +65,7 @@ export function PromptInspectModal({ open, onOpenChange, channelId }: PromptInsp
const contentLoading = view === "current" ? isLoading : snapshotLoading;
return (
-
+
);
}
diff --git a/interface/src/components/SetupBanner.tsx b/interface/src/components/SetupBanner.tsx
index 62b79da81..c75ead4c7 100644
--- a/interface/src/components/SetupBanner.tsx
+++ b/interface/src/components/SetupBanner.tsx
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { api } from "@/api/client";
-import { Banner } from "@/ui";
+import { Banner } from "@spaceui/primitives";
export function SetupBanner() {
const { data } = useQuery({
diff --git a/interface/src/components/Sidebar.tsx b/interface/src/components/Sidebar.tsx
index 9c76ae9ca..c791a92af 100644
--- a/interface/src/components/Sidebar.tsx
+++ b/interface/src/components/Sidebar.tsx
@@ -21,8 +21,7 @@ import { CSS } from "@dnd-kit/utilities";
import { api } from "@/api/client";
import type { ChannelLiveState } from "@/hooks/useChannelLiveState";
import { useAgentOrder } from "@/hooks/useAgentOrder";
-import { DashboardSquare01Icon, Settings01Icon, LeftToRightListBulletIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
+import { SquaresFour, Gear, List } from "@phosphor-icons/react";
import { CreateAgentDialog } from "@/components/CreateAgentDialog";
import { ProfileAvatar } from "@/components/ProfileAvatar";
@@ -147,7 +146,7 @@ export function Sidebar({ liveStates: _liveStates }: SidebarProps) {
}`}
title="Dashboard"
>
-
+
-
+
-
+
!v && onClose()}>
+ !v && onClose()}>
Create Task
@@ -682,7 +674,7 @@ function CreateTaskDialog({
-
+
);
}
@@ -710,7 +702,7 @@ function TaskDetailDialog({
onStatusChange: (status: TaskStatus) => void;
}) {
return (
-
+
);
}
diff --git a/interface/src/components/ToolCall.tsx b/interface/src/components/ToolCall.tsx
index 82f6c791e..a39219646 100644
--- a/interface/src/components/ToolCall.tsx
+++ b/interface/src/components/ToolCall.tsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { cx } from "@/ui/utils";
+import { cx } from "class-variance-authority";
import type { TranscriptStep, OpenCodePart } from "@/api/client";
// ---------------------------------------------------------------------------
diff --git a/interface/src/components/TopologyGraph.tsx b/interface/src/components/TopologyGraph.tsx
index c04e0f5e8..2a8e9fc6d 100644
--- a/interface/src/components/TopologyGraph.tsx
+++ b/interface/src/components/TopologyGraph.tsx
@@ -35,7 +35,8 @@ import {
type LinkDirection,
type LinkKind,
} from "@/api/client";
-import { Button, Input, TextArea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, cx } from "@/ui";
+import { Button, Input, TextArea, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@spaceui/primitives";
+import { cx } from "class-variance-authority";
import { Markdown } from "@/components/Markdown";
import { Link } from "@tanstack/react-router";
@@ -587,7 +588,7 @@ function HumanEditDialog({
if (!human) return null;
return (
-
+
);
}
@@ -774,7 +775,7 @@ function AgentEditDialog({
if (!agent) return null;
return (
-
+
);
}
diff --git a/interface/src/components/WebChatPanel.tsx b/interface/src/components/WebChatPanel.tsx
index e8836ce91..57a1dfad8 100644
--- a/interface/src/components/WebChatPanel.tsx
+++ b/interface/src/components/WebChatPanel.tsx
@@ -6,12 +6,10 @@ import {useLiveContext} from "@/hooks/useLiveContext";
import {Markdown} from "@/components/Markdown";
import {ConversationSettingsPanel} from "@/components/ConversationSettingsPanel";
import {ConversationsSidebar} from "@/components/ConversationsSidebar";
-import {Button} from "@/ui/Button";
-import {Popover, PopoverTrigger, PopoverContent} from "@/ui/Popover";
+import {Button, PopoverRoot, PopoverTrigger, PopoverContent} from "@spaceui/primitives";
import {api, type ConversationDefaultsResponse, type ConversationSettings} from "@/api/client";
import {useQuery, useMutation, useQueryClient} from "@tanstack/react-query";
-import {Settings02Icon} from "@hugeicons/core-free-icons";
-import {HugeiconsIcon} from "@hugeicons/react";
+import {GearSix} from "@phosphor-icons/react";
interface WebChatPanelProps {
agentId: string;
@@ -347,10 +345,10 @@ export function WebChatPanel({agentId}: WebChatPanelProps) {
)}
-
+
@@ -371,7 +369,7 @@ export function WebChatPanel({agentId}: WebChatPanelProps) {
)}
-
+
{/* Messages */}
diff --git a/interface/src/hooks/useTheme.ts b/interface/src/hooks/useTheme.ts
index 797b76456..0fd029113 100644
--- a/interface/src/hooks/useTheme.ts
+++ b/interface/src/hooks/useTheme.ts
@@ -1,6 +1,13 @@
import { useState, useEffect, useCallback } from "react";
-export type ThemeId = "default" | "vanilla" | "midnight" | "noir";
+export type ThemeId =
+ | "default"
+ | "vanilla"
+ | "midnight"
+ | "noir"
+ | "slate"
+ | "nord"
+ | "mocha";
export interface ThemeOption {
id: ThemeId;
@@ -13,13 +20,13 @@ export const THEMES: ThemeOption[] = [
{
id: "default",
name: "Default",
- description: "Dark theme with purple accent",
+ description: "Dark theme with blue accent",
className: "",
},
{
id: "vanilla",
name: "Vanilla",
- description: "Light theme with blue accent",
+ description: "Light theme",
className: "vanilla-theme",
},
{
@@ -34,6 +41,24 @@ export const THEMES: ThemeOption[] = [
description: "Pure black and white theme",
className: "noir-theme",
},
+ {
+ id: "slate",
+ name: "Slate",
+ description: "Cool gray dark theme",
+ className: "slate-theme",
+ },
+ {
+ id: "nord",
+ name: "Nord",
+ description: "Arctic blue-gray theme",
+ className: "nord-theme",
+ },
+ {
+ id: "mocha",
+ name: "Mocha",
+ description: "Warm brown dark theme",
+ className: "mocha-theme",
+ },
];
const STORAGE_KEY = "spacebot-theme";
diff --git a/interface/src/main.tsx b/interface/src/main.tsx
index ed9d516c9..75042b080 100644
--- a/interface/src/main.tsx
+++ b/interface/src/main.tsx
@@ -1,7 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
-import "./ui/style/style.scss";
+import "./styles.css";
import "@fontsource/ibm-plex-sans/400.css";
import "@fontsource/ibm-plex-sans/500.css";
import "@fontsource/ibm-plex-sans/600.css";
diff --git a/interface/src/routes/AgentChannels.tsx b/interface/src/routes/AgentChannels.tsx
index 707fde9fa..93c934811 100644
--- a/interface/src/routes/AgentChannels.tsx
+++ b/interface/src/routes/AgentChannels.tsx
@@ -2,10 +2,9 @@ import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { api } from "@/api/client";
import { ChannelCard } from "@/components/ChannelCard";
-import { Button, SearchInput } from "@/ui";
+import { Button, SearchInput } from "@spaceui/primitives";
import type { ChannelLiveState } from "@/hooks/useChannelLiveState";
-import { Settings02Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
+import { GearSix } from "@phosphor-icons/react";
import { useNavigate } from "@tanstack/react-router";
interface AgentChannelsProps {
@@ -71,7 +70,7 @@ export function AgentChannels({ agentId, liveStates }: AgentChannelsProps) {
-
+
No channels yet
diff --git a/interface/src/routes/AgentConfig.tsx b/interface/src/routes/AgentConfig.tsx
index 9bbeb184a..9b38b9fbc 100644
--- a/interface/src/routes/AgentConfig.tsx
+++ b/interface/src/routes/AgentConfig.tsx
@@ -1,7 +1,10 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, type AgentConfigResponse, type AgentConfigUpdateRequest } from "@/api/client";
-import { Button, Input, SettingSidebarButton, TextArea, Toggle, NumberStepper, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, cx } from "@/ui";
+import { Button, Input, TextArea, NumberStepper, SelectRoot, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@spaceui/primitives";
+import { SettingSidebarButton } from "@/ui/SettingSidebarButton";
+import { Toggle } from "@/ui/Toggle";
+import { cx } from "class-variance-authority";
import { ModelSelect } from "@/components/ModelSelect";
import { TagInput } from "@/components/TagInput";
import { Markdown } from "@/components/Markdown";
@@ -894,7 +897,7 @@ function ConfigSectionEditor({ sectionId, label, description, detail, config, on
{supportsAdaptiveThinking(localValues[key] as string) && (
-
+
)}
@@ -1172,7 +1175,7 @@ function ConfigSectionEditor({ sectionId, label, description, detail, config, on
What happens when a worker calls "close" or finishes.
-
+
);
@@ -1194,7 +1197,7 @@ function ConfigSectionEditor({ sectionId, label, description, detail, config, on
Kernel-enforced filesystem containment for shell subprocesses.
-
+
diff --git a/interface/src/routes/AgentCortex.tsx b/interface/src/routes/AgentCortex.tsx
index 5f4a9dd3a..1fc85fd6a 100644
--- a/interface/src/routes/AgentCortex.tsx
+++ b/interface/src/routes/AgentCortex.tsx
@@ -8,9 +8,8 @@ import {
} from "@/api/client";
import { CortexChatPanel } from "@/components/CortexChatPanel";
import { formatTimeAgo } from "@/lib/format";
-import { IdeaIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-import { FilterButton, Button } from "@/ui";
+import { Lightbulb } from "@phosphor-icons/react";
+import { FilterButton, Button } from "@spaceui/primitives";
const PAGE_SIZE = 50;
@@ -177,7 +176,7 @@ export function AgentCortex({ agentId }: AgentCortexProps) {
className={chatOpen ? "bg-app-selected text-ink" : ""}
title="Toggle cortex chat"
>
-
+
diff --git a/interface/src/routes/AgentCron.tsx b/interface/src/routes/AgentCron.tsx
index a6530c37c..f1589edd2 100644
--- a/interface/src/routes/AgentCron.tsx
+++ b/interface/src/routes/AgentCron.tsx
@@ -2,26 +2,25 @@ import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, type CronJobWithStats, type CreateCronRequest, type ChannelInfo } from "@/api/client";
import { formatCronSchedule, formatTimeAgo } from "@/lib/format";
-import { Clock05Icon, PauseIcon, PlayIcon, FlashIcon, PencilEdit02Icon, Delete02Icon, ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
+import { Clock, Pause, Play, Lightning, PencilSimple, Trash, CaretDown, CaretUp } from "@phosphor-icons/react";
import {
Button,
- Dialog,
+ DialogRoot,
DialogContent,
DialogHeader,
DialogTitle,
Badge,
Input,
TextArea,
- Toggle,
Label,
NumberStepper,
- Select,
+ SelectRoot,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
-} from "@/ui";
+} from "@spaceui/primitives";
+import { Toggle } from "@/ui/Toggle";
// -- Helpers --
@@ -280,7 +279,7 @@ export function AgentCron({ agentId }: AgentCronProps) {
-
+
No cron jobs yet
@@ -317,7 +316,7 @@ export function AgentCron({ agentId }: AgentCronProps) {
{/* Create / Edit Modal */}
-
May drift. Prefer cron expressions.
@@ -439,7 +438,7 @@ export function AgentCron({ agentId }: AgentCronProps) {
{/* Right column — delivery & options */}
-
+
{(formData.delivery_target === "" || !deliveryTargets.some((t) => t.value === formData.delivery_target)) && (
-
+
{/* Delete Confirmation */}
-
+
);
}
@@ -694,7 +693,7 @@ function CronJobCard({
variant="ghost"
size="sm"
>
-
+ {job.enabled ? : }
@@ -727,7 +726,7 @@ function CronJobCard({
onClick={onToggleExpand}
className="flex w-full items-center justify-center gap-1.5 border-t border-app-line/50 px-3 py-1.5 text-tiny text-ink-faint transition-colors hover:bg-app-lightBox/30 hover:text-ink-dull"
>
-
+ {isExpanded ? : }
{isExpanded ? "Hide history" : "Show history"}
diff --git a/interface/src/routes/AgentIngest.tsx b/interface/src/routes/AgentIngest.tsx
index 582569093..86a331bf6 100644
--- a/interface/src/routes/AgentIngest.tsx
+++ b/interface/src/routes/AgentIngest.tsx
@@ -2,7 +2,7 @@ import {useState, useRef, useCallback} from "react";
import {useQuery, useMutation, useQueryClient} from "@tanstack/react-query";
import {api, type IngestFileInfo} from "@/api/client";
import {formatTimeAgo} from "@/lib/format";
-import {Badge} from "@/ui";
+import {Badge} from "@spaceui/primitives";
import {clsx} from "clsx";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faTrash} from "@fortawesome/free-solid-svg-icons";
diff --git a/interface/src/routes/AgentMemories.tsx b/interface/src/routes/AgentMemories.tsx
index 579aebb1f..5d87858d1 100644
--- a/interface/src/routes/AgentMemories.tsx
+++ b/interface/src/routes/AgentMemories.tsx
@@ -12,18 +12,17 @@ import {
import { CortexChatPanel } from "@/components/CortexChatPanel";
import { MemoryGraph } from "@/components/MemoryGraph";
import {
- Button,
- DropdownMenu,
+ CircleButton,
+ CircleButtonGroup,
+ DropdownMenuRoot,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
- ToggleGroup,
SearchInput,
FilterButton,
-} from "@/ui";
+} from "@spaceui/primitives";
import { formatTimeAgo } from "@/lib/format";
-import { ArrowDown01Icon, LeftToRightListBulletIcon, WorkflowSquare01Icon, IdeaIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
+import { CaretDown, List, TreeStructure, Lightbulb } from "@phosphor-icons/react";
type ViewMode = "list" | "graph";
@@ -156,10 +155,10 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
/>
{/* Sort dropdown */}
-
+
{SORT_OPTIONS.find((o) => o.value === sort)?.label ?? sort}
-
+
{SORT_OPTIONS.map((option) => (
@@ -172,30 +171,31 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
))}
-
+
{/* View mode toggle */}
- , title: "List view" },
- { value: "graph", label: , title: "Graph view" },
- ]}
- />
+
+ setViewMode("list")}
+ variant={viewMode === "list" ? "active" : "default"}
+ title="List view"
+ />
+ setViewMode("graph")}
+ variant={viewMode === "graph" ? "active" : "default"}
+ title="Graph view"
+ />
+
{/* Cortex chat toggle */}
-
-
-
+ setChatOpen(!chatOpen)}
+ variant={chatOpen ? "accent" : "default"}
+ title="Toggle cortex chat"
+ />
{/* Type filter pills */}
@@ -274,10 +274,10 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
className="absolute left-0 top-0 w-full"
style={{ transform: `translateY(${virtualRow.start}px)` }}
>
-
-
+
);
}
@@ -2433,7 +2438,7 @@ function OpenCodeSection({ settings, isLoading }: GlobalSettingsSectionProps) {
] as const).map(({ label, value, setter }) => (
{label}
-
+
))}
@@ -3147,7 +3152,7 @@ function ChatGptOAuthDialog({
onRestart,
}: ChatGptOAuthDialogProps) {
return (
-
+
);
}
diff --git a/interface/src/styles.css b/interface/src/styles.css
new file mode 100644
index 000000000..b338a6ecc
--- /dev/null
+++ b/interface/src/styles.css
@@ -0,0 +1,185 @@
+@import "tailwindcss";
+
+/* SpaceUI design tokens */
+@import "@spaceui/tokens/src/css/theme.css";
+@import "@spaceui/tokens/src/css/base.css";
+
+/* Themes */
+@import "@spaceui/tokens/src/css/themes/light.css";
+@import "@spaceui/tokens/src/css/themes/dark.css";
+@import "@spaceui/tokens/src/css/themes/midnight.css";
+@import "@spaceui/tokens/src/css/themes/noir.css";
+@import "@spaceui/tokens/src/css/themes/slate.css";
+@import "@spaceui/tokens/src/css/themes/nord.css";
+@import "@spaceui/tokens/src/css/themes/mocha.css";
+
+/* Tell Tailwind to scan SpaceUI component source for classes */
+@source "../../../spaceui/packages/primitives/src";
+@source "../../../spaceui/packages/ai/src";
+@source "../../../spaceui/packages/forms/src";
+@source "../../../spaceui/packages/explorer/src";
+
+/* Override default font to IBM Plex Sans */
+@theme {
+ --font-sans: "IBM Plex Sans", system-ui, sans-serif;
+}
+
+/* Custom utilities */
+@utility no-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+/* Base styles */
+html,
+body {
+ overflow: hidden;
+ height: 100%;
+}
+
+html {
+ font-size: 106.25%;
+ background: var(--color-app);
+}
+
+body {
+ font-family: var(--font-sans);
+ color: var(--color-ink);
+ background: var(--color-app);
+}
+
+/* Markdown prose styling for message content */
+.markdown {
+ line-height: 1.625;
+
+ p {
+ margin-block: 0.25rem;
+ }
+ p:first-child {
+ margin-top: 0;
+ }
+ p:last-child {
+ margin-bottom: 0;
+ }
+ strong {
+ font-weight: 600;
+ color: var(--color-ink);
+ }
+ em {
+ font-style: italic;
+ }
+ a {
+ color: var(--color-accent);
+ text-decoration: underline;
+ text-decoration-color: color-mix(in srgb, var(--color-accent) 30%, transparent);
+ &:hover {
+ text-decoration-color: color-mix(in srgb, var(--color-accent) 60%, transparent);
+ }
+ }
+ code {
+ border-radius: 0.25rem;
+ background: var(--color-app-dark-box);
+ padding: 0.125rem 0.375rem;
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--color-ink);
+ }
+ pre {
+ margin-block: 0.5rem;
+ overflow-x: auto;
+ border-radius: 0.375rem;
+ background: var(--color-app-dark-box);
+ padding: 0.75rem;
+ code {
+ background: transparent;
+ padding: 0;
+ }
+ }
+ ul,
+ ol {
+ margin-block: 0.375rem;
+ padding-left: 1.25rem;
+ }
+ ul {
+ list-style-type: disc;
+ }
+ ol {
+ list-style-type: decimal;
+ }
+ li {
+ margin-block: 0.125rem;
+ }
+ blockquote {
+ margin-block: 0.5rem;
+ border-left: 2px solid var(--color-app-line);
+ padding-left: 0.75rem;
+ font-style: italic;
+ color: var(--color-ink-faint);
+ }
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ margin-top: 0.75rem;
+ margin-bottom: 0.25rem;
+ font-weight: 600;
+ color: var(--color-ink);
+ }
+ h1 {
+ font-size: 1.125rem;
+ }
+ h2 {
+ font-size: 1rem;
+ }
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-size: 0.8rem;
+ }
+ hr {
+ margin-block: 0.75rem;
+ border-color: var(--color-app-line);
+ }
+ table {
+ margin-block: 0.5rem;
+ width: 100%;
+ font-size: 0.8rem;
+ }
+ th {
+ border: 1px solid var(--color-app-line);
+ background: var(--color-app-dark-box);
+ padding: 0.25rem 0.5rem;
+ text-align: left;
+ font-weight: 500;
+ color: var(--color-ink);
+ }
+ td {
+ border: 1px solid var(--color-app-line);
+ padding: 0.25rem 0.5rem;
+ }
+ img {
+ margin-block: 0.5rem;
+ max-width: 100%;
+ border-radius: 0.25rem;
+ }
+}
+
+/* Hide React Query devtools toggle button */
+.tsqd-open-btn-container {
+ opacity: 0 !important;
+}
+
+/* Override React Flow's default group node styles */
+.react-flow__node-group {
+ background-color: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ width: auto !important;
+ box-shadow: none !important;
+}
diff --git a/interface/src/ui/Badge.tsx b/interface/src/ui/Badge.tsx
deleted file mode 100644
index eb5879e17..000000000
--- a/interface/src/ui/Badge.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-export const badgeStyles = cva(
- [
- "inline-flex items-center rounded-full border font-medium transition-colors",
- "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
- ],
- {
- variants: {
- variant: {
- default: [
- "border-transparent bg-app-button text-ink-dull",
- "hover:bg-app-hover hover:text-ink",
- ],
- accent: [
- "border-transparent bg-accent/15 text-accent",
- "hover:bg-accent/25",
- ],
- amber: [
- "border-transparent bg-amber-500/15 text-amber-500",
- "hover:bg-amber-500/25",
- ],
- violet: [
- "border-transparent bg-violet-500/15 text-violet-500",
- "hover:bg-violet-500/25",
- ],
- blue: [
- "border-transparent bg-blue-500/15 text-blue-500",
- "hover:bg-blue-500/25",
- ],
- red: [
- "border-transparent bg-red-500/15 text-red-500",
- "hover:bg-red-500/25",
- ],
- green: [
- "border-transparent bg-emerald-500/15 text-emerald-500",
- "hover:bg-emerald-500/25",
- ],
- outline: [
- "border-app-line text-ink-dull",
- "hover:border-ink-faint hover:text-ink",
- ],
- },
- size: {
- sm: "h-5 px-2 text-tiny gap-1",
- md: "h-6 px-2.5 text-xs gap-1.5",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "sm",
- },
- }
-);
-
-export type BadgeBaseProps = VariantProps;
-
-export interface BadgeProps
- extends React.HTMLAttributes,
- BadgeBaseProps {
- children?: React.ReactNode;
-}
-
-export const Badge = React.forwardRef(
- ({ className, variant, size, children, ...props }, ref) => {
- return (
-
- {children}
-
- );
- }
-);
-
-Badge.displayName = "Badge";
diff --git a/interface/src/ui/Banner.tsx b/interface/src/ui/Banner.tsx
deleted file mode 100644
index 2a21bee3a..000000000
--- a/interface/src/ui/Banner.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as React from "react";
-import { cx } from "./utils";
-
-export type BannerVariant = "info" | "warning" | "error" | "success" | "cyan";
-
-interface BannerProps extends React.HTMLAttributes {
- variant?: BannerVariant;
- dot?: "pulse" | "static" | "none";
- children: React.ReactNode;
-}
-
-const variantStyles: Record = {
- info: "bg-blue-500/10 text-blue-400 border-blue-500/20",
- warning: "bg-amber-500/10 text-amber-400 border-amber-500/20",
- error: "bg-red-500/10 text-red-400 border-red-500/20",
- success: "bg-green-500/10 text-green-400 border-green-500/20",
- cyan: "bg-cyan-500/10 text-cyan-400 border-cyan-500/20",
-};
-
-export function Banner({
- variant = "info",
- dot = "static",
- className,
- children,
- ...props
-}: BannerProps) {
- return (
-
-
- {dot !== "none" && (
-
- )}
- {children}
-
-
- );
-}
-
-export function BannerActions({
- children,
- className,
-}: {
- children: React.ReactNode;
- className?: string;
-}) {
- return (
-
- {children}
-
- );
-}
diff --git a/interface/src/ui/Button.tsx b/interface/src/ui/Button.tsx
deleted file mode 100644
index 411f6236b..000000000
--- a/interface/src/ui/Button.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from "react";
-import {cva, type VariantProps} from "class-variance-authority";
-import {cx} from "./utils";
-import {Loader} from "./Loader";
-
-export const buttonStyles = cva(
- [
- "inline-flex items-center justify-center rounded-lg font-medium transition-colors",
- "disabled:pointer-events-none disabled:opacity-50",
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
- ],
- {
- variants: {
- size: {
- default: "h-9 px-4 py-2 text-sm",
- sm: "h-8 rounded-md px-3 text-xs",
- lg: "h-10 rounded-md px-8 text-sm",
- icon: "h-8 w-8 rounded-md p-0",
- },
- variant: {
- default: ["bg-accent text-white shadow", "hover:bg-accent/90"],
- destructive: [
- "border border-app-line bg-transparent text-ink-dull",
- "hover:bg-red-600 hover:text-white hover:border-red-600",
- ],
- outline: [
- "border border-app-line bg-transparent",
- "hover:bg-app-hover/40 hover:text-ink",
- ],
- secondary: [
- "bg-app-darkBox text-ink-dull",
- "hover:bg-app-lightBox hover:text-ink",
- ],
- ghost: ["hover:bg-app-darkBox hover:text-ink-dull", "text-ink-faint"],
- link: ["text-accent underline-offset-4", "hover:underline"],
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-export type ButtonBaseProps = VariantProps;
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes, ButtonBaseProps {
- loading?: boolean;
- leftIcon?: React.ReactNode;
- rightIcon?: React.ReactNode;
- children?: React.ReactNode;
-}
-
-export const Button = React.forwardRef(
- (
- {
- className,
- variant,
- size,
- loading,
- leftIcon,
- rightIcon,
- children,
- disabled,
- ...props
- },
- ref,
- ) => {
- const isDisabled = disabled || loading;
-
- return (
-
- {loading && }
- {!loading && leftIcon && (
- {leftIcon}
- )}
- {children}
- {!loading && rightIcon && (
- {rightIcon}
- )}
-
- );
- },
-);
-
-Button.displayName = "Button";
diff --git a/interface/src/ui/Card.tsx b/interface/src/ui/Card.tsx
deleted file mode 100644
index 320d4c351..000000000
--- a/interface/src/ui/Card.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-export const cardStyles = cva(
- [
- "rounded-xl border transition-colors",
- ],
- {
- variants: {
- variant: {
- default: [
- "border-app-line bg-app-darkBox",
- ],
- darker: [
- "border-app-line bg-app-darkerBox",
- ],
- ghost: [
- "border-transparent bg-transparent",
- ],
- },
- padding: {
- none: "",
- sm: "p-3",
- md: "p-5",
- lg: "p-6",
- },
- },
- defaultVariants: {
- variant: "default",
- padding: "md",
- },
- }
-);
-
-export type CardBaseProps = VariantProps;
-
-export interface CardProps
- extends React.HTMLAttributes,
- CardBaseProps {}
-
-export const Card = React.forwardRef(
- ({ className, variant, padding, ...props }, ref) => {
- return (
-
- );
- }
-);
-
-Card.displayName = "Card";
-
-export const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardHeader.displayName = "CardHeader";
-
-export const CardTitle = React.forwardRef<
- HTMLHeadingElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardTitle.displayName = "CardTitle";
-
-export const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardDescription.displayName = "CardDescription";
-
-export const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardContent.displayName = "CardContent";
-
-export const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardFooter.displayName = "CardFooter";
diff --git a/interface/src/ui/Checkbox.tsx b/interface/src/ui/Checkbox.tsx
deleted file mode 100644
index 709382451..000000000
--- a/interface/src/ui/Checkbox.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
-import { Tick02Icon, MinusSignIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-const checkboxStyles = cva(
- [
- "peer h-4 w-4 shrink-0 rounded border border-app-line",
- "ring-offset-app-box focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
- "disabled:cursor-not-allowed disabled:opacity-50",
- "data-[state=checked]:bg-accent data-[state=checked]:border-accent",
- "data-[state=indeterminate]:bg-accent data-[state=indeterminate]:border-accent",
- ]
-);
-
-const indicatorStyles = cva(
- "flex items-center justify-center text-current"
-);
-
-export interface CheckboxProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {
- indeterminate?: boolean;
-}
-
-export const Checkbox = React.forwardRef<
- React.ElementRef,
- CheckboxProps
->(({ className, indeterminate, checked, ...props }, ref) => (
-
-
- {indeterminate ? (
-
- ) : (
-
- )}
-
-
-));
-
-Checkbox.displayName = CheckboxPrimitive.Root.displayName;
-
-export interface CheckboxLabelProps {
- children: React.ReactNode;
- disabled?: boolean;
- className?: string;
-}
-
-export const CheckboxLabel: React.FC = ({
- children,
- disabled,
- className,
-}) => (
-
- {children}
-
-);
-
-export interface CheckboxFieldProps {
- label: React.ReactNode;
- description?: string;
- disabled?: boolean;
- className?: string;
- checkboxProps?: Omit;
-}
-
-export const CheckboxField: React.FC = ({
- label,
- description,
- disabled,
- className,
- checkboxProps,
-}) => (
-
-);
diff --git a/interface/src/ui/Dialog.tsx b/interface/src/ui/Dialog.tsx
deleted file mode 100644
index 5e6ebcf33..000000000
--- a/interface/src/ui/Dialog.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as React from "react";
-import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { cx } from "./utils";
-import { Cancel01Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-
-const Dialog = DialogPrimitive.Root;
-const DialogTrigger = DialogPrimitive.Trigger;
-const DialogPortal = DialogPrimitive.Portal;
-const DialogClose = DialogPrimitive.Close;
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
-
-const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
- {children}
-
-
- Close
-
-
-
-));
-DialogContent.displayName = DialogPrimitive.Content.displayName;
-
-const DialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-DialogHeader.displayName = "DialogHeader";
-
-const DialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-DialogFooter.displayName = "DialogFooter";
-
-const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogTitle.displayName = DialogPrimitive.Title.displayName;
-
-const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DialogDescription.displayName = DialogPrimitive.Description.displayName;
-
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-};
diff --git a/interface/src/ui/Divider.tsx b/interface/src/ui/Divider.tsx
deleted file mode 100644
index cd66cc071..000000000
--- a/interface/src/ui/Divider.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { tw } from "./utils";
-
-export const Divider = tw.div`h-px w-full bg-app-divider`;
-
-export const VerticalDivider = tw.div`h-full w-px bg-app-divider`;
diff --git a/interface/src/ui/Dropdown.tsx b/interface/src/ui/Dropdown.tsx
deleted file mode 100644
index 6cc11d867..000000000
--- a/interface/src/ui/Dropdown.tsx
+++ /dev/null
@@ -1,210 +0,0 @@
-import * as React from "react";
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
-import { cx } from "./utils";
-import { Tick02Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-
-const DropdownMenu = DropdownMenuPrimitive.Root;
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, children, ...props }, ref) => (
-
- {children}
-
-
-));
-DropdownMenuSubTrigger.displayName =
- DropdownMenuPrimitive.SubTrigger.displayName;
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSubContent.displayName =
- DropdownMenuPrimitive.SubContent.displayName;
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
-
-
-));
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, checked, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuCheckboxItem.displayName =
- DropdownMenuPrimitive.CheckboxItem.displayName;
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
-
-const DropdownMenuShortcut = ({
- className,
- ...props
-}: React.HTMLAttributes) => {
- return (
-
- );
-};
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
-
-export {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuGroup,
- DropdownMenuPortal,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuRadioGroup,
-};
-
-export const Dropdown = {
- Root: DropdownMenu,
- Trigger: DropdownMenuTrigger,
- Content: DropdownMenuContent,
- Item: DropdownMenuItem,
- CheckboxItem: DropdownMenuCheckboxItem,
- RadioItem: DropdownMenuRadioItem,
- Label: DropdownMenuLabel,
- Separator: DropdownMenuSeparator,
- Shortcut: DropdownMenuShortcut,
- Group: DropdownMenuGroup,
- Sub: DropdownMenuSub,
- SubContent: DropdownMenuSubContent,
- SubTrigger: DropdownMenuSubTrigger,
- RadioGroup: DropdownMenuRadioGroup,
-};
diff --git a/interface/src/ui/FilterButton.tsx b/interface/src/ui/FilterButton.tsx
deleted file mode 100644
index 14c8a8a96..000000000
--- a/interface/src/ui/FilterButton.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as React from "react";
-import { cx } from "./utils";
-
-export interface FilterButtonProps
- extends React.ButtonHTMLAttributes {
- active?: boolean;
- colorClass?: string;
- children: React.ReactNode;
-}
-
-export const FilterButton = React.forwardRef<
- HTMLButtonElement,
- FilterButtonProps
->(({ active, colorClass, className, children, ...props }, ref) => (
-
- {children}
-
-));
-
-FilterButton.displayName = "FilterButton";
diff --git a/interface/src/ui/Input.tsx b/interface/src/ui/Input.tsx
deleted file mode 100644
index 87946252d..000000000
--- a/interface/src/ui/Input.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import * as React from "react";
-import {cva, type VariantProps} from "class-variance-authority";
-import {cx} from "./utils";
-import {Search01Icon} from "@hugeicons/core-free-icons";
-import {HugeiconsIcon} from "@hugeicons/react";
-
-export const inputSizes = {
- sm: "h-8 text-sm px-3 py-1.5",
- md: "h-9 text-sm px-3",
- lg: "h-10 text-base px-3.5",
-} as const;
-
-export const inputStyles = cva(
- [
- "rounded-md border text-sm leading-4",
- "outline-none transition-colors",
- "text-ink",
- ],
- {
- variants: {
- variant: {
- default: ["border-app-line bg-app-darkBox", "focus-within:border-accent/50"],
- transparent: [
- "border-transparent bg-app-box",
- "focus-within:border-app-line",
- ],
- },
- error: {
- true: "border-red-500/50 focus-within:border-red-500",
- },
- size: inputSizes,
- },
- defaultVariants: {
- variant: "default",
- size: "sm",
- },
- },
-);
-
-type InputVariants = VariantProps;
-
-export interface InputProps
- extends
- Omit, "size">,
- InputVariants {
- icon?: React.ReactNode;
- right?: React.ReactNode;
-}
-
-export const Input = React.forwardRef(
- ({className, variant, size, error, icon, right, ...props}, ref) => {
- return (
-
- {icon && {icon}}
-
- {right && {right}}
-
- );
- },
-);
-
-Input.displayName = "Input";
-
-export interface TextAreaProps
- extends
- React.TextareaHTMLAttributes,
- Pick {}
-
-export const TextArea = React.forwardRef(
- ({className, variant = "default", error, ...props}, ref) => {
- return (
-
- );
- },
-);
-
-TextArea.displayName = "TextArea";
-
-export const SearchInput = React.forwardRef<
- HTMLInputElement,
- Omit
->(({size = "sm", ...props}, ref) => (
-
- }
- size={size}
- {...props}
- />
-));
-
-SearchInput.displayName = "SearchInput";
-
-export const Label = React.forwardRef<
- HTMLLabelElement,
- React.LabelHTMLAttributes
->(({className, ...props}, ref) => (
-
-));
-
-Label.displayName = "Label";
-
-export const PasswordInput = React.forwardRef<
- HTMLInputElement,
- Omit
->(({...props}, ref) => {
- const [show, setShow] = React.useState(false);
-
- return (
- setShow(!show)}
- className="text-xs text-ink-dull hover:text-ink transition-colors"
- >
- {show ? "Hide" : "Show"}
-
- }
- {...props}
- />
- );
-});
-
-PasswordInput.displayName = "PasswordInput";
diff --git a/interface/src/ui/Loader.tsx b/interface/src/ui/Loader.tsx
deleted file mode 100644
index 12a49171a..000000000
--- a/interface/src/ui/Loader.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-const loaderStyles = cva(
- ["animate-spin text-ink-dull"],
- {
- variants: {
- size: {
- sm: "h-4 w-4",
- md: "h-6 w-6",
- lg: "h-8 w-8",
- },
- },
- defaultVariants: {
- size: "md",
- },
- }
-);
-
-export interface LoaderProps
- extends React.SVGAttributes,
- VariantProps {}
-
-export const Loader = React.forwardRef(
- ({ className, size, ...props }, ref) => {
- return (
-
- );
- }
-);
-
-Loader.displayName = "Loader";
diff --git a/interface/src/ui/NumberStepper.tsx b/interface/src/ui/NumberStepper.tsx
deleted file mode 100644
index 3b1a1ef3d..000000000
--- a/interface/src/ui/NumberStepper.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import * as React from "react";
-import { Button } from "./Button";
-import { PlusSignIcon, MinusSignIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-
-export interface NumberStepperProps {
- label?: string;
- description?: string;
- value: number;
- onChange: (value: number) => void;
- min?: number;
- max?: number;
- step?: number;
- suffix?: string;
- type?: "integer" | "float";
- showProgress?: boolean;
- variant?: "default" | "compact";
-}
-
-export const NumberStepper = React.forwardRef(
- (
- {
- label,
- description,
- value,
- onChange,
- min,
- max,
- step = 1,
- suffix,
- type = "integer",
- showProgress = false,
- variant = "default",
- },
- ref
- ) => {
- const safeValue = value ?? 0;
-
- const clamp = (v: number) => {
- let clamped = v;
- if (min !== undefined && clamped < min) clamped = min;
- if (max !== undefined && clamped > max) clamped = max;
- if (type === "float") {
- clamped = Math.round(clamped / step) * step;
- }
- return clamped;
- };
-
- const increment = () => onChange(clamp(safeValue + step));
- const decrement = () => onChange(clamp(safeValue - step));
-
- const handleInput = (e: React.ChangeEvent) => {
- const raw = e.target.value;
- if (type === "integer" && (raw === "" || raw === "-")) return;
- if (type === "float" && (raw === "" || raw === "0." || raw === ".")) return;
- const parsed = Number(raw);
- if (!Number.isNaN(parsed)) onChange(clamp(parsed));
- };
-
- const displayValue =
- type === "float" ? safeValue.toFixed(2) : safeValue.toString();
- const inputWidth = variant === "compact"
- ? (type === "float" ? "w-12" : "w-14")
- : (type === "float" ? "w-16" : "w-20");
-
- const progress =
- showProgress && min !== undefined && max !== undefined
- ? ((safeValue - min) / (max - min)) * 100
- : 0;
-
- return (
-
- {label &&
}
- {description &&
{description}
}
-
-
-
-
-
-
-
-
-
-
- {showProgress && (
-
- )}
- {suffix &&
{suffix}}
-
-
- );
- }
-);
-
-NumberStepper.displayName = "NumberStepper";
diff --git a/interface/src/ui/Popover.tsx b/interface/src/ui/Popover.tsx
deleted file mode 100644
index 05ceefffd..000000000
--- a/interface/src/ui/Popover.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as PopoverPrimitive from "@radix-ui/react-popover";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-const popoverContentStyles = cva(
- [
- "z-50 w-72 rounded-lg border border-app-line bg-app-box p-4 shadow-lg",
- "outline-none data-[state=open]:animate-in data-[state=closed]:animate-out",
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
- "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
- "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- ]
-);
-
-export interface PopoverContentProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {}
-
-export const Popover = PopoverPrimitive.Root;
-
-export const PopoverTrigger = PopoverPrimitive.Trigger;
-
-export const PopoverAnchor = PopoverPrimitive.Anchor;
-
-export const PopoverContent = React.forwardRef<
- React.ElementRef,
- PopoverContentProps
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
-
-
-
-));
-
-PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-
-export const PopoverClose = PopoverPrimitive.Close;
-
-export interface PopoverHeaderProps extends React.HTMLAttributes {
- title?: string;
- description?: string;
-}
-
-export const PopoverHeader: React.FC = ({
- title,
- description,
- className,
- children,
- ...props
-}) => (
-
- {title &&
{title}
}
- {description &&
{description}
}
- {children}
-
-);
-
-export interface PopoverFooterProps extends React.HTMLAttributes {}
-
-export const PopoverFooter: React.FC = ({
- className,
- children,
- ...props
-}) => (
-
- {children}
-
-);
diff --git a/interface/src/ui/ProgressBar.tsx b/interface/src/ui/ProgressBar.tsx
deleted file mode 100644
index 6354c3ff0..000000000
--- a/interface/src/ui/ProgressBar.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-import { Tick02Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-
-const progressBarStyles = cva(
- "relative h-2 w-full overflow-hidden rounded-full bg-app-button",
- {
- variants: {
- size: {
- sm: "h-1",
- md: "h-2",
- lg: "h-3",
- xl: "h-4",
- },
- },
- defaultVariants: {
- size: "md",
- },
- }
-);
-
-const progressIndicatorStyles = cva(
- "h-full w-full flex-1 bg-accent transition-all",
- {
- variants: {
- variant: {
- default: "bg-accent",
- success: "bg-green-500",
- warning: "bg-amber-500",
- error: "bg-red-500",
- neutral: "bg-ink-dull",
- },
- animated: {
- true: "animate-pulse",
- },
- },
- defaultVariants: {
- variant: "default",
- animated: false,
- },
- }
-);
-
-export interface ProgressBarProps
- extends React.HTMLAttributes,
- VariantProps,
- VariantProps {
- value: number;
- max?: number;
- showLabel?: boolean;
- labelPosition?: "inside" | "outside";
-}
-
-export const ProgressBar = React.forwardRef(
- (
- {
- className,
- value,
- max = 100,
- size,
- variant,
- animated,
- showLabel = false,
- labelPosition = "outside",
- ...props
- },
- ref
- ) => {
- const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
-
- return (
-
-
-
- {showLabel && labelPosition === "inside" && size !== "sm" && (
-
- {Math.round(percentage)}%
-
- )}
-
- {showLabel && labelPosition === "outside" && (
-
- {Math.round(percentage)}%
-
- {value} / {max}
-
-
- )}
-
- );
- }
-);
-
-ProgressBar.displayName = "ProgressBar";
-
-export interface CircularProgressProps
- extends React.SVGProps,
- VariantProps {
- value: number;
- max?: number;
- size?: number;
- strokeWidth?: number;
- showLabel?: boolean;
-}
-
-export const CircularProgress: React.FC = ({
- value,
- max = 100,
- size = 40,
- strokeWidth = 4,
- variant,
- showLabel = false,
- className,
- ...props
-}) => {
- const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
- const radius = (size - strokeWidth) / 2;
- const circumference = radius * 2 * Math.PI;
- const offset = circumference - (percentage / 100) * circumference;
-
- const variantColor = {
- default: "stroke-accent",
- success: "stroke-green-500",
- warning: "stroke-amber-500",
- error: "stroke-red-500",
- neutral: "stroke-ink-dull",
- }[variant || "default"];
-
- return (
-
-
- {showLabel && (
-
- {Math.round(percentage)}%
-
- )}
-
- );
-};
-
-export interface ProgressStep {
- label: string;
- description?: string;
- status?: "pending" | "current" | "completed" | "error";
-}
-
-export interface ProgressStepsProps extends React.HTMLAttributes {
- steps: ProgressStep[];
- currentStep: number;
- orientation?: "horizontal" | "vertical";
-}
-
-export const ProgressSteps: React.FC = ({
- steps,
- currentStep,
- orientation = "horizontal",
- className,
- ...props
-}) => {
- return (
-
- {steps.map((step, index) => {
- const status =
- step.status ||
- (index < currentStep
- ? "completed"
- : index === currentStep
- ? "current"
- : "pending");
-
- return (
-
-
- {status === "completed" ? (
-
- ) : (
- index + 1
- )}
-
-
-
- {step.label}
-
- {step.description && (
-
{step.description}
- )}
-
- {orientation === "horizontal" && index < steps.length - 1 && (
-
- )}
-
- );
- })}
-
- );
-};
diff --git a/interface/src/ui/RadioGroup.tsx b/interface/src/ui/RadioGroup.tsx
deleted file mode 100644
index b7a4a5634..000000000
--- a/interface/src/ui/RadioGroup.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
-import { CircleIcon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-const radioGroupStyles = cva("grid gap-3");
-
-const radioGroupItemStyles = cva(
- [
- "aspect-square h-4 w-4 rounded-full border border-app-line",
- "text-accent ring-offset-app-box focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
- "disabled:cursor-not-allowed disabled:opacity-50",
- "data-[state=checked]:border-accent",
- ]
-);
-
-const radioIndicatorStyles = cva("flex items-center justify-center");
-
-export interface RadioGroupProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {}
-
-export const RadioGroup = React.forwardRef<
- React.ElementRef,
- RadioGroupProps
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-
-RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
-
-export interface RadioGroupItemProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {}
-
-export const RadioGroupItem = React.forwardRef<
- React.ElementRef,
- RadioGroupItemProps
->(({ className, ...props }, ref) => {
- return (
-
-
-
-
-
- );
-});
-
-RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
-
-export interface RadioLabelProps {
- children: React.ReactNode;
- disabled?: boolean;
- className?: string;
-}
-
-export const RadioLabel: React.FC = ({
- children,
- disabled,
- className,
-}) => (
-
- {children}
-
-);
-
-export interface RadioGroupFieldProps {
- value: string;
- label: React.ReactNode;
- description?: string;
- disabled?: boolean;
- className?: string;
-}
-
-export const RadioGroupField: React.FC = ({
- value,
- label,
- description,
- disabled,
- className,
-}) => (
-
-);
diff --git a/interface/src/ui/Select.tsx b/interface/src/ui/Select.tsx
deleted file mode 100644
index 541ba3ac3..000000000
--- a/interface/src/ui/Select.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import * as React from "react";
-import * as SelectPrimitive from "@radix-ui/react-select";
-import { cx } from "./utils";
-import { Tick02Icon, ArrowDown01Icon, ArrowUp01Icon } from "@hugeicons/core-free-icons";
-import { HugeiconsIcon } from "@hugeicons/react";
-
-export type SelectProps = React.ComponentPropsWithoutRef;
-
-const Select = SelectPrimitive.Root;
-const SelectGroup = SelectPrimitive.Group;
-const SelectValue = SelectPrimitive.Value;
-
-const SelectTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
- span]:line-clamp-1",
- className
- )}
- {...props}
- >
- {children}
-
-
-
-
-));
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
-
-const SelectScrollUpButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
-
-const SelectScrollDownButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-SelectScrollDownButton.displayName =
- SelectPrimitive.ScrollDownButton.displayName;
-
-const SelectContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, position = "popper", ...props }, ref) => (
-
-
-
-
- {children}
-
-
-
-
-));
-SelectContent.displayName = SelectPrimitive.Content.displayName;
-
-const SelectLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SelectLabel.displayName = SelectPrimitive.Label.displayName;
-
-const SelectItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
-
- {children}
-
-));
-SelectItem.displayName = SelectPrimitive.Item.displayName;
-
-const SelectSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
-
-export {
- Select,
- SelectGroup,
- SelectValue,
- SelectTrigger,
- SelectContent,
- SelectLabel,
- SelectItem,
- SelectSeparator,
- SelectScrollUpButton,
- SelectScrollDownButton,
-};
diff --git a/interface/src/ui/SettingSidebarButton.tsx b/interface/src/ui/SettingSidebarButton.tsx
index 5e120651d..f0be79848 100644
--- a/interface/src/ui/SettingSidebarButton.tsx
+++ b/interface/src/ui/SettingSidebarButton.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { cx } from "./utils";
+import { cx } from "class-variance-authority";
export interface SettingSidebarButtonProps
extends React.ButtonHTMLAttributes {
diff --git a/interface/src/ui/Slider.tsx b/interface/src/ui/Slider.tsx
deleted file mode 100644
index cfcdb6536..000000000
--- a/interface/src/ui/Slider.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as SliderPrimitive from "@radix-ui/react-slider";
-import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "./utils";
-
-const sliderTrackStyles = cva(
- "relative flex w-full grow overflow-hidden rounded-full bg-app-button h-2"
-);
-
-const sliderRangeStyles = cva("absolute h-full bg-accent rounded-full");
-
-const sliderThumbStyles = cva(
- [
- "block h-5 w-5 rounded-full border-2 border-accent bg-app-box",
- "ring-offset-app-box transition-colors",
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
- "disabled:pointer-events-none disabled:opacity-50",
- "cursor-grab active:cursor-grabbing",
- ]
-);
-
-const sliderMarkStyles = cva(
- "absolute w-1 h-1 rounded-full bg-app-line top-1/2 -translate-y-1/2"
-);
-
-export interface SliderProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {
- marks?: number[];
-}
-
-export const Slider = React.forwardRef<
- React.ElementRef,
- SliderProps
->(({ className, marks, ...props }, ref) => (
-
-
-
- {marks?.map((mark, index) => (
-
- ))}
-
- {props.defaultValue?.map((_, index) => (
-
- )) ||
- (props.value?.map((_, index) => (
-
- )) || )}
-
-));
-
-Slider.displayName = SliderPrimitive.Root.displayName;
-
-export interface SliderLabelProps {
- min: number;
- max: number;
- value: number[];
- className?: string;
-}
-
-export const SliderLabel: React.FC = ({
- min,
- max,
- value,
- className,
-}) => (
-
- {min}
-
- {value.length > 1 ? `${value[0]} - ${value[1]}` : value[0]}
-
- {max}
-
-);
-
-export interface SliderFieldProps extends SliderProps {
- label?: string;
- description?: string;
- showValue?: boolean;
- minLabel?: string;
- maxLabel?: string;
-}
-
-export const SliderField: React.FC = ({
- label,
- description,
- showValue = true,
- minLabel,
- maxLabel,
- value,
- defaultValue,
- min = 0,
- max = 100,
- className,
- ...props
-}) => {
- const currentValue = value || defaultValue || [min];
-
- return (
-
- {label && (
-
- {label}
- {showValue && (
-
- {currentValue.length > 1
- ? `${currentValue[0]} - ${currentValue[1]}`
- : currentValue[0]}
-
- )}
-
- )}
-
- {description &&
{description}
}
- {(minLabel || maxLabel) && (
-
- {minLabel || min}
- {maxLabel || max}
-
- )}
-
- );
-};
diff --git a/interface/src/ui/Tabs.tsx b/interface/src/ui/Tabs.tsx
deleted file mode 100644
index dd273e3e3..000000000
--- a/interface/src/ui/Tabs.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import * as React from "react";
-import * as TabsPrimitive from "@radix-ui/react-tabs";
-import { cx } from "./utils";
-
-const Tabs = TabsPrimitive.Root;
-
-const TabsList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsList.displayName = TabsPrimitive.List.displayName;
-
-const TabsTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
-
-const TabsContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-TabsContent.displayName = TabsPrimitive.Content.displayName;
-
-export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/interface/src/ui/Toggle.tsx b/interface/src/ui/Toggle.tsx
index 754963c99..f538f38e8 100644
--- a/interface/src/ui/Toggle.tsx
+++ b/interface/src/ui/Toggle.tsx
@@ -1,7 +1,6 @@
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
-import {cva, type VariantProps} from "class-variance-authority";
-import {cx} from "./utils";
+import {cva, cx, type VariantProps} from "class-variance-authority";
const switchStyles = cva(
[
diff --git a/interface/src/ui/ToggleGroup.tsx b/interface/src/ui/ToggleGroup.tsx
deleted file mode 100644
index c59e60ab4..000000000
--- a/interface/src/ui/ToggleGroup.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import * as React from "react";
-import { cx } from "./utils";
-
-export interface ToggleGroupOption {
- value: T;
- label: React.ReactNode;
- title?: string;
-}
-
-export interface ToggleGroupProps {
- options: ToggleGroupOption[];
- value: T;
- onChange: (value: T) => void;
- className?: string;
-}
-
-export function ToggleGroup({
- options,
- value,
- onChange,
- className,
-}: ToggleGroupProps) {
- return (
-
- {options.map((option) => {
- const isActive = option.value === value;
-
- return (
- onChange(option.value)}
- title={option.title}
- className={cx(
- "inline-flex h-8 w-8 items-center justify-center transition-colors",
- isActive
- ? "bg-app-selected text-ink"
- : "text-ink-faint hover:bg-app-hover/40 hover:text-ink-dull"
- )}
- >
- {option.label}
-
- );
- })}
-
- );
-}
diff --git a/interface/src/ui/Tooltip.tsx b/interface/src/ui/Tooltip.tsx
deleted file mode 100644
index 483a3aec1..000000000
--- a/interface/src/ui/Tooltip.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as React from "react";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-import { cx } from "./utils";
-
-const TooltipProvider = TooltipPrimitive.Provider;
-
-const Tooltip = TooltipPrimitive.Root;
-
-const TooltipTrigger = TooltipPrimitive.Trigger;
-
-const TooltipContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
-));
-TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/interface/src/ui/Typography.tsx b/interface/src/ui/Typography.tsx
deleted file mode 100644
index a3fc773da..000000000
--- a/interface/src/ui/Typography.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { tw } from "./utils";
-
-export const CategoryHeading = tw.h3`text-xs font-semibold text-ink-dull uppercase tracking-wide`;
-
-export const SectionHeading = tw.h2`text-sm font-semibold text-ink`;
-
-export const ScreenHeading = tw.h1`text-xl font-plex font-bold text-ink`;
-
-export const PageHeading = tw.h1`text-2xl font-plex font-bold text-ink`;
-
-export const BodyText = tw.p`text-sm text-ink leading-relaxed`;
-
-export const MutedText = tw.p`text-xs text-ink-dull leading-relaxed`;
-
-export const Kbd = tw.kbd`h-4.5 flex items-center justify-center rounded-md border border-app-line bg-app-box px-1.5 text-tiny font-sans text-ink-dull`;
diff --git a/interface/src/ui/forms/CheckBoxField.tsx b/interface/src/ui/forms/CheckBoxField.tsx
deleted file mode 100644
index 4b709ecbb..000000000
--- a/interface/src/ui/forms/CheckBoxField.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { useFormContext, Controller } from "react-hook-form";
-import { CheckboxField, type CheckboxProps } from "../Checkbox";
-import { ErrorMessage } from "./Form";
-import { cx } from "../utils";
-
-export interface CheckBoxFieldProps extends Omit {
- name: string;
- label?: React.ReactNode;
- description?: string;
- className?: string;
-}
-
-export const CheckBoxField: React.FC = ({
- name,
- label,
- description,
- className,
- ...props
-}) => {
- const { control, formState } = useFormContext();
- const error = formState.errors[name]?.message as string | undefined;
-
- return (
- (
-
-
- {error && }
-
- )}
- />
- );
-};
-
-CheckBoxField.displayName = "CheckBoxField";
diff --git a/interface/src/ui/forms/Form.tsx b/interface/src/ui/forms/Form.tsx
deleted file mode 100644
index 7050e7ac6..000000000
--- a/interface/src/ui/forms/Form.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-"use client";
-
-import { useFormContext, FormProvider, useFormState } from "react-hook-form";
-import { cx } from "../utils";
-
-export { FormProvider, useFormContext, useFormState };
-
-export const Form = FormProvider;
-
-export interface ErrorMessageProps {
- error?: string;
- className?: string;
-}
-
-export const ErrorMessage = ({ error, className }: ErrorMessageProps) => {
- if (!error) return null;
- return (
- {error}
- );
-};
diff --git a/interface/src/ui/forms/FormField.tsx b/interface/src/ui/forms/FormField.tsx
deleted file mode 100644
index 483427910..000000000
--- a/interface/src/ui/forms/FormField.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-"use client";
-
-import * as React from "react";
-import {
- useFormContext,
- Controller,
- type FieldPath,
- type FieldValues,
- type ControllerProps,
-} from "react-hook-form";
-import { cx } from "../utils";
-import { Label } from "../Input";
-
-interface FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
-> {
- name: TName;
-}
-
-const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
-);
-
-const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext);
- const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
-
- const fieldState = getFieldState(fieldContext.name, formState);
-
- if (!fieldContext) {
- throw new Error("useFormField should be used within ");
- }
-
- const { id } = itemContext;
-
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- };
-};
-
-interface FormItemContextValue {
- id: string;
-}
-
-const FormItemContext = React.createContext(
- {} as FormItemContextValue
-);
-
-const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
->({
- ...props
-}: ControllerProps) => {
- return (
-
-
-
- );
-};
-
-interface FormItemProps extends React.HTMLAttributes {
- name: string;
- label?: string;
- description?: string;
-}
-
-const FormItem = React.forwardRef(
- ({ className, name, label, description, children, ...props }, ref) => {
- const id = React.useId();
-
- return (
-
-
- {label &&
}
- {children}
- {description && (
-
{description}
- )}
-
-
-
- );
- }
-);
-FormItem.displayName = "FormItem";
-
-const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField();
-
- return (
-
- );
-});
-FormLabel.displayName = "FormLabel";
-
-const FormControl = React.forwardRef<
- React.ElementRef<"div">,
- React.ComponentPropsWithoutRef<"div">
->(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
-
- return (
-
- );
-});
-FormControl.displayName = "FormControl";
-
-const FormDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
-
- return (
-
- );
-});
-FormDescription.displayName = "FormDescription";
-
-const FormMessage = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField();
- const body = error ? String(error?.message) : children;
-
- if (!body) {
- return null;
- }
-
- return (
-
- {body}
-
- );
-});
-FormMessage.displayName = "FormMessage";
-
-export {
- useFormField,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-};
diff --git a/interface/src/ui/forms/InputField.tsx b/interface/src/ui/forms/InputField.tsx
deleted file mode 100644
index e60857c78..000000000
--- a/interface/src/ui/forms/InputField.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { useFormContext, Controller } from "react-hook-form";
-import { Input, type InputProps, Label } from "../Input";
-import { ErrorMessage } from "./Form";
-import { cx } from "../utils";
-
-export interface InputFieldProps extends Omit {
- name: string;
- label?: string;
- description?: string;
- className?: string;
-}
-
-export const InputField = React.forwardRef(
- ({ name, label, description, className, ...props }, ref) => {
- const { control, formState } = useFormContext();
- const error = formState.errors[name]?.message as string | undefined;
-
- return (
- (
-
- {label &&
}
-
- {description && !error && (
-
{description}
- )}
- {error &&
}
-
- )}
- />
- );
- }
-);
-
-InputField.displayName = "InputField";
diff --git a/interface/src/ui/forms/SelectField.tsx b/interface/src/ui/forms/SelectField.tsx
deleted file mode 100644
index 92f2e86aa..000000000
--- a/interface/src/ui/forms/SelectField.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { useFormContext, Controller } from "react-hook-form";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "../Select";
-import { Label } from "../Input";
-import { ErrorMessage } from "./Form";
-import { cx } from "../utils";
-
-export interface SelectOption {
- value: string;
- label: string;
- disabled?: boolean;
-}
-
-export interface SelectFieldProps {
- name: string;
- label?: string;
- description?: string;
- options: SelectOption[];
- placeholder?: string;
- className?: string;
- disabled?: boolean;
-}
-
-export const SelectField: React.FC = ({
- name,
- label,
- description,
- options,
- placeholder,
- className,
- disabled,
-}) => {
- const { control, formState } = useFormContext();
- const error = formState.errors[name]?.message as string | undefined;
-
- return (
- (
-
- {label &&
}
-
- {description && !error && (
-
{description}
- )}
- {error &&
}
-
- )}
- />
- );
-};
-
-SelectField.displayName = "SelectField";
diff --git a/interface/src/ui/forms/SwitchField.tsx b/interface/src/ui/forms/SwitchField.tsx
deleted file mode 100644
index dacde8874..000000000
--- a/interface/src/ui/forms/SwitchField.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { useFormContext, Controller } from "react-hook-form";
-import { Toggle, type ToggleProps } from "../Toggle";
-import { ErrorMessage } from "./Form";
-import { cx } from "../utils";
-
-export interface SwitchFieldProps extends Omit {
- name: string;
- label?: string;
- description?: string;
- className?: string;
-}
-
-export const SwitchField: React.FC = ({
- name,
- label,
- description,
- className,
- ...props
-}) => {
- const { control, formState } = useFormContext();
- const error = formState.errors[name]?.message as string | undefined;
-
- return (
- (
-
-
- {error &&
}
-
- )}
- />
- );
-};
-
-SwitchField.displayName = "SwitchField";
diff --git a/interface/src/ui/forms/TextAreaField.tsx b/interface/src/ui/forms/TextAreaField.tsx
deleted file mode 100644
index d83d81bd5..000000000
--- a/interface/src/ui/forms/TextAreaField.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { useFormContext, Controller } from "react-hook-form";
-import { TextArea, type TextAreaProps, Label } from "../Input";
-import { ErrorMessage } from "./Form";
-import { cx } from "../utils";
-
-export interface TextAreaFieldProps extends Omit {
- name: string;
- label?: string;
- description?: string;
- className?: string;
-}
-
-export const TextAreaField = React.forwardRef<
- HTMLTextAreaElement,
- TextAreaFieldProps
->(({ name, label, description, className, ...props }, ref) => {
- const { control, formState } = useFormContext();
- const error = formState.errors[name]?.message as string | undefined;
-
- return (
- (
-
- {label &&
}
-
- {description && !error && (
-
{description}
- )}
- {error &&
}
-
- )}
- />
- );
-});
-
-TextAreaField.displayName = "TextAreaField";
diff --git a/interface/src/ui/forms/index.ts b/interface/src/ui/forms/index.ts
deleted file mode 100644
index c8348526d..000000000
--- a/interface/src/ui/forms/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-"use client";
-
-export * from "./Form";
-export * from "./FormField";
-export * from "./InputField";
-export * from "./TextAreaField";
-export * from "./CheckBoxField";
-export * from "./SwitchField";
-export * from "./SelectField";
diff --git a/interface/src/ui/index.ts b/interface/src/ui/index.ts
deleted file mode 100644
index c690699ee..000000000
--- a/interface/src/ui/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-export { cva, cx, cn, tw, type VariantProps } from "./utils";
-
-export * from "./Button";
-export * from "./Input";
-export * from "./Card";
-export * from "./Badge";
-export * from "./Banner";
-export * from "./Toggle";
-export * from "./Dialog";
-export * from "./Dropdown";
-export * from "./Select";
-export * from "./Tabs";
-export * from "./Tooltip";
-export * from "./Typography";
-export * from "./Divider";
-export * from "./Loader";
-export * from "./Checkbox";
-export * from "./RadioGroup";
-export * from "./Popover";
-export * from "./Slider";
-export * from "./ProgressBar";
-export * from "./SettingSidebarButton";
-export * from "./ToggleGroup";
-export * from "./FilterButton";
-export * from "./NumberStepper";
-export * as Forms from "./forms";
diff --git a/interface/src/ui/style/colors.scss b/interface/src/ui/style/colors.scss
deleted file mode 100644
index 5a6270348..000000000
--- a/interface/src/ui/style/colors.scss
+++ /dev/null
@@ -1,198 +0,0 @@
-:root {
- --dark-hue: 230;
- --light-hue: 230;
-
- // global
- --color-black: 0, 0%, 0%;
- --color-white: 0, 0%, 100%;
- // accent theme colors
- --color-accent: 282, 70%, 57%;
- --color-accent-faint: 282, 65%, 64%;
- --color-accent-deep: 282, 75%, 48%;
- // text
- --color-ink: var(--dark-hue), 15%, 94%;
- --color-ink-dull: var(--dark-hue), 8%, 65%;
- --color-ink-faint: var(--dark-hue), 5%, 45%;
- // sidebar
- --color-sidebar: var(--dark-hue), 8%, 3%;
- --color-sidebar-box: var(--dark-hue), 8%, 6%;
- --color-sidebar-line: var(--dark-hue), 8%, 12%;
- --color-sidebar-ink: var(--dark-hue), 15%, 94%;
- --color-sidebar-ink-dull: var(--dark-hue), 8%, 65%;
- --color-sidebar-ink-faint: var(--dark-hue), 5%, 45%;
- --color-sidebar-divider: var(--dark-hue), 8%, 5%;
- --color-sidebar-button: var(--dark-hue), 8%, 12%;
- --color-sidebar-selected: var(--dark-hue), 8%, 15%;
- --color-sidebar-shade: var(--dark-hue), 8%, 12%;
- // main
- --color-app: var(--dark-hue), 8%, 5%;
- --color-app-box: var(--dark-hue), 8%, 8%;
- --color-app-dark-box: var(--dark-hue), 8%, 6%;
- --color-app-darker-box: var(--dark-hue), 8%, 4%;
- --color-app-light-box: var(--dark-hue), 8%, 15%;
- --color-app-overlay: var(--dark-hue), 8%, 7%;
- --color-app-input: var(--dark-hue), 8%, 10%;
- --color-app-focus: var(--dark-hue), 8%, 4%;
- --color-app-line: var(--dark-hue), 8%, 14%;
- --color-app-divider: var(--dark-hue), 8%, 3%;
- --color-app-button: var(--dark-hue), 8%, 12%;
- --color-app-hover: var(--dark-hue), 8%, 16%;
- --color-app-selected: var(--dark-hue), 8%, 18%;
- --color-app-selected-item: var(--dark-hue), 8%, 8%;
- --color-app-active: var(--dark-hue), 8%, 20%;
- --color-app-shade: var(--dark-hue), 8%, 0%;
- --color-app-frame: var(--dark-hue), 8%, 16%;
- --color-app-slider: var(--dark-hue), 8%, 10%;
- --color-app-explorer-scrollbar: var(--dark-hue), 8%, 18%;
- // menu
- --color-menu: var(--dark-hue), 8%, 4%;
- --color-menu-line: var(--dark-hue), 8%, 8%;
- --color-menu-ink: var(--dark-hue), 15%, 94%;
- --color-menu-faint: var(--dark-hue), 5%, 65%;
- --color-menu-hover: var(--dark-hue), 8%, 16%;
- --color-menu-selected: var(--dark-hue), 5%, 18%;
- --color-menu-shade: var(--dark-hue), 5%, 0%;
-}
-
-.vanilla-theme {
- --color-black: 0, 0%, 0%;
- --color-white: 0, 0%, 100%;
- --color-accent: 208, 100%, 57%;
- --color-accent-faint: 208, 100%, 67%;
- --color-accent-deep: 208, 100%, 47%;
- --color-ink: var(--light-hue), 5%, 25%;
- --color-ink-dull: var(--light-hue), 5%, 40%;
- --color-ink-faint: var(--light-hue), 5%, 60%;
- --color-sidebar: var(--light-hue), 5%, 96%;
- --color-sidebar-box: var(--light-hue), 5%, 100%;
- --color-sidebar-line: var(--light-hue), 10%, 85%;
- --color-sidebar-ink: var(--light-hue), 5%, 25%;
- --color-sidebar-ink-dull: var(--light-hue), 5%, 35%;
- --color-sidebar-ink-faint: var(--light-hue), 5%, 45%;
- --color-sidebar-divider: var(--light-hue), 15%, 93%;
- --color-sidebar-button: var(--light-hue), 15%, 100%;
- --color-sidebar-selected: var(--light-hue), 10%, 80%;
- --color-sidebar-shade: var(--light-hue), 15%, 100%;
- --color-app: var(--light-hue), 5%, 100%;
- --color-app-box: var(--light-hue), 5%, 98%;
- --color-app-dark-box: var(--light-hue), 5%, 97%;
- --color-app-darker-box: var(--light-hue), 5%, 95%;
- --color-app-light-box: var(--light-hue), 5%, 95%;
- --color-app-overlay: var(--light-hue), 5%, 100%;
- --color-app-input: var(--light-hue), 5%, 100%;
- --color-app-focus: var(--light-hue), 5%, 98%;
- --color-app-line: var(--light-hue), 5%, 90%;
- --color-app-button: var(--light-hue), 5%, 100%;
- --color-app-divider: var(--light-hue), 5%, 80%;
- --color-app-selected: var(--light-hue), 5%, 93%;
- --color-app-selected-item: var(--light-hue), 5%, 96%;
- --color-app-hover: var(--light-hue), 5%, 97%;
- --color-app-active: var(--light-hue), 5%, 87%;
- --color-app-shade: var(--light-hue), 15%, 50%;
- --color-app-frame: 0, 0%, 100%;
- --color-app-slider: var(--light-hue), 5%, 95%;
- --color-app-explorer-scrollbar: var(--light-hue), 5%, 75%;
- --color-menu: var(--light-hue), 5%, 98%;
- --color-menu-line: var(--light-hue), 5%, 95%;
- --color-menu-ink: var(--light-hue), 5%, 20%;
- --color-menu-faint: var(--light-hue), 5%, 80%;
- --color-menu-hover: var(--light-hue), 15%, 20%;
- --color-menu-selected: var(--light-hue), 5%, 30%;
- --color-menu-shade: var(--light-hue), 5%, 0%;
-}
-
-.midnight-theme {
- --dark-hue: 240;
- --color-black: 0, 0%, 0%;
- --color-white: 0, 0%, 100%;
- --color-accent: 208, 100%, 57%;
- --color-accent-faint: 208, 100%, 64%;
- --color-accent-deep: 208, 100%, 47%;
- --color-ink: var(--dark-hue), 20%, 88%;
- --color-ink-dull: var(--dark-hue), 8%, 65%;
- --color-ink-faint: var(--dark-hue), 8%, 50%;
- --color-sidebar: var(--dark-hue), 30%, 4%;
- --color-sidebar-box: var(--dark-hue), 25%, 8%;
- --color-sidebar-line: var(--dark-hue), 20%, 15%;
- --color-sidebar-ink: var(--dark-hue), 20%, 88%;
- --color-sidebar-ink-dull: var(--dark-hue), 8%, 65%;
- --color-sidebar-ink-faint: var(--dark-hue), 8%, 50%;
- --color-sidebar-divider: var(--dark-hue), 25%, 9%;
- --color-sidebar-button: var(--dark-hue), 25%, 10%;
- --color-sidebar-selected: var(--dark-hue), 25%, 16%;
- --color-sidebar-shade: var(--dark-hue), 20%, 15%;
- --color-app: var(--dark-hue), 25%, 8%;
- --color-app-box: var(--dark-hue), 20%, 12%;
- --color-app-dark-box: var(--dark-hue), 25%, 9%;
- --color-app-darker-box: var(--dark-hue), 28%, 6%;
- --color-app-light-box: var(--dark-hue), 20%, 16%;
- --color-app-overlay: var(--dark-hue), 25%, 11%;
- --color-app-input: var(--dark-hue), 20%, 14%;
- --color-app-focus: var(--dark-hue), 25%, 6%;
- --color-app-line: var(--dark-hue), 20%, 16%;
- --color-app-divider: var(--dark-hue), 25%, 3%;
- --color-app-button: var(--dark-hue), 20%, 16%;
- --color-app-hover: var(--dark-hue), 20%, 18%;
- --color-app-selected: var(--dark-hue), 20%, 20%;
- --color-app-selected-item: var(--dark-hue), 20%, 12%;
- --color-app-active: var(--dark-hue), 20%, 22%;
- --color-app-shade: var(--dark-hue), 25%, 0%;
- --color-app-frame: var(--dark-hue), 20%, 18%;
- --color-app-slider: var(--dark-hue), 20%, 14%;
- --color-app-explorer-scrollbar: var(--dark-hue), 25%, 20%;
- --color-menu: var(--dark-hue), 25%, 6%;
- --color-menu-line: var(--dark-hue), 20%, 10%;
- --color-menu-ink: var(--dark-hue), 20%, 88%;
- --color-menu-faint: var(--dark-hue), 5%, 75%;
- --color-menu-hover: var(--dark-hue), 20%, 22%;
- --color-menu-selected: var(--dark-hue), 10%, 22%;
- --color-menu-shade: var(--dark-hue), 10%, 0%;
-}
-
-.noir-theme {
- --dark-hue: 0;
- --color-black: 0, 0%, 0%;
- --color-white: 0, 0%, 100%;
- --color-accent: 208, 100%, 57%;
- --color-accent-faint: 208, 100%, 64%;
- --color-accent-deep: 208, 100%, 47%;
- --color-ink: 0, 0%, 90%;
- --color-ink-dull: 0, 0%, 70%;
- --color-ink-faint: 0, 0%, 50%;
- --color-sidebar: 0, 0%, 0%;
- --color-sidebar-box: 0, 0%, 4%;
- --color-sidebar-line: 0, 0%, 12%;
- --color-sidebar-ink: 0, 0%, 90%;
- --color-sidebar-ink-dull: 0, 0%, 70%;
- --color-sidebar-ink-faint: 0, 0%, 50%;
- --color-sidebar-divider: 0, 0%, 5%;
- --color-sidebar-button: 0, 0%, 6%;
- --color-sidebar-selected: 0, 0%, 12%;
- --color-sidebar-shade: 0, 0%, 0%;
- --color-app: 0, 0%, 3%;
- --color-app-box: 0, 0%, 8%;
- --color-app-dark-box: 0, 0%, 5%;
- --color-app-darker-box: 0, 0%, 2%;
- --color-app-light-box: 0, 0%, 12%;
- --color-app-overlay: 0, 0%, 7%;
- --color-app-input: 0, 0%, 10%;
- --color-app-focus: 0, 0%, 2%;
- --color-app-line: 0, 0%, 14%;
- --color-app-divider: 0, 0%, 0%;
- --color-app-button: 0, 0%, 12%;
- --color-app-hover: 0, 0%, 14%;
- --color-app-selected: 0, 0%, 16%;
- --color-app-selected-item: 0, 0%, 8%;
- --color-app-active: 0, 0%, 18%;
- --color-app-shade: 0, 0%, 0%;
- --color-app-frame: 0, 0%, 14%;
- --color-app-slider: 0, 0%, 10%;
- --color-app-explorer-scrollbar: 0, 0%, 18%;
- --color-menu: 0, 0%, 2%;
- --color-menu-line: 0, 0%, 8%;
- --color-menu-ink: 0, 0%, 90%;
- --color-menu-faint: 0, 0%, 70%;
- --color-menu-hover: 0, 0%, 16%;
- --color-menu-selected: 0, 0%, 18%;
- --color-menu-shade: 0, 0%, 0%;
-}
diff --git a/interface/src/ui/style/style.scss b/interface/src/ui/style/style.scss
deleted file mode 100644
index 5200fdea9..000000000
--- a/interface/src/ui/style/style.scss
+++ /dev/null
@@ -1,134 +0,0 @@
-@use './colors.scss';
-
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@layer utilities {
- .no-scrollbar::-webkit-scrollbar {
- display: none;
- }
-
- .no-scrollbar {
- -ms-overflow-style: none;
- scrollbar-width: none;
- }
-
-}
-
-html, body {
- overflow: hidden;
- height: 100%;
-}
-
-html {
- font-size: 106.25%;
- background: hsla(var(--color-app), 1);
-}
-
-body {
- @apply font-plex text-ink bg-app;
-}
-
-/* Markdown prose styling for message content */
-.markdown {
- @apply leading-relaxed;
-
- p {
- @apply my-1;
- }
-
- p:first-child {
- @apply mt-0;
- }
-
- p:last-child {
- @apply mb-0;
- }
-
- strong {
- @apply font-semibold text-ink;
- }
-
- em {
- @apply italic;
- }
-
- a {
- @apply text-accent underline decoration-accent/30 hover:decoration-accent/60;
- }
-
- code {
- @apply rounded bg-app-darkBox px-1.5 py-0.5 font-mono text-xs text-ink;
- }
-
- pre {
- @apply my-2 overflow-x-auto rounded-md bg-app-darkBox p-3;
-
- code {
- @apply bg-transparent p-0;
- }
- }
-
- ul, ol {
- @apply my-1.5 pl-5;
- }
-
- ul {
- @apply list-disc;
- }
-
- ol {
- @apply list-decimal;
- }
-
- li {
- @apply my-0.5;
- }
-
- blockquote {
- @apply my-2 border-l-2 border-app-line pl-3 italic text-ink-faint;
- }
-
- h1, h2, h3, h4, h5, h6 {
- @apply mt-3 mb-1 font-semibold text-ink;
- }
-
- h1 { @apply text-lg; }
- h2 { @apply text-base; }
- h3, h4, h5, h6 { @apply text-sm; }
-
- hr {
- @apply my-3 border-app-line;
- }
-
- table {
- @apply my-2 w-full text-sm;
- }
-
- th {
- @apply border border-app-line bg-app-darkBox px-2 py-1 text-left font-medium text-ink;
- }
-
- td {
- @apply border border-app-line px-2 py-1;
- }
-
- img {
- @apply my-2 max-w-full rounded;
- }
-}
-
-// Hide React Query devtools toggle button (still accessible by clicking the area)
-.tsqd-open-btn-container {
- opacity: 0 !important;
-}
-
-// Override React Flow's default group node styles
-.react-flow__node-group {
- background-color: transparent !important;
- border: none !important;
- padding: 0 !important;
- width: auto !important;
- box-shadow: none !important;
-}
diff --git a/interface/src/ui/utils.tsx b/interface/src/ui/utils.tsx
deleted file mode 100644
index e8a7e34ed..000000000
--- a/interface/src/ui/utils.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import * as React from "react";
-import { cva as cvaOriginal, cx as cxOriginal, type VariantProps } from "class-variance-authority";
-
-export { VariantProps };
-export const cva = cvaOriginal;
-export const cx = cxOriginal;
-
-type ElementType = keyof React.JSX.IntrinsicElements;
-
-type TailwindFactory = {
- [K in ElementType]: (
- strings: TemplateStringsArray,
- ...values: (string | number | undefined | null | false)[]
- ) => React.ForwardRefExoticComponent<
- React.PropsWithoutRef & React.RefAttributes
- >;
-} & {
- >(
- component: T
- ): (
- strings: TemplateStringsArray,
- ...values: (string | number | undefined | null | false)[]
- ) => React.ForwardRefExoticComponent<
- React.PropsWithoutRef> & React.RefAttributes
- >;
-};
-
-function twFactory>(type: T) {
- return (
- strings: TemplateStringsArray,
- ...values: (string | number | undefined | null | false)[]
- ): React.ForwardRefExoticComponent => {
- const baseClasses = strings.reduce((acc, str, i) => {
- const value = values[i];
- if (value === undefined || value === null || value === false) {
- return acc + str;
- }
- return acc + str + String(value);
- }, "");
-
- return React.forwardRef((props: any, ref) => {
- const { className, ...rest } = props;
- const finalClassName = cx(baseClasses, className);
-
- if (typeof type === "string") {
- return React.createElement(type, {
- ref,
- className: finalClassName,
- ...rest,
- });
- }
-
- return React.createElement(type, {
- ref,
- className: finalClassName,
- ...rest,
- });
- }) as any;
- };
-}
-
-export const tw = new Proxy((() => {}) as unknown as TailwindFactory, {
- get(_, property: string) {
- return twFactory(property as ElementType);
- },
- apply(_, __, [element]: [React.ComponentType]) {
- return twFactory(element);
- },
-});
-
-export function cn(...inputs: (string | number | undefined | null | false)[]) {
- return cx(inputs);
-}
diff --git a/interface/tailwind.config.ts b/interface/tailwind.config.ts
deleted file mode 100644
index 39092c556..000000000
--- a/interface/tailwind.config.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import defaultTheme from "tailwindcss/defaultTheme";
-import type { Config } from "tailwindcss";
-
-function alpha(variableName: string) {
- return `hsla(var(${variableName}), )`;
-}
-
-export default {
- content: ["./index.html", "./src/**/*.{ts,tsx}"],
-
- theme: {
- screens: {
- xs: "475px",
- sm: "650px",
- md: "868px",
- lg: "1024px",
- xl: "1280px",
- },
- fontFamily: {
- sans: [...defaultTheme.fontFamily.sans],
- mono: [...defaultTheme.fontFamily.mono],
- plex: ["IBM Plex Sans", ...defaultTheme.fontFamily.sans],
- },
- fontSize: {
- tiny: ".70rem",
- xs: ".75rem",
- sm: ".80rem",
- base: "1rem",
- lg: "1.125rem",
- xl: "1.25rem",
- "2xl": "1.5rem",
- "3xl": "1.875rem",
- "4xl": "2.25rem",
- "5xl": "3rem",
- "6xl": "4rem",
- "7xl": "5rem",
- },
- extend: {
- colors: {
- accent: {
- DEFAULT: alpha("--color-accent"),
- faint: alpha("--color-accent-faint"),
- deep: alpha("--color-accent-deep"),
- },
- ink: {
- DEFAULT: alpha("--color-ink"),
- dull: alpha("--color-ink-dull"),
- faint: alpha("--color-ink-faint"),
- },
- sidebar: {
- DEFAULT: alpha("--color-sidebar"),
- box: alpha("--color-sidebar-box"),
- line: alpha("--color-sidebar-line"),
- ink: alpha("--color-sidebar-ink"),
- inkFaint: alpha("--color-sidebar-ink-faint"),
- inkDull: alpha("--color-sidebar-ink-dull"),
- divider: alpha("--color-sidebar-divider"),
- button: alpha("--color-sidebar-button"),
- selected: alpha("--color-sidebar-selected"),
- shade: alpha("--color-sidebar-shade"),
- },
- app: {
- DEFAULT: alpha("--color-app"),
- box: alpha("--color-app-box"),
- darkBox: alpha("--color-app-dark-box"),
- darkerBox: alpha("--color-app-darker-box"),
- lightBox: alpha("--color-app-light-box"),
- overlay: alpha("--color-app-overlay"),
- input: alpha("--color-app-input"),
- focus: alpha("--color-app-focus"),
- line: alpha("--color-app-line"),
- divider: alpha("--color-app-divider"),
- button: alpha("--color-app-button"),
- selected: alpha("--color-app-selected"),
- selectedItem: alpha("--color-app-selected-item"),
- hover: alpha("--color-app-hover"),
- active: alpha("--color-app-active"),
- shade: alpha("--color-app-shade"),
- frame: alpha("--color-app-frame"),
- slider: alpha("--color-app-slider"),
- explorerScrollbar: alpha("--color-app-explorer-scrollbar"),
- },
- menu: {
- DEFAULT: alpha("--color-menu"),
- line: alpha("--color-menu-line"),
- hover: alpha("--color-menu-hover"),
- selected: alpha("--color-menu-selected"),
- shade: alpha("--color-menu-shade"),
- ink: alpha("--color-menu-ink"),
- faint: alpha("--color-menu-faint"),
- },
- },
- transitionTimingFunction: {
- "in-sine": "cubic-bezier(0.12, 0, 0.39, 0)",
- "out-sine": "cubic-bezier(0.61, 1, 0.88, 1)",
- "in-out-sine": "cubic-bezier(0.37, 0, 0.63, 1)",
- "in-cubic": "cubic-bezier(0.32, 0, 0.67, 0)",
- "out-cubic": "cubic-bezier(0.33, 1, 0.68, 1)",
- "in-out-cubic": "cubic-bezier(0.65, 0, 0.35, 1)",
- "in-expo": "cubic-bezier(0.7, 0, 0.84, 0)",
- "out-expo": "cubic-bezier(0.16, 1, 0.3, 1)",
- "in-out-expo": "cubic-bezier(0.87, 0, 0.13, 1)",
- },
- },
- },
- plugins: [
- require("tailwindcss-animate"),
- ],
-} satisfies Config;
diff --git a/interface/vite.config.ts b/interface/vite.config.ts
index 31b763366..06903218c 100644
--- a/interface/vite.config.ts
+++ b/interface/vite.config.ts
@@ -1,30 +1,109 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
import path from "node:path";
+import tailwindcss from "@tailwindcss/vite";
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+const spaceui = path.resolve(__dirname, "../../spaceui/packages");
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
+
resolve: {
- alias: {
- "@": path.resolve(__dirname, "src"),
- },
+ dedupe: ["react", "react-dom"],
+ alias: [
+ // Pin React to a single copy (prevents "Invalid hook call")
+ {
+ find: /^react$/,
+ replacement: path.resolve(
+ __dirname,
+ "./node_modules/react/index.js",
+ ),
+ },
+ {
+ find: /^react\/jsx-runtime$/,
+ replacement: path.resolve(
+ __dirname,
+ "./node_modules/react/jsx-runtime.js",
+ ),
+ },
+ {
+ find: /^react\/jsx-dev-runtime$/,
+ replacement: path.resolve(
+ __dirname,
+ "./node_modules/react/jsx-dev-runtime.js",
+ ),
+ },
+ {
+ find: /^react-dom$/,
+ replacement: path.resolve(
+ __dirname,
+ "./node_modules/react-dom/index.js",
+ ),
+ },
+ {
+ find: /^react-dom\/client$/,
+ replacement: path.resolve(
+ __dirname,
+ "./node_modules/react-dom/client.js",
+ ),
+ },
+
+ // SpaceUI — resolve to source for HMR
+ {
+ find: "@spaceui/tokens/src/css",
+ replacement: `${spaceui}/tokens/src/css`,
+ },
+ {
+ find: "@spaceui/tokens",
+ replacement: `${spaceui}/tokens`,
+ },
+ {
+ find: "@spaceui/primitives",
+ replacement: `${spaceui}/primitives/src/index.ts`,
+ },
+ {
+ find: "@spaceui/ai",
+ replacement: `${spaceui}/ai/src/index.ts`,
+ },
+ {
+ find: "@spaceui/forms",
+ replacement: `${spaceui}/forms/src/index.ts`,
+ },
+ {
+ find: "@spaceui/explorer",
+ replacement: `${spaceui}/explorer/src/index.ts`,
+ },
+
+ // Project alias
+ { find: "@", replacement: path.resolve(__dirname, "src") },
+ ],
+ },
+
+ optimizeDeps: {
+ exclude: [
+ "@spaceui/tokens",
+ "@spaceui/primitives",
+ "@spaceui/ai",
+ "@spaceui/forms",
+ "@spaceui/explorer",
+ ],
},
+
server: {
port: 19840,
+ fs: {
+ allow: [
+ path.resolve(__dirname, ".."),
+ path.resolve(__dirname, "../../spaceui"),
+ ],
+ },
proxy: {
"/api": {
target: "http://127.0.0.1:19898",
changeOrigin: true,
- // SSE: the default http-proxy timeout (2 min) kills long-lived
- // event-stream connections. Setting timeout to 0 disables it.
- // The proxyRes handler also strips buffering hints so chunks
- // flush immediately.
timeout: 0,
configure: (proxy) => {
proxy.on("proxyReq", (_proxyReq, req, _res) => {
- // Disable socket timeout for SSE requests so Node
- // doesn't close the connection after 2 minutes of
- // "inactivity" (SSE heartbeats aren't frequent enough).
if (req.headers.accept?.includes("text/event-stream")) {
_proxyReq.socket?.setTimeout?.(0);
}
@@ -34,7 +113,6 @@ export default defineConfig({
if (ct.includes("text/event-stream")) {
proxyRes.headers["cache-control"] = "no-cache";
proxyRes.headers["x-accel-buffering"] = "no";
- // Keep the socket alive indefinitely for SSE
proxyRes.socket?.setTimeout?.(0);
req.socket?.setTimeout?.(0);
}
@@ -43,6 +121,7 @@ export default defineConfig({
},
},
},
+
build: {
outDir: "dist",
emptyOutDir: true,
diff --git a/packages/api-client/src/client.ts b/packages/api-client/src/client.ts
index c94a7d742..76b433389 100644
--- a/packages/api-client/src/client.ts
+++ b/packages/api-client/src/client.ts
@@ -237,4 +237,55 @@ export const apiClient = {
}),
});
},
+
+ // ── Voice / TTS ──────────────────────────────────────────────────────
+
+ webChatSendAudio(
+ agentId: string,
+ sessionId: string,
+ audioBlob: Blob,
+ senderName?: string,
+ ): Promise {
+ const formData = new FormData();
+ formData.append("agent_id", agentId);
+ formData.append("session_id", sessionId);
+ formData.append("sender_name", senderName ?? "user");
+ formData.append("audio", audioBlob, "voice.webm");
+ return fetch(`${getApiBase()}/webchat/send-audio`, {
+ method: "POST",
+ body: formData,
+ ...(authToken ? { headers: { Authorization: `Bearer ${authToken}` } } : {}),
+ });
+ },
+
+ async ttsGenerate(
+ text: string,
+ options?: { profileId?: string; engine?: string; agentId?: string },
+ ): Promise {
+ const response = await fetch(`${getApiBase()}/tts/generate`, {
+ method: "POST",
+ headers: buildHeaders(),
+ body: JSON.stringify({
+ text,
+ profile_id: options?.profileId,
+ engine: options?.engine,
+ agent_id: options?.agentId,
+ }),
+ });
+ if (!response.ok) throw new Error(`TTS error: ${response.status}`);
+ return response.arrayBuffer();
+ },
+
+ ttsProfiles(agentId?: string) {
+ const params = agentId ? `?agent_id=${encodeURIComponent(agentId)}` : "";
+ return request(`/tts/profiles${params}`);
+ },
};
+
+export interface TtsProfile {
+ id: string;
+ name?: string | null;
+ default_engine?: string | null;
+ preset_engine?: string | null;
+ effects_chain?: unknown;
+}
diff --git a/packages/api-client/src/events.ts b/packages/api-client/src/events.ts
index e330734c8..8c2836777 100644
--- a/packages/api-client/src/events.ts
+++ b/packages/api-client/src/events.ts
@@ -137,6 +137,14 @@ export interface CortexChatMessageEvent {
tool_calls?: CortexChatToolCall[];
}
+export interface SpokenResponseEvent {
+ type: "spoken_response";
+ agent_id: string;
+ channel_id: string;
+ spoken_text: string;
+ full_text: string;
+}
+
export type ApiEvent =
| InboundMessageEvent
| OutboundMessageEvent
@@ -152,4 +160,5 @@ export type ApiEvent =
| ToolCompletedEvent
| OpenCodePartUpdatedEvent
| WorkerTextEvent
- | CortexChatMessageEvent;
+ | CortexChatMessageEvent
+ | SpokenResponseEvent;
From c53a1e38f87e286b3e4973b253b0920345f78e45 Mon Sep 17 00:00:00 2001
From: James Pine
Date: Sat, 4 Apr 2026 08:15:26 -0700
Subject: [PATCH 02/52] SpaceUI migration: sidebar redesign, global projects,
logo detection
- Redesign sidebar to match Spacedrive style: 220px width, labeled nav items,
section headers, consistent row heights, hover states, agent sub-nav
- Make projects global (not per-agent): migration drops agent_id from projects
table, all store/API/tool methods updated, projects route moved to /projects
- Add automatic project logo detection: scans .github/, public/, src-tauri/
for logo/favicon files on create/scan, serves via GET /projects/{id}/logo
- Replace local Toggle with SpaceUI Switch across all files
- Replace SearchInput with SearchBar in AgentChannels
- Use SelectPill + Popover for memory sort dropdown
- Use CircleButtonGroup in CortexChatPanel header
- Add instance selector (SelectPill) in top bar
- New nav structure: Dashboard, Org Chart, Workbench, Tasks + settings footer
- Agent sub-items: Chat, Channels, Memory, Skills, Schedule (accordion style)
- Settings buttons: size md, variant outline
- Input default size changed to md in SpaceUI, focus ring set to accent
Co-Authored-By: Claude Opus 4.6 (1M context)
---
interface/src/api/client.ts | 51 +-
interface/src/components/AgentTabs.tsx | 1 -
.../src/components/ChannelSettingCard.tsx | 6 +-
interface/src/components/CortexChatPanel.tsx | 80 ++--
.../src/components/PromptInspectModal.tsx | 6 +-
interface/src/components/Sidebar.tsx | 305 ++++++++----
interface/src/components/TopBar.tsx | 19 +-
interface/src/router.tsx | 42 +-
interface/src/routes/AgentChannels.tsx | 6 +-
interface/src/routes/AgentConfig.tsx | 7 +-
interface/src/routes/AgentCron.tsx | 8 +-
interface/src/routes/AgentMemories.tsx | 448 ++++++++++--------
interface/src/routes/AgentProjects.tsx | 47 +-
interface/src/routes/Settings.tsx | 112 ++---
migrations/20260404000001_projects_global.sql | 29 ++
migrations/20260404000002_project_logo.sql | 2 +
src/agent/channel.rs | 1 -
src/api/agents.rs | 2 +-
src/api/channels.rs | 1 -
src/api/projects.rs | 236 +++++----
src/api/server.rs | 1 +
src/main.rs | 2 +-
src/projects.rs | 11 +-
src/projects/store.rs | 174 +++++--
src/tools.rs | 1 -
src/tools/project_manage.rs | 21 +-
src/tools/spawn_worker.rs | 5 +-
27 files changed, 983 insertions(+), 641 deletions(-)
create mode 100644 migrations/20260404000001_projects_global.sql
create mode 100644 migrations/20260404000002_project_logo.sql
diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts
index 6435b3e9c..5fa0d65f4 100644
--- a/interface/src/api/client.ts
+++ b/interface/src/api/client.ts
@@ -13,7 +13,7 @@ export function getServerUrl(): string {
return _serverUrl;
}
-function getApiBase(): string {
+export function getApiBase(): string {
if (_serverUrl) return `${_serverUrl}/api`;
return BASE_PATH + "/api";
}
@@ -1153,12 +1153,12 @@ export type ProjectStatus = "active" | "archived";
export interface Project {
id: string;
- agent_id: string;
name: string;
description: string;
icon: string;
tags: string[];
root_path: string;
+ logo_path: string | null;
settings: Record;
status: ProjectStatus;
created_at: string;
@@ -2232,92 +2232,93 @@ export const api = {
},
// Projects API
- listProjects: (agentId: string, status?: ProjectStatus) => {
- const search = new URLSearchParams({ agent_id: agentId });
+ listProjects: (status?: ProjectStatus) => {
+ const search = new URLSearchParams();
if (status) search.set("status", status);
- return fetchJson(`/agents/projects?${search}`);
+ const qs = search.toString();
+ return fetchJson(`/agents/projects${qs ? `?${qs}` : ""}`);
},
- getProject: (agentId: string, projectId: string) =>
+ getProject: (projectId: string) =>
fetchJson(
- `/agents/projects/${encodeURIComponent(projectId)}?agent_id=${encodeURIComponent(agentId)}`,
+ `/agents/projects/${encodeURIComponent(projectId)}`,
),
- createProject: async (agentId: string, request: CreateProjectRequest): Promise => {
+ createProject: async (request: CreateProjectRequest): Promise => {
const response = await fetch(`${getApiBase()}/agents/projects`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ ...request, agent_id: agentId }),
+ body: JSON.stringify(request),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise;
},
- updateProject: async (agentId: string, projectId: string, request: UpdateProjectRequest): Promise => {
+ updateProject: async (projectId: string, request: UpdateProjectRequest): Promise => {
const response = await fetch(`${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ ...request, agent_id: agentId }),
+ body: JSON.stringify(request),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise;
},
- deleteProject: async (agentId: string, projectId: string): Promise => {
+ deleteProject: async (projectId: string): Promise => {
const response = await fetch(
- `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}?agent_id=${encodeURIComponent(agentId)}`,
+ `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}`,
{ method: "DELETE" },
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise;
},
- scanProject: async (agentId: string, projectId: string): Promise => {
+ scanProject: async (projectId: string): Promise => {
const response = await fetch(
- `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/scan?agent_id=${encodeURIComponent(agentId)}`,
+ `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/scan`,
{ method: "POST" },
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise;
},
- projectDiskUsage: (agentId: string, projectId: string) =>
+ projectDiskUsage: (projectId: string) =>
fetchJson(
- `/agents/projects/${encodeURIComponent(projectId)}/disk-usage?agent_id=${encodeURIComponent(agentId)}`,
+ `/agents/projects/${encodeURIComponent(projectId)}/disk-usage`,
),
- createProjectRepo: async (agentId: string, projectId: string, request: CreateRepoRequest): Promise<{ repo: ProjectRepo }> => {
+ createProjectRepo: async (projectId: string, request: CreateRepoRequest): Promise<{ repo: ProjectRepo }> => {
const response = await fetch(`${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/repos`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ ...request, agent_id: agentId }),
+ body: JSON.stringify(request),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise<{ repo: ProjectRepo }>;
},
- deleteProjectRepo: async (agentId: string, projectId: string, repoId: string): Promise => {
+ deleteProjectRepo: async (projectId: string, repoId: string): Promise => {
const response = await fetch(
- `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/repos/${encodeURIComponent(repoId)}?agent_id=${encodeURIComponent(agentId)}`,
+ `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/repos/${encodeURIComponent(repoId)}`,
{ method: "DELETE" },
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise;
},
- createProjectWorktree: async (agentId: string, projectId: string, request: CreateWorktreeRequest): Promise<{ worktree: ProjectWorktree }> => {
+ createProjectWorktree: async (projectId: string, request: CreateWorktreeRequest): Promise<{ worktree: ProjectWorktree }> => {
const response = await fetch(`${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/worktrees`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ ...request, agent_id: agentId }),
+ body: JSON.stringify(request),
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return response.json() as Promise<{ worktree: ProjectWorktree }>;
},
- deleteProjectWorktree: async (agentId: string, projectId: string, worktreeId: string): Promise => {
+ deleteProjectWorktree: async (projectId: string, worktreeId: string): Promise => {
const response = await fetch(
- `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/worktrees/${encodeURIComponent(worktreeId)}?agent_id=${encodeURIComponent(agentId)}`,
+ `${getApiBase()}/agents/projects/${encodeURIComponent(projectId)}/worktrees/${encodeURIComponent(worktreeId)}`,
{ method: "DELETE" },
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
diff --git a/interface/src/components/AgentTabs.tsx b/interface/src/components/AgentTabs.tsx
index 7ea211d29..1f0aaa8b4 100644
--- a/interface/src/components/AgentTabs.tsx
+++ b/interface/src/components/AgentTabs.tsx
@@ -8,7 +8,6 @@ const tabs = [
{ label: "Memories", to: "/agents/$agentId/memories" as const, exact: false },
{ label: "Ingest", to: "/agents/$agentId/ingest" as const, exact: false },
{ label: "Workers", to: "/agents/$agentId/workers" as const, exact: false },
- { label: "Projects", to: "/agents/$agentId/projects" as const, exact: false },
{ label: "Tasks", to: "/agents/$agentId/tasks" as const, exact: false },
{ label: "Cortex", to: "/agents/$agentId/cortex" as const, exact: false },
{ label: "Skills", to: "/agents/$agentId/skills" as const, exact: false },
diff --git a/interface/src/components/ChannelSettingCard.tsx b/interface/src/components/ChannelSettingCard.tsx
index 20aaa0e37..5af599395 100644
--- a/interface/src/components/ChannelSettingCard.tsx
+++ b/interface/src/components/ChannelSettingCard.tsx
@@ -20,8 +20,8 @@ import {
DialogHeader,
DialogTitle,
DialogFooter,
-} from "@spaceui/primitives";
-import { Toggle } from "@/ui/Toggle";
+} from "@spacedrive/primitives";
+import { Switch } from "@spacedrive/primitives";
import {PlatformIcon} from "@/lib/platformIcons";
import {isValidE164, E164_ERROR_TEXT, validateSignalDmAllowedUsers} from "@/lib/format";
import {TagInput} from "@/components/TagInput";
@@ -379,7 +379,7 @@ export function InstanceCard({instance, expanded, onToggleExpand}: InstanceCardP
{instance.enabled ? "Receiving messages" : "Adapter paused"}
- toggleEnabled.mutate(checked)}
disabled={toggleEnabled.isPending}
diff --git a/interface/src/components/CortexChatPanel.tsx b/interface/src/components/CortexChatPanel.tsx
index 563d40bd4..3d11f380e 100644
--- a/interface/src/components/CortexChatPanel.tsx
+++ b/interface/src/components/CortexChatPanel.tsx
@@ -3,7 +3,7 @@ import {useCortexChat, type ToolActivity} from "@/hooks/useCortexChat";
import {Markdown} from "@/components/Markdown";
import {ToolCall, type ToolCallPair} from "@/components/ToolCall";
import {api, type CortexChatToolCall, type CortexChatThread} from "@/api/client";
-import {Button, PopoverRoot, PopoverContent, PopoverTrigger} from "@spaceui/primitives";
+import {Button, CircleButton, CircleButtonGroup, PopoverRoot, PopoverContent, PopoverTrigger} from "@spacedrive/primitives";
import {Plus, X, Clock, Trash} from "@phosphor-icons/react";
interface CortexChatPanelProps {
@@ -419,57 +419,47 @@ export function CortexChatPanel({
)}
-
-
-
-
-
-
-
-
-
- Threads
-
- setThreadListOpen(false)}
- />
-
-
-
+ setThreadListOpen(!threadListOpen)}
+ title="Thread history"
+ />
+
-
-
+ />
{onClose && (
-
-
-
+ />
)}
-
+
+
+
+
+
+
+
+ Threads
+
+ setThreadListOpen(false)}
+ />
+
+
)}
diff --git a/interface/src/components/PromptInspectModal.tsx b/interface/src/components/PromptInspectModal.tsx
index fa916dbff..4bca21606 100644
--- a/interface/src/components/PromptInspectModal.tsx
+++ b/interface/src/components/PromptInspectModal.tsx
@@ -5,8 +5,8 @@ import {
type PromptInspectResponse,
type PromptSnapshotSummary,
} from "@/api/client";
-import { Button, DialogRoot, DialogContent, DialogHeader, DialogTitle } from "@spaceui/primitives";
-import { Toggle } from "@/ui/Toggle";
+import { Button, DialogRoot, DialogContent, DialogHeader, DialogTitle } from "@spacedrive/primitives";
+import { Switch } from "@spacedrive/primitives";
interface PromptInspectModalProps {
open: boolean;
@@ -102,7 +102,7 @@ export function PromptInspectModal({ open, onOpenChange, channelId }: PromptInsp
Capture
-
;
}
+const agentSubItems = [
+ { path: "chat", icon: ChatCircleDots, label: "Chat" },
+ { path: "channels", icon: Broadcast, label: "Channels" },
+ { path: "memories", icon: Brain, label: "Memory" },
+ { path: "skills", icon: Lightning, label: "Skills" },
+ { path: "cron", icon: CalendarDots, label: "Schedule" },
+] as const;
+
interface SortableAgentItemProps {
agentId: string;
displayName?: string | null | undefined;
gradientStart?: string | null | undefined;
gradientEnd?: string | null | undefined;
isActive: boolean;
+ isExpanded: boolean;
+ onToggle: () => void;
}
-function SortableAgentItem({ agentId, displayName, gradientStart, gradientEnd, isActive }: SortableAgentItemProps) {
+function SortableAgentItem({ agentId, displayName, gradientStart, gradientEnd, isActive, isExpanded, onToggle }: SortableAgentItemProps) {
const {
attributes,
listeners,
@@ -47,6 +58,9 @@ function SortableAgentItem({ agentId, displayName, gradientStart, gradientEnd, i
isDragging,
} = useSortable({ id: agentId });
+ const matchRoute = useMatchRoute();
+ const isAgentRoot = !!matchRoute({ to: "/agents/$agentId", params: { agentId } });
+
const style = {
transform: CSS.Transform.toString(transform),
transition,
@@ -54,30 +68,79 @@ function SortableAgentItem({ agentId, displayName, gradientStart, gradientEnd, i
cursor: isDragging ? 'grabbing' : 'grab',
};
+ const name = displayName ?? agentId;
+
return (
{
+ onToggle();
+ }}
+ className={`flex w-full items-center gap-2.5 rounded-lg px-2 py-1.5 text-left transition-colors ${
+ isAgentRoot
+ ? "bg-sidebar-selected/40 text-sidebar-ink"
+ : isActive
+ ? "text-sidebar-ink"
+ : "text-sidebar-inkDull hover:bg-sidebar-selected/20 hover:text-sidebar-ink"
+ }`}
style={{ pointerEvents: isDragging ? 'none' : 'auto' }}
- title={displayName ?? agentId}
>
+
{name}
+ {isExpanded && (
+
+ {agentSubItems.map((item) => {
+ const subActive = !!matchRoute({
+ to: `/agents/$agentId/${item.path}`,
+ params: { agentId },
+ fuzzy: true,
+ });
+ const Icon = item.icon;
+ return (
+
+
+
+
+
{item.label}
+
+ );
+ })}
+
+ )}
);
}
+const navItems = [
+ { to: "/dashboard", icon: House, label: "Dashboard", exact: true },
+ { to: "/", icon: TreeStructure, label: "Org Chart", exact: true },
+ { to: "/orchestrate", icon: Wrench, label: "Workbench", exact: true },
+ { to: "/tasks", icon: CheckSquare, label: "Tasks", exact: true },
+] as const;
+
export function Sidebar({ liveStates: _liveStates }: SidebarProps) {
const [createOpen, setCreateOpen] = useState(false);
+ const [expandedAgent, setExpandedAgent] = useState(null);
+ const [hasDefaulted, setHasDefaulted] = useState(false);
const { data: agentsData } = useQuery({
queryKey: ["agents"],
@@ -91,9 +154,16 @@ export function Sidebar({ liveStates: _liveStates }: SidebarProps) {
staleTime: 10_000,
});
+ const { data: projectsData } = useQuery({
+ queryKey: ["projects"],
+ queryFn: () => api.listProjects("active"),
+ staleTime: 30_000,
+ });
+
const hasProvider = providersData?.has_any ?? false;
const agents = agentsData?.agents ?? [];
+ const projects = projectsData?.projects ?? [];
const agentIds = useMemo(() => agents.map((a) => a.id), [agents]);
const agentDisplayNames = useMemo(() => {
@@ -108,11 +178,13 @@ export function Sidebar({ liveStates: _liveStates }: SidebarProps) {
}, [agents]);
const [agentOrder, setAgentOrder] = useAgentOrder(agentIds);
+ // Default-expand the first agent once loaded
+ if (!hasDefaulted && agentOrder.length > 0) {
+ setExpandedAgent(agentOrder[0]);
+ setHasDefaulted(true);
+ }
+
const matchRoute = useMatchRoute();
- const isOverview = matchRoute({ to: "/" });
- const isTasks = matchRoute({ to: "/tasks" });
- const isSettings = matchRoute({ to: "/settings" });
- const isOrchestrate = matchRoute({ to: "/orchestrate" });
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -136,84 +208,145 @@ export function Sidebar({ liveStates: _liveStates }: SidebarProps) {
};
return (
-
diff --git a/interface/src/routes/AgentMemories.tsx b/interface/src/routes/AgentMemories.tsx
index 5d87858d1..3239e671a 100644
--- a/interface/src/routes/AgentMemories.tsx
+++ b/interface/src/routes/AgentMemories.tsx
@@ -1,7 +1,7 @@
-import { useCallback, useEffect, useRef, useState } from "react";
-import { useQuery } from "@tanstack/react-query";
-import { useVirtualizer } from "@tanstack/react-virtual";
-import { AnimatePresence, motion } from "framer-motion";
+import {useCallback, useEffect, useRef, useState} from "react";
+import {useQuery} from "@tanstack/react-query";
+import {useVirtualizer} from "@tanstack/react-virtual";
+import {AnimatePresence, motion} from "framer-motion";
import {
api,
MEMORY_TYPES,
@@ -9,27 +9,27 @@ import {
type MemorySort,
type MemoryType,
} from "@/api/client";
-import { CortexChatPanel } from "@/components/CortexChatPanel";
-import { MemoryGraph } from "@/components/MemoryGraph";
+import {CortexChatPanel} from "@/components/CortexChatPanel";
+import {MemoryGraph} from "@/components/MemoryGraph";
import {
CircleButton,
CircleButtonGroup,
- DropdownMenuRoot,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
- SearchInput,
+ SearchBar,
FilterButton,
-} from "@spaceui/primitives";
-import { formatTimeAgo } from "@/lib/format";
-import { CaretDown, List, TreeStructure, Lightbulb } from "@phosphor-icons/react";
+ SelectPill,
+ Popover,
+ OptionList,
+ OptionListItem,
+} from "@spacedrive/primitives";
+import {formatTimeAgo} from "@/lib/format";
+import {List, TreeStructure, Lightbulb} from "@phosphor-icons/react";
type ViewMode = "list" | "graph";
-const SORT_OPTIONS: { value: MemorySort; label: string }[] = [
- { value: "recent", label: "Recent" },
- { value: "importance", label: "Importance" },
- { value: "most_accessed", label: "Most Accessed" },
+const SORT_OPTIONS: {value: MemorySort; label: string}[] = [
+ {value: "recent", label: "Recent"},
+ {value: "importance", label: "Importance"},
+ {value: "most_accessed", label: "Most Accessed"},
];
const TYPE_COLORS: Record = {
@@ -43,21 +43,23 @@ const TYPE_COLORS: Record = {
todo: "bg-red-500/15 text-red-400",
};
-function TypeBadge({ type: memoryType }: { type: MemoryType }) {
+function TypeBadge({type: memoryType}: {type: MemoryType}) {
return (
-
+
{memoryType}
);
}
-function ImportanceBar({ value }: { value: number }) {
+function ImportanceBar({value}: {value: number}) {
return (
{value.toFixed(2)}
@@ -69,7 +71,7 @@ interface AgentMemoriesProps {
agentId: string;
}
-export function AgentMemories({ agentId }: AgentMemoriesProps) {
+export function AgentMemories({agentId}: AgentMemoriesProps) {
const [viewMode, setViewMode] = useState
("list");
const [searchQuery, setSearchQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] = useState("");
@@ -77,6 +79,7 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
const [typeFilter, setTypeFilter] = useState(null);
const [expandedId, setExpandedId] = useState(null);
const [chatOpen, setChatOpen] = useState(true);
+ const [sortOpen, setSortOpen] = useState(false);
const parentRef = useRef(null);
@@ -120,19 +123,27 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
const scores: Record | null = isSearching
? Object.fromEntries(
- (searchQueryResult.data?.results ?? []).map((r) => [r.memory.id, r.score]),
+ (searchQueryResult.data?.results ?? []).map((r) => [
+ r.memory.id,
+ r.score,
+ ]),
)
: null;
- const isLoading = isSearching ? searchQueryResult.isLoading : listQuery.isLoading;
+ const isLoading = isSearching
+ ? searchQueryResult.isLoading
+ : listQuery.isLoading;
const isError = isSearching ? searchQueryResult.isError : listQuery.isError;
const virtualizer = useVirtualizer({
count: memories.length,
getScrollElement: () => parentRef.current,
- estimateSize: useCallback((index: number) => {
- return expandedId === memories[index]?.id ? 200 : 48;
- }, [expandedId, memories]),
+ estimateSize: useCallback(
+ (index: number) => {
+ return expandedId === memories[index]?.id ? 200 : 48;
+ },
+ [expandedId, memories],
+ ),
overscan: 10,
});
@@ -144,206 +155,227 @@ export function AgentMemories({ agentId }: AgentMemoriesProps) {
return (
- {/* Toolbar */}
-
- {/* Search */}
-
setSearchQuery(event.target.value)}
- className="flex-1"
- />
+ {/* Toolbar */}
+
+ {/* Search */}
+
setSearchQuery(event.target.value)}
+ className="flex-1"
+ />
- {/* Sort dropdown */}
-
-
- {SORT_OPTIONS.find((o) => o.value === sort)?.label ?? sort}
-
-
-
- {SORT_OPTIONS.map((option) => (
- setSort(option.value)}
- className={option.value === sort ? "bg-app-hover text-ink !bg-app-hover" : ""}
- >
- {option.label}
-
- ))}
-
-
+ {/* Sort pill */}
+
+
+
+ {SORT_OPTIONS.find((o) => o.value === sort)?.label ?? sort}
+
+
+
+
+ {SORT_OPTIONS.map((option) => (
+ {
+ setSort(option.value);
+ setSortOpen(false);
+ }}
+ >
+ {option.label}
+
+ ))}
+
+
+
- {/* View mode toggle */}
-
- setViewMode("list")}
- variant={viewMode === "list" ? "active" : "default"}
- title="List view"
- />
- setViewMode("graph")}
- variant={viewMode === "graph" ? "active" : "default"}
- title="Graph view"
- />
-
+ {/* View mode toggle */}
+
+ setViewMode("list")}
+ variant={viewMode === "list" ? "active" : "default"}
+ title="List view"
+ />
+ setViewMode("graph")}
+ variant={viewMode === "graph" ? "active" : "default"}
+ title="Graph view"
+ />
+
- {/* Cortex chat toggle */}
- setChatOpen(!chatOpen)}
- variant={chatOpen ? "accent" : "default"}
- title="Toggle cortex chat"
- />
-
+ {/* Cortex chat toggle */}
+ setChatOpen(!chatOpen)}
+ variant={chatOpen ? "accent" : "default"}
+ title="Toggle cortex chat"
+ />
+
- {/* Type filter pills */}
-
-
setTypeFilter(null)}
- active={typeFilter === null}
- >
- All
-
- {MEMORY_TYPES.map((type_) => (
+ {/* Type filter pills */}
+
setTypeFilter(typeFilter === type_ ? null : type_)}
- active={typeFilter === type_}
- colorClass={TYPE_COLORS[type_]}
+ onClick={() => setTypeFilter(null)}
+ active={typeFilter === null}
>
- {type_}
+ All
- ))}
- {memories.length > 0 && (
-
- {memories.length} {isSearching ? "results" : "memories"}
-
- )}
-
+ {MEMORY_TYPES.map((type_) => (
+
setTypeFilter(typeFilter === type_ ? null : type_)}
+ active={typeFilter === type_}
+ colorClass={TYPE_COLORS[type_]}
+ >
+ {type_}
+
+ ))}
+ {memories.length > 0 && (
+
+ {memories.length} {isSearching ? "results" : "memories"}
+
+ )}
+
- {viewMode === "graph" ? (
-
- ) : (
- <>
- {/* Table header */}
-
- Type
- {isSearching ? "Content / Score" : "Content"}
- Importance
- Source
- Created
-
+ {viewMode === "graph" ? (
+
+ ) : (
+ <>
+ {/* Table header */}
+
+ Type
+ {isSearching ? "Content / Score" : "Content"}
+ Importance
+ Source
+ Created
+
- {/* Virtualized rows */}
- {isLoading ? (
-
-
-
- {isSearching ? "Searching..." : "Loading memories..."}
+ {/* Virtualized rows */}
+ {isLoading ? (
+
+
+
+ {isSearching ? "Searching..." : "Loading memories..."}
+
-
- ) : isError ? (
-
-
Failed to load memories
-
- ) : memories.length === 0 ? (
-
-
- {isSearching ? "No results found" : "No memories yet"}
-
-
- ) : (
-
-
- {virtualizer.getVirtualItems().map((virtualRow) => {
- const memory = memories[virtualRow.index];
- if (!memory) return null;
- const isExpanded = expandedId === memory.id;
- const score = scores?.[memory.id];
+ ) : isError ? (
+
+
Failed to load memories
+
+ ) : memories.length === 0 ? (
+
+
+ {isSearching ? "No results found" : "No memories yet"}
+
+
+ ) : (
+
+
+ {virtualizer.getVirtualItems().map((virtualRow) => {
+ const memory = memories[virtualRow.index];
+ if (!memory) return null;
+ const isExpanded = expandedId === memory.id;
+ const score = scores?.[memory.id];
- return (
-
-
setExpandedId(isExpanded ? null : memory.id)}
- className="grid w-full grid-cols-[80px_1fr_100px_120px_100px] items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-app-hover"
- >
-
-
-
- {memory.content}
-
- {score !== undefined && (
-
- score: {score.toFixed(3)}
-
- )}
-
-
-
- {memory.source ?? "-"}
-
-
- {formatTimeAgo(memory.created_at)}
-
-
+ return (
+
+
+ setExpandedId(isExpanded ? null : memory.id)
+ }
+ className="grid w-full grid-cols-[80px_1fr_100px_120px_100px] items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-app-hover"
+ >
+
+
+
+ {memory.content}
+
+ {score !== undefined && (
+
+ score: {score.toFixed(3)}
+
+ )}
+
+
+
+ {memory.source ?? "-"}
+
+
+ {formatTimeAgo(memory.created_at)}
+
+
- {/* Expanded detail */}
-
- {isExpanded && (
-
-
-
- {memory.content}
-
-
-
ID: {memory.id}
-
Accessed: {memory.access_count}x
-
Last accessed: {formatTimeAgo(memory.last_accessed_at)}
-
Updated: {formatTimeAgo(memory.updated_at)}
- {memory.channel_id && (
-
Channel: {memory.channel_id}
- )}
+ {/* Expanded detail */}
+
+ {isExpanded && (
+
+
+
+ {memory.content}
+
+
+ ID: {memory.id}
+ Accessed: {memory.access_count}x
+
+ Last accessed:{" "}
+ {formatTimeAgo(memory.last_accessed_at)}
+
+
+ Updated: {formatTimeAgo(memory.updated_at)}
+
+ {memory.channel_id && (
+ Channel: {memory.channel_id}
+ )}
+
-
-
- )}
-
-
- );
- })}
+
+ )}
+
+
+ );
+ })}
+
-
- )}
- >
- )}
+ )}
+ >
+ )}
{/* Cortex chat panel */}
{chatOpen && (
diff --git a/interface/src/routes/AgentProjects.tsx b/interface/src/routes/AgentProjects.tsx
index d32b4bdc8..00f2a32da 100644
--- a/interface/src/routes/AgentProjects.tsx
+++ b/interface/src/routes/AgentProjects.tsx
@@ -8,7 +8,7 @@ import {
type CreateProjectRequest,
type CreateWorktreeRequest,
} from "@/api/client";
-import { Badge, Button, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Input, Label, TextArea } from "@spaceui/primitives";
+import { Badge, Button, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, Input, Label, TextArea } from "@spacedrive/primitives";
import { formatTimeAgo } from "@/lib/format";
import { clsx } from "clsx";
import { AnimatePresence, motion } from "framer-motion";
@@ -105,11 +105,9 @@ function ProjectCard({
function CreateProjectDialog({
open,
onOpenChange,
- agentId,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
- agentId: string;
}) {
const queryClient = useQueryClient();
const [name, setName] = useState("");
@@ -120,9 +118,9 @@ function CreateProjectDialog({
const createMutation = useMutation({
mutationFn: (request: CreateProjectRequest) =>
- api.createProject(agentId, request),
+ api.createProject(request),
onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ["projects", agentId] });
+ queryClient.invalidateQueries({ queryKey: ["projects"] });
onOpenChange(false);
setName("");
setRootPath("");
@@ -235,13 +233,11 @@ function CreateProjectDialog({
function CreateWorktreeDialog({
open,
onOpenChange,
- agentId,
projectId,
repos,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
- agentId: string;
projectId: string;
repos: ProjectRepo[];
}) {
@@ -261,10 +257,10 @@ function CreateWorktreeDialog({
const createMutation = useMutation({
mutationFn: (request: CreateWorktreeRequest) =>
- api.createProjectWorktree(agentId, projectId, request),
+ api.createProjectWorktree(projectId, request),
onSuccess: () => {
queryClient.invalidateQueries({
- queryKey: ["project", agentId, projectId],
+ queryKey: ["project", projectId],
});
onOpenChange(false);
setBranch("");
@@ -546,55 +542,53 @@ function WorktreeCard({
// ---------------------------------------------------------------------------
function ProjectDetail({
- agentId,
projectId,
onBack,
}: {
- agentId: string;
projectId: string;
onBack: () => void;
}) {
const queryClient = useQueryClient();
const { data: project, isLoading } = useQuery({
- queryKey: ["project", agentId, projectId],
- queryFn: () => api.getProject(agentId, projectId),
+ queryKey: ["project", projectId],
+ queryFn: () => api.getProject(projectId),
refetchInterval: 10_000,
});
const scanMutation = useMutation({
- mutationFn: () => api.scanProject(agentId, projectId),
+ mutationFn: () => api.scanProject(projectId),
onSuccess: () => {
queryClient.invalidateQueries({
- queryKey: ["project", agentId, projectId],
+ queryKey: ["project", projectId],
});
},
});
const deleteProjectMutation = useMutation({
- mutationFn: () => api.deleteProject(agentId, projectId),
+ mutationFn: () => api.deleteProject(projectId),
onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ["projects", agentId] });
+ queryClient.invalidateQueries({ queryKey: ["projects"] });
onBack();
},
});
const deleteRepoMutation = useMutation({
mutationFn: (repoId: string) =>
- api.deleteProjectRepo(agentId, projectId, repoId),
+ api.deleteProjectRepo(projectId, repoId),
onSuccess: () => {
queryClient.invalidateQueries({
- queryKey: ["project", agentId, projectId],
+ queryKey: ["project", projectId],
});
},
});
const deleteWorktreeMutation = useMutation({
mutationFn: (worktreeId: string) =>
- api.deleteProjectWorktree(agentId, projectId, worktreeId),
+ api.deleteProjectWorktree(projectId, worktreeId),
onSuccess: () => {
queryClient.invalidateQueries({
- queryKey: ["project", agentId, projectId],
+ queryKey: ["project", projectId],
});
},
});
@@ -812,7 +806,6 @@ function ProjectDetail({
(
null,
);
const [showCreate, setShowCreate] = useState(false);
const { data, isLoading } = useQuery({
- queryKey: ["projects", agentId],
- queryFn: () => api.listProjects(agentId),
+ queryKey: ["projects"],
+ queryFn: () => api.listProjects(),
refetchInterval: 15_000,
});
@@ -892,7 +885,6 @@ export function AgentProjects({ agentId }: { agentId: string }) {
if (selectedProjectId) {
return (
setSelectedProjectId(null)}
/>
@@ -908,7 +900,7 @@ export function AgentProjects({ agentId }: { agentId: string }) {
Projects
- Workspaces, repos, and worktrees this agent knows about.
+ Workspaces, repos, and worktrees across all agents.
setShowCreate(true)}>
@@ -952,7 +944,6 @@ export function AgentProjects({ agentId }: { agentId: string }) {
);
diff --git a/interface/src/routes/Settings.tsx b/interface/src/routes/Settings.tsx
index 4f756883d..e87a1fd60 100644
--- a/interface/src/routes/Settings.tsx
+++ b/interface/src/routes/Settings.tsx
@@ -1,9 +1,9 @@
import { useState, useEffect, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, type GlobalSettingsResponse, type UpdateStatus, type SecretCategory, type SecretListItem, type StoreState } from "@/api/client";
-import { Badge, Button, Input, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, SelectRoot, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@spaceui/primitives";
+import { Badge, Button, Input, DialogRoot, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, SelectRoot, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@spacedrive/primitives";
import { SettingSidebarButton } from "@/ui/SettingSidebarButton";
-import { Toggle } from "@/ui/Toggle";
+import { Switch } from "@spacedrive/primitives";
import { useSearch, useNavigate } from "@tanstack/react-router";
import { PlatformCatalog, InstanceCard, AddInstanceCard } from "@/components/ChannelSettingCard";
import { ModelSelect } from "@/components/ModelSelect";
@@ -982,7 +982,7 @@ export function Settings() {
disabled={!editingProvider || !modelInput.trim() || (editingProvider === "azure" && (!azureBaseUrl.trim() || !azureApiVersion.trim() || !azureDeployment.trim()))}
loading={testModelMutation.isPending}
variant="outline"
- size="sm"
+ size="md"
>
Test model
@@ -1021,33 +1021,33 @@ export function Settings() {
onClick={() => removeMutation.mutate(editingProvider, { onSuccess: (result) => { if (result.success) handleClose(); } })}
loading={removeMutation.isPending}
variant="destructive"
- size="sm"
+ size="md"
>
Remove
)}
-
+
Cancel
Save
>
) : (
<>
-
+
Cancel
Save
@@ -1498,7 +1498,7 @@ function SecretsSection() {
onClick={() => unlockMutation.mutate(unlockKeyInput.trim())}
disabled={!unlockKeyInput.trim()}
loading={unlockMutation.isPending}
- size="sm"
+ size="md"
>
Unlock
@@ -1542,7 +1542,7 @@ function SecretsSection() {
{canMutate && (
-
+
Add secret
)}
@@ -1566,10 +1566,10 @@ function SecretsSection() {
{secrets.length === 0 && canMutate && (
-
+
Add secret
- migrateMutation.mutate()} variant="outline" size="sm" loading={migrateMutation.isPending}>
+ migrateMutation.mutate()} variant="outline" size="md" loading={migrateMutation.isPending}>
Migrate from config
@@ -1587,7 +1587,7 @@ function SecretsSection() {
{secret.name}
{secret.category}
@@ -1599,10 +1599,10 @@ function SecretsSection() {
{canMutate && (
- handleOpenEdit(secret)} variant="outline" size="sm">
+ handleOpenEdit(secret)} variant="outline" size="md">
Update
- setDeleteTarget(secret.name)} variant="outline" size="sm">
+ setDeleteTarget(secret.name)} variant="outline" size="md">
Delete
@@ -1615,14 +1615,14 @@ function SecretsSection() {
{/* Bottom actions for encrypted stores */}
{storeStatus?.encrypted && !storeStatus.platform_managed && canMutate && (
-
lockMutation.mutate()} variant="outline" size="sm" loading={lockMutation.isPending}>
+ lockMutation.mutate()} variant="outline" size="md" loading={lockMutation.isPending}>
Lock store
- setRotateDialogOpen(true)} variant="outline" size="sm">
+ setRotateDialogOpen(true)} variant="outline" size="md">
Rotate master key
- migrateMutation.mutate()} variant="outline" size="sm" loading={migrateMutation.isPending}>
+ migrateMutation.mutate()} variant="outline" size="md" loading={migrateMutation.isPending}>
Migrate from config
@@ -1631,7 +1631,7 @@ function SecretsSection() {
{/* Migrate button for unencrypted stores */}
{state === "unencrypted" && secrets.length > 0 && (
- migrateMutation.mutate()} variant="outline" size="sm" loading={migrateMutation.isPending}>
+ migrateMutation.mutate()} variant="outline" size="md" loading={migrateMutation.isPending}>
Migrate from config
@@ -1715,8 +1715,8 @@ function SecretsSection() {
{ setAddDialogOpen(false); setEditingSecret(null); }}
- variant="ghost"
- size="sm"
+ variant="outline"
+ size="md"
>
Cancel
@@ -1724,7 +1724,7 @@ function SecretsSection() {
onClick={handleSave}
disabled={(!editingSecret && !nameInput.trim()) || !valueInput}
loading={putMutation.isPending}
- size="sm"
+ size="md"
>
Save
@@ -1743,14 +1743,14 @@ function SecretsSection() {
- setDeleteTarget(null)} variant="ghost" size="sm">
+ setDeleteTarget(null)} variant="outline" size="md">
Cancel
{ if (deleteTarget) deleteMutation.mutate(deleteTarget); }}
loading={deleteMutation.isPending}
variant="destructive"
- size="sm"
+ size="md"
>
Delete
@@ -1783,15 +1783,15 @@ function SecretsSection() {
setEncryptDialogOpen(false)}
- variant="ghost"
- size="sm"
+ variant="outline"
+ size="md"
>
Cancel
encryptMutation.mutate()}
loading={encryptMutation.isPending}
- size="sm"
+ size="md"
>
Enable encryption
@@ -1811,7 +1811,7 @@ function SecretsSection() {
{masterKeyDisplay}
-
+
{masterKeyCopied ? "Copied" : "Copy"}
@@ -1827,7 +1827,7 @@ function SecretsSection() {
setMasterKeyDisplay(null);
setMasterKeyCopied(false);
}}
- size="sm"
+ size="md"
>
Done
@@ -1848,13 +1848,13 @@ function SecretsSection() {
- setRotateDialogOpen(false)} variant="ghost" size="sm">
+ setRotateDialogOpen(false)} variant="outline" size="md">
Cancel
rotateMutation.mutate()}
loading={rotateMutation.isPending}
- size="sm"
+ size="md"
>
Rotate key
@@ -1939,7 +1939,7 @@ function ApiKeysSection({ settings, isLoading }: GlobalSettingsSectionProps) {
setMessage(null);
}}
variant="outline"
- size="sm"
+ size="md"
>
{settings?.brave_search_key ? "Update" : "Add key"}
@@ -1947,7 +1947,7 @@ function ApiKeysSection({ settings, isLoading }: GlobalSettingsSectionProps) {
Remove
@@ -1989,14 +1989,14 @@ function ApiKeysSection({ settings, isLoading }: GlobalSettingsSectionProps) {
}}
/>
- setEditingBraveKey(false)} variant="ghost" size="sm">
+ setEditingBraveKey(false)} variant="outline" size="md">
Cancel
Save
@@ -2076,8 +2076,8 @@ function ServerSection({ settings, isLoading }: GlobalSettingsSectionProps) {
Disable to prevent the HTTP API from starting
-
@@ -2618,7 +2618,7 @@ function UpdatesSection() {
checkNowMutation.mutate();
}}
loading={checkNowMutation.isPending || isFetching}
- size="sm"
+ size="md"
variant="outline"
>
Check now
@@ -2679,7 +2679,7 @@ function UpdatesSection() {
}}
disabled={!data?.can_apply || !data?.update_available}
loading={applyMutation.isPending}
- size="sm"
+ size="md"
>
Update now
@@ -2711,8 +2711,8 @@ function UpdatesSection() {
Docker Compose
handleCopy("compose", dockerComposeCommands.join("\n"))}
- variant="ghost"
- size="sm"
+ variant="outline"
+ size="md"
>
{copiedBlock === "compose" ? "Copied" : "Copy"}
@@ -2724,8 +2724,8 @@ function UpdatesSection() {
docker run
handleCopy("docker-run", dockerRunCommands.join("\n"))}
- variant="ghost"
- size="sm"
+ variant="outline"
+ size="md"
>
{copiedBlock === "docker-run" ? "Copied" : "Copy"}
@@ -2741,8 +2741,8 @@ function UpdatesSection() {
Source Install
handleCopy("native", nativeCommands.join("\n"))}
- variant="ghost"
- size="sm"
+ variant="outline"
+ size="md"
>
{copiedBlock === "native" ? "Copied" : "Copy"}
@@ -3023,7 +3023,7 @@ function ConfigFileSection() {
{isDirty && (
-
+
Revert
)}
@@ -3031,7 +3031,7 @@ function ConfigFileSection() {
onClick={handleSave}
disabled={!isDirty || !!validationError}
loading={updateMutation.isPending}
- size="sm"
+ size="md"
>
Save
@@ -3112,11 +3112,11 @@ function ProviderCard({
-
+
{primaryLabel}
{shouldShowRemove && (
-
+
Remove
)}
@@ -3194,7 +3194,7 @@ function ChatGptOAuthDialog({
{deviceCodeInfo.userCode}
-
+
{deviceCodeCopied ? "Copied" : "Copy"}
@@ -3209,7 +3209,7 @@ function ChatGptOAuthDialog({
Open login page
@@ -3242,19 +3242,19 @@ function ChatGptOAuthDialog({
{message && !deviceCodeInfo ? (
/* Completed — show Done (or Retry for errors) */
message.type === "success" ? (
- onOpenChange(false)} size="sm">
+ onOpenChange(false)} size="md">
Done
) : (
<>
- onOpenChange(false)} variant="ghost" size="sm">
+ onOpenChange(false)} variant="outline" size="md">
Close
Try again
@@ -3262,7 +3262,7 @@ function ChatGptOAuthDialog({
)
) : (
<>
- onOpenChange(false)} variant="ghost" size="sm">
+ onOpenChange(false)} variant="outline" size="md">
Cancel
{deviceCodeInfo && (
@@ -3271,7 +3271,7 @@ function ChatGptOAuthDialog({
disabled={isRequesting}
loading={isRequesting}
variant="outline"
- size="sm"
+ size="md"
>
Get new code
diff --git a/migrations/20260404000001_projects_global.sql b/migrations/20260404000001_projects_global.sql
new file mode 100644
index 000000000..f4526347b
--- /dev/null
+++ b/migrations/20260404000001_projects_global.sql
@@ -0,0 +1,29 @@
+-- Make projects a global concept (not per-agent).
+--
+-- Previously projects were scoped by agent_id with a unique constraint on
+-- (agent_id, root_path). Now projects are shared across all agents with a
+-- unique constraint on just root_path.
+--
+-- SQLite doesn't support DROP CONSTRAINT, so we recreate the table.
+
+CREATE TABLE projects_new (
+ id TEXT PRIMARY KEY NOT NULL,
+ name TEXT NOT NULL DEFAULT '',
+ description TEXT NOT NULL DEFAULT '',
+ icon TEXT NOT NULL DEFAULT '',
+ tags TEXT NOT NULL DEFAULT '[]',
+ root_path TEXT NOT NULL,
+ settings TEXT NOT NULL DEFAULT '{}',
+ status TEXT NOT NULL DEFAULT 'active',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO projects_new (id, name, description, icon, tags, root_path, settings, status, created_at, updated_at)
+SELECT id, name, description, icon, tags, root_path, settings, status, created_at, updated_at
+FROM projects;
+
+DROP TABLE projects;
+ALTER TABLE projects_new RENAME TO projects;
+
+CREATE UNIQUE INDEX idx_projects_root_path ON projects(root_path);
diff --git a/migrations/20260404000002_project_logo.sql b/migrations/20260404000002_project_logo.sql
new file mode 100644
index 000000000..8d7a7b6b1
--- /dev/null
+++ b/migrations/20260404000002_project_logo.sql
@@ -0,0 +1,2 @@
+-- Add logo_path column to projects table for auto-detected project logos.
+ALTER TABLE projects ADD COLUMN logo_path TEXT;
diff --git a/src/agent/channel.rs b/src/agent/channel.rs
index 1539bda2e..c627dc9f4 100644
--- a/src/agent/channel.rs
+++ b/src/agent/channel.rs
@@ -2194,7 +2194,6 @@ impl Channel {
let store = &self.deps.project_store;
let projects = match store
.list_projects(
- &self.deps.agent_id,
Some(crate::projects::ProjectStatus::Active),
)
.await
diff --git a/src/api/agents.rs b/src/api/agents.rs
index 90d790a56..29cba5e92 100644
--- a/src/api/agents.rs
+++ b/src/api/agents.rs
@@ -867,7 +867,7 @@ pub async fn create_agent_internal(
let project_store = std::sync::Arc::new(crate::projects::ProjectStore::new(db.sqlite.clone()));
// Inject active project root paths into the sandbox allowlist.
- crate::projects::refresh_sandbox_project_paths(&project_store, &arc_agent_id, &sandbox).await;
+ crate::projects::refresh_sandbox_project_paths(&project_store, &sandbox).await;
let deps = crate::AgentDeps {
agent_id: arc_agent_id.clone(),
diff --git a/src/api/channels.rs b/src/api/channels.rs
index bb996dbfa..6efc9aedc 100644
--- a/src/api/channels.rs
+++ b/src/api/channels.rs
@@ -709,7 +709,6 @@ pub(super) async fn inspect_prompt(
let store = &channel_state.deps.project_store;
let projects = store
.list_projects(
- &channel_state.deps.agent_id,
Some(crate::projects::ProjectStatus::Active),
)
.await
diff --git a/src/api/projects.rs b/src/api/projects.rs
index 414b0c8ad..a67dd2276 100644
--- a/src/api/projects.rs
+++ b/src/api/projects.rs
@@ -4,7 +4,8 @@ use super::state::ApiState;
use axum::Json;
use axum::extract::{Path, Query, State};
-use axum::http::StatusCode;
+use axum::http::{StatusCode, header};
+use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
@@ -48,21 +49,14 @@ fn sanitize_segment(name: &str) -> Result {
// Query / request types
// ---------------------------------------------------------------------------
-#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)]
-pub(super) struct AgentQuery {
- agent_id: String,
-}
-
#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)]
pub(super) struct ProjectListQuery {
- agent_id: String,
#[serde(default)]
status: Option,
}
#[derive(Deserialize, utoipa::ToSchema)]
pub(super) struct CreateProjectRequest {
- agent_id: String,
name: String,
#[serde(default)]
description: Option,
@@ -80,7 +74,6 @@ pub(super) struct CreateProjectRequest {
#[derive(Deserialize, utoipa::ToSchema)]
pub(super) struct UpdateProjectRequest {
- agent_id: String,
#[serde(default)]
name: Option,
#[serde(default)]
@@ -97,7 +90,6 @@ pub(super) struct UpdateProjectRequest {
#[derive(Deserialize, utoipa::ToSchema)]
pub(super) struct CreateRepoRequest {
- agent_id: String,
name: String,
path: String,
#[serde(default)]
@@ -110,7 +102,6 @@ pub(super) struct CreateRepoRequest {
#[derive(Deserialize, utoipa::ToSchema)]
pub(super) struct CreateWorktreeRequest {
- agent_id: String,
repo_id: String,
branch: String,
#[serde(default)]
@@ -169,11 +160,12 @@ fn default_true() -> bool {
/// Refresh the sandbox allowlist with project root paths after a project
/// create, delete, or scan. Best-effort — logs and continues on error.
-async fn refresh_sandbox(state: &ApiState, agent_id: &str) {
+async fn refresh_sandbox(state: &ApiState) {
let stores = state.project_stores.load();
+ let Some(store) = stores.values().next() else { return };
let sandboxes = state.sandboxes.load();
- if let (Some(store), Some(sandbox)) = (stores.get(agent_id), sandboxes.get(agent_id)) {
- crate::projects::refresh_sandbox_project_paths(store, agent_id, sandbox).await;
+ for sandbox in sandboxes.values() {
+ crate::projects::refresh_sandbox_project_paths(store, sandbox).await;
}
}
@@ -309,7 +301,7 @@ async fn compute_and_cache_disk_usage(
// Handlers
// ---------------------------------------------------------------------------
-/// GET /agents/projects — list projects for an agent.
+/// GET /agents/projects — list projects.
#[utoipa::path(
get,
path = "/agents/projects",
@@ -318,7 +310,7 @@ async fn compute_and_cache_disk_usage(
),
responses(
(status = 200, body = ProjectListResponse),
- (status = 404, description = "Agent not found"),
+ (status = 404, description = "No project store available"),
),
tag = "projects",
)]
@@ -327,12 +319,12 @@ pub(super) async fn list_projects(
Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let status = query.status.as_deref().and_then(ProjectStatus::parse);
let projects = store
- .list_projects(&query.agent_id, status)
+ .list_projects(status)
.await
.map_err(|error| {
tracing::error!(%error, "failed to list projects");
@@ -349,7 +341,7 @@ pub(super) async fn list_projects(
request_body = CreateProjectRequest,
responses(
(status = 200, body = ProjectResponse),
- (status = 404, description = "Agent not found"),
+ (status = 404, description = "No project store available"),
),
tag = "projects",
)]
@@ -358,11 +350,10 @@ pub(super) async fn create_project(
Json(request): Json,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&request.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let project = store
.create_project(CreateProjectInput {
- agent_id: request.agent_id.clone(),
name: request.name,
description: request.description.unwrap_or_default(),
icon: request.icon.unwrap_or_default(),
@@ -379,43 +370,53 @@ pub(super) async fn create_project(
})?;
// Refresh sandbox allowlist with new project path.
- refresh_sandbox(&state, &request.agent_id).await;
+ refresh_sandbox(&state).await;
// Auto-discover repos, worktrees, and disk usage in the background so the
// API responds immediately. The UI will pick up discovered repos on its
// next query invalidation / refetch.
- if request.auto_discover {
+ {
let root = std::path::PathBuf::from(&request.root_path);
if root.is_dir() {
let store = store.clone();
let project_id = project.id.clone();
+ let auto_discover = request.auto_discover;
tokio::spawn(async move {
- match crate::projects::git::discover_repos(&root).await {
- Ok(discovered) => {
- for repo in discovered {
- if let Err(error) = store
- .create_repo(CreateRepoInput {
- project_id: project_id.clone(),
- name: repo.name,
- path: repo.relative_path,
- remote_url: repo.remote_url,
- default_branch: repo.default_branch,
- current_branch: repo.current_branch,
- description: String::new(),
- })
- .await
- {
- tracing::warn!(%error, "failed to register discovered repo");
+ // Detect and set project logo.
+ if let Some(logo) = crate::projects::detect_logo(&root) {
+ if let Err(error) = store.set_logo_path(&project_id, Some(&logo)).await {
+ tracing::warn!(%error, "failed to set detected logo path");
+ }
+ }
+
+ if auto_discover {
+ match crate::projects::git::discover_repos(&root).await {
+ Ok(discovered) => {
+ for repo in discovered {
+ if let Err(error) = store
+ .create_repo(CreateRepoInput {
+ project_id: project_id.clone(),
+ name: repo.name,
+ path: repo.relative_path,
+ remote_url: repo.remote_url,
+ default_branch: repo.default_branch,
+ current_branch: repo.current_branch,
+ description: String::new(),
+ })
+ .await
+ {
+ tracing::warn!(%error, "failed to register discovered repo");
+ }
}
}
+ Err(error) => {
+ tracing::warn!(%error, "failed to discover repos in project root");
+ }
}
- Err(error) => {
- tracing::warn!(%error, "failed to discover repos in project root");
- }
- }
- discover_and_register_worktrees(&store, &project_id, &root).await;
- compute_and_cache_disk_usage(&store, &project_id, &root).await;
+ discover_and_register_worktrees(&store, &project_id, &root).await;
+ compute_and_cache_disk_usage(&store, &project_id, &root).await;
+ }
tracing::info!(project_id = %project_id, "background project scan complete");
});
@@ -424,7 +425,7 @@ pub(super) async fn create_project(
// Return the project immediately (repos/worktrees populate asynchronously).
let full = store
- .get_project_with_relations(&request.agent_id, &project.id)
+ .get_project_with_relations(&project.id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to load project with relations");
@@ -441,24 +442,22 @@ pub(super) async fn create_project(
path = "/agents/projects/{id}",
params(
("id" = String, Path, description = "Project ID"),
- AgentQuery,
),
responses(
(status = 200, body = ProjectResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
pub(super) async fn get_project(
State(state): State>,
Path(project_id): Path,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let project = store
- .get_project_with_relations(&query.agent_id, &project_id)
+ .get_project_with_relations(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to get project");
@@ -479,7 +478,7 @@ pub(super) async fn get_project(
request_body = UpdateProjectRequest,
responses(
(status = 200, body = ProjectResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
@@ -489,13 +488,12 @@ pub(super) async fn update_project(
Json(request): Json,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&request.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let status = request.status.as_deref().and_then(ProjectStatus::parse);
store
.update_project(
- &request.agent_id,
&project_id,
UpdateProjectInput {
name: request.name,
@@ -504,6 +502,7 @@ pub(super) async fn update_project(
tags: request.tags,
settings: request.settings,
status,
+ ..Default::default()
},
)
.await
@@ -515,7 +514,7 @@ pub(super) async fn update_project(
// Reload with relations.
let full = store
- .get_project_with_relations(&request.agent_id, &project_id)
+ .get_project_with_relations(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to reload project");
@@ -532,24 +531,22 @@ pub(super) async fn update_project(
path = "/agents/projects/{id}",
params(
("id" = String, Path, description = "Project ID"),
- AgentQuery,
),
responses(
(status = 200, body = ActionResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
pub(super) async fn delete_project(
State(state): State>,
Path(project_id): Path,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let deleted = store
- .delete_project(&query.agent_id, &project_id)
+ .delete_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to delete project");
@@ -561,7 +558,7 @@ pub(super) async fn delete_project(
}
// Refresh sandbox allowlist after removing project path.
- refresh_sandbox(&state, &query.agent_id).await;
+ refresh_sandbox(&state).await;
Ok(Json(ActionResponse {
success: true,
@@ -575,24 +572,22 @@ pub(super) async fn delete_project(
path = "/agents/projects/{id}/scan",
params(
("id" = String, Path, description = "Project ID"),
- AgentQuery,
),
responses(
(status = 200, body = ProjectResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
pub(super) async fn scan_project(
State(state): State>,
Path(project_id): Path,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let project = store
- .get_project(&query.agent_id, &project_id)
+ .get_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to get project for scan");
@@ -651,9 +646,18 @@ pub(super) async fn scan_project(
// Recompute and cache disk usage.
compute_and_cache_disk_usage(store, &project_id, &root).await;
+ // Re-detect project logo.
+ let logo = crate::projects::detect_logo(&root);
+ if let Err(error) = store
+ .set_logo_path(&project_id, logo.as_deref())
+ .await
+ {
+ tracing::warn!(%error, "failed to update logo path during scan");
+ }
+
// Reload with relations.
let full = store
- .get_project_with_relations(&query.agent_id, &project_id)
+ .get_project_with_relations(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to reload project after scan");
@@ -662,7 +666,7 @@ pub(super) async fn scan_project(
.ok_or(StatusCode::NOT_FOUND)?;
// Refresh sandbox allowlist (scan may have added new repos/worktrees).
- refresh_sandbox(&state, &query.agent_id).await;
+ refresh_sandbox(&state).await;
Ok(Json(ProjectResponse { project: full }))
}
@@ -677,7 +681,7 @@ pub(super) async fn scan_project(
request_body = CreateRepoRequest,
responses(
(status = 200, body = RepoResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
@@ -687,11 +691,11 @@ pub(super) async fn create_repo(
Json(request): Json,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&request.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
// Verify project exists.
store
- .get_project(&request.agent_id, &project_id)
+ .get_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to verify project");
@@ -728,21 +732,19 @@ pub(super) async fn create_repo(
params(
("project_id" = String, Path, description = "Project ID"),
("repo_id" = String, Path, description = "Repository ID"),
- AgentQuery,
),
responses(
(status = 200, body = ActionResponse),
- (status = 404, description = "Agent, project, or repo not found"),
+ (status = 404, description = "Project or repo not found"),
),
tag = "projects",
)]
pub(super) async fn delete_repo(
State(state): State>,
Path((project_id, repo_id)): Path<(String, String)>,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
// Verify the repo belongs to this project.
let repo = store
@@ -782,7 +784,7 @@ pub(super) async fn delete_repo(
request_body = CreateWorktreeRequest,
responses(
(status = 200, body = WorktreeResponse),
- (status = 404, description = "Agent, project, or repo not found"),
+ (status = 404, description = "Project or repo not found"),
),
tag = "projects",
)]
@@ -792,11 +794,11 @@ pub(super) async fn create_worktree(
Json(request): Json,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&request.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
// Look up the project and repo.
let project = store
- .get_project(&request.agent_id, &project_id)
+ .get_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to get project");
@@ -880,21 +882,19 @@ pub(super) async fn create_worktree(
params(
("project_id" = String, Path, description = "Project ID"),
("worktree_id" = String, Path, description = "Worktree ID"),
- AgentQuery,
),
responses(
(status = 200, body = ActionResponse),
- (status = 404, description = "Agent, project, or worktree not found"),
+ (status = 404, description = "Project or worktree not found"),
),
tag = "projects",
)]
pub(super) async fn delete_worktree(
State(state): State>,
Path((project_id, worktree_id)): Path<(String, String)>,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
// Look up worktree and project for the git removal.
let worktree = store
@@ -912,7 +912,7 @@ pub(super) async fn delete_worktree(
}
let project = store
- .get_project(&query.agent_id, &project_id)
+ .get_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to get project");
@@ -965,24 +965,22 @@ pub(super) async fn delete_worktree(
path = "/agents/projects/{id}/disk-usage",
params(
("id" = String, Path, description = "Project ID"),
- AgentQuery,
),
responses(
(status = 200, body = DiskUsageResponse),
- (status = 404, description = "Agent or project not found"),
+ (status = 404, description = "Project not found"),
),
tag = "projects",
)]
pub(super) async fn disk_usage(
State(state): State>,
Path(project_id): Path,
- Query(query): Query,
) -> Result, StatusCode> {
let stores = state.project_stores.load();
- let store = stores.get(&query.agent_id).ok_or(StatusCode::NOT_FOUND)?;
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
let project = store
- .get_project(&query.agent_id, &project_id)
+ .get_project(&project_id)
.await
.map_err(|error| {
tracing::error!(%error, "failed to get project for disk usage");
@@ -1039,6 +1037,68 @@ pub(super) async fn disk_usage(
}))
}
+/// GET /agents/projects/{id}/logo — serve the detected project logo.
+#[utoipa::path(
+ get,
+ path = "/agents/projects/{id}/logo",
+ params(
+ ("id" = String, Path, description = "Project ID"),
+ ),
+ responses(
+ (status = 200, description = "Logo image"),
+ (status = 404, description = "No logo found"),
+ ),
+ tag = "projects",
+)]
+pub(super) async fn serve_logo(
+ State(state): State>,
+ Path(project_id): Path,
+) -> Result {
+ let stores = state.project_stores.load();
+ let store = stores.values().next().ok_or(StatusCode::NOT_FOUND)?;
+
+ let project = store
+ .get_project(&project_id)
+ .await
+ .map_err(|error| {
+ tracing::error!(%error, "failed to get project for logo");
+ StatusCode::INTERNAL_SERVER_ERROR
+ })?
+ .ok_or(StatusCode::NOT_FOUND)?;
+
+ let logo_rel = project.logo_path.ok_or(StatusCode::NOT_FOUND)?;
+ let root = std::path::PathBuf::from(&project.root_path);
+ let logo_abs = root.join(&logo_rel);
+
+ // Ensure the resolved path is inside the project root (prevent traversal).
+ let canonical_root = root.canonicalize().map_err(|_| StatusCode::NOT_FOUND)?;
+ let canonical_logo = logo_abs.canonicalize().map_err(|_| StatusCode::NOT_FOUND)?;
+ if !canonical_logo.starts_with(&canonical_root) {
+ return Err(StatusCode::NOT_FOUND);
+ }
+
+ let data = tokio::fs::read(&canonical_logo)
+ .await
+ .map_err(|_| StatusCode::NOT_FOUND)?;
+
+ let content_type = match logo_rel
+ .rsplit('.')
+ .next()
+ .unwrap_or("")
+ .to_ascii_lowercase()
+ .as_str()
+ {
+ "png" => "image/png",
+ "svg" => "image/svg+xml",
+ "ico" => "image/x-icon",
+ "jpg" | "jpeg" => "image/jpeg",
+ "webp" => "image/webp",
+ _ => "application/octet-stream",
+ };
+
+ Ok(([(header::CONTENT_TYPE, content_type)], data))
+}
+
/// Recursively calculate directory size. Best-effort — skips entries it can't
/// read. Uses `symlink_metadata` to avoid following symlinks (prevents infinite
/// recursion and escaping project root).
diff --git a/src/api/server.rs b/src/api/server.rs
index 4e78f137d..9fe8e9262 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -142,6 +142,7 @@ pub fn api_router() -> OpenApiRouter> {
projects::delete_project
))
.routes(routes!(projects::scan_project))
+ .routes(routes!(projects::serve_logo))
.routes(routes!(projects::disk_usage))
.routes(routes!(projects::create_repo))
.routes(routes!(projects::delete_repo))
diff --git a/src/main.rs b/src/main.rs
index aae7974e0..7aa8e5231 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2942,7 +2942,7 @@ async fn initialize_agents(
// Inject active project root paths into the sandbox allowlist so
// workers can access project directories even outside the workspace.
- spacebot::projects::refresh_sandbox_project_paths(&project_store, &agent_id, &sandbox)
+ spacebot::projects::refresh_sandbox_project_paths(&project_store, &sandbox)
.await;
let deps = spacebot::AgentDeps {
diff --git a/src/projects.rs b/src/projects.rs
index 498f17831..04a790ec8 100644
--- a/src/projects.rs
+++ b/src/projects.rs
@@ -5,21 +5,20 @@ pub mod store;
pub use store::{
CreateProjectInput, CreateRepoInput, CreateWorktreeInput, Project, ProjectRepo, ProjectStatus,
- ProjectStore, ProjectWorktree, UpdateProjectInput,
+ ProjectStore, ProjectWorktree, UpdateProjectInput, detect_logo,
};
/// Refresh the sandbox allowlist with all active project root paths.
///
-/// Queries all active projects for the agent and injects their root paths
-/// into the sandbox config. Takes effect immediately for subsequent subprocess
-/// calls. Should be called after project create/delete/scan.
+/// Queries all active projects and injects their root paths into the sandbox
+/// config. Takes effect immediately for subsequent subprocess calls. Should be
+/// called after project create/delete/scan.
pub async fn refresh_sandbox_project_paths(
project_store: &ProjectStore,
- agent_id: &str,
sandbox: &crate::sandbox::Sandbox,
) {
let projects = match project_store
- .list_projects(agent_id, Some(ProjectStatus::Active))
+ .list_projects(Some(ProjectStatus::Active))
.await
{
Ok(projects) => projects,
diff --git a/src/projects/store.rs b/src/projects/store.rs
index 2749406da..0fcd7c9d0 100644
--- a/src/projects/store.rs
+++ b/src/projects/store.rs
@@ -6,6 +6,7 @@ use anyhow::Context as _;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sqlx::{Row as _, SqlitePool};
+use std::path::Path;
// Enums
@@ -63,12 +64,12 @@ pub struct ProjectSettings {
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Project {
pub id: String,
- pub agent_id: String,
pub name: String,
pub description: String,
pub icon: String,
pub tags: Vec,
pub root_path: String,
+ pub logo_path: Option,
pub settings: Value,
pub status: ProjectStatus,
pub created_at: String,
@@ -134,7 +135,6 @@ pub struct ProjectWorktreeWithRepo {
#[derive(Debug, Clone)]
pub struct CreateProjectInput {
- pub agent_id: String,
pub name: String,
pub description: String,
pub icon: String,
@@ -149,6 +149,7 @@ pub struct UpdateProjectInput {
pub description: Option,
pub icon: Option,
pub tags: Option>,
+ pub logo_path: Option,
pub settings: Option,
pub status: Option,
}
@@ -196,12 +197,11 @@ impl ProjectStore {
sqlx::query(
r#"
- INSERT INTO projects (id, agent_id, name, description, icon, tags, root_path, settings, status)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')
+ INSERT INTO projects (id, name, description, icon, tags, root_path, settings, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active')
"#,
)
.bind(&id)
- .bind(&input.agent_id)
.bind(&input.name)
.bind(&input.description)
.bind(&input.icon)
@@ -213,15 +213,14 @@ impl ProjectStore {
.context("failed to insert project")?;
Ok(self
- .get_project(&input.agent_id, &id)
+ .get_project(&id)
.await?
.context("project not found after insert")?)
}
- pub async fn get_project(&self, agent_id: &str, project_id: &str) -> Result