diff --git a/web/package-lock.json b/web/package-lock.json
index d753e2a0cd..1a75aff334 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -8,7 +8,7 @@
"name": "web",
"version": "0.0.0",
"dependencies": {
- "@nous-research/ui": "^0.8.0",
+ "@nous-research/ui": "^0.9.0",
"@observablehq/plot": "^0.6.17",
"@react-three/fiber": "^9.6.0",
"@tailwindcss/vite": "^4.2.1",
@@ -44,6 +44,68 @@
"vite": "^7.3.1"
}
},
+ "../../design-language": {
+ "name": "@nous-research/ui",
+ "version": "0.9.0",
+ "license": "MIT",
+ "dependencies": {
+ "@nanostores/react": "^1.0.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "nanostores": "^1.0.1",
+ "sanitize-html": "^2.16.0",
+ "tailwind-merge": "^3.3.1",
+ "tw-animate-css": "^1.4.0"
+ },
+ "devDependencies": {
+ "@observablehq/plot": "^0.6.17",
+ "@react-three/fiber": "^9.4.0",
+ "@storybook/addon-a11y": "^10.3.1",
+ "@storybook/addon-docs": "^10.3.1",
+ "@storybook/react-vite": "^10.3.1",
+ "@tailwindcss/cli": "^4",
+ "@tailwindcss/vite": "^4",
+ "@types/node": "25.6.0",
+ "@types/react": "^19.2.3",
+ "@types/react-dom": "^19.2.3",
+ "@types/sanitize-html": "^2.16.0",
+ "@types/three": "^0.183.1",
+ "@vitejs/plugin-react": "^5.0.0",
+ "gsap": "^3.13.0",
+ "leva": "^0.10.1",
+ "storybook": "^10.3.1",
+ "tailwindcss": "^4",
+ "three": "^0.180.0",
+ "typescript": "^5",
+ "vite": "^6.0.0"
+ },
+ "peerDependencies": {
+ "@observablehq/plot": "^0.6.17",
+ "@react-three/fiber": "^9.4.0",
+ "gsap": "^3.13.0",
+ "leva": "^0.10.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "three": "^0.180.0"
+ },
+ "peerDependenciesMeta": {
+ "@observablehq/plot": {
+ "optional": true
+ },
+ "@react-three/fiber": {
+ "optional": true
+ },
+ "gsap": {
+ "optional": true
+ },
+ "leva": {
+ "optional": true
+ },
+ "three": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -1058,72 +1120,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@nanostores/react": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-1.1.0.tgz",
- "integrity": "sha512-MbH35fjhcf7LAubYX5vhOChYUfTLzNLqH/mBGLVsHkcvjy0F8crO1WQwdmQ2xKbAmtpalDa2zBt3Hlg5kqr8iw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": "^20.0.0 || >=22.0.0"
- },
- "peerDependencies": {
- "nanostores": "^1.2.0",
- "react": ">=18.0.0"
- }
- },
"node_modules/@nous-research/ui": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@nous-research/ui/-/ui-0.8.0.tgz",
- "integrity": "sha512-3hnsyCgBfzgJZkjSQIhEPd6yi7eevZddbfA+AfZ/j+ese1zcdhc0/7ini477MOiHlepjQ73n/DBx0p3XVYPqPg==",
- "license": "MIT",
- "dependencies": {
- "@nanostores/react": "^1.0.0",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "nanostores": "^1.0.1",
- "sanitize-html": "^2.16.0",
- "tailwind-merge": "^3.3.1",
- "tw-animate-css": "^1.4.0"
- },
- "peerDependencies": {
- "@observablehq/plot": "^0.6.17",
- "@react-three/fiber": "^9.4.0",
- "gsap": "^3.13.0",
- "leva": "^0.10.1",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "three": "^0.180.0"
- },
- "peerDependenciesMeta": {
- "@observablehq/plot": {
- "optional": true
- },
- "@react-three/fiber": {
- "optional": true
- },
- "gsap": {
- "optional": true
- },
- "leva": {
- "optional": true
- },
- "three": {
- "optional": true
- }
- }
+ "resolved": "../../design-language",
+ "link": true
},
"node_modules/@observablehq/plot": {
"version": "0.6.17",
"resolved": "https://registry.npmjs.org/@observablehq/plot/-/plot-0.6.17.tgz",
"integrity": "sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g==",
"license": "ISC",
- "peer": true,
"dependencies": {
"d3": "^7.9.0",
"interval-tree-1d": "^1.0.0",
@@ -1776,7 +1781,6 @@
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz",
"integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.17.8",
"@types/webxr": "*",
@@ -3668,15 +3672,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/delaunator": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
@@ -3704,73 +3699,6 @@
"node": ">=8"
}
},
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/dom-serializer/node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "BSD-2-Clause"
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
- "license": "BSD-2-Clause",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
"node_modules/electron-to-chromium": {
"version": "1.5.344",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
@@ -3791,18 +3719,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/entities": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
- "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
"node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
@@ -3858,6 +3774,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -4251,8 +4168,7 @@
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
- "license": "Standard 'no charge' license: https://gsap.com/standard-license.",
- "peer": true
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
},
"node_modules/has-flag": {
"version": "4.0.0",
@@ -4281,25 +4197,6 @@
"hermes-estree": "0.25.1"
}
},
- "node_modules/htmlparser2": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
- "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.2.2",
- "entities": "^7.0.1"
- }
- },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -4558,7 +4455,6 @@
"resolved": "https://registry.npmjs.org/leva/-/leva-0.10.1.tgz",
"integrity": "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@radix-ui/react-portal": "^1.1.4",
"@radix-ui/react-tooltip": "^1.1.8",
@@ -4986,22 +4882,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/nanostores": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz",
- "integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": "^20.0.0 || >=22.0.0"
- }
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5088,12 +4968,6 @@
"node": ">=6"
}
},
- "node_modules/parse-srcset": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
- "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
- "license": "MIT"
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5383,29 +5257,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
- "node_modules/sanitize-html": {
- "version": "2.17.3",
- "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.3.tgz",
- "integrity": "sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==",
- "license": "MIT",
- "dependencies": {
- "deepmerge": "^4.2.2",
- "escape-string-regexp": "^4.0.0",
- "htmlparser2": "^10.1.0",
- "is-plain-object": "^5.0.0",
- "parse-srcset": "^1.0.2",
- "postcss": "^8.3.11"
- }
- },
- "node_modules/sanitize-html/node_modules/is-plain-object": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
- "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -5615,15 +5466,6 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
- "node_modules/tw-animate-css": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
- "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Wombosvideo"
- }
- },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/web/package.json b/web/package.json
index 5557cad05d..94cfea01f1 100644
--- a/web/package.json
+++ b/web/package.json
@@ -13,7 +13,7 @@
"preview": "vite preview"
},
"dependencies": {
- "@nous-research/ui": "^0.8.0",
+ "@nous-research/ui": "^0.9.0",
"@observablehq/plot": "^0.6.17",
"@react-three/fiber": "^9.6.0",
"@tailwindcss/vite": "^4.2.1",
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 65a9ede4a5..c860ae3903 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -42,7 +42,12 @@ import {
X,
Zap,
} from "lucide-react";
-import { Button, SelectionSwitcher, Typography } from "@nous-research/ui";
+import {
+ Button,
+ ListItem,
+ SelectionSwitcher,
+ Typography,
+} from "@nous-research/ui";
import { cn } from "@/lib/utils";
import { Backdrop } from "@/components/Backdrop";
import { SidebarFooter } from "@/components/SidebarFooter";
@@ -391,13 +396,13 @@ export default function App() {
{mobileOpen && (
-
)}
@@ -660,21 +665,19 @@ function SidebarSystemActions({ onNavigate }: { onNavigate: () => void }) {
return (
-
+
);
})}
diff --git a/web/src/components/AutoField.tsx b/web/src/components/AutoField.tsx
index 30977de91c..4b213be490 100644
--- a/web/src/components/AutoField.tsx
+++ b/web/src/components/AutoField.tsx
@@ -1,7 +1,6 @@
-import { Select, SelectOption } from "@nous-research/ui";
+import { Select, SelectOption, Switch } from "@nous-research/ui";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { Switch } from "@/components/ui/switch";
function FieldHint({ schema, schemaKey }: { schema: Record; schemaKey: string }) {
const keyPath = schemaKey.includes(".") ? schemaKey : "";
diff --git a/web/src/components/ChatSidebar.tsx b/web/src/components/ChatSidebar.tsx
index 47a104d9d0..85935a468d 100644
--- a/web/src/components/ChatSidebar.tsx
+++ b/web/src/components/ChatSidebar.tsx
@@ -313,19 +313,21 @@ export function ChatSidebar({ channel, className }: ChatSidebarProps) {
model
-
+
{STATE_LABEL[state]}
diff --git a/web/src/components/LanguageSwitcher.tsx b/web/src/components/LanguageSwitcher.tsx
index 6a397c4b04..24684bce2c 100644
--- a/web/src/components/LanguageSwitcher.tsx
+++ b/web/src/components/LanguageSwitcher.tsx
@@ -1,4 +1,4 @@
-import { Typography } from "@nous-research/ui";
+import { Button, Typography } from "@nous-research/ui";
import { useI18n } from "@/i18n/context";
/**
@@ -11,22 +11,25 @@ export function LanguageSwitcher() {
const toggle = () => setLocale(locale === "en" ? "zh" : "en");
return (
-
+
);
}
diff --git a/web/src/components/ModelPickerDialog.tsx b/web/src/components/ModelPickerDialog.tsx
index 3628192a72..b0b58971a5 100644
--- a/web/src/components/ModelPickerDialog.tsx
+++ b/web/src/components/ModelPickerDialog.tsx
@@ -1,4 +1,4 @@
-import { Button } from "@nous-research/ui";
+import { Button, ListItem } from "@nous-research/ui";
import { Input } from "@/components/ui/input";
import type { GatewayClient } from "@/lib/gatewayClient";
import { Check, Loader2, Search, X } from "lucide-react";
@@ -280,14 +280,12 @@ function ProviderColumn({
{providers.map((p) => {
const active = p.slug === selectedSlug;
return (
-
+
);
})}
@@ -360,23 +358,19 @@ function ModelColumn({
m === currentModel && provider.slug === currentProviderSlug;
return (
-
+
);
})
)}
diff --git a/web/src/components/SlashPopover.tsx b/web/src/components/SlashPopover.tsx
index 1c4b273b3b..eb4a3b63ab 100644
--- a/web/src/components/SlashPopover.tsx
+++ b/web/src/components/SlashPopover.tsx
@@ -1,4 +1,5 @@
import type { GatewayClient } from "@/lib/gatewayClient";
+import { ListItem } from "@nous-research/ui";
import { ChevronRight } from "lucide-react";
import {
forwardRef,
@@ -139,18 +140,14 @@ export const SlashPopover = forwardRef(
const active = i === selected;
return (
-
+
);
})}
diff --git a/web/src/components/ThemeSwitcher.tsx b/web/src/components/ThemeSwitcher.tsx
index 778afc21e4..2d1cf3531e 100644
--- a/web/src/components/ThemeSwitcher.tsx
+++ b/web/src/components/ThemeSwitcher.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { Palette, Check } from "lucide-react";
-import { Typography } from "@nous-research/ui";
+import { Button, ListItem, Typography } from "@nous-research/ui";
import { BUILTIN_THEMES, useTheme } from "@/themes";
import { useI18n } from "@/i18n";
import { cn } from "@/lib/utils";
@@ -50,27 +50,26 @@ export function ThemeSwitcher({ dropUp = false }: ThemeSwitcherProps) {
return (
-
+
+
+
+
+ {label}
+
+
+
{open && (
{
setTheme(th.name);
close();
}}
- className={cn(
- "flex w-full items-center gap-3 px-3 py-2 text-left transition-colors cursor-pointer",
- "hover:bg-midground/10",
- isActive ? "text-midground" : "text-midground/60",
- )}
+ className="gap-3"
>
{preset ? (
@@ -138,7 +133,7 @@ export function ThemeSwitcher({ dropUp = false }: ThemeSwitcherProps) {
isActive ? "opacity-100" : "opacity-0",
)}
/>
-
+
);
})}
diff --git a/web/src/components/ToolCall.tsx b/web/src/components/ToolCall.tsx
index 8ac1ebce61..5cf25b3731 100644
--- a/web/src/components/ToolCall.tsx
+++ b/web/src/components/ToolCall.tsx
@@ -1,3 +1,4 @@
+import { ListItem } from "@nous-research/ui";
import {
AlertCircle,
Check,
@@ -87,12 +88,11 @@ export function ToolCall({ tool }: { tool: ToolEntry }) {
-
+
{open && hasBody && (
diff --git a/web/src/components/ui/segmented.tsx b/web/src/components/ui/segmented.tsx
deleted file mode 100644
index eb4346e9e8..0000000000
--- a/web/src/components/ui/segmented.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export function Segmented
({
- className,
- onChange,
- options,
- size = "sm",
- value,
-}: SegmentedProps) {
- return (
-
- {options.map((opt) => {
- const active = opt.value === value;
-
- return (
-
- );
- })}
-
- );
-}
-
-export function FilterGroup({
- children,
- className,
- label,
-}: FilterGroupProps) {
- return (
-
-
- {label}
-
- {children}
-
- );
-}
-
-interface FilterGroupProps {
- children: React.ReactNode;
- className?: string;
- label: string;
-}
-
-interface SegmentedOption {
- label: string;
- value: T;
-}
-
-interface SegmentedProps {
- className?: string;
- onChange: (value: T) => void;
- options: SegmentedOption[];
- size?: "sm" | "md";
- value: T;
-}
diff --git a/web/src/components/ui/switch.tsx b/web/src/components/ui/switch.tsx
deleted file mode 100644
index ad2031277f..0000000000
--- a/web/src/components/ui/switch.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { cn } from "@/lib/utils";
-
-export function Switch({
- checked,
- onCheckedChange,
- className,
- disabled,
- id,
-}: {
- checked: boolean;
- onCheckedChange: (v: boolean) => void;
- className?: string;
- disabled?: boolean;
- id?: string;
-}) {
- return (
-
- );
-}
diff --git a/web/src/components/ui/tabs.tsx b/web/src/components/ui/tabs.tsx
deleted file mode 100644
index ffc2e36a7a..0000000000
--- a/web/src/components/ui/tabs.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { useState } from "react";
-import { cn } from "@/lib/utils";
-
-export function Tabs({
- defaultValue,
- children,
- className,
-}: {
- defaultValue: string;
- children: (active: string, setActive: (v: string) => void) => React.ReactNode;
- className?: string;
-}) {
- const [active, setActive] = useState(defaultValue);
- return {children(active, setActive)}
;
-}
-
-export function TabsList({ className, ...props }: React.HTMLAttributes) {
- return (
-
- );
-}
-
-export function TabsTrigger({
- active,
- value,
- onClick,
- className,
- ...props
-}: React.ButtonHTMLAttributes & { active: boolean; value: string }) {
- return (
-
- );
-}
diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx
index 3bd5189c38..a38116fafa 100644
--- a/web/src/pages/ChatPage.tsx
+++ b/web/src/pages/ChatPage.tsx
@@ -192,22 +192,22 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
return;
}
setEnd(
- ,
+
+
+ {modelToolsLabel}
+
+ ,
);
return () => setEnd(null);
}, [isActive, narrow, mobilePanelOpen, modelToolsLabel, setEnd]);
@@ -690,13 +690,13 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
createPortal(
<>
{mobilePanelOpen && (
-
)}
@@ -783,29 +783,29 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
className="hermes-chat-xterm-host min-h-0 min-w-0 flex-1"
/>
-
+
{!narrow && (
diff --git a/web/src/pages/ConfigPage.tsx b/web/src/pages/ConfigPage.tsx
index 7b074cc367..e75756752a 100644
--- a/web/src/pages/ConfigPage.tsx
+++ b/web/src/pages/ConfigPage.tsx
@@ -33,7 +33,7 @@ import { getNestedValue, setNestedValue } from "@/lib/nested";
import { useToast } from "@/hooks/useToast";
import { Toast } from "@/components/Toast";
import { AutoField } from "@/components/AutoField";
-import { Button } from "@nous-research/ui";
+import { Button, ListItem } from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Badge } from "@nous-research/ui";
@@ -118,18 +118,20 @@ export default function ConfigPage() {
onChange={(e) => setSearchQuery(e.target.value)}
/>
{searchQuery && (
-
+
+
)}
,
);
return () => setEnd(null);
- }, [config, schema, searchQuery, setEnd, t.common.search]);
+ }, [config, schema, searchQuery, setEnd, t.common.clear, t.common.search]);
function prettyCategoryName(cat: string): string {
const key = cat as keyof typeof t.config.categories;
@@ -507,23 +509,14 @@ export default function ConfigPage() {
const isActive = !isSearching && activeCategory === cat;
return (
-
+
);
})}
diff --git a/web/src/pages/EnvPage.tsx b/web/src/pages/EnvPage.tsx
index 429a112faf..51c26c2494 100644
--- a/web/src/pages/EnvPage.tsx
+++ b/web/src/pages/EnvPage.tsx
@@ -21,7 +21,7 @@ import { Toast } from "@/components/Toast";
import { useConfirmDelete } from "@/hooks/useConfirmDelete";
import { useToast } from "@/hooks/useToast";
import { OAuthProvidersCard } from "@/components/OAuthProvidersCard";
-import { Button } from "@nous-research/ui";
+import { Button, ListItem } from "@nous-research/ui";
import {
Card,
CardContent,
@@ -368,10 +368,10 @@ function ProviderGroupCard({
return (
{/* Header β always visible */}
-
+
{expanded && (
@@ -823,20 +823,16 @@ function CollapsibleUnset({
return (
<>
-
:
}
onClick={() => setCollapsed(!collapsed)}
+ aria-expanded={!collapsed}
+ className="self-start mt-1 normal-case tracking-normal text-xs text-muted-foreground hover:text-foreground"
>
- {collapsed ? (
-
- ) : (
-
- )}
-
- {t.env.notConfigured.replace("{count}", String(unsetEntries.length))}
-
-
+ {t.env.notConfigured.replace("{count}", String(unsetEntries.length))}
+
{!collapsed &&
unsetEntries.map(([key, info]) => (
diff --git a/web/src/pages/LogsPage.tsx b/web/src/pages/LogsPage.tsx
index 18da4a0877..92f6b1c5aa 100644
--- a/web/src/pages/LogsPage.tsx
+++ b/web/src/pages/LogsPage.tsx
@@ -7,12 +7,15 @@ import {
} from "react";
import { FileText, RefreshCw } from "lucide-react";
import { api } from "@/lib/api";
-import { Button } from "@nous-research/ui";
+import {
+ Badge,
+ Button,
+ FilterGroup,
+ Segmented,
+ Switch,
+} from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Badge } from "@nous-research/ui";
-import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
-import { FilterGroup, Segmented } from "@/components/ui/segmented";
import { useI18n } from "@/i18n";
import { usePageHeader } from "@/contexts/usePageHeader";
import { PluginSlot } from "@/plugins";
diff --git a/web/src/pages/SessionsPage.tsx b/web/src/pages/SessionsPage.tsx
index da8a610969..8a15e0383a 100644
--- a/web/src/pages/SessionsPage.tsx
+++ b/web/src/pages/SessionsPage.tsx
@@ -36,7 +36,7 @@ import { timeAgo } from "@/lib/utils";
import { Markdown } from "@/components/Markdown";
import { PlatformsCard } from "@/components/PlatformsCard";
import { Toast } from "@/components/Toast";
-import { Button } from "@nous-research/ui";
+import { Button, ListItem } from "@nous-research/ui";
import { Badge } from "@nous-research/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog";
@@ -105,11 +105,11 @@ function ToolCallBlock({
return (
-
+
{open && (
{args}
@@ -455,13 +455,15 @@ export default function SessionsPage() {
className="h-8 pr-7 pl-8 text-xs"
/>
{search && (
-
+
+
)}
,
);
@@ -475,6 +477,7 @@ export default function SessionsPage() {
searching,
setAfterTitle,
setEnd,
+ t.common.clear,
t.sessions.searchPlaceholder,
total,
]);
diff --git a/web/src/pages/SkillsPage.tsx b/web/src/pages/SkillsPage.tsx
index a5a07470de..742cbfe0c2 100644
--- a/web/src/pages/SkillsPage.tsx
+++ b/web/src/pages/SkillsPage.tsx
@@ -20,9 +20,9 @@ import type { SkillInfo, ToolsetInfo } from "@/lib/api";
import { useToast } from "@/hooks/useToast";
import { Toast } from "@/components/Toast";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Badge } from "@nous-research/ui";
+import { Badge, Button, ListItem, Switch } from "@nous-research/ui";
+import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
-import { Switch } from "@/components/ui/switch";
import { useI18n } from "@/i18n";
import { usePageHeader } from "@/contexts/usePageHeader";
import { PluginSlot } from "@/plugins";
@@ -207,13 +207,15 @@ export default function SkillsPage() {
onChange={(e) => setSearch(e.target.value)}
/>
{search && (
-
+
+
)}
,
);
@@ -297,22 +299,13 @@ export default function SkillsPage() {
const isActive = activeCategory === key;
return (
-
+
);
})}
@@ -535,24 +528,18 @@ function SkillRow({
function PanelItem({ active, icon: Icon, label, onClick }: PanelItemProps) {
return (
-
+
);
}
diff --git a/web/src/plugins/registry.ts b/web/src/plugins/registry.ts
index c005503d39..e04713617d 100644
--- a/web/src/plugins/registry.ts
+++ b/web/src/plugins/registry.ts
@@ -24,7 +24,7 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Tabs, TabsList, TabsTrigger } from "@nous-research/ui";
import { useI18n } from "@/i18n";
import { registerSlot, PluginSlot } from "./slots";