fix(components): refactor to use design system
This commit is contained in:
292
web/package-lock.json
generated
292
web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
</header>
|
||||
|
||||
{mobileOpen && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
aria-label={t.app.closeNavigation}
|
||||
onClick={closeMobile}
|
||||
className={cn(
|
||||
"lg:hidden fixed inset-0 z-40",
|
||||
"bg-black/60 backdrop-blur-sm cursor-pointer",
|
||||
"lg:hidden fixed inset-0 z-40 p-0 block",
|
||||
"bg-black/60 backdrop-blur-sm",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -660,21 +665,19 @@ function SidebarSystemActions({ onNavigate }: { onNavigate: () => void }) {
|
||||
|
||||
return (
|
||||
<li key={action}>
|
||||
<button
|
||||
type="button"
|
||||
<ListItem
|
||||
onClick={() => handleClick(action)}
|
||||
disabled={disabled}
|
||||
aria-busy={busy}
|
||||
active={busy}
|
||||
className={cn(
|
||||
"group relative flex w-full items-center gap-3",
|
||||
"px-5 py-1.5",
|
||||
"gap-3 px-5 py-1.5 whitespace-nowrap",
|
||||
"font-mondwest text-[0.75rem] tracking-[0.1em]",
|
||||
"text-left whitespace-nowrap transition-opacity cursor-pointer",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground",
|
||||
"transition-opacity",
|
||||
busy
|
||||
? "text-midground opacity-100"
|
||||
: "opacity-60 hover:opacity-100",
|
||||
"disabled:cursor-not-allowed disabled:opacity-30",
|
||||
"disabled:opacity-30",
|
||||
)}
|
||||
>
|
||||
{isPending ? (
|
||||
@@ -703,7 +706,7 @@ function SidebarSystemActions({ onNavigate }: { onNavigate: () => void }) {
|
||||
style={{ mixBlendMode: "plus-lighter" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</ListItem>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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<string, unknown>; schemaKey: string }) {
|
||||
const keyPath = schemaKey.includes(".") ? schemaKey : "";
|
||||
|
||||
@@ -313,19 +313,21 @@ export function ChatSidebar({ channel, className }: ChatSidebarProps) {
|
||||
model
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
size="sm"
|
||||
disabled={!canPickModel}
|
||||
onClick={() => setModelOpen(true)}
|
||||
className="flex items-center gap-1 truncate text-sm font-medium hover:underline disabled:cursor-not-allowed disabled:opacity-60 disabled:no-underline"
|
||||
suffix={
|
||||
canPickModel ? (
|
||||
<ChevronDown className="opacity-60" />
|
||||
) : undefined
|
||||
}
|
||||
className="self-start min-w-0 px-0 py-0 normal-case tracking-normal text-sm font-medium hover:underline disabled:no-underline"
|
||||
title={info.model ?? "switch model"}
|
||||
>
|
||||
<span className="truncate">{modelLabel}</span>
|
||||
|
||||
{canPickModel && (
|
||||
<ChevronDown className="h-3 w-3 shrink-0 opacity-60" />
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Badge tone={STATE_TONE[state]}>{STATE_LABEL[state]}</Badge>
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
onClick={toggle}
|
||||
className="group relative inline-flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
title={t.language.switchTo}
|
||||
aria-label={t.language.switchTo}
|
||||
className="px-2 py-1 normal-case tracking-normal font-normal text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<span className="text-base leading-none">
|
||||
{locale === "en" ? "🇬🇧" : "🇨🇳"}
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="text-base leading-none">
|
||||
{locale === "en" ? "🇬🇧" : "🇨🇳"}
|
||||
</span>
|
||||
|
||||
<Typography
|
||||
mondwest
|
||||
className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
|
||||
>
|
||||
{locale === "en" ? "EN" : "中文"}
|
||||
</Typography>
|
||||
</span>
|
||||
<Typography
|
||||
mondwest
|
||||
className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
|
||||
>
|
||||
{locale === "en" ? "EN" : "中文"}
|
||||
</Typography>
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
<ListItem
|
||||
key={p.slug}
|
||||
type="button"
|
||||
active={active}
|
||||
onClick={() => onSelect(p.slug)}
|
||||
className={`w-full text-left px-3 py-2 text-xs border-l-2 transition-colors cursor-pointer flex items-start gap-2 ${
|
||||
active
|
||||
? "bg-primary/10 border-l-primary text-foreground"
|
||||
: "border-l-transparent text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||
className={`items-start text-xs border-l-2 ${
|
||||
active ? "border-l-primary" : "border-l-transparent"
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -299,7 +297,7 @@ function ProviderColumn({
|
||||
{p.slug} · {p.total_models ?? p.models?.length ?? 0} models
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -360,23 +358,19 @@ function ModelColumn({
|
||||
m === currentModel && provider.slug === currentProviderSlug;
|
||||
|
||||
return (
|
||||
<button
|
||||
<ListItem
|
||||
key={m}
|
||||
type="button"
|
||||
active={active}
|
||||
onClick={() => onSelect(m)}
|
||||
onDoubleClick={() => onConfirm(m)}
|
||||
className={`w-full text-left px-3 py-1.5 text-xs font-mono transition-colors cursor-pointer flex items-center gap-2 ${
|
||||
active
|
||||
? "bg-primary/15 text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||
}`}
|
||||
className="px-3 py-1.5 text-xs font-mono"
|
||||
>
|
||||
<Check
|
||||
className={`h-3 w-3 shrink-0 ${active ? "text-primary" : "text-transparent"}`}
|
||||
/>
|
||||
<span className="flex-1 truncate">{m}</span>
|
||||
{isCurrent && <CurrentTag />}
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
@@ -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<SlashPopoverHandle, Props>(
|
||||
const active = i === selected;
|
||||
|
||||
return (
|
||||
<button
|
||||
<ListItem
|
||||
key={`${it.text}-${i}`}
|
||||
type="button"
|
||||
active={active}
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
onMouseEnter={() => setSelected(i)}
|
||||
onClick={() => apply(it)}
|
||||
className={`w-full flex items-center gap-2 px-3 py-1.5 text-left cursor-pointer transition-colors ${
|
||||
active
|
||||
? "bg-primary/10 text-foreground"
|
||||
: "text-muted-foreground hover:bg-muted/60"
|
||||
}`}
|
||||
className="px-3 py-1.5"
|
||||
>
|
||||
<ChevronRight
|
||||
className={`h-3 w-3 shrink-0 ${active ? "text-primary" : "text-transparent"}`}
|
||||
@@ -165,7 +162,7 @@ export const SlashPopover = forwardRef<SlashPopoverHandle, Props>(
|
||||
{it.meta}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div ref={wrapperRef} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
className={cn(
|
||||
"group relative inline-flex items-center gap-1.5 px-2 py-1 text-xs",
|
||||
"text-muted-foreground hover:text-foreground transition-colors cursor-pointer",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground",
|
||||
)}
|
||||
className="px-2 py-1 normal-case tracking-normal font-normal text-xs text-muted-foreground hover:text-foreground"
|
||||
title={t.theme?.switchTheme ?? "Switch theme"}
|
||||
aria-label={t.theme?.switchTheme ?? "Switch theme"}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
<Palette className="h-3.5 w-3.5" />
|
||||
<Typography
|
||||
mondwest
|
||||
className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
</button>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Palette className="h-3.5 w-3.5" />
|
||||
|
||||
<Typography
|
||||
mondwest
|
||||
className="hidden sm:inline tracking-wide uppercase text-[0.65rem]"
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
@@ -97,20 +96,16 @@ export function ThemeSwitcher({ dropUp = false }: ThemeSwitcherProps) {
|
||||
const preset = BUILTIN_THEMES[th.name];
|
||||
|
||||
return (
|
||||
<button
|
||||
<ListItem
|
||||
key={th.name}
|
||||
type="button"
|
||||
active={isActive}
|
||||
role="option"
|
||||
aria-selected={isActive}
|
||||
onClick={() => {
|
||||
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 ? (
|
||||
<ThemeSwatch theme={preset.name} />
|
||||
@@ -138,7 +133,7 @@ export function ThemeSwitcher({ dropUp = false }: ThemeSwitcherProps) {
|
||||
isActive ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ListItem } from "@nous-research/ui";
|
||||
import {
|
||||
AlertCircle,
|
||||
Check,
|
||||
@@ -87,12 +88,11 @@ export function ToolCall({ tool }: { tool: ToolEntry }) {
|
||||
<div
|
||||
className={`rounded-md border overflow-hidden ${STATUS_TONE[tool.status]}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
<ListItem
|
||||
onClick={() => setUserOverride(!open)}
|
||||
disabled={!hasBody}
|
||||
aria-expanded={open}
|
||||
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-left text-xs hover:bg-foreground/2 disabled:cursor-default cursor-pointer transition-colors"
|
||||
className="px-2.5 py-1.5 text-xs hover:bg-foreground/2 disabled:cursor-default"
|
||||
>
|
||||
{hasBody ? (
|
||||
<Chevron className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
@@ -132,7 +132,7 @@ export function ToolCall({ tool }: { tool: ToolEntry }) {
|
||||
{elapsed}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</ListItem>
|
||||
|
||||
{open && hasBody && (
|
||||
<div className="border-t border-border/60 px-3 py-2 space-y-2 text-xs font-mono">
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function Segmented<T extends string>({
|
||||
className,
|
||||
onChange,
|
||||
options,
|
||||
size = "sm",
|
||||
value,
|
||||
}: SegmentedProps<T>) {
|
||||
return (
|
||||
<div
|
||||
role="radiogroup"
|
||||
className={cn(
|
||||
"inline-flex border border-border bg-background/30",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{options.map((opt) => {
|
||||
const active = opt.value === value;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={active}
|
||||
onClick={() => onChange(opt.value)}
|
||||
className={cn(
|
||||
"font-mondwest tracking-[0.1em] uppercase",
|
||||
"transition-colors cursor-pointer whitespace-nowrap",
|
||||
"border-r border-border last:border-r-0",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground/30",
|
||||
size === "sm" && "h-7 px-2.5 text-[0.65rem]",
|
||||
size === "md" && "h-8 px-3 text-xs",
|
||||
active
|
||||
? "bg-foreground/90 text-background"
|
||||
: "text-muted-foreground hover:bg-foreground/10 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FilterGroup({
|
||||
children,
|
||||
className,
|
||||
label,
|
||||
}: FilterGroupProps) {
|
||||
return (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<span className="font-mondwest text-[0.65rem] tracking-[0.12em] uppercase text-muted-foreground/70">
|
||||
{label}
|
||||
</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface FilterGroupProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SegmentedOption<T extends string> {
|
||||
label: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
interface SegmentedProps<T extends string> {
|
||||
className?: string;
|
||||
onChange: (value: T) => void;
|
||||
options: SegmentedOption<T>[];
|
||||
size?: "sm" | "md";
|
||||
value: T;
|
||||
}
|
||||
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
id={id}
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center border border-border transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-foreground/30",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
checked ? "bg-foreground/15 border-foreground/30" : "bg-background",
|
||||
className,
|
||||
)}
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"pointer-events-none block h-3.5 w-3.5 transition-transform",
|
||||
checked ? "translate-x-4 bg-foreground" : "translate-x-0.5 bg-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -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 <div className={cn("flex flex-col gap-4", className)}>{children(active, setActive)}</div>;
|
||||
}
|
||||
|
||||
export function TabsList({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-9 items-center justify-start border-b border-border text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function TabsTrigger({
|
||||
active,
|
||||
value,
|
||||
onClick,
|
||||
className,
|
||||
...props
|
||||
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { active: boolean; value: string }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center whitespace-nowrap px-3 py-1.5 font-mondwest text-xs tracking-[0.1em] uppercase transition-all cursor-pointer",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
active
|
||||
? "text-foreground after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-foreground"
|
||||
: "hover:text-foreground",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -192,22 +192,22 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
return;
|
||||
}
|
||||
setEnd(
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
onClick={() => setMobilePanelOpenRaw(true)}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded border border-current/20",
|
||||
"px-2 py-1 text-[0.65rem] font-medium tracking-wide normal-case",
|
||||
"text-midground/80 hover:text-midground hover:bg-midground/5",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-midground",
|
||||
"shrink-0 cursor-pointer",
|
||||
)}
|
||||
aria-expanded={mobilePanelOpen}
|
||||
aria-controls="chat-side-panel"
|
||||
className={cn(
|
||||
"shrink-0 rounded border border-current/20",
|
||||
"px-2 py-1 text-[0.65rem] font-medium tracking-wide normal-case",
|
||||
"text-midground/80 hover:text-midground hover:bg-midground/5",
|
||||
)}
|
||||
>
|
||||
<PanelRight className="h-3 w-3 shrink-0" />
|
||||
{modelToolsLabel}
|
||||
</button>,
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<PanelRight className="h-3 w-3 shrink-0" />
|
||||
{modelToolsLabel}
|
||||
</span>
|
||||
</Button>,
|
||||
);
|
||||
return () => setEnd(null);
|
||||
}, [isActive, narrow, mobilePanelOpen, modelToolsLabel, setEnd]);
|
||||
@@ -690,13 +690,13 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
createPortal(
|
||||
<>
|
||||
{mobilePanelOpen && (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
aria-label={t.app.closeModelTools}
|
||||
onClick={closeMobilePanel}
|
||||
className={cn(
|
||||
"fixed inset-0 z-[55]",
|
||||
"bg-black/60 backdrop-blur-sm cursor-pointer",
|
||||
"fixed inset-0 z-[55] p-0 block",
|
||||
"bg-black/60 backdrop-blur-sm",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
ghost
|
||||
onClick={handleCopyLast}
|
||||
title="Copy last assistant response as raw markdown"
|
||||
aria-label="Copy last assistant response"
|
||||
className={cn(
|
||||
"absolute z-10 flex items-center gap-1.5",
|
||||
"absolute z-10",
|
||||
"rounded border border-current/30",
|
||||
"bg-black/20 backdrop-blur-sm",
|
||||
"opacity-60 hover:opacity-100 hover:border-current/60",
|
||||
"transition-opacity duration-150",
|
||||
"focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-current",
|
||||
"cursor-pointer",
|
||||
"transition-opacity duration-150 normal-case font-normal tracking-normal",
|
||||
"bottom-2 right-2 px-2 py-1 text-[0.65rem] sm:bottom-3 sm:right-3 sm:px-2.5 sm:py-1.5 sm:text-xs",
|
||||
"lg:bottom-4 lg:right-4",
|
||||
)}
|
||||
style={{ color: TERMINAL_THEME.foreground }}
|
||||
>
|
||||
<Copy className="h-3 w-3 shrink-0" />
|
||||
<span className="hidden min-[400px]:inline tracking-wide">
|
||||
{copyState === "copied" ? "copied" : "copy last response"}
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Copy className="h-3 w-3 shrink-0" />
|
||||
<span className="hidden min-[400px]:inline tracking-wide">
|
||||
{copyState === "copied" ? "copied" : "copy last response"}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!narrow && (
|
||||
|
||||
@@ -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 && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
<Button
|
||||
ghost
|
||||
size="xs"
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setSearchQuery("")}
|
||||
aria-label={t.common.clear}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
<X />
|
||||
</Button>
|
||||
)}
|
||||
</div>,
|
||||
);
|
||||
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 (
|
||||
<button
|
||||
<ListItem
|
||||
key={cat}
|
||||
type="button"
|
||||
active={isActive}
|
||||
onClick={() => {
|
||||
setSearchQuery("");
|
||||
setActiveCategory(cat);
|
||||
}}
|
||||
className={`
|
||||
group flex items-center gap-2 px-2 py-1
|
||||
rounded-sm text-left text-[11px] cursor-pointer whitespace-nowrap
|
||||
transition-colors
|
||||
${
|
||||
isActive
|
||||
? "bg-foreground/10 text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-foreground/5"
|
||||
}
|
||||
`}
|
||||
className="rounded-sm whitespace-nowrap px-2 py-1 text-[11px]"
|
||||
>
|
||||
<CategoryIcon
|
||||
category={cat}
|
||||
@@ -541,7 +534,7 @@ export default function ConfigPage() {
|
||||
>
|
||||
{categoryCounts[cat] || 0}
|
||||
</span>
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="border border-border">
|
||||
{/* Header — always visible */}
|
||||
<button
|
||||
type="button"
|
||||
<ListItem
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex w-full items-center justify-between gap-3 px-4 py-3 cursor-pointer hover:bg-primary/5 transition-colors"
|
||||
aria-expanded={expanded}
|
||||
className="justify-between gap-3 px-4 py-3 hover:bg-primary/5"
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
{expanded ? (
|
||||
@@ -406,7 +406,7 @@ function ProviderGroupCard({
|
||||
.replace("{s}", group.entries.length !== 1 ? "s" : "")}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</ListItem>
|
||||
|
||||
{expanded && (
|
||||
<div className="border-t border-border px-4 py-3 grid gap-2">
|
||||
@@ -823,20 +823,16 @@ function CollapsibleUnset({
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer pt-1"
|
||||
<Button
|
||||
ghost
|
||||
size="sm"
|
||||
prefix={collapsed ? <ChevronRight /> : <ChevronDown />}
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
aria-expanded={!collapsed}
|
||||
className="self-start mt-1 normal-case tracking-normal text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
)}
|
||||
<span>
|
||||
{t.env.notConfigured.replace("{count}", String(unsetEntries.length))}
|
||||
</span>
|
||||
</button>
|
||||
{t.env.notConfigured.replace("{count}", String(unsetEntries.length))}
|
||||
</Button>
|
||||
|
||||
{!collapsed &&
|
||||
unsetEntries.map(([key, info]) => (
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 (
|
||||
<div className="mt-2 border border-warning/20 bg-warning/5">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 px-3 py-2 text-xs text-warning cursor-pointer hover:bg-warning/10 transition-colors"
|
||||
<ListItem
|
||||
onClick={() => setOpen(!open)}
|
||||
aria-label={`${open ? t.common.collapse : t.common.expand} tool call ${toolCall.function.name}`}
|
||||
aria-expanded={open}
|
||||
className="px-3 py-2 text-xs text-warning hover:bg-warning/10 hover:text-warning"
|
||||
>
|
||||
{open ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
@@ -120,7 +120,7 @@ function ToolCallBlock({
|
||||
{toolCall.function.name}
|
||||
</span>
|
||||
<span className="text-warning/50 ml-auto">{toolCall.id}</span>
|
||||
</button>
|
||||
</ListItem>
|
||||
{open && (
|
||||
<pre className="border-t border-warning/20 px-3 py-2 text-xs text-warning/80 overflow-x-auto whitespace-pre-wrap font-mono">
|
||||
{args}
|
||||
@@ -455,13 +455,15 @@ export default function SessionsPage() {
|
||||
className="h-8 pr-7 pl-8 text-xs"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer text-muted-foreground hover:text-foreground"
|
||||
<Button
|
||||
ghost
|
||||
size="xs"
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setSearch("")}
|
||||
aria-label={t.common.clear}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
<X />
|
||||
</Button>
|
||||
)}
|
||||
</div>,
|
||||
);
|
||||
@@ -475,6 +477,7 @@ export default function SessionsPage() {
|
||||
searching,
|
||||
setAfterTitle,
|
||||
setEnd,
|
||||
t.common.clear,
|
||||
t.sessions.searchPlaceholder,
|
||||
total,
|
||||
]);
|
||||
|
||||
@@ -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 && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
<Button
|
||||
ghost
|
||||
size="xs"
|
||||
className="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setSearch("")}
|
||||
aria-label={t.common.clear}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
<X />
|
||||
</Button>
|
||||
)}
|
||||
</div>,
|
||||
);
|
||||
@@ -297,22 +299,13 @@ export default function SkillsPage() {
|
||||
const isActive = activeCategory === key;
|
||||
|
||||
return (
|
||||
<button
|
||||
<ListItem
|
||||
key={key}
|
||||
type="button"
|
||||
active={isActive}
|
||||
onClick={() =>
|
||||
setActiveCategory(isActive ? null : key)
|
||||
}
|
||||
className={`
|
||||
group flex items-center gap-2 px-2 py-1
|
||||
rounded-sm text-left text-[11px] cursor-pointer
|
||||
transition-colors
|
||||
${
|
||||
isActive
|
||||
? "bg-foreground/10 text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-foreground/5"
|
||||
}
|
||||
`}
|
||||
className="rounded-sm px-2 py-1 text-[11px]"
|
||||
>
|
||||
<span className="flex-1 truncate">{name}</span>
|
||||
<span
|
||||
@@ -324,7 +317,7 @@ export default function SkillsPage() {
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -535,24 +528,18 @@ function SkillRow({
|
||||
|
||||
function PanelItem({ active, icon: Icon, label, onClick }: PanelItemProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<ListItem
|
||||
active={active}
|
||||
onClick={onClick}
|
||||
className={`
|
||||
group flex items-center gap-2 px-2.5 py-1.5
|
||||
font-mondwest text-[0.7rem] tracking-[0.08em] uppercase
|
||||
rounded-sm text-left cursor-pointer whitespace-nowrap
|
||||
transition-colors
|
||||
${
|
||||
active
|
||||
? "bg-foreground/90 text-background"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-foreground/10"
|
||||
}
|
||||
`}
|
||||
className={cn(
|
||||
"rounded-sm whitespace-nowrap px-2.5 py-1.5",
|
||||
"font-mondwest text-[0.7rem] tracking-[0.08em] uppercase",
|
||||
active && "bg-foreground/90 text-background hover:text-background",
|
||||
)}
|
||||
>
|
||||
<Icon className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="flex-1 truncate">{label}</span>
|
||||
</button>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user