Spaces:
Running
Running
DeepSite v2 🐳
Browse files- README.md +4 -1
- components.json +23 -0
- package-lock.json +1090 -8
- package.json +17 -0
- server.js +42 -13
- src/assets/index.css +129 -0
- src/components/ask-ai/ask-ai.tsx +83 -46
- src/components/deploy-button/deploy-button.tsx +81 -138
- src/components/footer/footer.tsx +177 -0
- src/components/header/header.tsx +45 -18
- src/components/history/history.tsx +138 -0
- src/components/load-button/load-button.tsx +43 -51
- src/components/login/login.tsx +4 -4
- src/components/magicui/grid-pattern.tsx +69 -0
- src/components/preview/preview.tsx +24 -90
- src/components/settings/settings.tsx +153 -95
- src/components/tabs/tabs.tsx +0 -120
- src/components/theme/mode-toggle.tsx +37 -0
- src/components/theme/theme-provider.tsx +75 -0
- src/components/ui/avatar.tsx +51 -0
- src/components/ui/button.tsx +63 -0
- src/components/ui/dropdown-menu.tsx +258 -0
- src/components/ui/input.tsx +21 -0
- src/components/ui/popover.tsx +46 -0
- src/components/ui/select.tsx +189 -0
- src/components/ui/sonner.tsx +23 -0
- src/components/ui/tabs.tsx +64 -0
- src/components/ui/toggle-group.tsx +71 -0
- src/components/ui/toggle.tsx +47 -0
- src/lib/utils.ts +6 -0
- src/main.tsx +1 -3
- src/{components → views}/App.tsx +148 -136
- tsconfig.app.json +1 -0
- tsconfig.json +7 -3
- utils/providers.js +22 -5
- utils/types.ts +6 -0
- vite.config.ts +6 -3
README.md
CHANGED
@@ -10,10 +10,13 @@ license: mit
|
|
10 |
short_description: Generate any application with DeepSeek
|
11 |
models:
|
12 |
- deepseek-ai/DeepSeek-V3-0324
|
|
|
13 |
---
|
14 |
|
15 |
# DeepSite 🐳
|
|
|
16 |
DeepSite is a coding platform powered by DeepSeek AI, designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
|
17 |
|
18 |
## How to use it locally
|
19 |
-
|
|
|
|
10 |
short_description: Generate any application with DeepSeek
|
11 |
models:
|
12 |
- deepseek-ai/DeepSeek-V3-0324
|
13 |
+
- deepseek-ai/DeepSeek-R1-0528
|
14 |
---
|
15 |
|
16 |
# DeepSite 🐳
|
17 |
+
|
18 |
DeepSite is a coding platform powered by DeepSeek AI, designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
|
19 |
|
20 |
## How to use it locally
|
21 |
+
|
22 |
+
Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
|
components.json
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
3 |
+
"style": "new-york",
|
4 |
+
"rsc": false,
|
5 |
+
"tsx": true,
|
6 |
+
"tailwind": {
|
7 |
+
"config": "",
|
8 |
+
"css": "src/assets/index.css",
|
9 |
+
"baseColor": "neutral",
|
10 |
+
"baseColorLight": "slate",
|
11 |
+
"baseColorDark": "neutral",
|
12 |
+
"cssVariables": false,
|
13 |
+
"prefix": ""
|
14 |
+
},
|
15 |
+
"aliases": {
|
16 |
+
"components": "@/components",
|
17 |
+
"utils": "@/lib/utils",
|
18 |
+
"ui": "@/components/ui",
|
19 |
+
"lib": "@/lib",
|
20 |
+
"hooks": "@/hooks"
|
21 |
+
},
|
22 |
+
"iconLibrary": "lucide"
|
23 |
+
}
|
package-lock.json
CHANGED
@@ -11,13 +11,26 @@
|
|
11 |
"@huggingface/hub": "^1.1.1",
|
12 |
"@huggingface/inference": "^3.6.1",
|
13 |
"@monaco-editor/react": "^4.7.0",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
"@tailwindcss/vite": "^4.0.15",
|
15 |
"@xenova/transformers": "^2.17.2",
|
16 |
"body-parser": "^1.20.3",
|
|
|
17 |
"classnames": "^2.5.1",
|
|
|
18 |
"cookie-parser": "^1.4.7",
|
19 |
"dotenv": "^16.4.7",
|
20 |
"express": "^4.21.2",
|
|
|
|
|
21 |
"react": "^19.0.0",
|
22 |
"react-dom": "^19.0.0",
|
23 |
"react-icons": "^5.5.0",
|
@@ -25,11 +38,14 @@
|
|
25 |
"react-speech-recognition": "^4.0.0",
|
26 |
"react-toastify": "^11.0.5",
|
27 |
"react-use": "^17.6.0",
|
|
|
|
|
28 |
"tailwindcss": "^4.0.15"
|
29 |
},
|
30 |
"devDependencies": {
|
31 |
"@eslint/js": "^9.21.0",
|
32 |
"@types/express": "^5.0.1",
|
|
|
33 |
"@types/react": "^19.0.10",
|
34 |
"@types/react-dom": "^19.0.4",
|
35 |
"@types/react-speech-recognition": "^3.9.6",
|
@@ -38,6 +54,7 @@
|
|
38 |
"eslint-plugin-react-hooks": "^5.1.0",
|
39 |
"eslint-plugin-react-refresh": "^0.4.19",
|
40 |
"globals": "^15.15.0",
|
|
|
41 |
"typescript": "~5.7.2",
|
42 |
"typescript-eslint": "^8.24.1",
|
43 |
"vite": "^6.2.0"
|
@@ -845,6 +862,44 @@
|
|
845 |
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
846 |
}
|
847 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
848 |
"node_modules/@huggingface/hub": {
|
849 |
"version": "1.1.1",
|
850 |
"resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-1.1.1.tgz",
|
@@ -1104,6 +1159,821 @@
|
|
1104 |
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
1105 |
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
1106 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1107 |
"node_modules/@rollup/rollup-android-arm-eabi": {
|
1108 |
"version": "4.36.0",
|
1109 |
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
|
@@ -1702,11 +2572,12 @@
|
|
1702 |
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
1703 |
},
|
1704 |
"node_modules/@types/node": {
|
1705 |
-
"version": "22.
|
1706 |
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.
|
1707 |
-
"integrity": "sha512-
|
|
|
1708 |
"dependencies": {
|
1709 |
-
"undici-types": "~6.
|
1710 |
}
|
1711 |
},
|
1712 |
"node_modules/@types/qs": {
|
@@ -1733,7 +2604,7 @@
|
|
1733 |
"version": "19.0.4",
|
1734 |
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
1735 |
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
1736 |
-
"
|
1737 |
"peerDependencies": {
|
1738 |
"@types/react": "^19.0.0"
|
1739 |
}
|
@@ -2101,6 +2972,18 @@
|
|
2101 |
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
2102 |
"dev": true
|
2103 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2104 |
"node_modules/array-flatten": {
|
2105 |
"version": "1.1.1",
|
2106 |
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
@@ -2465,6 +3348,18 @@
|
|
2465 |
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
2466 |
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
2467 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2468 |
"node_modules/classnames": {
|
2469 |
"version": "2.5.1",
|
2470 |
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
@@ -2474,6 +3369,7 @@
|
|
2474 |
"version": "2.1.1",
|
2475 |
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
2476 |
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
|
|
2477 |
"engines": {
|
2478 |
"node": ">=6"
|
2479 |
}
|
@@ -2724,6 +3620,12 @@
|
|
2724 |
"node": ">=8"
|
2725 |
}
|
2726 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
2727 |
"node_modules/devlop": {
|
2728 |
"version": "1.1.0",
|
2729 |
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
@@ -3392,6 +4294,15 @@
|
|
3392 |
"url": "https://github.com/sponsors/ljharb"
|
3393 |
}
|
3394 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3395 |
"node_modules/get-proto": {
|
3396 |
"version": "1.0.1",
|
3397 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
@@ -4101,6 +5012,15 @@
|
|
4101 |
"yallist": "^3.0.2"
|
4102 |
}
|
4103 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4104 |
"node_modules/math-intrinsics": {
|
4105 |
"version": "1.1.0",
|
4106 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
@@ -4858,6 +5778,16 @@
|
|
4858 |
"node": ">= 0.6"
|
4859 |
}
|
4860 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4861 |
"node_modules/node-abi": {
|
4862 |
"version": "3.74.0",
|
4863 |
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
|
@@ -5387,6 +6317,53 @@
|
|
5387 |
"node": ">=0.10.0"
|
5388 |
}
|
5389 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5390 |
"node_modules/react-speech-recognition": {
|
5391 |
"version": "4.0.0",
|
5392 |
"resolved": "https://registry.npmjs.org/react-speech-recognition/-/react-speech-recognition-4.0.0.tgz",
|
@@ -5395,6 +6372,28 @@
|
|
5395 |
"react": ">=16.8.0"
|
5396 |
}
|
5397 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5398 |
"node_modules/react-toastify": {
|
5399 |
"version": "11.0.5",
|
5400 |
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
|
@@ -5875,6 +6874,16 @@
|
|
5875 |
"is-arrayish": "^0.3.1"
|
5876 |
}
|
5877 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5878 |
"node_modules/source-map": {
|
5879 |
"version": "0.6.1",
|
5880 |
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
@@ -6031,6 +7040,16 @@
|
|
6031 |
"node": ">=8"
|
6032 |
}
|
6033 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6034 |
"node_modules/tailwindcss": {
|
6035 |
"version": "4.0.15",
|
6036 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz",
|
@@ -6159,6 +7178,16 @@
|
|
6159 |
"node": "*"
|
6160 |
}
|
6161 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6162 |
"node_modules/type-check": {
|
6163 |
"version": "0.4.0",
|
6164 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
@@ -6219,9 +7248,10 @@
|
|
6219 |
}
|
6220 |
},
|
6221 |
"node_modules/undici-types": {
|
6222 |
-
"version": "6.
|
6223 |
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.
|
6224 |
-
"integrity": "sha512-
|
|
|
6225 |
},
|
6226 |
"node_modules/unified": {
|
6227 |
"version": "11.0.5",
|
@@ -6351,6 +7381,58 @@
|
|
6351 |
"punycode": "^2.1.0"
|
6352 |
}
|
6353 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6354 |
"node_modules/util-deprecate": {
|
6355 |
"version": "1.0.2",
|
6356 |
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
|
11 |
"@huggingface/hub": "^1.1.1",
|
12 |
"@huggingface/inference": "^3.6.1",
|
13 |
"@monaco-editor/react": "^4.7.0",
|
14 |
+
"@radix-ui/react-avatar": "^1.1.10",
|
15 |
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
16 |
+
"@radix-ui/react-popover": "^1.1.14",
|
17 |
+
"@radix-ui/react-select": "^2.2.5",
|
18 |
+
"@radix-ui/react-slot": "^1.2.3",
|
19 |
+
"@radix-ui/react-switch": "^1.2.5",
|
20 |
+
"@radix-ui/react-tabs": "^1.1.12",
|
21 |
+
"@radix-ui/react-toggle": "^1.1.9",
|
22 |
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
23 |
"@tailwindcss/vite": "^4.0.15",
|
24 |
"@xenova/transformers": "^2.17.2",
|
25 |
"body-parser": "^1.20.3",
|
26 |
+
"class-variance-authority": "^0.7.1",
|
27 |
"classnames": "^2.5.1",
|
28 |
+
"clsx": "^2.1.1",
|
29 |
"cookie-parser": "^1.4.7",
|
30 |
"dotenv": "^16.4.7",
|
31 |
"express": "^4.21.2",
|
32 |
+
"lucide-react": "^0.511.0",
|
33 |
+
"next-themes": "^0.4.6",
|
34 |
"react": "^19.0.0",
|
35 |
"react-dom": "^19.0.0",
|
36 |
"react-icons": "^5.5.0",
|
|
|
38 |
"react-speech-recognition": "^4.0.0",
|
39 |
"react-toastify": "^11.0.5",
|
40 |
"react-use": "^17.6.0",
|
41 |
+
"sonner": "^2.0.3",
|
42 |
+
"tailwind-merge": "^3.3.0",
|
43 |
"tailwindcss": "^4.0.15"
|
44 |
},
|
45 |
"devDependencies": {
|
46 |
"@eslint/js": "^9.21.0",
|
47 |
"@types/express": "^5.0.1",
|
48 |
+
"@types/node": "^22.15.21",
|
49 |
"@types/react": "^19.0.10",
|
50 |
"@types/react-dom": "^19.0.4",
|
51 |
"@types/react-speech-recognition": "^3.9.6",
|
|
|
54 |
"eslint-plugin-react-hooks": "^5.1.0",
|
55 |
"eslint-plugin-react-refresh": "^0.4.19",
|
56 |
"globals": "^15.15.0",
|
57 |
+
"tw-animate-css": "^1.3.0",
|
58 |
"typescript": "~5.7.2",
|
59 |
"typescript-eslint": "^8.24.1",
|
60 |
"vite": "^6.2.0"
|
|
|
862 |
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
863 |
}
|
864 |
},
|
865 |
+
"node_modules/@floating-ui/core": {
|
866 |
+
"version": "1.7.0",
|
867 |
+
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz",
|
868 |
+
"integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
|
869 |
+
"license": "MIT",
|
870 |
+
"dependencies": {
|
871 |
+
"@floating-ui/utils": "^0.2.9"
|
872 |
+
}
|
873 |
+
},
|
874 |
+
"node_modules/@floating-ui/dom": {
|
875 |
+
"version": "1.7.0",
|
876 |
+
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz",
|
877 |
+
"integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
|
878 |
+
"license": "MIT",
|
879 |
+
"dependencies": {
|
880 |
+
"@floating-ui/core": "^1.7.0",
|
881 |
+
"@floating-ui/utils": "^0.2.9"
|
882 |
+
}
|
883 |
+
},
|
884 |
+
"node_modules/@floating-ui/react-dom": {
|
885 |
+
"version": "2.1.2",
|
886 |
+
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
|
887 |
+
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
|
888 |
+
"license": "MIT",
|
889 |
+
"dependencies": {
|
890 |
+
"@floating-ui/dom": "^1.0.0"
|
891 |
+
},
|
892 |
+
"peerDependencies": {
|
893 |
+
"react": ">=16.8.0",
|
894 |
+
"react-dom": ">=16.8.0"
|
895 |
+
}
|
896 |
+
},
|
897 |
+
"node_modules/@floating-ui/utils": {
|
898 |
+
"version": "0.2.9",
|
899 |
+
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
900 |
+
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
901 |
+
"license": "MIT"
|
902 |
+
},
|
903 |
"node_modules/@huggingface/hub": {
|
904 |
"version": "1.1.1",
|
905 |
"resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-1.1.1.tgz",
|
|
|
1159 |
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
1160 |
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
1161 |
},
|
1162 |
+
"node_modules/@radix-ui/number": {
|
1163 |
+
"version": "1.1.1",
|
1164 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
|
1165 |
+
"integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
|
1166 |
+
"license": "MIT"
|
1167 |
+
},
|
1168 |
+
"node_modules/@radix-ui/primitive": {
|
1169 |
+
"version": "1.1.2",
|
1170 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
|
1171 |
+
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
|
1172 |
+
"license": "MIT"
|
1173 |
+
},
|
1174 |
+
"node_modules/@radix-ui/react-arrow": {
|
1175 |
+
"version": "1.1.7",
|
1176 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
|
1177 |
+
"integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
|
1178 |
+
"license": "MIT",
|
1179 |
+
"dependencies": {
|
1180 |
+
"@radix-ui/react-primitive": "2.1.3"
|
1181 |
+
},
|
1182 |
+
"peerDependencies": {
|
1183 |
+
"@types/react": "*",
|
1184 |
+
"@types/react-dom": "*",
|
1185 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1186 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1187 |
+
},
|
1188 |
+
"peerDependenciesMeta": {
|
1189 |
+
"@types/react": {
|
1190 |
+
"optional": true
|
1191 |
+
},
|
1192 |
+
"@types/react-dom": {
|
1193 |
+
"optional": true
|
1194 |
+
}
|
1195 |
+
}
|
1196 |
+
},
|
1197 |
+
"node_modules/@radix-ui/react-avatar": {
|
1198 |
+
"version": "1.1.10",
|
1199 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
|
1200 |
+
"integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==",
|
1201 |
+
"license": "MIT",
|
1202 |
+
"dependencies": {
|
1203 |
+
"@radix-ui/react-context": "1.1.2",
|
1204 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1205 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1206 |
+
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
1207 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1208 |
+
},
|
1209 |
+
"peerDependencies": {
|
1210 |
+
"@types/react": "*",
|
1211 |
+
"@types/react-dom": "*",
|
1212 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1213 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1214 |
+
},
|
1215 |
+
"peerDependenciesMeta": {
|
1216 |
+
"@types/react": {
|
1217 |
+
"optional": true
|
1218 |
+
},
|
1219 |
+
"@types/react-dom": {
|
1220 |
+
"optional": true
|
1221 |
+
}
|
1222 |
+
}
|
1223 |
+
},
|
1224 |
+
"node_modules/@radix-ui/react-collection": {
|
1225 |
+
"version": "1.1.7",
|
1226 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
|
1227 |
+
"integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
|
1228 |
+
"license": "MIT",
|
1229 |
+
"dependencies": {
|
1230 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1231 |
+
"@radix-ui/react-context": "1.1.2",
|
1232 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1233 |
+
"@radix-ui/react-slot": "1.2.3"
|
1234 |
+
},
|
1235 |
+
"peerDependencies": {
|
1236 |
+
"@types/react": "*",
|
1237 |
+
"@types/react-dom": "*",
|
1238 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1239 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1240 |
+
},
|
1241 |
+
"peerDependenciesMeta": {
|
1242 |
+
"@types/react": {
|
1243 |
+
"optional": true
|
1244 |
+
},
|
1245 |
+
"@types/react-dom": {
|
1246 |
+
"optional": true
|
1247 |
+
}
|
1248 |
+
}
|
1249 |
+
},
|
1250 |
+
"node_modules/@radix-ui/react-compose-refs": {
|
1251 |
+
"version": "1.1.2",
|
1252 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
1253 |
+
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
1254 |
+
"license": "MIT",
|
1255 |
+
"peerDependencies": {
|
1256 |
+
"@types/react": "*",
|
1257 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1258 |
+
},
|
1259 |
+
"peerDependenciesMeta": {
|
1260 |
+
"@types/react": {
|
1261 |
+
"optional": true
|
1262 |
+
}
|
1263 |
+
}
|
1264 |
+
},
|
1265 |
+
"node_modules/@radix-ui/react-context": {
|
1266 |
+
"version": "1.1.2",
|
1267 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
1268 |
+
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
1269 |
+
"license": "MIT",
|
1270 |
+
"peerDependencies": {
|
1271 |
+
"@types/react": "*",
|
1272 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1273 |
+
},
|
1274 |
+
"peerDependenciesMeta": {
|
1275 |
+
"@types/react": {
|
1276 |
+
"optional": true
|
1277 |
+
}
|
1278 |
+
}
|
1279 |
+
},
|
1280 |
+
"node_modules/@radix-ui/react-direction": {
|
1281 |
+
"version": "1.1.1",
|
1282 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
|
1283 |
+
"integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
|
1284 |
+
"license": "MIT",
|
1285 |
+
"peerDependencies": {
|
1286 |
+
"@types/react": "*",
|
1287 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1288 |
+
},
|
1289 |
+
"peerDependenciesMeta": {
|
1290 |
+
"@types/react": {
|
1291 |
+
"optional": true
|
1292 |
+
}
|
1293 |
+
}
|
1294 |
+
},
|
1295 |
+
"node_modules/@radix-ui/react-dismissable-layer": {
|
1296 |
+
"version": "1.1.10",
|
1297 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz",
|
1298 |
+
"integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==",
|
1299 |
+
"license": "MIT",
|
1300 |
+
"dependencies": {
|
1301 |
+
"@radix-ui/primitive": "1.1.2",
|
1302 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1303 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1304 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1305 |
+
"@radix-ui/react-use-escape-keydown": "1.1.1"
|
1306 |
+
},
|
1307 |
+
"peerDependencies": {
|
1308 |
+
"@types/react": "*",
|
1309 |
+
"@types/react-dom": "*",
|
1310 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1311 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1312 |
+
},
|
1313 |
+
"peerDependenciesMeta": {
|
1314 |
+
"@types/react": {
|
1315 |
+
"optional": true
|
1316 |
+
},
|
1317 |
+
"@types/react-dom": {
|
1318 |
+
"optional": true
|
1319 |
+
}
|
1320 |
+
}
|
1321 |
+
},
|
1322 |
+
"node_modules/@radix-ui/react-dropdown-menu": {
|
1323 |
+
"version": "2.1.15",
|
1324 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz",
|
1325 |
+
"integrity": "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==",
|
1326 |
+
"license": "MIT",
|
1327 |
+
"dependencies": {
|
1328 |
+
"@radix-ui/primitive": "1.1.2",
|
1329 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1330 |
+
"@radix-ui/react-context": "1.1.2",
|
1331 |
+
"@radix-ui/react-id": "1.1.1",
|
1332 |
+
"@radix-ui/react-menu": "2.1.15",
|
1333 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1334 |
+
"@radix-ui/react-use-controllable-state": "1.2.2"
|
1335 |
+
},
|
1336 |
+
"peerDependencies": {
|
1337 |
+
"@types/react": "*",
|
1338 |
+
"@types/react-dom": "*",
|
1339 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1340 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1341 |
+
},
|
1342 |
+
"peerDependenciesMeta": {
|
1343 |
+
"@types/react": {
|
1344 |
+
"optional": true
|
1345 |
+
},
|
1346 |
+
"@types/react-dom": {
|
1347 |
+
"optional": true
|
1348 |
+
}
|
1349 |
+
}
|
1350 |
+
},
|
1351 |
+
"node_modules/@radix-ui/react-focus-guards": {
|
1352 |
+
"version": "1.1.2",
|
1353 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
|
1354 |
+
"integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==",
|
1355 |
+
"license": "MIT",
|
1356 |
+
"peerDependencies": {
|
1357 |
+
"@types/react": "*",
|
1358 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1359 |
+
},
|
1360 |
+
"peerDependenciesMeta": {
|
1361 |
+
"@types/react": {
|
1362 |
+
"optional": true
|
1363 |
+
}
|
1364 |
+
}
|
1365 |
+
},
|
1366 |
+
"node_modules/@radix-ui/react-focus-scope": {
|
1367 |
+
"version": "1.1.7",
|
1368 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
|
1369 |
+
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
|
1370 |
+
"license": "MIT",
|
1371 |
+
"dependencies": {
|
1372 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1373 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1374 |
+
"@radix-ui/react-use-callback-ref": "1.1.1"
|
1375 |
+
},
|
1376 |
+
"peerDependencies": {
|
1377 |
+
"@types/react": "*",
|
1378 |
+
"@types/react-dom": "*",
|
1379 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1380 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1381 |
+
},
|
1382 |
+
"peerDependenciesMeta": {
|
1383 |
+
"@types/react": {
|
1384 |
+
"optional": true
|
1385 |
+
},
|
1386 |
+
"@types/react-dom": {
|
1387 |
+
"optional": true
|
1388 |
+
}
|
1389 |
+
}
|
1390 |
+
},
|
1391 |
+
"node_modules/@radix-ui/react-id": {
|
1392 |
+
"version": "1.1.1",
|
1393 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
1394 |
+
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
|
1395 |
+
"license": "MIT",
|
1396 |
+
"dependencies": {
|
1397 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1398 |
+
},
|
1399 |
+
"peerDependencies": {
|
1400 |
+
"@types/react": "*",
|
1401 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1402 |
+
},
|
1403 |
+
"peerDependenciesMeta": {
|
1404 |
+
"@types/react": {
|
1405 |
+
"optional": true
|
1406 |
+
}
|
1407 |
+
}
|
1408 |
+
},
|
1409 |
+
"node_modules/@radix-ui/react-menu": {
|
1410 |
+
"version": "2.1.15",
|
1411 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz",
|
1412 |
+
"integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==",
|
1413 |
+
"license": "MIT",
|
1414 |
+
"dependencies": {
|
1415 |
+
"@radix-ui/primitive": "1.1.2",
|
1416 |
+
"@radix-ui/react-collection": "1.1.7",
|
1417 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1418 |
+
"@radix-ui/react-context": "1.1.2",
|
1419 |
+
"@radix-ui/react-direction": "1.1.1",
|
1420 |
+
"@radix-ui/react-dismissable-layer": "1.1.10",
|
1421 |
+
"@radix-ui/react-focus-guards": "1.1.2",
|
1422 |
+
"@radix-ui/react-focus-scope": "1.1.7",
|
1423 |
+
"@radix-ui/react-id": "1.1.1",
|
1424 |
+
"@radix-ui/react-popper": "1.2.7",
|
1425 |
+
"@radix-ui/react-portal": "1.1.9",
|
1426 |
+
"@radix-ui/react-presence": "1.1.4",
|
1427 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1428 |
+
"@radix-ui/react-roving-focus": "1.1.10",
|
1429 |
+
"@radix-ui/react-slot": "1.2.3",
|
1430 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1431 |
+
"aria-hidden": "^1.2.4",
|
1432 |
+
"react-remove-scroll": "^2.6.3"
|
1433 |
+
},
|
1434 |
+
"peerDependencies": {
|
1435 |
+
"@types/react": "*",
|
1436 |
+
"@types/react-dom": "*",
|
1437 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1438 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1439 |
+
},
|
1440 |
+
"peerDependenciesMeta": {
|
1441 |
+
"@types/react": {
|
1442 |
+
"optional": true
|
1443 |
+
},
|
1444 |
+
"@types/react-dom": {
|
1445 |
+
"optional": true
|
1446 |
+
}
|
1447 |
+
}
|
1448 |
+
},
|
1449 |
+
"node_modules/@radix-ui/react-popover": {
|
1450 |
+
"version": "1.1.14",
|
1451 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz",
|
1452 |
+
"integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==",
|
1453 |
+
"license": "MIT",
|
1454 |
+
"dependencies": {
|
1455 |
+
"@radix-ui/primitive": "1.1.2",
|
1456 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1457 |
+
"@radix-ui/react-context": "1.1.2",
|
1458 |
+
"@radix-ui/react-dismissable-layer": "1.1.10",
|
1459 |
+
"@radix-ui/react-focus-guards": "1.1.2",
|
1460 |
+
"@radix-ui/react-focus-scope": "1.1.7",
|
1461 |
+
"@radix-ui/react-id": "1.1.1",
|
1462 |
+
"@radix-ui/react-popper": "1.2.7",
|
1463 |
+
"@radix-ui/react-portal": "1.1.9",
|
1464 |
+
"@radix-ui/react-presence": "1.1.4",
|
1465 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1466 |
+
"@radix-ui/react-slot": "1.2.3",
|
1467 |
+
"@radix-ui/react-use-controllable-state": "1.2.2",
|
1468 |
+
"aria-hidden": "^1.2.4",
|
1469 |
+
"react-remove-scroll": "^2.6.3"
|
1470 |
+
},
|
1471 |
+
"peerDependencies": {
|
1472 |
+
"@types/react": "*",
|
1473 |
+
"@types/react-dom": "*",
|
1474 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1475 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1476 |
+
},
|
1477 |
+
"peerDependenciesMeta": {
|
1478 |
+
"@types/react": {
|
1479 |
+
"optional": true
|
1480 |
+
},
|
1481 |
+
"@types/react-dom": {
|
1482 |
+
"optional": true
|
1483 |
+
}
|
1484 |
+
}
|
1485 |
+
},
|
1486 |
+
"node_modules/@radix-ui/react-popper": {
|
1487 |
+
"version": "1.2.7",
|
1488 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
|
1489 |
+
"integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==",
|
1490 |
+
"license": "MIT",
|
1491 |
+
"dependencies": {
|
1492 |
+
"@floating-ui/react-dom": "^2.0.0",
|
1493 |
+
"@radix-ui/react-arrow": "1.1.7",
|
1494 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1495 |
+
"@radix-ui/react-context": "1.1.2",
|
1496 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1497 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1498 |
+
"@radix-ui/react-use-layout-effect": "1.1.1",
|
1499 |
+
"@radix-ui/react-use-rect": "1.1.1",
|
1500 |
+
"@radix-ui/react-use-size": "1.1.1",
|
1501 |
+
"@radix-ui/rect": "1.1.1"
|
1502 |
+
},
|
1503 |
+
"peerDependencies": {
|
1504 |
+
"@types/react": "*",
|
1505 |
+
"@types/react-dom": "*",
|
1506 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1507 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1508 |
+
},
|
1509 |
+
"peerDependenciesMeta": {
|
1510 |
+
"@types/react": {
|
1511 |
+
"optional": true
|
1512 |
+
},
|
1513 |
+
"@types/react-dom": {
|
1514 |
+
"optional": true
|
1515 |
+
}
|
1516 |
+
}
|
1517 |
+
},
|
1518 |
+
"node_modules/@radix-ui/react-portal": {
|
1519 |
+
"version": "1.1.9",
|
1520 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
|
1521 |
+
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
1522 |
+
"license": "MIT",
|
1523 |
+
"dependencies": {
|
1524 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1525 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1526 |
+
},
|
1527 |
+
"peerDependencies": {
|
1528 |
+
"@types/react": "*",
|
1529 |
+
"@types/react-dom": "*",
|
1530 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1531 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1532 |
+
},
|
1533 |
+
"peerDependenciesMeta": {
|
1534 |
+
"@types/react": {
|
1535 |
+
"optional": true
|
1536 |
+
},
|
1537 |
+
"@types/react-dom": {
|
1538 |
+
"optional": true
|
1539 |
+
}
|
1540 |
+
}
|
1541 |
+
},
|
1542 |
+
"node_modules/@radix-ui/react-presence": {
|
1543 |
+
"version": "1.1.4",
|
1544 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
|
1545 |
+
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
|
1546 |
+
"license": "MIT",
|
1547 |
+
"dependencies": {
|
1548 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1549 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1550 |
+
},
|
1551 |
+
"peerDependencies": {
|
1552 |
+
"@types/react": "*",
|
1553 |
+
"@types/react-dom": "*",
|
1554 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1555 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1556 |
+
},
|
1557 |
+
"peerDependenciesMeta": {
|
1558 |
+
"@types/react": {
|
1559 |
+
"optional": true
|
1560 |
+
},
|
1561 |
+
"@types/react-dom": {
|
1562 |
+
"optional": true
|
1563 |
+
}
|
1564 |
+
}
|
1565 |
+
},
|
1566 |
+
"node_modules/@radix-ui/react-primitive": {
|
1567 |
+
"version": "2.1.3",
|
1568 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
1569 |
+
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
1570 |
+
"license": "MIT",
|
1571 |
+
"dependencies": {
|
1572 |
+
"@radix-ui/react-slot": "1.2.3"
|
1573 |
+
},
|
1574 |
+
"peerDependencies": {
|
1575 |
+
"@types/react": "*",
|
1576 |
+
"@types/react-dom": "*",
|
1577 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1578 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1579 |
+
},
|
1580 |
+
"peerDependenciesMeta": {
|
1581 |
+
"@types/react": {
|
1582 |
+
"optional": true
|
1583 |
+
},
|
1584 |
+
"@types/react-dom": {
|
1585 |
+
"optional": true
|
1586 |
+
}
|
1587 |
+
}
|
1588 |
+
},
|
1589 |
+
"node_modules/@radix-ui/react-roving-focus": {
|
1590 |
+
"version": "1.1.10",
|
1591 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz",
|
1592 |
+
"integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==",
|
1593 |
+
"license": "MIT",
|
1594 |
+
"dependencies": {
|
1595 |
+
"@radix-ui/primitive": "1.1.2",
|
1596 |
+
"@radix-ui/react-collection": "1.1.7",
|
1597 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1598 |
+
"@radix-ui/react-context": "1.1.2",
|
1599 |
+
"@radix-ui/react-direction": "1.1.1",
|
1600 |
+
"@radix-ui/react-id": "1.1.1",
|
1601 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1602 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1603 |
+
"@radix-ui/react-use-controllable-state": "1.2.2"
|
1604 |
+
},
|
1605 |
+
"peerDependencies": {
|
1606 |
+
"@types/react": "*",
|
1607 |
+
"@types/react-dom": "*",
|
1608 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1609 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1610 |
+
},
|
1611 |
+
"peerDependenciesMeta": {
|
1612 |
+
"@types/react": {
|
1613 |
+
"optional": true
|
1614 |
+
},
|
1615 |
+
"@types/react-dom": {
|
1616 |
+
"optional": true
|
1617 |
+
}
|
1618 |
+
}
|
1619 |
+
},
|
1620 |
+
"node_modules/@radix-ui/react-select": {
|
1621 |
+
"version": "2.2.5",
|
1622 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz",
|
1623 |
+
"integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==",
|
1624 |
+
"license": "MIT",
|
1625 |
+
"dependencies": {
|
1626 |
+
"@radix-ui/number": "1.1.1",
|
1627 |
+
"@radix-ui/primitive": "1.1.2",
|
1628 |
+
"@radix-ui/react-collection": "1.1.7",
|
1629 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1630 |
+
"@radix-ui/react-context": "1.1.2",
|
1631 |
+
"@radix-ui/react-direction": "1.1.1",
|
1632 |
+
"@radix-ui/react-dismissable-layer": "1.1.10",
|
1633 |
+
"@radix-ui/react-focus-guards": "1.1.2",
|
1634 |
+
"@radix-ui/react-focus-scope": "1.1.7",
|
1635 |
+
"@radix-ui/react-id": "1.1.1",
|
1636 |
+
"@radix-ui/react-popper": "1.2.7",
|
1637 |
+
"@radix-ui/react-portal": "1.1.9",
|
1638 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1639 |
+
"@radix-ui/react-slot": "1.2.3",
|
1640 |
+
"@radix-ui/react-use-callback-ref": "1.1.1",
|
1641 |
+
"@radix-ui/react-use-controllable-state": "1.2.2",
|
1642 |
+
"@radix-ui/react-use-layout-effect": "1.1.1",
|
1643 |
+
"@radix-ui/react-use-previous": "1.1.1",
|
1644 |
+
"@radix-ui/react-visually-hidden": "1.2.3",
|
1645 |
+
"aria-hidden": "^1.2.4",
|
1646 |
+
"react-remove-scroll": "^2.6.3"
|
1647 |
+
},
|
1648 |
+
"peerDependencies": {
|
1649 |
+
"@types/react": "*",
|
1650 |
+
"@types/react-dom": "*",
|
1651 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1652 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1653 |
+
},
|
1654 |
+
"peerDependenciesMeta": {
|
1655 |
+
"@types/react": {
|
1656 |
+
"optional": true
|
1657 |
+
},
|
1658 |
+
"@types/react-dom": {
|
1659 |
+
"optional": true
|
1660 |
+
}
|
1661 |
+
}
|
1662 |
+
},
|
1663 |
+
"node_modules/@radix-ui/react-slot": {
|
1664 |
+
"version": "1.2.3",
|
1665 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
1666 |
+
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
1667 |
+
"license": "MIT",
|
1668 |
+
"dependencies": {
|
1669 |
+
"@radix-ui/react-compose-refs": "1.1.2"
|
1670 |
+
},
|
1671 |
+
"peerDependencies": {
|
1672 |
+
"@types/react": "*",
|
1673 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1674 |
+
},
|
1675 |
+
"peerDependenciesMeta": {
|
1676 |
+
"@types/react": {
|
1677 |
+
"optional": true
|
1678 |
+
}
|
1679 |
+
}
|
1680 |
+
},
|
1681 |
+
"node_modules/@radix-ui/react-switch": {
|
1682 |
+
"version": "1.2.5",
|
1683 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz",
|
1684 |
+
"integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==",
|
1685 |
+
"license": "MIT",
|
1686 |
+
"dependencies": {
|
1687 |
+
"@radix-ui/primitive": "1.1.2",
|
1688 |
+
"@radix-ui/react-compose-refs": "1.1.2",
|
1689 |
+
"@radix-ui/react-context": "1.1.2",
|
1690 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1691 |
+
"@radix-ui/react-use-controllable-state": "1.2.2",
|
1692 |
+
"@radix-ui/react-use-previous": "1.1.1",
|
1693 |
+
"@radix-ui/react-use-size": "1.1.1"
|
1694 |
+
},
|
1695 |
+
"peerDependencies": {
|
1696 |
+
"@types/react": "*",
|
1697 |
+
"@types/react-dom": "*",
|
1698 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1699 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1700 |
+
},
|
1701 |
+
"peerDependenciesMeta": {
|
1702 |
+
"@types/react": {
|
1703 |
+
"optional": true
|
1704 |
+
},
|
1705 |
+
"@types/react-dom": {
|
1706 |
+
"optional": true
|
1707 |
+
}
|
1708 |
+
}
|
1709 |
+
},
|
1710 |
+
"node_modules/@radix-ui/react-tabs": {
|
1711 |
+
"version": "1.1.12",
|
1712 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
|
1713 |
+
"integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==",
|
1714 |
+
"license": "MIT",
|
1715 |
+
"dependencies": {
|
1716 |
+
"@radix-ui/primitive": "1.1.2",
|
1717 |
+
"@radix-ui/react-context": "1.1.2",
|
1718 |
+
"@radix-ui/react-direction": "1.1.1",
|
1719 |
+
"@radix-ui/react-id": "1.1.1",
|
1720 |
+
"@radix-ui/react-presence": "1.1.4",
|
1721 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1722 |
+
"@radix-ui/react-roving-focus": "1.1.10",
|
1723 |
+
"@radix-ui/react-use-controllable-state": "1.2.2"
|
1724 |
+
},
|
1725 |
+
"peerDependencies": {
|
1726 |
+
"@types/react": "*",
|
1727 |
+
"@types/react-dom": "*",
|
1728 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1729 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1730 |
+
},
|
1731 |
+
"peerDependenciesMeta": {
|
1732 |
+
"@types/react": {
|
1733 |
+
"optional": true
|
1734 |
+
},
|
1735 |
+
"@types/react-dom": {
|
1736 |
+
"optional": true
|
1737 |
+
}
|
1738 |
+
}
|
1739 |
+
},
|
1740 |
+
"node_modules/@radix-ui/react-toggle": {
|
1741 |
+
"version": "1.1.9",
|
1742 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz",
|
1743 |
+
"integrity": "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==",
|
1744 |
+
"license": "MIT",
|
1745 |
+
"dependencies": {
|
1746 |
+
"@radix-ui/primitive": "1.1.2",
|
1747 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1748 |
+
"@radix-ui/react-use-controllable-state": "1.2.2"
|
1749 |
+
},
|
1750 |
+
"peerDependencies": {
|
1751 |
+
"@types/react": "*",
|
1752 |
+
"@types/react-dom": "*",
|
1753 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1754 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1755 |
+
},
|
1756 |
+
"peerDependenciesMeta": {
|
1757 |
+
"@types/react": {
|
1758 |
+
"optional": true
|
1759 |
+
},
|
1760 |
+
"@types/react-dom": {
|
1761 |
+
"optional": true
|
1762 |
+
}
|
1763 |
+
}
|
1764 |
+
},
|
1765 |
+
"node_modules/@radix-ui/react-toggle-group": {
|
1766 |
+
"version": "1.1.10",
|
1767 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.10.tgz",
|
1768 |
+
"integrity": "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==",
|
1769 |
+
"license": "MIT",
|
1770 |
+
"dependencies": {
|
1771 |
+
"@radix-ui/primitive": "1.1.2",
|
1772 |
+
"@radix-ui/react-context": "1.1.2",
|
1773 |
+
"@radix-ui/react-direction": "1.1.1",
|
1774 |
+
"@radix-ui/react-primitive": "2.1.3",
|
1775 |
+
"@radix-ui/react-roving-focus": "1.1.10",
|
1776 |
+
"@radix-ui/react-toggle": "1.1.9",
|
1777 |
+
"@radix-ui/react-use-controllable-state": "1.2.2"
|
1778 |
+
},
|
1779 |
+
"peerDependencies": {
|
1780 |
+
"@types/react": "*",
|
1781 |
+
"@types/react-dom": "*",
|
1782 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1783 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1784 |
+
},
|
1785 |
+
"peerDependenciesMeta": {
|
1786 |
+
"@types/react": {
|
1787 |
+
"optional": true
|
1788 |
+
},
|
1789 |
+
"@types/react-dom": {
|
1790 |
+
"optional": true
|
1791 |
+
}
|
1792 |
+
}
|
1793 |
+
},
|
1794 |
+
"node_modules/@radix-ui/react-use-callback-ref": {
|
1795 |
+
"version": "1.1.1",
|
1796 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
1797 |
+
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
1798 |
+
"license": "MIT",
|
1799 |
+
"peerDependencies": {
|
1800 |
+
"@types/react": "*",
|
1801 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1802 |
+
},
|
1803 |
+
"peerDependenciesMeta": {
|
1804 |
+
"@types/react": {
|
1805 |
+
"optional": true
|
1806 |
+
}
|
1807 |
+
}
|
1808 |
+
},
|
1809 |
+
"node_modules/@radix-ui/react-use-controllable-state": {
|
1810 |
+
"version": "1.2.2",
|
1811 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
1812 |
+
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
1813 |
+
"license": "MIT",
|
1814 |
+
"dependencies": {
|
1815 |
+
"@radix-ui/react-use-effect-event": "0.0.2",
|
1816 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1817 |
+
},
|
1818 |
+
"peerDependencies": {
|
1819 |
+
"@types/react": "*",
|
1820 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1821 |
+
},
|
1822 |
+
"peerDependenciesMeta": {
|
1823 |
+
"@types/react": {
|
1824 |
+
"optional": true
|
1825 |
+
}
|
1826 |
+
}
|
1827 |
+
},
|
1828 |
+
"node_modules/@radix-ui/react-use-effect-event": {
|
1829 |
+
"version": "0.0.2",
|
1830 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
|
1831 |
+
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
|
1832 |
+
"license": "MIT",
|
1833 |
+
"dependencies": {
|
1834 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1835 |
+
},
|
1836 |
+
"peerDependencies": {
|
1837 |
+
"@types/react": "*",
|
1838 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1839 |
+
},
|
1840 |
+
"peerDependenciesMeta": {
|
1841 |
+
"@types/react": {
|
1842 |
+
"optional": true
|
1843 |
+
}
|
1844 |
+
}
|
1845 |
+
},
|
1846 |
+
"node_modules/@radix-ui/react-use-escape-keydown": {
|
1847 |
+
"version": "1.1.1",
|
1848 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
|
1849 |
+
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
|
1850 |
+
"license": "MIT",
|
1851 |
+
"dependencies": {
|
1852 |
+
"@radix-ui/react-use-callback-ref": "1.1.1"
|
1853 |
+
},
|
1854 |
+
"peerDependencies": {
|
1855 |
+
"@types/react": "*",
|
1856 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1857 |
+
},
|
1858 |
+
"peerDependenciesMeta": {
|
1859 |
+
"@types/react": {
|
1860 |
+
"optional": true
|
1861 |
+
}
|
1862 |
+
}
|
1863 |
+
},
|
1864 |
+
"node_modules/@radix-ui/react-use-is-hydrated": {
|
1865 |
+
"version": "0.1.0",
|
1866 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz",
|
1867 |
+
"integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==",
|
1868 |
+
"license": "MIT",
|
1869 |
+
"dependencies": {
|
1870 |
+
"use-sync-external-store": "^1.5.0"
|
1871 |
+
},
|
1872 |
+
"peerDependencies": {
|
1873 |
+
"@types/react": "*",
|
1874 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1875 |
+
},
|
1876 |
+
"peerDependenciesMeta": {
|
1877 |
+
"@types/react": {
|
1878 |
+
"optional": true
|
1879 |
+
}
|
1880 |
+
}
|
1881 |
+
},
|
1882 |
+
"node_modules/@radix-ui/react-use-layout-effect": {
|
1883 |
+
"version": "1.1.1",
|
1884 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
1885 |
+
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
1886 |
+
"license": "MIT",
|
1887 |
+
"peerDependencies": {
|
1888 |
+
"@types/react": "*",
|
1889 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1890 |
+
},
|
1891 |
+
"peerDependenciesMeta": {
|
1892 |
+
"@types/react": {
|
1893 |
+
"optional": true
|
1894 |
+
}
|
1895 |
+
}
|
1896 |
+
},
|
1897 |
+
"node_modules/@radix-ui/react-use-previous": {
|
1898 |
+
"version": "1.1.1",
|
1899 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
|
1900 |
+
"integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
|
1901 |
+
"license": "MIT",
|
1902 |
+
"peerDependencies": {
|
1903 |
+
"@types/react": "*",
|
1904 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1905 |
+
},
|
1906 |
+
"peerDependenciesMeta": {
|
1907 |
+
"@types/react": {
|
1908 |
+
"optional": true
|
1909 |
+
}
|
1910 |
+
}
|
1911 |
+
},
|
1912 |
+
"node_modules/@radix-ui/react-use-rect": {
|
1913 |
+
"version": "1.1.1",
|
1914 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
|
1915 |
+
"integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
|
1916 |
+
"license": "MIT",
|
1917 |
+
"dependencies": {
|
1918 |
+
"@radix-ui/rect": "1.1.1"
|
1919 |
+
},
|
1920 |
+
"peerDependencies": {
|
1921 |
+
"@types/react": "*",
|
1922 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1923 |
+
},
|
1924 |
+
"peerDependenciesMeta": {
|
1925 |
+
"@types/react": {
|
1926 |
+
"optional": true
|
1927 |
+
}
|
1928 |
+
}
|
1929 |
+
},
|
1930 |
+
"node_modules/@radix-ui/react-use-size": {
|
1931 |
+
"version": "1.1.1",
|
1932 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
|
1933 |
+
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
|
1934 |
+
"license": "MIT",
|
1935 |
+
"dependencies": {
|
1936 |
+
"@radix-ui/react-use-layout-effect": "1.1.1"
|
1937 |
+
},
|
1938 |
+
"peerDependencies": {
|
1939 |
+
"@types/react": "*",
|
1940 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1941 |
+
},
|
1942 |
+
"peerDependenciesMeta": {
|
1943 |
+
"@types/react": {
|
1944 |
+
"optional": true
|
1945 |
+
}
|
1946 |
+
}
|
1947 |
+
},
|
1948 |
+
"node_modules/@radix-ui/react-visually-hidden": {
|
1949 |
+
"version": "1.2.3",
|
1950 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
|
1951 |
+
"integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
|
1952 |
+
"license": "MIT",
|
1953 |
+
"dependencies": {
|
1954 |
+
"@radix-ui/react-primitive": "2.1.3"
|
1955 |
+
},
|
1956 |
+
"peerDependencies": {
|
1957 |
+
"@types/react": "*",
|
1958 |
+
"@types/react-dom": "*",
|
1959 |
+
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
1960 |
+
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
1961 |
+
},
|
1962 |
+
"peerDependenciesMeta": {
|
1963 |
+
"@types/react": {
|
1964 |
+
"optional": true
|
1965 |
+
},
|
1966 |
+
"@types/react-dom": {
|
1967 |
+
"optional": true
|
1968 |
+
}
|
1969 |
+
}
|
1970 |
+
},
|
1971 |
+
"node_modules/@radix-ui/rect": {
|
1972 |
+
"version": "1.1.1",
|
1973 |
+
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
|
1974 |
+
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
|
1975 |
+
"license": "MIT"
|
1976 |
+
},
|
1977 |
"node_modules/@rollup/rollup-android-arm-eabi": {
|
1978 |
"version": "4.36.0",
|
1979 |
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
|
|
|
2572 |
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
2573 |
},
|
2574 |
"node_modules/@types/node": {
|
2575 |
+
"version": "22.15.21",
|
2576 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz",
|
2577 |
+
"integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==",
|
2578 |
+
"license": "MIT",
|
2579 |
"dependencies": {
|
2580 |
+
"undici-types": "~6.21.0"
|
2581 |
}
|
2582 |
},
|
2583 |
"node_modules/@types/qs": {
|
|
|
2604 |
"version": "19.0.4",
|
2605 |
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
2606 |
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
2607 |
+
"devOptional": true,
|
2608 |
"peerDependencies": {
|
2609 |
"@types/react": "^19.0.0"
|
2610 |
}
|
|
|
2972 |
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
2973 |
"dev": true
|
2974 |
},
|
2975 |
+
"node_modules/aria-hidden": {
|
2976 |
+
"version": "1.2.6",
|
2977 |
+
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
2978 |
+
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
2979 |
+
"license": "MIT",
|
2980 |
+
"dependencies": {
|
2981 |
+
"tslib": "^2.0.0"
|
2982 |
+
},
|
2983 |
+
"engines": {
|
2984 |
+
"node": ">=10"
|
2985 |
+
}
|
2986 |
+
},
|
2987 |
"node_modules/array-flatten": {
|
2988 |
"version": "1.1.1",
|
2989 |
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
|
|
3348 |
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
3349 |
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
3350 |
},
|
3351 |
+
"node_modules/class-variance-authority": {
|
3352 |
+
"version": "0.7.1",
|
3353 |
+
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
3354 |
+
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
3355 |
+
"license": "Apache-2.0",
|
3356 |
+
"dependencies": {
|
3357 |
+
"clsx": "^2.1.1"
|
3358 |
+
},
|
3359 |
+
"funding": {
|
3360 |
+
"url": "https://polar.sh/cva"
|
3361 |
+
}
|
3362 |
+
},
|
3363 |
"node_modules/classnames": {
|
3364 |
"version": "2.5.1",
|
3365 |
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
|
|
3369 |
"version": "2.1.1",
|
3370 |
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
3371 |
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
3372 |
+
"license": "MIT",
|
3373 |
"engines": {
|
3374 |
"node": ">=6"
|
3375 |
}
|
|
|
3620 |
"node": ">=8"
|
3621 |
}
|
3622 |
},
|
3623 |
+
"node_modules/detect-node-es": {
|
3624 |
+
"version": "1.1.0",
|
3625 |
+
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
3626 |
+
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
3627 |
+
"license": "MIT"
|
3628 |
+
},
|
3629 |
"node_modules/devlop": {
|
3630 |
"version": "1.1.0",
|
3631 |
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
|
|
4294 |
"url": "https://github.com/sponsors/ljharb"
|
4295 |
}
|
4296 |
},
|
4297 |
+
"node_modules/get-nonce": {
|
4298 |
+
"version": "1.0.1",
|
4299 |
+
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
4300 |
+
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
4301 |
+
"license": "MIT",
|
4302 |
+
"engines": {
|
4303 |
+
"node": ">=6"
|
4304 |
+
}
|
4305 |
+
},
|
4306 |
"node_modules/get-proto": {
|
4307 |
"version": "1.0.1",
|
4308 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
|
|
5012 |
"yallist": "^3.0.2"
|
5013 |
}
|
5014 |
},
|
5015 |
+
"node_modules/lucide-react": {
|
5016 |
+
"version": "0.511.0",
|
5017 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz",
|
5018 |
+
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==",
|
5019 |
+
"license": "ISC",
|
5020 |
+
"peerDependencies": {
|
5021 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
5022 |
+
}
|
5023 |
+
},
|
5024 |
"node_modules/math-intrinsics": {
|
5025 |
"version": "1.1.0",
|
5026 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
|
5778 |
"node": ">= 0.6"
|
5779 |
}
|
5780 |
},
|
5781 |
+
"node_modules/next-themes": {
|
5782 |
+
"version": "0.4.6",
|
5783 |
+
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
5784 |
+
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
5785 |
+
"license": "MIT",
|
5786 |
+
"peerDependencies": {
|
5787 |
+
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
5788 |
+
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
5789 |
+
}
|
5790 |
+
},
|
5791 |
"node_modules/node-abi": {
|
5792 |
"version": "3.74.0",
|
5793 |
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
|
|
|
6317 |
"node": ">=0.10.0"
|
6318 |
}
|
6319 |
},
|
6320 |
+
"node_modules/react-remove-scroll": {
|
6321 |
+
"version": "2.7.0",
|
6322 |
+
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz",
|
6323 |
+
"integrity": "sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg==",
|
6324 |
+
"license": "MIT",
|
6325 |
+
"dependencies": {
|
6326 |
+
"react-remove-scroll-bar": "^2.3.7",
|
6327 |
+
"react-style-singleton": "^2.2.3",
|
6328 |
+
"tslib": "^2.1.0",
|
6329 |
+
"use-callback-ref": "^1.3.3",
|
6330 |
+
"use-sidecar": "^1.1.3"
|
6331 |
+
},
|
6332 |
+
"engines": {
|
6333 |
+
"node": ">=10"
|
6334 |
+
},
|
6335 |
+
"peerDependencies": {
|
6336 |
+
"@types/react": "*",
|
6337 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
6338 |
+
},
|
6339 |
+
"peerDependenciesMeta": {
|
6340 |
+
"@types/react": {
|
6341 |
+
"optional": true
|
6342 |
+
}
|
6343 |
+
}
|
6344 |
+
},
|
6345 |
+
"node_modules/react-remove-scroll-bar": {
|
6346 |
+
"version": "2.3.8",
|
6347 |
+
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
|
6348 |
+
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
|
6349 |
+
"license": "MIT",
|
6350 |
+
"dependencies": {
|
6351 |
+
"react-style-singleton": "^2.2.2",
|
6352 |
+
"tslib": "^2.0.0"
|
6353 |
+
},
|
6354 |
+
"engines": {
|
6355 |
+
"node": ">=10"
|
6356 |
+
},
|
6357 |
+
"peerDependencies": {
|
6358 |
+
"@types/react": "*",
|
6359 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
6360 |
+
},
|
6361 |
+
"peerDependenciesMeta": {
|
6362 |
+
"@types/react": {
|
6363 |
+
"optional": true
|
6364 |
+
}
|
6365 |
+
}
|
6366 |
+
},
|
6367 |
"node_modules/react-speech-recognition": {
|
6368 |
"version": "4.0.0",
|
6369 |
"resolved": "https://registry.npmjs.org/react-speech-recognition/-/react-speech-recognition-4.0.0.tgz",
|
|
|
6372 |
"react": ">=16.8.0"
|
6373 |
}
|
6374 |
},
|
6375 |
+
"node_modules/react-style-singleton": {
|
6376 |
+
"version": "2.2.3",
|
6377 |
+
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
6378 |
+
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
|
6379 |
+
"license": "MIT",
|
6380 |
+
"dependencies": {
|
6381 |
+
"get-nonce": "^1.0.0",
|
6382 |
+
"tslib": "^2.0.0"
|
6383 |
+
},
|
6384 |
+
"engines": {
|
6385 |
+
"node": ">=10"
|
6386 |
+
},
|
6387 |
+
"peerDependencies": {
|
6388 |
+
"@types/react": "*",
|
6389 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
6390 |
+
},
|
6391 |
+
"peerDependenciesMeta": {
|
6392 |
+
"@types/react": {
|
6393 |
+
"optional": true
|
6394 |
+
}
|
6395 |
+
}
|
6396 |
+
},
|
6397 |
"node_modules/react-toastify": {
|
6398 |
"version": "11.0.5",
|
6399 |
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
|
|
|
6874 |
"is-arrayish": "^0.3.1"
|
6875 |
}
|
6876 |
},
|
6877 |
+
"node_modules/sonner": {
|
6878 |
+
"version": "2.0.3",
|
6879 |
+
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
|
6880 |
+
"integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
|
6881 |
+
"license": "MIT",
|
6882 |
+
"peerDependencies": {
|
6883 |
+
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
6884 |
+
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
6885 |
+
}
|
6886 |
+
},
|
6887 |
"node_modules/source-map": {
|
6888 |
"version": "0.6.1",
|
6889 |
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
|
7040 |
"node": ">=8"
|
7041 |
}
|
7042 |
},
|
7043 |
+
"node_modules/tailwind-merge": {
|
7044 |
+
"version": "3.3.0",
|
7045 |
+
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
|
7046 |
+
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
|
7047 |
+
"license": "MIT",
|
7048 |
+
"funding": {
|
7049 |
+
"type": "github",
|
7050 |
+
"url": "https://github.com/sponsors/dcastil"
|
7051 |
+
}
|
7052 |
+
},
|
7053 |
"node_modules/tailwindcss": {
|
7054 |
"version": "4.0.15",
|
7055 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz",
|
|
|
7178 |
"node": "*"
|
7179 |
}
|
7180 |
},
|
7181 |
+
"node_modules/tw-animate-css": {
|
7182 |
+
"version": "1.3.0",
|
7183 |
+
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.0.tgz",
|
7184 |
+
"integrity": "sha512-jrJ0XenzS9KVuDThJDvnhalbl4IYiMQ/XvpA0a2FL8KmlK+6CSMviO7ROY/I7z1NnUs5NnDhlM6fXmF40xPxzw==",
|
7185 |
+
"dev": true,
|
7186 |
+
"license": "MIT",
|
7187 |
+
"funding": {
|
7188 |
+
"url": "https://github.com/sponsors/Wombosvideo"
|
7189 |
+
}
|
7190 |
+
},
|
7191 |
"node_modules/type-check": {
|
7192 |
"version": "0.4.0",
|
7193 |
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
|
|
7248 |
}
|
7249 |
},
|
7250 |
"node_modules/undici-types": {
|
7251 |
+
"version": "6.21.0",
|
7252 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
7253 |
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
7254 |
+
"license": "MIT"
|
7255 |
},
|
7256 |
"node_modules/unified": {
|
7257 |
"version": "11.0.5",
|
|
|
7381 |
"punycode": "^2.1.0"
|
7382 |
}
|
7383 |
},
|
7384 |
+
"node_modules/use-callback-ref": {
|
7385 |
+
"version": "1.3.3",
|
7386 |
+
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
7387 |
+
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
|
7388 |
+
"license": "MIT",
|
7389 |
+
"dependencies": {
|
7390 |
+
"tslib": "^2.0.0"
|
7391 |
+
},
|
7392 |
+
"engines": {
|
7393 |
+
"node": ">=10"
|
7394 |
+
},
|
7395 |
+
"peerDependencies": {
|
7396 |
+
"@types/react": "*",
|
7397 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
7398 |
+
},
|
7399 |
+
"peerDependenciesMeta": {
|
7400 |
+
"@types/react": {
|
7401 |
+
"optional": true
|
7402 |
+
}
|
7403 |
+
}
|
7404 |
+
},
|
7405 |
+
"node_modules/use-sidecar": {
|
7406 |
+
"version": "1.1.3",
|
7407 |
+
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
7408 |
+
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
|
7409 |
+
"license": "MIT",
|
7410 |
+
"dependencies": {
|
7411 |
+
"detect-node-es": "^1.1.0",
|
7412 |
+
"tslib": "^2.0.0"
|
7413 |
+
},
|
7414 |
+
"engines": {
|
7415 |
+
"node": ">=10"
|
7416 |
+
},
|
7417 |
+
"peerDependencies": {
|
7418 |
+
"@types/react": "*",
|
7419 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
7420 |
+
},
|
7421 |
+
"peerDependenciesMeta": {
|
7422 |
+
"@types/react": {
|
7423 |
+
"optional": true
|
7424 |
+
}
|
7425 |
+
}
|
7426 |
+
},
|
7427 |
+
"node_modules/use-sync-external-store": {
|
7428 |
+
"version": "1.5.0",
|
7429 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
7430 |
+
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
7431 |
+
"license": "MIT",
|
7432 |
+
"peerDependencies": {
|
7433 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
7434 |
+
}
|
7435 |
+
},
|
7436 |
"node_modules/util-deprecate": {
|
7437 |
"version": "1.0.2",
|
7438 |
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
package.json
CHANGED
@@ -14,13 +14,26 @@
|
|
14 |
"@huggingface/hub": "^1.1.1",
|
15 |
"@huggingface/inference": "^3.6.1",
|
16 |
"@monaco-editor/react": "^4.7.0",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
"@tailwindcss/vite": "^4.0.15",
|
18 |
"@xenova/transformers": "^2.17.2",
|
19 |
"body-parser": "^1.20.3",
|
|
|
20 |
"classnames": "^2.5.1",
|
|
|
21 |
"cookie-parser": "^1.4.7",
|
22 |
"dotenv": "^16.4.7",
|
23 |
"express": "^4.21.2",
|
|
|
|
|
24 |
"react": "^19.0.0",
|
25 |
"react-dom": "^19.0.0",
|
26 |
"react-icons": "^5.5.0",
|
@@ -28,11 +41,14 @@
|
|
28 |
"react-speech-recognition": "^4.0.0",
|
29 |
"react-toastify": "^11.0.5",
|
30 |
"react-use": "^17.6.0",
|
|
|
|
|
31 |
"tailwindcss": "^4.0.15"
|
32 |
},
|
33 |
"devDependencies": {
|
34 |
"@eslint/js": "^9.21.0",
|
35 |
"@types/express": "^5.0.1",
|
|
|
36 |
"@types/react": "^19.0.10",
|
37 |
"@types/react-dom": "^19.0.4",
|
38 |
"@types/react-speech-recognition": "^3.9.6",
|
@@ -41,6 +57,7 @@
|
|
41 |
"eslint-plugin-react-hooks": "^5.1.0",
|
42 |
"eslint-plugin-react-refresh": "^0.4.19",
|
43 |
"globals": "^15.15.0",
|
|
|
44 |
"typescript": "~5.7.2",
|
45 |
"typescript-eslint": "^8.24.1",
|
46 |
"vite": "^6.2.0"
|
|
|
14 |
"@huggingface/hub": "^1.1.1",
|
15 |
"@huggingface/inference": "^3.6.1",
|
16 |
"@monaco-editor/react": "^4.7.0",
|
17 |
+
"@radix-ui/react-avatar": "^1.1.10",
|
18 |
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
19 |
+
"@radix-ui/react-popover": "^1.1.14",
|
20 |
+
"@radix-ui/react-select": "^2.2.5",
|
21 |
+
"@radix-ui/react-slot": "^1.2.3",
|
22 |
+
"@radix-ui/react-switch": "^1.2.5",
|
23 |
+
"@radix-ui/react-tabs": "^1.1.12",
|
24 |
+
"@radix-ui/react-toggle": "^1.1.9",
|
25 |
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
26 |
"@tailwindcss/vite": "^4.0.15",
|
27 |
"@xenova/transformers": "^2.17.2",
|
28 |
"body-parser": "^1.20.3",
|
29 |
+
"class-variance-authority": "^0.7.1",
|
30 |
"classnames": "^2.5.1",
|
31 |
+
"clsx": "^2.1.1",
|
32 |
"cookie-parser": "^1.4.7",
|
33 |
"dotenv": "^16.4.7",
|
34 |
"express": "^4.21.2",
|
35 |
+
"lucide-react": "^0.511.0",
|
36 |
+
"next-themes": "^0.4.6",
|
37 |
"react": "^19.0.0",
|
38 |
"react-dom": "^19.0.0",
|
39 |
"react-icons": "^5.5.0",
|
|
|
41 |
"react-speech-recognition": "^4.0.0",
|
42 |
"react-toastify": "^11.0.5",
|
43 |
"react-use": "^17.6.0",
|
44 |
+
"sonner": "^2.0.3",
|
45 |
+
"tailwind-merge": "^3.3.0",
|
46 |
"tailwindcss": "^4.0.15"
|
47 |
},
|
48 |
"devDependencies": {
|
49 |
"@eslint/js": "^9.21.0",
|
50 |
"@types/express": "^5.0.1",
|
51 |
+
"@types/node": "^22.15.21",
|
52 |
"@types/react": "^19.0.10",
|
53 |
"@types/react-dom": "^19.0.4",
|
54 |
"@types/react-speech-recognition": "^3.9.6",
|
|
|
57 |
"eslint-plugin-react-hooks": "^5.1.0",
|
58 |
"eslint-plugin-react-refresh": "^0.4.19",
|
59 |
"globals": "^15.15.0",
|
60 |
+
"tw-animate-css": "^1.3.0",
|
61 |
"typescript": "~5.7.2",
|
62 |
"typescript-eslint": "^8.24.1",
|
63 |
"vite": "^6.2.0"
|
server.js
CHANGED
@@ -14,7 +14,7 @@ import { InferenceClient } from "@huggingface/inference";
|
|
14 |
import bodyParser from "body-parser";
|
15 |
|
16 |
import checkUser from "./middlewares/checkUser.js";
|
17 |
-
import { PROVIDERS } from "./utils/providers.js";
|
18 |
import { COLORS } from "./utils/colors.js";
|
19 |
|
20 |
// Load environment variables from .env file
|
@@ -30,7 +30,6 @@ const __dirname = path.dirname(__filename);
|
|
30 |
const PORT = process.env.APP_PORT || 3000;
|
31 |
const REDIRECT_URI =
|
32 |
process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`;
|
33 |
-
const MODEL_ID = "deepseek-ai/DeepSeek-V3-0324";
|
34 |
const MAX_REQUESTS_PER_IP = 2;
|
35 |
|
36 |
app.use(cookieParser());
|
@@ -213,7 +212,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
|
|
213 |
});
|
214 |
|
215 |
app.post("/api/ask-ai", async (req, res) => {
|
216 |
-
const { prompt, html, previousPrompt, provider } = req.body;
|
217 |
if (!prompt) {
|
218 |
return res.status(400).send({
|
219 |
ok: false,
|
@@ -221,6 +220,30 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
221 |
});
|
222 |
}
|
223 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
let { hf_token } = req.cookies;
|
225 |
let token = hf_token;
|
226 |
|
@@ -276,7 +299,7 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
276 |
|
277 |
try {
|
278 |
const chatCompletion = client.chatCompletionStream({
|
279 |
-
model:
|
280 |
provider: selectedProvider.id,
|
281 |
messages: [
|
282 |
{
|
@@ -318,15 +341,8 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
318 |
}
|
319 |
const chunk = value.choices[0]?.delta?.content;
|
320 |
if (chunk) {
|
321 |
-
|
322 |
-
|
323 |
-
completeResponse += chunk;
|
324 |
-
|
325 |
-
if (completeResponse.includes("</html>")) {
|
326 |
-
break;
|
327 |
-
}
|
328 |
-
} else {
|
329 |
-
let newChunk = chunk;
|
330 |
if (chunk.includes("</html>")) {
|
331 |
// Replace everything after the last </html> tag with an empty string
|
332 |
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
@@ -336,6 +352,19 @@ app.post("/api/ask-ai", async (req, res) => {
|
|
336 |
if (newChunk.includes("</html>")) {
|
337 |
break;
|
338 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
}
|
340 |
}
|
341 |
}
|
|
|
14 |
import bodyParser from "body-parser";
|
15 |
|
16 |
import checkUser from "./middlewares/checkUser.js";
|
17 |
+
import { MODELS, PROVIDERS } from "./utils/providers.js";
|
18 |
import { COLORS } from "./utils/colors.js";
|
19 |
|
20 |
// Load environment variables from .env file
|
|
|
30 |
const PORT = process.env.APP_PORT || 3000;
|
31 |
const REDIRECT_URI =
|
32 |
process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`;
|
|
|
33 |
const MAX_REQUESTS_PER_IP = 2;
|
34 |
|
35 |
app.use(cookieParser());
|
|
|
212 |
});
|
213 |
|
214 |
app.post("/api/ask-ai", async (req, res) => {
|
215 |
+
const { prompt, html, previousPrompt, provider, model } = req.body;
|
216 |
if (!prompt) {
|
217 |
return res.status(400).send({
|
218 |
ok: false,
|
|
|
220 |
});
|
221 |
}
|
222 |
|
223 |
+
if (!model) {
|
224 |
+
return res.status(400).send({
|
225 |
+
ok: false,
|
226 |
+
message: "Missing model field",
|
227 |
+
});
|
228 |
+
}
|
229 |
+
|
230 |
+
const selectedModel = MODELS.find(
|
231 |
+
(m) => m.value === model || m.label === model
|
232 |
+
);
|
233 |
+
if (!selectedModel) {
|
234 |
+
return res.status(400).send({
|
235 |
+
ok: false,
|
236 |
+
message: "Invalid model selected",
|
237 |
+
});
|
238 |
+
}
|
239 |
+
if (!selectedModel.providers.includes(provider) && provider !== "auto") {
|
240 |
+
return res.status(400).send({
|
241 |
+
ok: false,
|
242 |
+
openSelectProvider: true,
|
243 |
+
message: `The selected model does not support the ${provider} provider.`,
|
244 |
+
});
|
245 |
+
}
|
246 |
+
|
247 |
let { hf_token } = req.cookies;
|
248 |
let token = hf_token;
|
249 |
|
|
|
299 |
|
300 |
try {
|
301 |
const chatCompletion = client.chatCompletionStream({
|
302 |
+
model: selectedModel.value,
|
303 |
provider: selectedProvider.id,
|
304 |
messages: [
|
305 |
{
|
|
|
341 |
}
|
342 |
const chunk = value.choices[0]?.delta?.content;
|
343 |
if (chunk) {
|
344 |
+
let newChunk = chunk;
|
345 |
+
if (!selectedModel?.isThinker) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
if (chunk.includes("</html>")) {
|
347 |
// Replace everything after the last </html> tag with an empty string
|
348 |
newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
|
|
|
352 |
if (newChunk.includes("</html>")) {
|
353 |
break;
|
354 |
}
|
355 |
+
} else {
|
356 |
+
// check if in the completeResponse there is already a </html> tag but after the last </think> tag, if yes break the loop
|
357 |
+
const lastThinkTagIndex = completeResponse.lastIndexOf("</think>");
|
358 |
+
completeResponse += newChunk;
|
359 |
+
res.write(newChunk);
|
360 |
+
if (lastThinkTagIndex !== -1) {
|
361 |
+
const afterLastThinkTag = completeResponse.slice(
|
362 |
+
lastThinkTagIndex + "</think>".length
|
363 |
+
);
|
364 |
+
if (afterLastThinkTag.includes("</html>")) {
|
365 |
+
break;
|
366 |
+
}
|
367 |
+
}
|
368 |
}
|
369 |
}
|
370 |
}
|
src/assets/index.css
CHANGED
@@ -1,4 +1,7 @@
|
|
1 |
@import "tailwindcss";
|
|
|
|
|
|
|
2 |
|
3 |
* {
|
4 |
font-family: "Noto Sans";
|
@@ -7,3 +10,129 @@
|
|
7 |
.font-code {
|
8 |
font-family: "Source Code Pro";
|
9 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
@import "tailwindcss";
|
2 |
+
@import "tw-animate-css";
|
3 |
+
|
4 |
+
@custom-variant dark (&:is(.dark *));
|
5 |
|
6 |
* {
|
7 |
font-family: "Noto Sans";
|
|
|
10 |
.font-code {
|
11 |
font-family: "Source Code Pro";
|
12 |
}
|
13 |
+
|
14 |
+
@theme inline {
|
15 |
+
--radius-sm: calc(var(--radius) - 4px);
|
16 |
+
--radius-md: calc(var(--radius) - 2px);
|
17 |
+
--radius-lg: var(--radius);
|
18 |
+
--radius-xl: calc(var(--radius) + 4px);
|
19 |
+
--color-background: var(--background);
|
20 |
+
--color-foreground: var(--foreground);
|
21 |
+
--color-card: var(--card);
|
22 |
+
--color-card-foreground: var(--card-foreground);
|
23 |
+
--color-popover: var(--popover);
|
24 |
+
--color-popover-foreground: var(--popover-foreground);
|
25 |
+
--color-primary: var(--primary);
|
26 |
+
--color-primary-foreground: var(--primary-foreground);
|
27 |
+
--color-secondary: var(--secondary);
|
28 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
29 |
+
--color-muted: var(--muted);
|
30 |
+
--color-muted-foreground: var(--muted-foreground);
|
31 |
+
--color-accent: var(--accent);
|
32 |
+
--color-accent-foreground: var(--accent-foreground);
|
33 |
+
--color-destructive: var(--destructive);
|
34 |
+
--color-border: var(--border);
|
35 |
+
--color-input: var(--input);
|
36 |
+
--color-ring: var(--ring);
|
37 |
+
--color-chart-1: var(--chart-1);
|
38 |
+
--color-chart-2: var(--chart-2);
|
39 |
+
--color-chart-3: var(--chart-3);
|
40 |
+
--color-chart-4: var(--chart-4);
|
41 |
+
--color-chart-5: var(--chart-5);
|
42 |
+
--color-sidebar: var(--sidebar);
|
43 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
44 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
45 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
46 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
47 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
48 |
+
--color-sidebar-border: var(--sidebar-border);
|
49 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
50 |
+
}
|
51 |
+
|
52 |
+
:root {
|
53 |
+
--radius: 0.625rem;
|
54 |
+
--background: oklch(1 0 0);
|
55 |
+
--foreground: oklch(0.145 0 0);
|
56 |
+
--card: oklch(1 0 0);
|
57 |
+
--card-foreground: oklch(0.145 0 0);
|
58 |
+
--popover: oklch(1 0 0);
|
59 |
+
--popover-foreground: oklch(0.145 0 0);
|
60 |
+
--primary: oklch(0.205 0 0);
|
61 |
+
--primary-foreground: oklch(0.985 0 0);
|
62 |
+
--secondary: oklch(0.97 0 0);
|
63 |
+
--secondary-foreground: oklch(0.205 0 0);
|
64 |
+
--muted: oklch(0.97 0 0);
|
65 |
+
--muted-foreground: oklch(0.556 0 0);
|
66 |
+
--accent: oklch(0.97 0 0);
|
67 |
+
--accent-foreground: oklch(0.205 0 0);
|
68 |
+
--destructive: oklch(0.577 0.245 27.325);
|
69 |
+
--border: oklch(0.922 0 0);
|
70 |
+
--input: oklch(0.922 0 0);
|
71 |
+
--ring: oklch(0.708 0 0);
|
72 |
+
--chart-1: oklch(0.646 0.222 41.116);
|
73 |
+
--chart-2: oklch(0.6 0.118 184.704);
|
74 |
+
--chart-3: oklch(0.398 0.07 227.392);
|
75 |
+
--chart-4: oklch(0.828 0.189 84.429);
|
76 |
+
--chart-5: oklch(0.769 0.188 70.08);
|
77 |
+
--sidebar: oklch(0.985 0 0);
|
78 |
+
--sidebar-foreground: oklch(0.145 0 0);
|
79 |
+
--sidebar-primary: oklch(0.205 0 0);
|
80 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
81 |
+
--sidebar-accent: oklch(0.97 0 0);
|
82 |
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
83 |
+
--sidebar-border: oklch(0.922 0 0);
|
84 |
+
--sidebar-ring: oklch(0.708 0 0);
|
85 |
+
}
|
86 |
+
|
87 |
+
.dark {
|
88 |
+
--background: oklch(0.145 0 0);
|
89 |
+
--foreground: oklch(0.985 0 0);
|
90 |
+
--card: oklch(0.205 0 0);
|
91 |
+
--card-foreground: oklch(0.985 0 0);
|
92 |
+
--popover: oklch(0.205 0 0);
|
93 |
+
--popover-foreground: oklch(0.985 0 0);
|
94 |
+
--primary: oklch(0.922 0 0);
|
95 |
+
--primary-foreground: oklch(0.205 0 0);
|
96 |
+
--secondary: oklch(0.269 0 0);
|
97 |
+
--secondary-foreground: oklch(0.985 0 0);
|
98 |
+
--muted: oklch(0.269 0 0);
|
99 |
+
--muted-foreground: oklch(0.708 0 0);
|
100 |
+
--accent: oklch(0.269 0 0);
|
101 |
+
--accent-foreground: oklch(0.985 0 0);
|
102 |
+
--destructive: oklch(0.704 0.191 22.216);
|
103 |
+
--border: oklch(1 0 0 / 10%);
|
104 |
+
--input: oklch(1 0 0 / 15%);
|
105 |
+
--ring: oklch(0.556 0 0);
|
106 |
+
--chart-1: oklch(0.488 0.243 264.376);
|
107 |
+
--chart-2: oklch(0.696 0.17 162.48);
|
108 |
+
--chart-3: oklch(0.769 0.188 70.08);
|
109 |
+
--chart-4: oklch(0.627 0.265 303.9);
|
110 |
+
--chart-5: oklch(0.645 0.246 16.439);
|
111 |
+
--sidebar: oklch(0.205 0 0);
|
112 |
+
--sidebar-foreground: oklch(0.985 0 0);
|
113 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
114 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
115 |
+
--sidebar-accent: oklch(0.269 0 0);
|
116 |
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
117 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
118 |
+
--sidebar-ring: oklch(0.556 0 0);
|
119 |
+
}
|
120 |
+
|
121 |
+
@layer base {
|
122 |
+
* {
|
123 |
+
@apply border-border outline-ring/50;
|
124 |
+
}
|
125 |
+
body {
|
126 |
+
@apply bg-background text-foreground;
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
.monaco-editor .margin {
|
131 |
+
@apply !bg-neutral-900;
|
132 |
+
}
|
133 |
+
.monaco-editor .monaco-editor-background {
|
134 |
+
@apply !bg-neutral-900;
|
135 |
+
}
|
136 |
+
.monaco-editor .line-numbers {
|
137 |
+
@apply !text-neutral-500;
|
138 |
+
}
|
src/components/ask-ai/ask-ai.tsx
CHANGED
@@ -1,19 +1,20 @@
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
-
import { useState } from "react";
|
3 |
import { RiSparkling2Fill } from "react-icons/ri";
|
4 |
import { GrSend } from "react-icons/gr";
|
5 |
import classNames from "classnames";
|
6 |
-
import { toast } from "
|
7 |
-
import {
|
8 |
-
import { MdPreview } from "react-icons/md";
|
9 |
-
import { IoCopy } from "react-icons/io5";
|
10 |
|
11 |
import Login from "../login/login";
|
12 |
-
import { defaultHTML } from "
|
13 |
import SuccessSound from "./../../assets/success.mp3";
|
14 |
import Settings from "../settings/settings";
|
15 |
import ProModal from "../pro-modal/pro-modal";
|
16 |
-
|
|
|
|
|
|
|
17 |
|
18 |
function AskAI({
|
19 |
html,
|
@@ -21,7 +22,6 @@ function AskAI({
|
|
21 |
onScrollToBottom,
|
22 |
isAiWorking,
|
23 |
setisAiWorking,
|
24 |
-
setView,
|
25 |
onNewPrompt,
|
26 |
onSuccess,
|
27 |
}: {
|
@@ -30,20 +30,23 @@ function AskAI({
|
|
30 |
onScrollToBottom: () => void;
|
31 |
isAiWorking: boolean;
|
32 |
onNewPrompt: (prompt: string) => void;
|
33 |
-
setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
|
34 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
35 |
onSuccess: (h: string, p: string) => void;
|
36 |
}) {
|
|
|
|
|
37 |
const [open, setOpen] = useState(false);
|
38 |
const [prompt, setPrompt] = useState("");
|
39 |
const [hasAsked, setHasAsked] = useState(false);
|
40 |
const [previousPrompt, setPreviousPrompt] = useState("");
|
41 |
const [provider, setProvider] = useLocalStorage("provider", "auto");
|
|
|
42 |
const [openProvider, setOpenProvider] = useState(false);
|
43 |
const [providerError, setProviderError] = useState("");
|
44 |
const [openProModal, setOpenProModal] = useState(false);
|
45 |
-
|
46 |
-
const [
|
|
|
47 |
|
48 |
const audio = new Audio(SuccessSound);
|
49 |
audio.volume = 0.5;
|
@@ -62,6 +65,7 @@ function AskAI({
|
|
62 |
body: JSON.stringify({
|
63 |
prompt,
|
64 |
provider,
|
|
|
65 |
...(html === defaultHTML ? {} : { html }),
|
66 |
...(previousPrompt ? { previousPrompt } : {}),
|
67 |
}),
|
@@ -87,7 +91,9 @@ function AskAI({
|
|
87 |
}
|
88 |
const reader = request.body.getReader();
|
89 |
const decoder = new TextDecoder("utf-8");
|
90 |
-
|
|
|
|
|
91 |
const read = async () => {
|
92 |
const { done, value } = await reader.read();
|
93 |
if (done) {
|
@@ -97,7 +103,6 @@ function AskAI({
|
|
97 |
setisAiWorking(false);
|
98 |
setHasAsked(true);
|
99 |
audio.play();
|
100 |
-
setView("preview");
|
101 |
|
102 |
// Now we have the complete HTML including </html>, so set it to be sure
|
103 |
const finalDoc = contentResponse.match(
|
@@ -113,9 +118,19 @@ function AskAI({
|
|
113 |
|
114 |
const chunk = decoder.decode(value, { stream: true });
|
115 |
contentResponse += chunk;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
|
117 |
if (newHtml) {
|
118 |
-
|
119 |
let partialDoc = newHtml;
|
120 |
if (!partialDoc.includes("</html>")) {
|
121 |
partialDoc += "\n</html>";
|
@@ -137,8 +152,6 @@ function AskAI({
|
|
137 |
|
138 |
read();
|
139 |
}
|
140 |
-
|
141 |
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
142 |
} catch (error: any) {
|
143 |
setisAiWorking(false);
|
144 |
toast.error(error.message);
|
@@ -148,38 +161,59 @@ function AskAI({
|
|
148 |
}
|
149 |
};
|
150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
return (
|
152 |
-
<div
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
{defaultHTML !== html && (
|
158 |
-
<p
|
159 |
-
className="text-xl text-white/50 hover:text-white/80 -translate-y-[calc(100%+8px)] absolute top-0 right-0 cursor-pointer"
|
160 |
-
onClick={() => {
|
161 |
-
copyToClipboard(html);
|
162 |
-
toast.success("HTML copied to clipboard");
|
163 |
-
}}
|
164 |
>
|
165 |
-
<
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
)}
|
177 |
-
<div
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
<input
|
180 |
type="text"
|
181 |
disabled={isAiWorking}
|
182 |
-
className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-
|
183 |
placeholder={
|
184 |
hasAsked ? "What do you want to ask AI next?" : "Ask AI anything..."
|
185 |
}
|
@@ -195,18 +229,21 @@ function AskAI({
|
|
195 |
{/* <SpeechPrompt setPrompt={setPrompt} /> */}
|
196 |
<Settings
|
197 |
provider={provider as string}
|
|
|
198 |
onChange={setProvider}
|
|
|
199 |
open={openProvider}
|
200 |
error={providerError}
|
201 |
onClose={setOpenProvider}
|
202 |
/>
|
203 |
-
<
|
204 |
-
|
205 |
-
|
|
|
206 |
onClick={callAi}
|
207 |
>
|
208 |
<GrSend className="-translate-x-[1px]" />
|
209 |
-
</
|
210 |
</div>
|
211 |
</div>
|
212 |
<div
|
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
+
import { useState, useRef } from "react";
|
3 |
import { RiSparkling2Fill } from "react-icons/ri";
|
4 |
import { GrSend } from "react-icons/gr";
|
5 |
import classNames from "classnames";
|
6 |
+
import { toast } from "sonner";
|
7 |
+
import { useLocalStorage, useUpdateEffect } from "react-use";
|
|
|
|
|
8 |
|
9 |
import Login from "../login/login";
|
10 |
+
import { defaultHTML } from "../../../utils/consts";
|
11 |
import SuccessSound from "./../../assets/success.mp3";
|
12 |
import Settings from "../settings/settings";
|
13 |
import ProModal from "../pro-modal/pro-modal";
|
14 |
+
import { Button } from "../ui/button";
|
15 |
+
// @ts-expect-error not needed
|
16 |
+
import { MODELS } from "./../../../utils/providers";
|
17 |
+
import { X } from "lucide-react";
|
18 |
|
19 |
function AskAI({
|
20 |
html,
|
|
|
22 |
onScrollToBottom,
|
23 |
isAiWorking,
|
24 |
setisAiWorking,
|
|
|
25 |
onNewPrompt,
|
26 |
onSuccess,
|
27 |
}: {
|
|
|
30 |
onScrollToBottom: () => void;
|
31 |
isAiWorking: boolean;
|
32 |
onNewPrompt: (prompt: string) => void;
|
|
|
33 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
34 |
onSuccess: (h: string, p: string) => void;
|
35 |
}) {
|
36 |
+
const refThink = useRef<HTMLDivElement | null>(null);
|
37 |
+
|
38 |
const [open, setOpen] = useState(false);
|
39 |
const [prompt, setPrompt] = useState("");
|
40 |
const [hasAsked, setHasAsked] = useState(false);
|
41 |
const [previousPrompt, setPreviousPrompt] = useState("");
|
42 |
const [provider, setProvider] = useLocalStorage("provider", "auto");
|
43 |
+
const [model, setModel] = useLocalStorage("model", MODELS[0].value);
|
44 |
const [openProvider, setOpenProvider] = useState(false);
|
45 |
const [providerError, setProviderError] = useState("");
|
46 |
const [openProModal, setOpenProModal] = useState(false);
|
47 |
+
const [think, setThink] = useState<string | undefined>(undefined);
|
48 |
+
const [openThink, setOpenThink] = useState(false);
|
49 |
+
const [isThinking, setIsThinking] = useState(true);
|
50 |
|
51 |
const audio = new Audio(SuccessSound);
|
52 |
audio.volume = 0.5;
|
|
|
65 |
body: JSON.stringify({
|
66 |
prompt,
|
67 |
provider,
|
68 |
+
model,
|
69 |
...(html === defaultHTML ? {} : { html }),
|
70 |
...(previousPrompt ? { previousPrompt } : {}),
|
71 |
}),
|
|
|
91 |
}
|
92 |
const reader = request.body.getReader();
|
93 |
const decoder = new TextDecoder("utf-8");
|
94 |
+
const selectedModel = MODELS.find(
|
95 |
+
(m: { value: string }) => m.value === model
|
96 |
+
);
|
97 |
const read = async () => {
|
98 |
const { done, value } = await reader.read();
|
99 |
if (done) {
|
|
|
103 |
setisAiWorking(false);
|
104 |
setHasAsked(true);
|
105 |
audio.play();
|
|
|
106 |
|
107 |
// Now we have the complete HTML including </html>, so set it to be sure
|
108 |
const finalDoc = contentResponse.match(
|
|
|
118 |
|
119 |
const chunk = decoder.decode(value, { stream: true });
|
120 |
contentResponse += chunk;
|
121 |
+
if (selectedModel?.isThinker) {
|
122 |
+
const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
|
123 |
+
if (thinkMatch && !contentResponse?.includes("</think>")) {
|
124 |
+
if (!openThink && (think?.length ?? 0) < 3) {
|
125 |
+
setOpenThink(true);
|
126 |
+
}
|
127 |
+
setThink(thinkMatch.replace("<think>", "").trim());
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
|
132 |
if (newHtml) {
|
133 |
+
setIsThinking(false);
|
134 |
let partialDoc = newHtml;
|
135 |
if (!partialDoc.includes("</html>")) {
|
136 |
partialDoc += "\n</html>";
|
|
|
152 |
|
153 |
read();
|
154 |
}
|
|
|
|
|
155 |
} catch (error: any) {
|
156 |
setisAiWorking(false);
|
157 |
toast.error(error.message);
|
|
|
161 |
}
|
162 |
};
|
163 |
|
164 |
+
useUpdateEffect(() => {
|
165 |
+
if (refThink.current) {
|
166 |
+
refThink.current.scrollTop = refThink.current.scrollHeight;
|
167 |
+
}
|
168 |
+
}, [think]);
|
169 |
+
|
170 |
return (
|
171 |
+
<div className="bg-neutral-800 border border-neutral-700 rounded-lg ring-[5px] focus-within:ring-sky-500/50 ring-transparent z-10 absolute bottom-3 left-3 w-[calc(100%-20px)] group">
|
172 |
+
{think && (
|
173 |
+
<div
|
174 |
+
ref={refThink}
|
175 |
+
className="w-full px-5 pt-4 border-b border-neutral-700 max-h-[300px] overflow-y-auto relative"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
>
|
177 |
+
<div className="sticky top-0 z-1 flex items-center justify-end">
|
178 |
+
<Button
|
179 |
+
size="xs"
|
180 |
+
onClick={() => {
|
181 |
+
setThink("");
|
182 |
+
setOpenThink(false);
|
183 |
+
}}
|
184 |
+
>
|
185 |
+
<X />
|
186 |
+
Close
|
187 |
+
</Button>
|
188 |
+
</div>
|
189 |
+
<div className="-translate-y-5">
|
190 |
+
<p className="text-xs text-neutral-400">AI is thinking...</p>
|
191 |
+
<p className="text-[13px] text-neutral-300 mt-1 whitespace-pre-line">
|
192 |
+
{think}
|
193 |
+
</p>
|
194 |
+
</div>
|
195 |
+
</div>
|
196 |
)}
|
197 |
+
<div
|
198 |
+
className={classNames(
|
199 |
+
"w-full relative flex items-center justify-between pl-3.5 p-2 lg:p-2 lg:pl-4",
|
200 |
+
{
|
201 |
+
"animate-pulse": isAiWorking,
|
202 |
+
}
|
203 |
+
)}
|
204 |
+
>
|
205 |
+
{isAiWorking && (
|
206 |
+
<div className="absolute bg-neutral-800 rounded-lg bottom-0 left-10 w-[calc(100%-92px)] h-full z-1 flex items-center justify-start max-lg:text-sm">
|
207 |
+
<p className="text-neutral-400 font-code">
|
208 |
+
AI is {isThinking ? "thinking" : "coding"}...
|
209 |
+
</p>
|
210 |
+
</div>
|
211 |
+
)}
|
212 |
+
<RiSparkling2Fill className="size-5 text-neutral-400 group-focus-within:text-sky-500" />
|
213 |
<input
|
214 |
type="text"
|
215 |
disabled={isAiWorking}
|
216 |
+
className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-neutral-400 font-code"
|
217 |
placeholder={
|
218 |
hasAsked ? "What do you want to ask AI next?" : "Ask AI anything..."
|
219 |
}
|
|
|
229 |
{/* <SpeechPrompt setPrompt={setPrompt} /> */}
|
230 |
<Settings
|
231 |
provider={provider as string}
|
232 |
+
model={model as string}
|
233 |
onChange={setProvider}
|
234 |
+
onModelChange={setModel}
|
235 |
open={openProvider}
|
236 |
error={providerError}
|
237 |
onClose={setOpenProvider}
|
238 |
/>
|
239 |
+
<Button
|
240 |
+
variant="pink"
|
241 |
+
size="icon"
|
242 |
+
disabled={isAiWorking || !prompt.trim()}
|
243 |
onClick={callAi}
|
244 |
>
|
245 |
<GrSend className="-translate-x-[1px]" />
|
246 |
+
</Button>
|
247 |
</div>
|
248 |
</div>
|
249 |
<div
|
src/components/deploy-button/deploy-button.tsx
CHANGED
@@ -1,14 +1,14 @@
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import { useState } from "react";
|
3 |
-
import
|
4 |
-
import { toast } from "react-toastify";
|
5 |
-
import { FaPowerOff } from "react-icons/fa6";
|
6 |
|
7 |
import SpaceIcon from "@/assets/space.svg";
|
8 |
import Loading from "../loading/loading";
|
9 |
import Login from "../login/login";
|
10 |
import { Auth } from "./../../../utils/types";
|
11 |
import LoadButton from "../load-button/load-button";
|
|
|
|
|
12 |
|
13 |
const MsgToast = ({ url }: { url: string }) => (
|
14 |
<div className="w-full flex items-center justify-center gap-3">
|
@@ -26,18 +26,15 @@ const MsgToast = ({ url }: { url: string }) => (
|
|
26 |
|
27 |
function DeployButton({
|
28 |
html,
|
29 |
-
error = false,
|
30 |
auth,
|
31 |
setHtml,
|
32 |
prompts,
|
33 |
}: {
|
34 |
html: string;
|
35 |
-
error: boolean;
|
36 |
auth?: Auth;
|
37 |
setHtml: (html: string) => void;
|
38 |
prompts: string[];
|
39 |
}) {
|
40 |
-
const [open, setOpen] = useState(false);
|
41 |
const [loading, setLoading] = useState(false);
|
42 |
const [path, setPath] = useState<string | undefined>(undefined);
|
43 |
|
@@ -66,10 +63,7 @@ function DeployButton({
|
|
66 |
toast.success(
|
67 |
<MsgToast
|
68 |
url={`https://huggingface.co/spaces/${response.path ?? path}`}
|
69 |
-
|
70 |
-
{
|
71 |
-
autoClose: 10000,
|
72 |
-
}
|
73 |
);
|
74 |
setPath(response.path);
|
75 |
} else {
|
@@ -79,7 +73,6 @@ function DeployButton({
|
|
79 |
toast.error(err.message);
|
80 |
} finally {
|
81 |
setLoading(false);
|
82 |
-
setOpen(false);
|
83 |
}
|
84 |
};
|
85 |
|
@@ -87,135 +80,85 @@ function DeployButton({
|
|
87 |
<div className="flex items-center justify-end gap-5">
|
88 |
<LoadButton auth={auth} setHtml={setHtml} setPath={setPath} />
|
89 |
<div className="relative flex items-center justify-end">
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
<
|
94 |
-
|
95 |
-
</
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
</button>
|
110 |
-
<p className="mr-3 text-xs lg:text-sm text-gray-300">
|
111 |
-
<span className="max-lg:hidden">Connected as </span>
|
112 |
-
<a
|
113 |
-
href={`https://huggingface.co/${auth.preferred_username}`}
|
114 |
-
target="_blank"
|
115 |
-
className="underline hover:text-white"
|
116 |
-
>
|
117 |
-
{auth.preferred_username}
|
118 |
-
</a>
|
119 |
-
</p>
|
120 |
-
</>
|
121 |
-
))}
|
122 |
-
<button
|
123 |
-
className={classNames(
|
124 |
-
"relative cursor-pointer flex-none flex items-center justify-center rounded-md text-xs lg:text-sm font-semibold leading-5 lg:leading-6 py-1.5 px-5 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20",
|
125 |
-
{
|
126 |
-
"bg-pink-400": open,
|
127 |
-
"bg-pink-500": !open,
|
128 |
-
}
|
129 |
-
)}
|
130 |
-
onClick={() => setOpen(!open)}
|
131 |
-
>
|
132 |
-
{path ? "Update Space" : "Deploy to Space"}
|
133 |
-
</button>
|
134 |
-
<div
|
135 |
-
className={classNames(
|
136 |
-
"h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
|
137 |
-
{
|
138 |
-
"opacity-0 pointer-events-none": !open,
|
139 |
-
}
|
140 |
-
)}
|
141 |
-
onClick={() => setOpen(false)}
|
142 |
-
></div>
|
143 |
-
<div
|
144 |
-
className={classNames(
|
145 |
-
"absolute top-[calc(100%+8px)] right-0 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
|
146 |
-
{
|
147 |
-
"opacity-0 pointer-events-none": !open,
|
148 |
-
}
|
149 |
-
)}
|
150 |
-
>
|
151 |
-
{!auth ? (
|
152 |
-
<Login html={html}>
|
153 |
-
<p className="text-gray-500 text-sm mb-3">
|
154 |
-
Host this project for free and share it with your friends.
|
155 |
-
</p>
|
156 |
-
</Login>
|
157 |
-
) : (
|
158 |
-
<>
|
159 |
-
<header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
|
160 |
-
<span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
|
161 |
-
<img src={SpaceIcon} alt="Space Icon" className="size-4" />
|
162 |
-
Space
|
163 |
-
</span>
|
164 |
-
Configure Deployment
|
165 |
-
</header>
|
166 |
-
<main className="px-4 pt-3 pb-4 space-y-3">
|
167 |
-
<p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
|
168 |
-
{path ? (
|
169 |
-
<span>
|
170 |
-
Your space is live at{" "}
|
171 |
-
<a
|
172 |
-
href={`https://huggingface.co/spaces/${path}`}
|
173 |
-
target="_blank"
|
174 |
-
className="underline hover:text-amber-700"
|
175 |
-
>
|
176 |
-
huggingface.co/{path}
|
177 |
-
</a>
|
178 |
-
. You can update it by deploying again.
|
179 |
-
</span>
|
180 |
-
) : (
|
181 |
-
"Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
|
182 |
-
)}
|
183 |
</p>
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
<
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
</p>
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
</div>
|
220 |
</div>
|
221 |
);
|
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import { useState } from "react";
|
3 |
+
import { toast } from "sonner";
|
|
|
|
|
4 |
|
5 |
import SpaceIcon from "@/assets/space.svg";
|
6 |
import Loading from "../loading/loading";
|
7 |
import Login from "../login/login";
|
8 |
import { Auth } from "./../../../utils/types";
|
9 |
import LoadButton from "../load-button/load-button";
|
10 |
+
import { Button } from "../ui/button";
|
11 |
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
12 |
|
13 |
const MsgToast = ({ url }: { url: string }) => (
|
14 |
<div className="w-full flex items-center justify-center gap-3">
|
|
|
26 |
|
27 |
function DeployButton({
|
28 |
html,
|
|
|
29 |
auth,
|
30 |
setHtml,
|
31 |
prompts,
|
32 |
}: {
|
33 |
html: string;
|
|
|
34 |
auth?: Auth;
|
35 |
setHtml: (html: string) => void;
|
36 |
prompts: string[];
|
37 |
}) {
|
|
|
38 |
const [loading, setLoading] = useState(false);
|
39 |
const [path, setPath] = useState<string | undefined>(undefined);
|
40 |
|
|
|
63 |
toast.success(
|
64 |
<MsgToast
|
65 |
url={`https://huggingface.co/spaces/${response.path ?? path}`}
|
66 |
+
/>
|
|
|
|
|
|
|
67 |
);
|
68 |
setPath(response.path);
|
69 |
} else {
|
|
|
73 |
toast.error(err.message);
|
74 |
} finally {
|
75 |
setLoading(false);
|
|
|
76 |
}
|
77 |
};
|
78 |
|
|
|
80 |
<div className="flex items-center justify-end gap-5">
|
81 |
<LoadButton auth={auth} setHtml={setHtml} setPath={setPath} />
|
82 |
<div className="relative flex items-center justify-end">
|
83 |
+
<Popover>
|
84 |
+
<PopoverTrigger asChild>
|
85 |
+
<div>
|
86 |
+
<Button variant="pink" className="max-lg:hidden">
|
87 |
+
{path ? "Update Space" : "Deploy to Space"}
|
88 |
+
</Button>
|
89 |
+
<Button variant="pink" size="sm" className="lg:hidden">
|
90 |
+
{path ? "Update Space" : "Deploy to Space"}
|
91 |
+
</Button>
|
92 |
+
</div>
|
93 |
+
</PopoverTrigger>
|
94 |
+
<PopoverContent
|
95 |
+
className="p-0 overflow-hidden !bg-neutral-900"
|
96 |
+
align="end"
|
97 |
+
>
|
98 |
+
{!auth ? (
|
99 |
+
<Login html={html}>
|
100 |
+
<p className="text-muted-foreground text-sm mb-3">
|
101 |
+
Host this project for free and share it with your friends.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
</p>
|
103 |
+
</Login>
|
104 |
+
) : (
|
105 |
+
<>
|
106 |
+
<header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
|
107 |
+
<span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
|
108 |
+
<img src={SpaceIcon} alt="Space Icon" className="size-4" />
|
109 |
+
Space
|
110 |
+
</span>
|
111 |
+
Configure Deployment
|
112 |
+
</header>
|
113 |
+
<main className="px-4 pt-3 pb-4 space-y-3">
|
114 |
+
<p className="text-xs text-amber-600 bg-amber-500/10 rounded-md p-2">
|
115 |
+
{path ? (
|
116 |
+
<span>
|
117 |
+
Your space is live at{" "}
|
118 |
+
<a
|
119 |
+
href={`https://huggingface.co/spaces/${path}`}
|
120 |
+
target="_blank"
|
121 |
+
className="underline hover:text-amber-700"
|
122 |
+
>
|
123 |
+
huggingface.co/{path}
|
124 |
+
</a>
|
125 |
+
. You can update it by deploying again.
|
126 |
+
</span>
|
127 |
+
) : (
|
128 |
+
"Deploy your project to a space on the Hub. Spaces are a way to share your project with the world."
|
129 |
+
)}
|
130 |
</p>
|
131 |
+
{!path && (
|
132 |
+
<label className="block">
|
133 |
+
<p className="text-gray-600 text-sm font-medium mb-1.5">
|
134 |
+
Space Title
|
135 |
+
</p>
|
136 |
+
<input
|
137 |
+
type="text"
|
138 |
+
value={config.title}
|
139 |
+
className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
|
140 |
+
placeholder="My Awesome Space"
|
141 |
+
onChange={(e) =>
|
142 |
+
setConfig({ ...config, title: e.target.value })
|
143 |
+
}
|
144 |
+
/>
|
145 |
+
</label>
|
146 |
+
)}
|
147 |
+
<div className="pt-2 text-right">
|
148 |
+
<button
|
149 |
+
disabled={loading || (!path && !config.title)}
|
150 |
+
className="relative rounded-full bg-black px-5 py-2 text-white font-semibold text-xs hover:bg-black/90 transition-all duration-100 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
|
151 |
+
onClick={createSpace}
|
152 |
+
>
|
153 |
+
{path ? "Update Space" : "Create Space"}
|
154 |
+
{loading && <Loading />}
|
155 |
+
</button>
|
156 |
+
</div>
|
157 |
+
</main>
|
158 |
+
</>
|
159 |
+
)}
|
160 |
+
</PopoverContent>
|
161 |
+
</Popover>
|
162 |
</div>
|
163 |
</div>
|
164 |
);
|
src/components/footer/footer.tsx
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import classNames from "classnames";
|
2 |
+
import { FaMobileAlt, FaUserCircle } from "react-icons/fa";
|
3 |
+
import { ChevronDown, LogOut, RefreshCcw, SparkleIcon } from "lucide-react";
|
4 |
+
import { FaLaptopCode } from "react-icons/fa6";
|
5 |
+
import { Auth, HtmlHistory } from "../../../utils/types";
|
6 |
+
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
7 |
+
import {
|
8 |
+
DropdownMenu,
|
9 |
+
DropdownMenuContent,
|
10 |
+
DropdownMenuGroup,
|
11 |
+
DropdownMenuItem,
|
12 |
+
DropdownMenuLabel,
|
13 |
+
DropdownMenuSeparator,
|
14 |
+
DropdownMenuTrigger,
|
15 |
+
} from "../ui/dropdown-menu";
|
16 |
+
import { Button } from "../ui/button";
|
17 |
+
import { MdAdd } from "react-icons/md";
|
18 |
+
import History from "../history/history";
|
19 |
+
|
20 |
+
const DEVICES = [
|
21 |
+
{
|
22 |
+
name: "desktop",
|
23 |
+
icon: FaLaptopCode,
|
24 |
+
},
|
25 |
+
{
|
26 |
+
name: "mobile",
|
27 |
+
icon: FaMobileAlt,
|
28 |
+
},
|
29 |
+
];
|
30 |
+
|
31 |
+
function Footer({
|
32 |
+
onReset,
|
33 |
+
auth,
|
34 |
+
htmlHistory,
|
35 |
+
setHtml,
|
36 |
+
device,
|
37 |
+
setDevice,
|
38 |
+
iframeRef,
|
39 |
+
}: {
|
40 |
+
onReset: () => void;
|
41 |
+
auth?: Auth;
|
42 |
+
htmlHistory?: HtmlHistory[];
|
43 |
+
device: "desktop" | "mobile";
|
44 |
+
setHtml: (html: string) => void;
|
45 |
+
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
46 |
+
setDevice: React.Dispatch<React.SetStateAction<"desktop" | "mobile">>;
|
47 |
+
}) {
|
48 |
+
const handleRefreshIframe = () => {
|
49 |
+
if (iframeRef?.current) {
|
50 |
+
const iframe = iframeRef.current;
|
51 |
+
const content = iframe.srcdoc;
|
52 |
+
iframe.srcdoc = "";
|
53 |
+
setTimeout(() => {
|
54 |
+
iframe.srcdoc = content;
|
55 |
+
}, 10);
|
56 |
+
}
|
57 |
+
};
|
58 |
+
|
59 |
+
return (
|
60 |
+
<footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
|
61 |
+
<div className="flex items-center gap-2">
|
62 |
+
{auth &&
|
63 |
+
(auth?.isLocalUse ? (
|
64 |
+
<>
|
65 |
+
<div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
|
66 |
+
Local Usage
|
67 |
+
</div>
|
68 |
+
</>
|
69 |
+
) : (
|
70 |
+
<>
|
71 |
+
<DropdownMenu>
|
72 |
+
<DropdownMenuTrigger asChild>
|
73 |
+
<p className="mr-3 text-xs lg:text-sm text-gray-300 flex items-center gap-1 cursor-pointer hover:brightness-110">
|
74 |
+
<ChevronDown className="size-4" />
|
75 |
+
<Avatar className="size-7">
|
76 |
+
<AvatarImage src={auth?.picture} alt="@shadcn" />
|
77 |
+
<AvatarFallback className="text-sm">
|
78 |
+
{auth?.preferred_username?.charAt(0).toUpperCase() ??
|
79 |
+
"E"}
|
80 |
+
</AvatarFallback>
|
81 |
+
</Avatar>
|
82 |
+
{auth?.preferred_username ?? "enzostvs"}
|
83 |
+
</p>
|
84 |
+
</DropdownMenuTrigger>
|
85 |
+
<DropdownMenuContent className="w-56" align="start">
|
86 |
+
<DropdownMenuLabel className="font-bold flex items-center gap-2 justify-start">
|
87 |
+
<FaUserCircle className="" />
|
88 |
+
My Account
|
89 |
+
</DropdownMenuLabel>
|
90 |
+
<DropdownMenuSeparator />
|
91 |
+
<DropdownMenuGroup>
|
92 |
+
<a
|
93 |
+
href="https://huggingface.co/settings/billing"
|
94 |
+
target="_blank"
|
95 |
+
>
|
96 |
+
<DropdownMenuItem>Usage Quota</DropdownMenuItem>
|
97 |
+
</a>
|
98 |
+
<a
|
99 |
+
href={`https://huggingface.co/${auth?.preferred_username}`}
|
100 |
+
target="_blank"
|
101 |
+
>
|
102 |
+
<DropdownMenuItem>Hugging Face profile</DropdownMenuItem>
|
103 |
+
</a>
|
104 |
+
</DropdownMenuGroup>
|
105 |
+
<DropdownMenuSeparator />
|
106 |
+
<DropdownMenuItem
|
107 |
+
onClick={() => {
|
108 |
+
if (confirm("Are you sure you want to log out?")) {
|
109 |
+
// go to /auth/logout page
|
110 |
+
window.location.href = "/auth/logout";
|
111 |
+
}
|
112 |
+
}}
|
113 |
+
>
|
114 |
+
<LogOut className="size-4 text-red-500" />
|
115 |
+
Log out
|
116 |
+
</DropdownMenuItem>
|
117 |
+
</DropdownMenuContent>
|
118 |
+
</DropdownMenu>
|
119 |
+
</>
|
120 |
+
))}
|
121 |
+
{auth && <p className="text-neutral-700">|</p>}
|
122 |
+
<Button size="sm" variant="secondary" onClick={onReset}>
|
123 |
+
<MdAdd className="text-sm" />
|
124 |
+
<span>New Project</span>
|
125 |
+
</Button>
|
126 |
+
{htmlHistory && htmlHistory.length > 0 && (
|
127 |
+
<>
|
128 |
+
<p className="text-neutral-700">|</p>
|
129 |
+
<History history={htmlHistory} setHtml={setHtml} />
|
130 |
+
</>
|
131 |
+
)}
|
132 |
+
</div>
|
133 |
+
<div className="flex justify-end items-center gap-2.5">
|
134 |
+
<a
|
135 |
+
href="https://huggingface.co/spaces/victor/deepsite-gallery"
|
136 |
+
target="_blank"
|
137 |
+
>
|
138 |
+
<Button size="sm" variant="ghost">
|
139 |
+
<SparkleIcon className="size-3.5" />
|
140 |
+
<span className="max-lg:hidden">DeepSite Gallery</span>
|
141 |
+
</Button>
|
142 |
+
</a>
|
143 |
+
<Button size="sm" variant="default" onClick={handleRefreshIframe}>
|
144 |
+
<RefreshCcw className="size-3.5" />
|
145 |
+
<span className="max-lg:hidden">Refresh Preview</span>
|
146 |
+
</Button>
|
147 |
+
<div className="flex items-center rounded-full p-0.5 bg-neutral-700/70 relative overflow-hidden z-0 max-lg:hidden gap-0.5">
|
148 |
+
<div
|
149 |
+
className={classNames(
|
150 |
+
"absolute left-0.5 top-0.5 rounded-full bg-white size-7 -z-[1] transition-all duration-200",
|
151 |
+
{
|
152 |
+
"translate-x-[calc(100%+2px)]": device === "mobile",
|
153 |
+
}
|
154 |
+
)}
|
155 |
+
/>
|
156 |
+
{DEVICES.map((deviceItem) => (
|
157 |
+
<button
|
158 |
+
key={deviceItem.name}
|
159 |
+
className={classNames(
|
160 |
+
"rounded-full text-neutral-300 size-7 flex items-center justify-center cursor-pointer",
|
161 |
+
{
|
162 |
+
"!text-black": device === deviceItem.name,
|
163 |
+
"hover:bg-neutral-800": device !== deviceItem.name,
|
164 |
+
}
|
165 |
+
)}
|
166 |
+
onClick={() => setDevice(deviceItem.name as "desktop" | "mobile")}
|
167 |
+
>
|
168 |
+
<deviceItem.icon className="text-sm" />
|
169 |
+
</button>
|
170 |
+
))}
|
171 |
+
</div>
|
172 |
+
</div>
|
173 |
+
</footer>
|
174 |
+
);
|
175 |
+
}
|
176 |
+
|
177 |
+
export default Footer;
|
src/components/header/header.tsx
CHANGED
@@ -1,39 +1,66 @@
|
|
1 |
import { ReactNode } from "react";
|
2 |
-
import {
|
3 |
|
4 |
import Logo from "@/assets/logo.svg";
|
5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
function Header({
|
7 |
-
|
|
|
8 |
children,
|
9 |
}: {
|
10 |
-
|
|
|
11 |
children?: ReactNode;
|
12 |
}) {
|
13 |
return (
|
14 |
-
<header className="border-b border-
|
15 |
<div className="flex items-center justify-start gap-3">
|
16 |
-
<h1 className="text-white text-lg lg:text-xl font-bold flex items-center justify-start">
|
17 |
<img
|
18 |
src={Logo}
|
19 |
alt="DeepSite Logo"
|
20 |
-
className="size-6 lg:size-8 mr-2"
|
21 |
/>
|
22 |
DeepSite
|
|
|
|
|
|
|
|
|
23 |
</h1>
|
24 |
-
<p className="text-gray-700 max-md:hidden">|</p>
|
25 |
-
<button
|
26 |
-
className="max-md:hidden relative cursor-pointer flex-none flex items-center justify-center rounded-md text-xs font-semibold leading-4 py-1.5 px-3 hover:bg-gray-700 text-gray-100 shadow-sm dark:shadow-highlight/20 bg-gray-800"
|
27 |
-
onClick={onReset}
|
28 |
-
>
|
29 |
-
<MdAdd className="mr-1 text-base" />
|
30 |
-
New
|
31 |
-
</button>
|
32 |
-
<p className="text-gray-500 text-sm max-md:hidden">
|
33 |
-
Imagine and Share in 1-Click
|
34 |
-
</p>
|
35 |
</div>
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
</header>
|
38 |
);
|
39 |
}
|
|
|
1 |
import { ReactNode } from "react";
|
2 |
+
import { Eye, MessageCircleCode } from "lucide-react";
|
3 |
|
4 |
import Logo from "@/assets/logo.svg";
|
5 |
|
6 |
+
import { Button } from "./../../components/ui/button";
|
7 |
+
import classNames from "classnames";
|
8 |
+
|
9 |
+
const TABS = [
|
10 |
+
{
|
11 |
+
value: "chat",
|
12 |
+
label: "Chat",
|
13 |
+
icon: MessageCircleCode,
|
14 |
+
},
|
15 |
+
{
|
16 |
+
value: "preview",
|
17 |
+
label: "Preview",
|
18 |
+
icon: Eye,
|
19 |
+
},
|
20 |
+
];
|
21 |
+
|
22 |
function Header({
|
23 |
+
tab,
|
24 |
+
onNewTab,
|
25 |
children,
|
26 |
}: {
|
27 |
+
tab: string;
|
28 |
+
onNewTab: (tab: string) => void;
|
29 |
children?: ReactNode;
|
30 |
}) {
|
31 |
return (
|
32 |
+
<header className="border-b bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 lg:px-6 py-2 grid grid-cols-3 sticky top-0 z-20">
|
33 |
<div className="flex items-center justify-start gap-3">
|
34 |
+
<h1 className="text-neutral-900 dark:text-white text-lg lg:text-xl font-bold flex items-center justify-start">
|
35 |
<img
|
36 |
src={Logo}
|
37 |
alt="DeepSite Logo"
|
38 |
+
className="size-6 lg:size-8 mr-2 invert-100 dark:invert-0"
|
39 |
/>
|
40 |
DeepSite
|
41 |
+
<span className="font-mono bg-gradient-to-br from-sky-500 to-emerald-500 text-neutral-950 rounded-full text-xs ml-2 px-1.5 py-0.5">
|
42 |
+
{" "}
|
43 |
+
v2
|
44 |
+
</span>
|
45 |
</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
</div>
|
47 |
+
<div className="flex items-center justify-center gap-1">
|
48 |
+
{TABS.map((item) => (
|
49 |
+
<Button
|
50 |
+
key={item.value}
|
51 |
+
variant={tab === item.value ? "secondary" : "ghost"}
|
52 |
+
className={classNames("", {
|
53 |
+
"opacity-60": tab !== item.value,
|
54 |
+
})}
|
55 |
+
size="sm"
|
56 |
+
onClick={() => onNewTab(item.value)}
|
57 |
+
>
|
58 |
+
<item.icon className="size-4" />
|
59 |
+
<span className="hidden md:inline">{item.label}</span>
|
60 |
+
</Button>
|
61 |
+
))}
|
62 |
+
</div>
|
63 |
+
<div className="flex items-center justify-end gap-3">{children}</div>
|
64 |
</header>
|
65 |
);
|
66 |
}
|
src/components/history/history.tsx
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { HtmlHistory } from "../../../utils/types";
|
2 |
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
3 |
+
import { Button } from "../ui/button";
|
4 |
+
|
5 |
+
export default function History({
|
6 |
+
history,
|
7 |
+
setHtml,
|
8 |
+
}: {
|
9 |
+
history: HtmlHistory[];
|
10 |
+
setHtml: (html: string) => void;
|
11 |
+
}) {
|
12 |
+
return (
|
13 |
+
<Popover>
|
14 |
+
<PopoverTrigger asChild>
|
15 |
+
<Button variant="ghost" size="sm" className="max-lg:hidden">
|
16 |
+
{history?.length} versions
|
17 |
+
</Button>
|
18 |
+
</PopoverTrigger>
|
19 |
+
<PopoverContent
|
20 |
+
className="!p-0 overflow-hidden !bg-neutral-900"
|
21 |
+
align="start"
|
22 |
+
>
|
23 |
+
<header className="text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
|
24 |
+
Version History
|
25 |
+
</header>
|
26 |
+
<main className="px-4 space-y-3">
|
27 |
+
<ul className="max-h-[250px] overflow-y-auto">
|
28 |
+
{history?.map((item, index) => (
|
29 |
+
<li
|
30 |
+
key={index}
|
31 |
+
className="text-gray-300 text-xs py-2 border-b border-gray-800 last:border-0 flex items-center justify-between gap-2"
|
32 |
+
>
|
33 |
+
<div className="">
|
34 |
+
<span className="line-clamp-1">{item.prompt}</span>
|
35 |
+
<span className="text-gray-500 text-[10px]">
|
36 |
+
{new Date(item.createdAt).toLocaleDateString("en-US", {
|
37 |
+
month: "2-digit",
|
38 |
+
day: "2-digit",
|
39 |
+
year: "2-digit",
|
40 |
+
}) +
|
41 |
+
" " +
|
42 |
+
new Date(item.createdAt).toLocaleTimeString("en-US", {
|
43 |
+
hour: "2-digit",
|
44 |
+
minute: "2-digit",
|
45 |
+
second: "2-digit",
|
46 |
+
hour12: false,
|
47 |
+
})}
|
48 |
+
</span>
|
49 |
+
</div>
|
50 |
+
<button
|
51 |
+
className="bg-pink-500 text-white text-xs font-medium rounded-md px-2 py-1 transition-all duration-100 hover:bg-pink-600 cursor-pointer"
|
52 |
+
onClick={() => {
|
53 |
+
setHtml(item.html);
|
54 |
+
}}
|
55 |
+
>
|
56 |
+
Select
|
57 |
+
</button>
|
58 |
+
</li>
|
59 |
+
))}
|
60 |
+
</ul>
|
61 |
+
</main>
|
62 |
+
</PopoverContent>
|
63 |
+
</Popover>
|
64 |
+
// <div
|
65 |
+
// className="relative"
|
66 |
+
// onMouseEnter={() => setVisible(true)}
|
67 |
+
// onMouseLeave={() => setVisible(false)}
|
68 |
+
// >
|
69 |
+
// <button
|
70 |
+
// className={classNames(
|
71 |
+
// "text-gray-400 hover:text-gray-300 cursor-pointer text-sm nderline flex items-center justify-start gap-1",
|
72 |
+
// {
|
73 |
+
// "!text-gray-300": visible,
|
74 |
+
// }
|
75 |
+
// )}
|
76 |
+
// >
|
77 |
+
// <IoTimeOutline />
|
78 |
+
// {htmlHistory?.length} versions
|
79 |
+
// </button>
|
80 |
+
// <div
|
81 |
+
// className={classNames(
|
82 |
+
// "absolute bottom-0 left-0 min-w-sm w-full z-10 translate-y-full pt-2 transition-all duration-200",
|
83 |
+
// {
|
84 |
+
// "opacity-0 pointer-events-none": !visible,
|
85 |
+
// }
|
86 |
+
// )}
|
87 |
+
// >
|
88 |
+
// <div className="bg-gray-950 border border-gray-800 rounded-xl shadow-2xs p-4">
|
89 |
+
// <p className="text-xs font-bold text-white">Version History</p>
|
90 |
+
// <p className="text-gray-400 text-xs mt-1">
|
91 |
+
// This is a list of the full history of what AI has done to
|
92 |
+
// this.
|
93 |
+
// </p>
|
94 |
+
// <ul className="mt-2 max-h-[250px] overflow-y-auto">
|
95 |
+
// {htmlHistory?.map((item, index) => (
|
96 |
+
// <li
|
97 |
+
// key={index}
|
98 |
+
// className="text-gray-300 text-xs py-2 border-b border-gray-800 last:border-0 flex items-center justify-between gap-2"
|
99 |
+
// >
|
100 |
+
// <div className="">
|
101 |
+
// <span className="line-clamp-1">{item.prompt}</span>
|
102 |
+
// <span className="text-gray-500 text-[10px]">
|
103 |
+
// {new Date(item.createdAt).toLocaleDateString(
|
104 |
+
// "en-US",
|
105 |
+
// {
|
106 |
+
// month: "2-digit",
|
107 |
+
// day: "2-digit",
|
108 |
+
// year: "2-digit",
|
109 |
+
// }
|
110 |
+
// ) +
|
111 |
+
// " " +
|
112 |
+
// new Date(item.createdAt).toLocaleTimeString(
|
113 |
+
// "en-US",
|
114 |
+
// {
|
115 |
+
// hour: "2-digit",
|
116 |
+
// minute: "2-digit",
|
117 |
+
// second: "2-digit",
|
118 |
+
// hour12: false,
|
119 |
+
// }
|
120 |
+
// )}
|
121 |
+
// </span>
|
122 |
+
// </div>
|
123 |
+
// <button
|
124 |
+
// className="bg-pink-500 text-white text-xs font-medium rounded-md px-2 py-1 transition-all duration-100 hover:bg-pink-600 cursor-pointer"
|
125 |
+
// onClick={() => {
|
126 |
+
// setHtml(item.html);
|
127 |
+
// }}
|
128 |
+
// >
|
129 |
+
// Select
|
130 |
+
// </button>
|
131 |
+
// </li>
|
132 |
+
// ))}
|
133 |
+
// </ul>
|
134 |
+
// </div>
|
135 |
+
// </div>
|
136 |
+
// </div>
|
137 |
+
);
|
138 |
+
}
|
src/components/load-button/load-button.tsx
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
import classNames from "classnames";
|
2 |
import { useState } from "react";
|
3 |
-
import { toast } from "
|
4 |
|
5 |
import SpaceIcon from "@/assets/space.svg";
|
6 |
import Loading from "../loading/loading";
|
7 |
import { Auth } from "../../../utils/types";
|
|
|
|
|
|
|
8 |
|
9 |
function LoadButton({
|
10 |
auth,
|
@@ -17,7 +20,6 @@ function LoadButton({
|
|
17 |
}) {
|
18 |
const [open, setOpen] = useState(false);
|
19 |
const [loading, setLoading] = useState(false);
|
20 |
-
const [error, setError] = useState(false);
|
21 |
const [url, setUrl] = useState<string | undefined>(undefined);
|
22 |
|
23 |
const loadSpace = async () => {
|
@@ -38,12 +40,10 @@ function LoadButton({
|
|
38 |
setOpen(false);
|
39 |
} else {
|
40 |
toast.error(data.message);
|
41 |
-
setError(data.message);
|
42 |
}
|
43 |
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
44 |
} catch (error: any) {
|
45 |
toast.error(error.message);
|
46 |
-
setError(error.message);
|
47 |
}
|
48 |
setLoading(false);
|
49 |
};
|
@@ -54,81 +54,73 @@ function LoadButton({
|
|
54 |
"border-r border-gray-700 pr-5": auth,
|
55 |
})}
|
56 |
>
|
57 |
-
<
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
<div
|
73 |
-
className={classNames(
|
74 |
-
"absolute top-[calc(100%+8px)] right-2 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
|
75 |
-
{
|
76 |
-
"opacity-0 pointer-events-none": !open,
|
77 |
-
}
|
78 |
-
)}
|
79 |
-
>
|
80 |
-
<>
|
81 |
-
<header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
|
82 |
-
<span className="text-xs bg-pink-500/10 text-pink-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
|
83 |
<img src={SpaceIcon} alt="Space Icon" className="size-4" />
|
84 |
Space
|
85 |
</span>
|
86 |
Load Project
|
87 |
</header>
|
88 |
<main className="px-4 pt-3 pb-4 space-y-3">
|
89 |
-
<p className="text-sm text-
|
90 |
Load an existing DeepSite Space to continue working on it.
|
91 |
</p>
|
92 |
<label className="block">
|
93 |
-
<p className="text-
|
94 |
-
|
95 |
-
</p>
|
96 |
-
<input
|
97 |
type="text"
|
98 |
value={url}
|
99 |
-
className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
|
100 |
-
placeholder="https://huggingface.co/spaces/username/space-name"
|
101 |
onChange={(e) => setUrl(e.target.value)}
|
102 |
-
|
103 |
onBlur={(e) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
const pathParts = e.target.value.split("/");
|
105 |
setUrl(
|
106 |
`${pathParts[pathParts.length - 2]}/${
|
107 |
pathParts[pathParts.length - 1]
|
108 |
}`
|
109 |
);
|
110 |
-
setError(false);
|
111 |
}}
|
112 |
/>
|
113 |
</label>
|
114 |
-
{error && (
|
115 |
-
<p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2 break-all">
|
116 |
-
{error}
|
117 |
-
</p>
|
118 |
-
)}
|
119 |
<div className="pt-2 text-right">
|
120 |
-
<
|
121 |
-
|
122 |
-
|
|
|
123 |
onClick={loadSpace}
|
124 |
>
|
125 |
-
Load
|
126 |
{loading && <Loading />}
|
127 |
-
</
|
128 |
</div>
|
129 |
</main>
|
130 |
-
|
131 |
-
</
|
132 |
</div>
|
133 |
);
|
134 |
}
|
|
|
1 |
import classNames from "classnames";
|
2 |
import { useState } from "react";
|
3 |
+
import { toast } from "sonner";
|
4 |
|
5 |
import SpaceIcon from "@/assets/space.svg";
|
6 |
import Loading from "../loading/loading";
|
7 |
import { Auth } from "../../../utils/types";
|
8 |
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
9 |
+
import { Input } from "../ui/input";
|
10 |
+
import { Button } from "../ui/button";
|
11 |
|
12 |
function LoadButton({
|
13 |
auth,
|
|
|
20 |
}) {
|
21 |
const [open, setOpen] = useState(false);
|
22 |
const [loading, setLoading] = useState(false);
|
|
|
23 |
const [url, setUrl] = useState<string | undefined>(undefined);
|
24 |
|
25 |
const loadSpace = async () => {
|
|
|
40 |
setOpen(false);
|
41 |
} else {
|
42 |
toast.error(data.message);
|
|
|
43 |
}
|
44 |
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
45 |
} catch (error: any) {
|
46 |
toast.error(error.message);
|
|
|
47 |
}
|
48 |
setLoading(false);
|
49 |
};
|
|
|
54 |
"border-r border-gray-700 pr-5": auth,
|
55 |
})}
|
56 |
>
|
57 |
+
<Popover>
|
58 |
+
<PopoverTrigger asChild>
|
59 |
+
<p
|
60 |
+
className="underline hover:text-white cursor-pointer text-xs lg:text-sm text-gray-300"
|
61 |
+
onClick={() => setOpen(!open)}
|
62 |
+
>
|
63 |
+
Load Space
|
64 |
+
</p>
|
65 |
+
</PopoverTrigger>
|
66 |
+
<PopoverContent
|
67 |
+
className="p-0 overflow-hidden !bg-neutral-900"
|
68 |
+
align="end"
|
69 |
+
>
|
70 |
+
<header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
|
71 |
+
<span className="text-xs bg-pink-500 text-white rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
<img src={SpaceIcon} alt="Space Icon" className="size-4" />
|
73 |
Space
|
74 |
</span>
|
75 |
Load Project
|
76 |
</header>
|
77 |
<main className="px-4 pt-3 pb-4 space-y-3">
|
78 |
+
<p className="text-sm text-neutral-300 bg-neutral-300/15 border border-neutral-300/15 rounded-md px-3 py-2">
|
79 |
Load an existing DeepSite Space to continue working on it.
|
80 |
</p>
|
81 |
<label className="block">
|
82 |
+
<p className="text-muted-foreground text-sm mb-1.5">Space URL</p>
|
83 |
+
<Input
|
|
|
|
|
84 |
type="text"
|
85 |
value={url}
|
|
|
|
|
86 |
onChange={(e) => setUrl(e.target.value)}
|
87 |
+
placeholder="https://huggingface.co/spaces/username/space-name"
|
88 |
onBlur={(e) => {
|
89 |
+
if (!e.target.value) {
|
90 |
+
return setUrl(undefined);
|
91 |
+
}
|
92 |
+
if (
|
93 |
+
!e.target.value.startsWith(
|
94 |
+
"https://huggingface.co/spaces/"
|
95 |
+
) &&
|
96 |
+
!e.target.value.includes("/")
|
97 |
+
) {
|
98 |
+
toast.error("Please enter a valid Hugging Face Space URL.");
|
99 |
+
return;
|
100 |
+
}
|
101 |
const pathParts = e.target.value.split("/");
|
102 |
setUrl(
|
103 |
`${pathParts[pathParts.length - 2]}/${
|
104 |
pathParts[pathParts.length - 1]
|
105 |
}`
|
106 |
);
|
|
|
107 |
}}
|
108 |
/>
|
109 |
</label>
|
|
|
|
|
|
|
|
|
|
|
110 |
<div className="pt-2 text-right">
|
111 |
+
<Button
|
112 |
+
size="sm"
|
113 |
+
variant="default"
|
114 |
+
disabled={loading || !url}
|
115 |
onClick={loadSpace}
|
116 |
>
|
117 |
+
Load space
|
118 |
{loading && <Loading />}
|
119 |
+
</Button>
|
120 |
</div>
|
121 |
</main>
|
122 |
+
</PopoverContent>
|
123 |
+
</Popover>
|
124 |
</div>
|
125 |
);
|
126 |
}
|
src/components/login/login.tsx
CHANGED
@@ -23,17 +23,17 @@ function Login({
|
|
23 |
|
24 |
return (
|
25 |
<>
|
26 |
-
<header className="flex items-center text-sm px-4 py-
|
27 |
-
<span className="text-xs bg-red-500
|
28 |
REQUIRED
|
29 |
</span>
|
30 |
Login with Hugging Face
|
31 |
</header>
|
32 |
<main className="px-4 py-4 space-y-3">
|
33 |
{children}
|
34 |
-
<button onClick={handleClick}>
|
35 |
<img
|
36 |
-
src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg
|
37 |
alt="Sign in with Hugging Face"
|
38 |
className="mx-auto"
|
39 |
/>
|
|
|
23 |
|
24 |
return (
|
25 |
<>
|
26 |
+
<header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
|
27 |
+
<span className="text-xs bg-red-500 text-white rounded-full px-1.5 py-0.5 flex items-center justify-start gap-1.5">
|
28 |
REQUIRED
|
29 |
</span>
|
30 |
Login with Hugging Face
|
31 |
</header>
|
32 |
<main className="px-4 py-4 space-y-3">
|
33 |
{children}
|
34 |
+
<button onClick={handleClick} className="cursor-pointer">
|
35 |
<img
|
36 |
+
src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg.svg"
|
37 |
alt="Sign in with Hugging Face"
|
38 |
className="mx-auto"
|
39 |
/>
|
src/components/magicui/grid-pattern.tsx
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useId } from "react";
|
2 |
+
import { cn } from "../../lib/utils";
|
3 |
+
|
4 |
+
interface GridPatternProps extends React.SVGProps<SVGSVGElement> {
|
5 |
+
width?: number;
|
6 |
+
height?: number;
|
7 |
+
x?: number;
|
8 |
+
y?: number;
|
9 |
+
squares?: Array<[x: number, y: number]>;
|
10 |
+
strokeDasharray?: string;
|
11 |
+
className?: string;
|
12 |
+
[key: string]: unknown;
|
13 |
+
}
|
14 |
+
|
15 |
+
export function GridPattern({
|
16 |
+
width = 40,
|
17 |
+
height = 40,
|
18 |
+
x = -1,
|
19 |
+
y = -1,
|
20 |
+
strokeDasharray = "0",
|
21 |
+
squares,
|
22 |
+
className,
|
23 |
+
...props
|
24 |
+
}: GridPatternProps) {
|
25 |
+
const id = useId();
|
26 |
+
|
27 |
+
return (
|
28 |
+
<svg
|
29 |
+
aria-hidden="true"
|
30 |
+
className={cn(
|
31 |
+
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-neutral-700 -z-[1]",
|
32 |
+
className
|
33 |
+
)}
|
34 |
+
{...props}
|
35 |
+
>
|
36 |
+
<defs>
|
37 |
+
<pattern
|
38 |
+
id={id}
|
39 |
+
width={width}
|
40 |
+
height={height}
|
41 |
+
patternUnits="userSpaceOnUse"
|
42 |
+
x={x}
|
43 |
+
y={y}
|
44 |
+
>
|
45 |
+
<path
|
46 |
+
d={`M.5 ${height}V.5H${width}`}
|
47 |
+
fill="none"
|
48 |
+
strokeDasharray={strokeDasharray}
|
49 |
+
/>
|
50 |
+
</pattern>
|
51 |
+
</defs>
|
52 |
+
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
|
53 |
+
{squares && (
|
54 |
+
<svg x={x} y={y} className="overflow-visible">
|
55 |
+
{squares.map(([x, y]) => (
|
56 |
+
<rect
|
57 |
+
strokeWidth="0"
|
58 |
+
key={`${x}-${y}`}
|
59 |
+
width={width - 1}
|
60 |
+
height={height - 1}
|
61 |
+
x={x * width + 1}
|
62 |
+
y={y * height + 1}
|
63 |
+
/>
|
64 |
+
))}
|
65 |
+
</svg>
|
66 |
+
)}
|
67 |
+
</svg>
|
68 |
+
);
|
69 |
+
}
|
src/components/preview/preview.tsx
CHANGED
@@ -1,133 +1,67 @@
|
|
1 |
import classNames from "classnames";
|
2 |
-
import { useRef, useState } from "react";
|
3 |
-
import { FaLaptopCode } from "react-icons/fa6";
|
4 |
-
import { FaMobileAlt } from "react-icons/fa";
|
5 |
|
6 |
-
import {
|
7 |
-
import {
|
8 |
-
import {
|
9 |
|
10 |
function Preview({
|
11 |
html,
|
12 |
isResizing,
|
13 |
isAiWorking,
|
14 |
-
setView,
|
15 |
ref,
|
|
|
|
|
|
|
16 |
}: {
|
17 |
html: string;
|
18 |
isResizing: boolean;
|
19 |
isAiWorking: boolean;
|
20 |
-
setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
|
21 |
ref: React.RefObject<HTMLDivElement | null>;
|
|
|
|
|
|
|
22 |
}) {
|
23 |
-
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
24 |
-
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
25 |
-
|
26 |
-
const handleRefreshIframe = () => {
|
27 |
-
if (iframeRef.current) {
|
28 |
-
const iframe = iframeRef.current;
|
29 |
-
const content = iframe.srcdoc;
|
30 |
-
iframe.srcdoc = "";
|
31 |
-
setTimeout(() => {
|
32 |
-
iframe.srcdoc = content;
|
33 |
-
}, 10);
|
34 |
-
}
|
35 |
-
};
|
36 |
-
|
37 |
return (
|
38 |
<div
|
39 |
ref={ref}
|
40 |
className={classNames(
|
41 |
-
"w-full border-l border-gray-900
|
42 |
{
|
43 |
-
"
|
44 |
}
|
45 |
)}
|
46 |
onClick={(e) => {
|
47 |
if (isAiWorking) {
|
48 |
e.preventDefault();
|
49 |
e.stopPropagation();
|
50 |
-
toast.
|
51 |
}
|
52 |
}}
|
53 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
<iframe
|
55 |
ref={iframeRef}
|
56 |
title="output"
|
57 |
className={classNames(
|
58 |
-
"w-full select-none transition-all duration-200",
|
59 |
{
|
60 |
"pointer-events-none": isResizing || isAiWorking,
|
61 |
-
"max-w-md mx-auto h-[80dvh] rounded-[64px] border-[8px] border-
|
62 |
device === "mobile",
|
63 |
"h-full": device === "desktop",
|
|
|
|
|
64 |
}
|
65 |
)}
|
66 |
srcDoc={html}
|
67 |
/>
|
68 |
-
<div className="flex items-center justify-start gap-3 absolute bottom-3 lg:bottom-5 max-lg:left-3 lg:right-5">
|
69 |
-
<button
|
70 |
-
className="lg:hidden bg-gray-950 shadow-md text-white text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center gap-2 border border-gray-900 hover:brightness-150 transition-all duration-100 cursor-pointer"
|
71 |
-
onClick={() => setView("editor")}
|
72 |
-
>
|
73 |
-
<FaLaptopCode className="text-sm" />
|
74 |
-
Hide preview
|
75 |
-
</button>
|
76 |
-
{html === defaultHTML && (
|
77 |
-
<a
|
78 |
-
href="https://huggingface.co/spaces/victor/deepsite-gallery"
|
79 |
-
target="_blank"
|
80 |
-
className="bg-gray-200 text-gray-950 text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center gap-2 border border-gray-200 hover:bg-gray-300 transition-all duration-100 cursor-pointer"
|
81 |
-
>
|
82 |
-
🖼️ <span>DeepSite Gallery</span>
|
83 |
-
</a>
|
84 |
-
)}
|
85 |
-
{html !== defaultHTML && !isAiWorking && (
|
86 |
-
<div className="flex items-center rounded-lg p-1 bg-gray-200 relative overflow-hidden z-0 max-lg:hidden">
|
87 |
-
<div
|
88 |
-
className={classNames(
|
89 |
-
"absolute left-1 top-1 rounded-md bg-black w-10 h-8 -z-[1] transition-all duration-200",
|
90 |
-
{
|
91 |
-
"translate-x-full": device === "mobile",
|
92 |
-
}
|
93 |
-
)}
|
94 |
-
/>
|
95 |
-
<button
|
96 |
-
className={classNames(
|
97 |
-
"rounded-md text-gray-500 w-10 h-8 flex items-center justify-center cursor-pointer",
|
98 |
-
{
|
99 |
-
"!text-white": device === "desktop",
|
100 |
-
"hover:bg-gray-300/60": device !== "desktop",
|
101 |
-
}
|
102 |
-
)}
|
103 |
-
onClick={() => setDevice("desktop")}
|
104 |
-
>
|
105 |
-
<FaLaptopCode className="text-sm" />
|
106 |
-
</button>
|
107 |
-
<button
|
108 |
-
className={classNames(
|
109 |
-
"rounded-md text-gray-500 w-10 h-8 flex items-center justify-center cursor-pointer",
|
110 |
-
{
|
111 |
-
"!text-white": device === "mobile",
|
112 |
-
"hover:bg-gray-300/60": device !== "mobile",
|
113 |
-
}
|
114 |
-
)}
|
115 |
-
onClick={() => setDevice("mobile")}
|
116 |
-
>
|
117 |
-
<FaMobileAlt className="text-sm" />
|
118 |
-
</button>
|
119 |
-
</div>
|
120 |
-
)}
|
121 |
-
{!isAiWorking && (
|
122 |
-
<button
|
123 |
-
className="bg-white lg:bg-gray-950 shadow-md text-gray-950 lg:text-white text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center gap-2 border border-gray-100 lg:border-gray-900 hover:brightness-150 transition-all duration-100 cursor-pointer"
|
124 |
-
onClick={handleRefreshIframe}
|
125 |
-
>
|
126 |
-
<TbReload className="text-sm" />
|
127 |
-
Refresh Preview
|
128 |
-
</button>
|
129 |
-
)}
|
130 |
-
</div>
|
131 |
</div>
|
132 |
);
|
133 |
}
|
|
|
1 |
import classNames from "classnames";
|
|
|
|
|
|
|
2 |
|
3 |
+
import { toast } from "sonner";
|
4 |
+
import { GridPattern } from "./../magicui/grid-pattern";
|
5 |
+
import { cn } from "../../lib/utils";
|
6 |
|
7 |
function Preview({
|
8 |
html,
|
9 |
isResizing,
|
10 |
isAiWorking,
|
|
|
11 |
ref,
|
12 |
+
device,
|
13 |
+
currentTab,
|
14 |
+
iframeRef,
|
15 |
}: {
|
16 |
html: string;
|
17 |
isResizing: boolean;
|
18 |
isAiWorking: boolean;
|
|
|
19 |
ref: React.RefObject<HTMLDivElement | null>;
|
20 |
+
iframeRef?: React.RefObject<HTMLIFrameElement | null>;
|
21 |
+
device: "desktop" | "mobile";
|
22 |
+
currentTab: string;
|
23 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
return (
|
25 |
<div
|
26 |
ref={ref}
|
27 |
className={classNames(
|
28 |
+
"w-full border-l border-gray-900 h-full relative transition-all duration-200 z-0 flex items-center justify-center",
|
29 |
{
|
30 |
+
"lg:p-4": currentTab !== "preview",
|
31 |
}
|
32 |
)}
|
33 |
onClick={(e) => {
|
34 |
if (isAiWorking) {
|
35 |
e.preventDefault();
|
36 |
e.stopPropagation();
|
37 |
+
toast.warning("Please wait for the AI to finish working.");
|
38 |
}
|
39 |
}}
|
40 |
>
|
41 |
+
<GridPattern
|
42 |
+
x={-1}
|
43 |
+
y={-1}
|
44 |
+
strokeDasharray={"4 2"}
|
45 |
+
className={cn(
|
46 |
+
"[mask-image:radial-gradient(900px_circle_at_center,white,transparent)]"
|
47 |
+
)}
|
48 |
+
/>
|
49 |
<iframe
|
50 |
ref={iframeRef}
|
51 |
title="output"
|
52 |
className={classNames(
|
53 |
+
"w-full select-none transition-all duration-200 bg-black max-lg:h-full",
|
54 |
{
|
55 |
"pointer-events-none": isResizing || isAiWorking,
|
56 |
+
"lg:max-w-md lg:mx-auto lg:h-[80dvh] lg:!rounded-[64px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl":
|
57 |
device === "mobile",
|
58 |
"h-full": device === "desktop",
|
59 |
+
"lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[44px]":
|
60 |
+
currentTab !== "preview" && device === "desktop",
|
61 |
}
|
62 |
)}
|
63 |
srcDoc={html}
|
64 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
</div>
|
66 |
);
|
67 |
}
|
src/components/settings/settings.tsx
CHANGED
@@ -1,136 +1,194 @@
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import classNames from "classnames";
|
3 |
-
|
4 |
import { PiGearSixFill } from "react-icons/pi";
|
|
|
|
|
|
|
5 |
// @ts-expect-error not needed
|
6 |
-
import { PROVIDERS } from "./../../../utils/providers";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
function Settings({
|
9 |
open,
|
10 |
onClose,
|
11 |
provider,
|
|
|
12 |
error,
|
13 |
onChange,
|
|
|
14 |
}: {
|
15 |
open: boolean;
|
16 |
provider: string;
|
|
|
17 |
error?: string;
|
18 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
19 |
onChange: (provider: string) => void;
|
|
|
20 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
return (
|
22 |
<div className="">
|
23 |
-
<
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
<
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
"
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
<div
|
41 |
-
className={classNames(
|
42 |
-
"absolute top-0 -translate-y-[calc(100%+16px)] right-0 z-40 w-96 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
|
43 |
-
{
|
44 |
-
"opacity-0 pointer-events-none": !open,
|
45 |
-
}
|
46 |
-
)}
|
47 |
-
>
|
48 |
-
<header className="flex items-center text-sm px-4 py-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
|
49 |
-
<span className="text-xs bg-blue-500/10 text-blue-500 rounded-full pl-1.5 pr-2.5 py-0.5 flex items-center justify-start gap-1.5">
|
50 |
-
Provider
|
51 |
-
</span>
|
52 |
-
Customize Settings
|
53 |
-
</header>
|
54 |
-
<main className="px-4 pt-3 pb-4 space-y-4">
|
55 |
-
{/* toggle using tailwind css */}
|
56 |
-
<div>
|
57 |
<a
|
58 |
href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/74"
|
59 |
target="_blank"
|
60 |
-
className="w-full flex items-center justify-between text-
|
61 |
>
|
62 |
How to use it locally?
|
63 |
-
<
|
64 |
-
See the guide
|
65 |
-
</button>
|
66 |
</a>
|
67 |
-
|
68 |
-
<p className="text-
|
69 |
-
|
70 |
</p>
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
<div
|
109 |
-
key={id}
|
110 |
className={classNames(
|
111 |
-
"
|
112 |
{
|
113 |
-
"bg-
|
114 |
-
id === provider,
|
115 |
-
"hover:bg-gray-100 border-gray-100": id !== provider,
|
116 |
}
|
117 |
)}
|
118 |
onClick={() => {
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
}}
|
121 |
>
|
122 |
-
<
|
123 |
-
|
124 |
-
|
125 |
-
|
|
|
|
|
|
|
126 |
/>
|
127 |
-
{PROVIDERS[id].name}
|
128 |
</div>
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
</div>
|
131 |
-
</
|
132 |
-
</
|
133 |
-
</
|
134 |
</div>
|
135 |
);
|
136 |
}
|
|
|
1 |
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2 |
import classNames from "classnames";
|
|
|
3 |
import { PiGearSixFill } from "react-icons/pi";
|
4 |
+
import { RiCheckboxCircleFill } from "react-icons/ri";
|
5 |
+
|
6 |
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
7 |
// @ts-expect-error not needed
|
8 |
+
import { PROVIDERS, MODELS } from "./../../../utils/providers";
|
9 |
+
import { Button } from "../ui/button";
|
10 |
+
import {
|
11 |
+
Select,
|
12 |
+
SelectContent,
|
13 |
+
SelectGroup,
|
14 |
+
SelectItem,
|
15 |
+
SelectLabel,
|
16 |
+
SelectTrigger,
|
17 |
+
SelectValue,
|
18 |
+
} from "../ui/select";
|
19 |
+
import { useMemo } from "react";
|
20 |
+
import { useUpdateEffect } from "react-use";
|
21 |
|
22 |
function Settings({
|
23 |
open,
|
24 |
onClose,
|
25 |
provider,
|
26 |
+
model,
|
27 |
error,
|
28 |
onChange,
|
29 |
+
onModelChange,
|
30 |
}: {
|
31 |
open: boolean;
|
32 |
provider: string;
|
33 |
+
model: string;
|
34 |
error?: string;
|
35 |
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
36 |
onChange: (provider: string) => void;
|
37 |
+
onModelChange: (model: string) => void;
|
38 |
}) {
|
39 |
+
const modelAvailableProviders = useMemo(() => {
|
40 |
+
const availableProviders = MODELS.find(
|
41 |
+
(m: { value: string }) => m.value === model
|
42 |
+
)?.providers;
|
43 |
+
if (!availableProviders) return Object.keys(PROVIDERS);
|
44 |
+
return Object.keys(PROVIDERS).filter((id) =>
|
45 |
+
availableProviders.includes(id)
|
46 |
+
);
|
47 |
+
}, [model]);
|
48 |
+
|
49 |
+
useUpdateEffect(() => {
|
50 |
+
if (!modelAvailableProviders.includes(provider)) {
|
51 |
+
onChange("auto");
|
52 |
+
}
|
53 |
+
}, [model, provider]);
|
54 |
+
|
55 |
return (
|
56 |
<div className="">
|
57 |
+
<Popover open={open} onOpenChange={onClose}>
|
58 |
+
<PopoverTrigger asChild>
|
59 |
+
<Button variant="gray" size="icon">
|
60 |
+
<PiGearSixFill className="size-5" />
|
61 |
+
</Button>
|
62 |
+
</PopoverTrigger>
|
63 |
+
<PopoverContent
|
64 |
+
className="p-0 !w-96 overflow-hidden !bg-neutral-900"
|
65 |
+
align="center"
|
66 |
+
>
|
67 |
+
<header className="flex items-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
|
68 |
+
{/* <span className="text-xs bg-blue-500 text-white rounded-full px-1.5 py-0.5">
|
69 |
+
Provider
|
70 |
+
</span> */}
|
71 |
+
Customize Settings
|
72 |
+
</header>
|
73 |
+
<main className="px-4 pt-5 pb-6 space-y-5">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
<a
|
75 |
href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/74"
|
76 |
target="_blank"
|
77 |
+
className="w-full flex items-center justify-between text-neutral-300 bg-neutral-300/15 border border-neutral-300/15 pl-4 p-1.5 rounded-full text-sm font-medium hover:brightness-95"
|
78 |
>
|
79 |
How to use it locally?
|
80 |
+
<Button size="xs">See guide</Button>
|
|
|
|
|
81 |
</a>
|
82 |
+
{error !== "" && (
|
83 |
+
<p className="text-red-500 text-sm font-medium mb-2 flex items-center justify-between bg-red-500/10 p-2 rounded-md">
|
84 |
+
{error}
|
85 |
</p>
|
86 |
+
)}
|
87 |
+
<label className="block">
|
88 |
+
<p className="text-neutral-300 text-sm mb-2.5">
|
89 |
+
Choose a DeepSeek model
|
90 |
+
</p>
|
91 |
+
<Select defaultValue={model} onValueChange={onModelChange}>
|
92 |
+
<SelectTrigger className="w-full">
|
93 |
+
<SelectValue placeholder="Select a DeepSeek model" />
|
94 |
+
</SelectTrigger>
|
95 |
+
<SelectContent>
|
96 |
+
<SelectGroup>
|
97 |
+
<SelectLabel>DeepSeek models</SelectLabel>
|
98 |
+
{MODELS.map(
|
99 |
+
({
|
100 |
+
value,
|
101 |
+
label,
|
102 |
+
isNew = false,
|
103 |
+
}: {
|
104 |
+
value: string;
|
105 |
+
label: string;
|
106 |
+
isNew?: boolean;
|
107 |
+
}) => (
|
108 |
+
<SelectItem value={value} className="">
|
109 |
+
{label}
|
110 |
+
{isNew && (
|
111 |
+
<span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">
|
112 |
+
New
|
113 |
+
</span>
|
114 |
+
)}
|
115 |
+
</SelectItem>
|
116 |
+
)
|
117 |
+
)}
|
118 |
+
</SelectGroup>
|
119 |
+
</SelectContent>
|
120 |
+
</Select>
|
121 |
+
</label>
|
122 |
+
<div className="flex flex-col gap-3">
|
123 |
+
<div className="flex items-center justify-between">
|
124 |
+
<div>
|
125 |
+
<p className="text-neutral-300 text-sm mb-1.5">
|
126 |
+
Use auto-provider
|
127 |
+
</p>
|
128 |
+
<p className="text-xs text-neutral-400/70">
|
129 |
+
We'll automatically select the best provider for you based
|
130 |
+
on your prompt.
|
131 |
+
</p>
|
132 |
+
</div>
|
133 |
<div
|
|
|
134 |
className={classNames(
|
135 |
+
"bg-neutral-700 rounded-full min-w-10 w-10 h-6 flex items-center justify-between p-1 cursor-pointer transition-all duration-200",
|
136 |
{
|
137 |
+
"!bg-sky-500": provider === "auto",
|
|
|
|
|
138 |
}
|
139 |
)}
|
140 |
onClick={() => {
|
141 |
+
const model = MODELS.find(
|
142 |
+
(m: { value: string }) => m.value === model
|
143 |
+
);
|
144 |
+
onChange(
|
145 |
+
provider === "auto"
|
146 |
+
? model?.autoProvider ?? "novita"
|
147 |
+
: "auto"
|
148 |
+
);
|
149 |
}}
|
150 |
>
|
151 |
+
<div
|
152 |
+
className={classNames(
|
153 |
+
"w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",
|
154 |
+
{
|
155 |
+
"translate-x-4": provider === "auto",
|
156 |
+
}
|
157 |
+
)}
|
158 |
/>
|
|
|
159 |
</div>
|
160 |
+
</div>
|
161 |
+
<label className="block">
|
162 |
+
<p className="text-neutral-300 text-sm mb-2">
|
163 |
+
Inference Provider
|
164 |
+
</p>
|
165 |
+
<div className="grid grid-cols-2 gap-1.5">
|
166 |
+
{modelAvailableProviders.map((id: string) => (
|
167 |
+
<Button
|
168 |
+
key={id}
|
169 |
+
variant={id === provider ? "default" : "secondary"}
|
170 |
+
size="sm"
|
171 |
+
onClick={() => {
|
172 |
+
onChange(id);
|
173 |
+
}}
|
174 |
+
>
|
175 |
+
<img
|
176 |
+
src={`/providers/${id}.svg`}
|
177 |
+
alt={PROVIDERS[id].name}
|
178 |
+
className="size-5 mr-2"
|
179 |
+
/>
|
180 |
+
{PROVIDERS[id].name}
|
181 |
+
{id === provider && (
|
182 |
+
<RiCheckboxCircleFill className="ml-2 size-4 text-blue-500" />
|
183 |
+
)}
|
184 |
+
</Button>
|
185 |
+
))}
|
186 |
+
</div>
|
187 |
+
</label>
|
188 |
</div>
|
189 |
+
</main>
|
190 |
+
</PopoverContent>
|
191 |
+
</Popover>
|
192 |
</div>
|
193 |
);
|
194 |
}
|
src/components/tabs/tabs.tsx
DELETED
@@ -1,120 +0,0 @@
|
|
1 |
-
import { useState } from "react";
|
2 |
-
import classNames from "classnames";
|
3 |
-
import { IoTimeOutline } from "react-icons/io5";
|
4 |
-
|
5 |
-
import Deepseek from "./../../assets/deepseek-color.svg";
|
6 |
-
|
7 |
-
function Tabs({
|
8 |
-
htmlHistory,
|
9 |
-
setHtml,
|
10 |
-
children,
|
11 |
-
}: {
|
12 |
-
htmlHistory?: { html: string; createdAt: Date; prompt: string }[];
|
13 |
-
setHtml: (html: string) => void;
|
14 |
-
children?: React.ReactNode;
|
15 |
-
}) {
|
16 |
-
const [visible, setVisible] = useState(false);
|
17 |
-
|
18 |
-
return (
|
19 |
-
<div className="border-b border-gray-800 pl-4 lg:pl-7 pr-3 flex items-center justify-between">
|
20 |
-
<div className="flex items-center justify-start gap-4 flex-1">
|
21 |
-
<div
|
22 |
-
className="
|
23 |
-
space-x-6"
|
24 |
-
>
|
25 |
-
<button className="rounded-md text-sm cursor-pointer transition-all duration-100 font-medium relative py-2.5 text-white">
|
26 |
-
index.html
|
27 |
-
<span className="absolute bottom-0 left-0 h-0.5 w-full transition-all duration-100 bg-white" />
|
28 |
-
</button>
|
29 |
-
</div>
|
30 |
-
{htmlHistory && htmlHistory?.length > 1 && (
|
31 |
-
<div
|
32 |
-
className="relative"
|
33 |
-
onMouseEnter={() => setVisible(true)}
|
34 |
-
onMouseLeave={() => setVisible(false)}
|
35 |
-
>
|
36 |
-
<button
|
37 |
-
className={classNames(
|
38 |
-
"text-gray-400 hover:text-gray-300 cursor-pointer text-sm nderline flex items-center justify-start gap-1",
|
39 |
-
{
|
40 |
-
"!text-gray-300": visible,
|
41 |
-
}
|
42 |
-
)}
|
43 |
-
>
|
44 |
-
<IoTimeOutline />
|
45 |
-
{htmlHistory?.length} versions
|
46 |
-
</button>
|
47 |
-
<div
|
48 |
-
className={classNames(
|
49 |
-
"absolute bottom-0 left-0 min-w-sm w-full z-10 translate-y-full pt-2 transition-all duration-200",
|
50 |
-
{
|
51 |
-
"opacity-0 pointer-events-none": !visible,
|
52 |
-
}
|
53 |
-
)}
|
54 |
-
>
|
55 |
-
<div className="bg-gray-950 border border-gray-800 rounded-xl shadow-2xs p-4">
|
56 |
-
<p className="text-xs font-bold text-white">Version History</p>
|
57 |
-
<p className="text-gray-400 text-xs mt-1">
|
58 |
-
This is a list of the full history of what AI has done to
|
59 |
-
this.
|
60 |
-
</p>
|
61 |
-
<ul className="mt-2 max-h-[250px] overflow-y-auto">
|
62 |
-
{htmlHistory?.map((item, index) => (
|
63 |
-
<li
|
64 |
-
key={index}
|
65 |
-
className="text-gray-300 text-xs py-2 border-b border-gray-800 last:border-0 flex items-center justify-between gap-2"
|
66 |
-
>
|
67 |
-
<div className="">
|
68 |
-
<span className="line-clamp-1">{item.prompt}</span>
|
69 |
-
<span className="text-gray-500 text-[10px]">
|
70 |
-
{new Date(item.createdAt).toLocaleDateString(
|
71 |
-
"en-US",
|
72 |
-
{
|
73 |
-
month: "2-digit",
|
74 |
-
day: "2-digit",
|
75 |
-
year: "2-digit",
|
76 |
-
}
|
77 |
-
) +
|
78 |
-
" " +
|
79 |
-
new Date(item.createdAt).toLocaleTimeString(
|
80 |
-
"en-US",
|
81 |
-
{
|
82 |
-
hour: "2-digit",
|
83 |
-
minute: "2-digit",
|
84 |
-
second: "2-digit",
|
85 |
-
hour12: false,
|
86 |
-
}
|
87 |
-
)}
|
88 |
-
</span>
|
89 |
-
</div>
|
90 |
-
<button
|
91 |
-
className="bg-pink-500 text-white text-xs font-medium rounded-md px-2 py-1 transition-all duration-100 hover:bg-pink-600 cursor-pointer"
|
92 |
-
onClick={() => {
|
93 |
-
setHtml(item.html);
|
94 |
-
}}
|
95 |
-
>
|
96 |
-
Select
|
97 |
-
</button>
|
98 |
-
</li>
|
99 |
-
))}
|
100 |
-
</ul>
|
101 |
-
</div>
|
102 |
-
</div>
|
103 |
-
</div>
|
104 |
-
)}
|
105 |
-
</div>
|
106 |
-
<div className="flex items-center justify-end gap-3">
|
107 |
-
<a
|
108 |
-
href="https://huggingface.co/deepseek-ai/DeepSeek-V3-0324"
|
109 |
-
target="_blank"
|
110 |
-
className="text-[12px] text-gray-300 hover:brightness-120 flex items-center gap-1 font-code"
|
111 |
-
>
|
112 |
-
Powered by <img src={Deepseek} className="size-5" /> Deepseek
|
113 |
-
</a>
|
114 |
-
{children}
|
115 |
-
</div>
|
116 |
-
</div>
|
117 |
-
);
|
118 |
-
}
|
119 |
-
|
120 |
-
export default Tabs;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/components/theme/mode-toggle.tsx
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Moon, Sun } from "lucide-react";
|
2 |
+
|
3 |
+
import { Button } from "./../ui/button";
|
4 |
+
import {
|
5 |
+
DropdownMenu,
|
6 |
+
DropdownMenuContent,
|
7 |
+
DropdownMenuItem,
|
8 |
+
DropdownMenuTrigger,
|
9 |
+
} from "./../ui/dropdown-menu";
|
10 |
+
import { useTheme } from "./../theme/theme-provider";
|
11 |
+
|
12 |
+
export function ModeToggle() {
|
13 |
+
const { setTheme } = useTheme();
|
14 |
+
|
15 |
+
return (
|
16 |
+
<DropdownMenu>
|
17 |
+
<DropdownMenuTrigger asChild>
|
18 |
+
<Button variant="outline" size="icon">
|
19 |
+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
20 |
+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
21 |
+
<span className="sr-only">Toggle theme</span>
|
22 |
+
</Button>
|
23 |
+
</DropdownMenuTrigger>
|
24 |
+
<DropdownMenuContent align="end">
|
25 |
+
<DropdownMenuItem onClick={() => setTheme("light")}>
|
26 |
+
Light
|
27 |
+
</DropdownMenuItem>
|
28 |
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
29 |
+
Dark
|
30 |
+
</DropdownMenuItem>
|
31 |
+
<DropdownMenuItem onClick={() => setTheme("system")}>
|
32 |
+
System
|
33 |
+
</DropdownMenuItem>
|
34 |
+
</DropdownMenuContent>
|
35 |
+
</DropdownMenu>
|
36 |
+
);
|
37 |
+
}
|
src/components/theme/theme-provider.tsx
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { createContext, useContext, useEffect, useState } from "react";
|
2 |
+
|
3 |
+
type Theme = "dark" | "light" | "system";
|
4 |
+
|
5 |
+
type ThemeProviderProps = {
|
6 |
+
children: React.ReactNode;
|
7 |
+
defaultTheme?: Theme;
|
8 |
+
storageKey?: string;
|
9 |
+
className?: string;
|
10 |
+
};
|
11 |
+
|
12 |
+
type ThemeProviderState = {
|
13 |
+
theme: Theme;
|
14 |
+
setTheme: (theme: Theme) => void;
|
15 |
+
};
|
16 |
+
|
17 |
+
const initialState: ThemeProviderState = {
|
18 |
+
theme: "system",
|
19 |
+
setTheme: () => null,
|
20 |
+
};
|
21 |
+
|
22 |
+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
23 |
+
|
24 |
+
export function ThemeProvider({
|
25 |
+
children,
|
26 |
+
defaultTheme = "system",
|
27 |
+
storageKey = "vite-ui-theme",
|
28 |
+
className,
|
29 |
+
...props
|
30 |
+
}: ThemeProviderProps) {
|
31 |
+
const [theme, setTheme] = useState<Theme>(
|
32 |
+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
33 |
+
);
|
34 |
+
|
35 |
+
useEffect(() => {
|
36 |
+
const root = window.document.documentElement;
|
37 |
+
|
38 |
+
root.classList.remove("light", "dark");
|
39 |
+
|
40 |
+
if (theme === "system") {
|
41 |
+
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
42 |
+
.matches
|
43 |
+
? "dark"
|
44 |
+
: "light";
|
45 |
+
|
46 |
+
root.classList.add(systemTheme);
|
47 |
+
return;
|
48 |
+
}
|
49 |
+
|
50 |
+
root.classList.add(theme);
|
51 |
+
}, [theme]);
|
52 |
+
|
53 |
+
const value = {
|
54 |
+
theme,
|
55 |
+
setTheme: (theme: Theme) => {
|
56 |
+
localStorage.setItem(storageKey, theme);
|
57 |
+
setTheme(theme);
|
58 |
+
},
|
59 |
+
};
|
60 |
+
|
61 |
+
return (
|
62 |
+
<ThemeProviderContext.Provider {...props} value={value}>
|
63 |
+
<section className={className}>{children}</section>
|
64 |
+
</ThemeProviderContext.Provider>
|
65 |
+
);
|
66 |
+
}
|
67 |
+
|
68 |
+
export const useTheme = () => {
|
69 |
+
const context = useContext(ThemeProviderContext);
|
70 |
+
|
71 |
+
if (context === undefined)
|
72 |
+
throw new Error("useTheme must be used within a ThemeProvider");
|
73 |
+
|
74 |
+
return context;
|
75 |
+
};
|
src/components/ui/avatar.tsx
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
3 |
+
|
4 |
+
import { cn } from "./../../lib/utils";
|
5 |
+
|
6 |
+
function Avatar({
|
7 |
+
className,
|
8 |
+
...props
|
9 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
10 |
+
return (
|
11 |
+
<AvatarPrimitive.Root
|
12 |
+
data-slot="avatar"
|
13 |
+
className={cn(
|
14 |
+
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
15 |
+
className
|
16 |
+
)}
|
17 |
+
{...props}
|
18 |
+
/>
|
19 |
+
);
|
20 |
+
}
|
21 |
+
|
22 |
+
function AvatarImage({
|
23 |
+
className,
|
24 |
+
...props
|
25 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
26 |
+
return (
|
27 |
+
<AvatarPrimitive.Image
|
28 |
+
data-slot="avatar-image"
|
29 |
+
className={cn("aspect-square size-full", className)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
);
|
33 |
+
}
|
34 |
+
|
35 |
+
function AvatarFallback({
|
36 |
+
className,
|
37 |
+
...props
|
38 |
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
39 |
+
return (
|
40 |
+
<AvatarPrimitive.Fallback
|
41 |
+
data-slot="avatar-fallback"
|
42 |
+
className={cn(
|
43 |
+
"bg-neutral-100 flex size-full items-center justify-center rounded-full dark:bg-neutral-800",
|
44 |
+
className
|
45 |
+
)}
|
46 |
+
{...props}
|
47 |
+
/>
|
48 |
+
);
|
49 |
+
}
|
50 |
+
|
51 |
+
export { Avatar, AvatarImage, AvatarFallback };
|
src/components/ui/button.tsx
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import { Slot } from "@radix-ui/react-slot";
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
4 |
+
|
5 |
+
import { cn } from "./../../lib/utils";
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 cursor-pointer [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 focus-visible:ring-[3px] aria-invalid:ring-red-500/20 dark:aria-invalid:ring-red-500/40 aria-invalid:border-red-500 dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50 dark:aria-invalid:ring-red-900/20 dark:dark:aria-invalid:ring-red-900/40 dark:aria-invalid:border-red-900",
|
9 |
+
{
|
10 |
+
variants: {
|
11 |
+
variant: {
|
12 |
+
default:
|
13 |
+
"bg-neutral-900 text-neutral-50 shadow-xs hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90",
|
14 |
+
destructive:
|
15 |
+
"bg-red-500 text-white shadow-xs hover:bg-red-500/90 focus-visible:ring-red-500/20 dark:focus-visible:ring-red-500/40 dark:bg-red-500/60 dark:bg-red-900 dark:hover:bg-red-900/90 dark:focus-visible:ring-red-900/20 dark:dark:focus-visible:ring-red-900/40 dark:dark:bg-red-900/60",
|
16 |
+
outline:
|
17 |
+
"border bg-white shadow-xs hover:bg-neutral-100 hover:text-neutral-900 dark:bg-neutral-200/30 dark:border-neutral-200 dark:hover:bg-neutral-200/50 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50 dark:dark:bg-neutral-800/30 dark:dark:border-neutral-800 dark:dark:hover:bg-neutral-800/50",
|
18 |
+
secondary:
|
19 |
+
"bg-neutral-100 text-neutral-900 shadow-xs hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
|
20 |
+
ghost:
|
21 |
+
"hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-100/50 dark:hover:bg-neutral-800 dark:hover:text-neutral-50 dark:dark:hover:bg-neutral-800/50",
|
22 |
+
link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50",
|
23 |
+
pink: "bg-sky-500 text-white hover:brightness-110",
|
24 |
+
gray: "bg-neutral-900 text-neutral-300 hover:brightness-110",
|
25 |
+
},
|
26 |
+
size: {
|
27 |
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
28 |
+
sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
|
29 |
+
lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
|
30 |
+
icon: "size-9",
|
31 |
+
iconXs: "size-7",
|
32 |
+
xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
|
33 |
+
},
|
34 |
+
},
|
35 |
+
defaultVariants: {
|
36 |
+
variant: "default",
|
37 |
+
size: "default",
|
38 |
+
},
|
39 |
+
}
|
40 |
+
);
|
41 |
+
|
42 |
+
function Button({
|
43 |
+
className,
|
44 |
+
variant,
|
45 |
+
size,
|
46 |
+
asChild = false,
|
47 |
+
...props
|
48 |
+
}: React.ComponentProps<"button"> &
|
49 |
+
VariantProps<typeof buttonVariants> & {
|
50 |
+
asChild?: boolean;
|
51 |
+
}) {
|
52 |
+
const Comp = asChild ? Slot : "button";
|
53 |
+
|
54 |
+
return (
|
55 |
+
<Comp
|
56 |
+
data-slot="button"
|
57 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
58 |
+
{...props}
|
59 |
+
/>
|
60 |
+
);
|
61 |
+
}
|
62 |
+
|
63 |
+
export { Button, buttonVariants };
|
src/components/ui/dropdown-menu.tsx
ADDED
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
3 |
+
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
4 |
+
|
5 |
+
import { cn } from "./../../lib/utils";
|
6 |
+
|
7 |
+
function DropdownMenu({
|
8 |
+
...props
|
9 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
10 |
+
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
11 |
+
}
|
12 |
+
|
13 |
+
function DropdownMenuPortal({
|
14 |
+
...props
|
15 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
16 |
+
return (
|
17 |
+
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
18 |
+
);
|
19 |
+
}
|
20 |
+
|
21 |
+
function DropdownMenuTrigger({
|
22 |
+
...props
|
23 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
24 |
+
return (
|
25 |
+
<DropdownMenuPrimitive.Trigger
|
26 |
+
data-slot="dropdown-menu-trigger"
|
27 |
+
{...props}
|
28 |
+
/>
|
29 |
+
);
|
30 |
+
}
|
31 |
+
|
32 |
+
function DropdownMenuContent({
|
33 |
+
className,
|
34 |
+
sideOffset = 4,
|
35 |
+
...props
|
36 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
37 |
+
return (
|
38 |
+
<DropdownMenuPrimitive.Portal>
|
39 |
+
<DropdownMenuPrimitive.Content
|
40 |
+
data-slot="dropdown-menu-content"
|
41 |
+
sideOffset={sideOffset}
|
42 |
+
className={cn(
|
43 |
+
"bg-white text-neutral-950 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-neutral-200 p-1 shadow-md dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800",
|
44 |
+
className
|
45 |
+
)}
|
46 |
+
{...props}
|
47 |
+
/>
|
48 |
+
</DropdownMenuPrimitive.Portal>
|
49 |
+
);
|
50 |
+
}
|
51 |
+
|
52 |
+
function DropdownMenuGroup({
|
53 |
+
...props
|
54 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
55 |
+
return (
|
56 |
+
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
57 |
+
);
|
58 |
+
}
|
59 |
+
|
60 |
+
function DropdownMenuItem({
|
61 |
+
className,
|
62 |
+
inset,
|
63 |
+
variant = "default",
|
64 |
+
...props
|
65 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
66 |
+
inset?: boolean;
|
67 |
+
variant?: "default" | "destructive";
|
68 |
+
}) {
|
69 |
+
return (
|
70 |
+
<DropdownMenuPrimitive.Item
|
71 |
+
data-slot="dropdown-menu-item"
|
72 |
+
data-inset={inset}
|
73 |
+
data-variant={variant}
|
74 |
+
className={cn(
|
75 |
+
"focus:bg-neutral-100 cursor-pointer focus:text-neutral-900 data-[variant=destructive]:text-red-500 data-[variant=destructive]:focus:bg-red-500/10 dark:data-[variant=destructive]:focus:bg-red-500/20 data-[variant=destructive]:focus:text-red-500 data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-neutral-500 relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[variant=destructive]:text-red-900 dark:data-[variant=destructive]:focus:bg-red-900/10 dark:dark:data-[variant=destructive]:focus:bg-red-900/20 dark:data-[variant=destructive]:focus:text-red-900 dark:[&_svg:not([class*='text-'])]:text-neutral-400",
|
76 |
+
className
|
77 |
+
)}
|
78 |
+
{...props}
|
79 |
+
/>
|
80 |
+
);
|
81 |
+
}
|
82 |
+
|
83 |
+
function DropdownMenuCheckboxItem({
|
84 |
+
className,
|
85 |
+
children,
|
86 |
+
checked,
|
87 |
+
...props
|
88 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
89 |
+
return (
|
90 |
+
<DropdownMenuPrimitive.CheckboxItem
|
91 |
+
data-slot="dropdown-menu-checkbox-item"
|
92 |
+
className={cn(
|
93 |
+
"focus:bg-neutral-100 focus:text-neutral-900 relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
94 |
+
className
|
95 |
+
)}
|
96 |
+
checked={checked}
|
97 |
+
{...props}
|
98 |
+
>
|
99 |
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
100 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
101 |
+
<CheckIcon className="size-4" />
|
102 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
103 |
+
</span>
|
104 |
+
{children}
|
105 |
+
</DropdownMenuPrimitive.CheckboxItem>
|
106 |
+
);
|
107 |
+
}
|
108 |
+
|
109 |
+
function DropdownMenuRadioGroup({
|
110 |
+
...props
|
111 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
112 |
+
return (
|
113 |
+
<DropdownMenuPrimitive.RadioGroup
|
114 |
+
data-slot="dropdown-menu-radio-group"
|
115 |
+
{...props}
|
116 |
+
/>
|
117 |
+
);
|
118 |
+
}
|
119 |
+
|
120 |
+
function DropdownMenuRadioItem({
|
121 |
+
className,
|
122 |
+
children,
|
123 |
+
...props
|
124 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
125 |
+
return (
|
126 |
+
<DropdownMenuPrimitive.RadioItem
|
127 |
+
data-slot="dropdown-menu-radio-item"
|
128 |
+
className={cn(
|
129 |
+
"focus:bg-neutral-100 focus:text-neutral-900 relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
130 |
+
className
|
131 |
+
)}
|
132 |
+
{...props}
|
133 |
+
>
|
134 |
+
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
135 |
+
<DropdownMenuPrimitive.ItemIndicator>
|
136 |
+
<CircleIcon className="size-2 fill-current" />
|
137 |
+
</DropdownMenuPrimitive.ItemIndicator>
|
138 |
+
</span>
|
139 |
+
{children}
|
140 |
+
</DropdownMenuPrimitive.RadioItem>
|
141 |
+
);
|
142 |
+
}
|
143 |
+
|
144 |
+
function DropdownMenuLabel({
|
145 |
+
className,
|
146 |
+
inset,
|
147 |
+
...props
|
148 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
149 |
+
inset?: boolean;
|
150 |
+
}) {
|
151 |
+
return (
|
152 |
+
<DropdownMenuPrimitive.Label
|
153 |
+
data-slot="dropdown-menu-label"
|
154 |
+
data-inset={inset}
|
155 |
+
className={cn(
|
156 |
+
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
157 |
+
className
|
158 |
+
)}
|
159 |
+
{...props}
|
160 |
+
/>
|
161 |
+
);
|
162 |
+
}
|
163 |
+
|
164 |
+
function DropdownMenuSeparator({
|
165 |
+
className,
|
166 |
+
...props
|
167 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
168 |
+
return (
|
169 |
+
<DropdownMenuPrimitive.Separator
|
170 |
+
data-slot="dropdown-menu-separator"
|
171 |
+
className={cn(
|
172 |
+
"bg-neutral-200 -mx-1 my-1 h-px dark:bg-neutral-800",
|
173 |
+
className
|
174 |
+
)}
|
175 |
+
{...props}
|
176 |
+
/>
|
177 |
+
);
|
178 |
+
}
|
179 |
+
|
180 |
+
function DropdownMenuShortcut({
|
181 |
+
className,
|
182 |
+
...props
|
183 |
+
}: React.ComponentProps<"span">) {
|
184 |
+
return (
|
185 |
+
<span
|
186 |
+
data-slot="dropdown-menu-shortcut"
|
187 |
+
className={cn(
|
188 |
+
"text-neutral-500 ml-auto text-xs tracking-widest dark:text-neutral-400",
|
189 |
+
className
|
190 |
+
)}
|
191 |
+
{...props}
|
192 |
+
/>
|
193 |
+
);
|
194 |
+
}
|
195 |
+
|
196 |
+
function DropdownMenuSub({
|
197 |
+
...props
|
198 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
199 |
+
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
200 |
+
}
|
201 |
+
|
202 |
+
function DropdownMenuSubTrigger({
|
203 |
+
className,
|
204 |
+
inset,
|
205 |
+
children,
|
206 |
+
...props
|
207 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
208 |
+
inset?: boolean;
|
209 |
+
}) {
|
210 |
+
return (
|
211 |
+
<DropdownMenuPrimitive.SubTrigger
|
212 |
+
data-slot="dropdown-menu-sub-trigger"
|
213 |
+
data-inset={inset}
|
214 |
+
className={cn(
|
215 |
+
"focus:bg-neutral-100 focus:text-neutral-900 data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-900 flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-50",
|
216 |
+
className
|
217 |
+
)}
|
218 |
+
{...props}
|
219 |
+
>
|
220 |
+
{children}
|
221 |
+
<ChevronRightIcon className="ml-auto size-4" />
|
222 |
+
</DropdownMenuPrimitive.SubTrigger>
|
223 |
+
);
|
224 |
+
}
|
225 |
+
|
226 |
+
function DropdownMenuSubContent({
|
227 |
+
className,
|
228 |
+
...props
|
229 |
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
230 |
+
return (
|
231 |
+
<DropdownMenuPrimitive.SubContent
|
232 |
+
data-slot="dropdown-menu-sub-content"
|
233 |
+
className={cn(
|
234 |
+
"bg-white text-neutral-950 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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border border-neutral-200 p-1 shadow-lg dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800",
|
235 |
+
className
|
236 |
+
)}
|
237 |
+
{...props}
|
238 |
+
/>
|
239 |
+
);
|
240 |
+
}
|
241 |
+
|
242 |
+
export {
|
243 |
+
DropdownMenu,
|
244 |
+
DropdownMenuPortal,
|
245 |
+
DropdownMenuTrigger,
|
246 |
+
DropdownMenuContent,
|
247 |
+
DropdownMenuGroup,
|
248 |
+
DropdownMenuLabel,
|
249 |
+
DropdownMenuItem,
|
250 |
+
DropdownMenuCheckboxItem,
|
251 |
+
DropdownMenuRadioGroup,
|
252 |
+
DropdownMenuRadioItem,
|
253 |
+
DropdownMenuSeparator,
|
254 |
+
DropdownMenuShortcut,
|
255 |
+
DropdownMenuSub,
|
256 |
+
DropdownMenuSubTrigger,
|
257 |
+
DropdownMenuSubContent,
|
258 |
+
};
|
src/components/ui/input.tsx
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
|
3 |
+
import { cn } from "./../../lib/utils";
|
4 |
+
|
5 |
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
6 |
+
return (
|
7 |
+
<input
|
8 |
+
type={type}
|
9 |
+
data-slot="input"
|
10 |
+
className={cn(
|
11 |
+
"file:text-neutral-950 placeholder:text-neutral-500 selection:bg-neutral-900 selection:text-neutral-50 dark:bg-neutral-200/30 border-neutral-200 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:file:text-neutral-50 dark:placeholder:text-neutral-400 dark:selection:bg-neutral-50 dark:selection:text-neutral-900 dark:dark:bg-neutral-800/30 dark:border-neutral-800",
|
12 |
+
"focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 focus-visible:ring-[3px] dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50",
|
13 |
+
"aria-invalid:ring-red-500/20 dark:aria-invalid:ring-red-500/40 aria-invalid:border-red-500 dark:aria-invalid:ring-red-900/20 dark:dark:aria-invalid:ring-red-900/40 dark:aria-invalid:border-red-900",
|
14 |
+
className
|
15 |
+
)}
|
16 |
+
{...props}
|
17 |
+
/>
|
18 |
+
);
|
19 |
+
}
|
20 |
+
|
21 |
+
export { Input };
|
src/components/ui/popover.tsx
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
3 |
+
|
4 |
+
import { cn } from "./../../lib/utils";
|
5 |
+
|
6 |
+
function Popover({
|
7 |
+
...props
|
8 |
+
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
9 |
+
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
|
10 |
+
}
|
11 |
+
|
12 |
+
function PopoverTrigger({
|
13 |
+
...props
|
14 |
+
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
15 |
+
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
|
16 |
+
}
|
17 |
+
|
18 |
+
function PopoverContent({
|
19 |
+
className,
|
20 |
+
align = "center",
|
21 |
+
sideOffset = 4,
|
22 |
+
...props
|
23 |
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
24 |
+
return (
|
25 |
+
<PopoverPrimitive.Portal>
|
26 |
+
<PopoverPrimitive.Content
|
27 |
+
data-slot="popover-content"
|
28 |
+
align={align}
|
29 |
+
sideOffset={sideOffset}
|
30 |
+
className={cn(
|
31 |
+
"bg-white text-neutral-950 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 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border border-neutral-200 p-4 shadow-md outline-hidden dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800",
|
32 |
+
className
|
33 |
+
)}
|
34 |
+
{...props}
|
35 |
+
/>
|
36 |
+
</PopoverPrimitive.Portal>
|
37 |
+
);
|
38 |
+
}
|
39 |
+
|
40 |
+
function PopoverAnchor({
|
41 |
+
...props
|
42 |
+
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
43 |
+
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
44 |
+
}
|
45 |
+
|
46 |
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
src/components/ui/select.tsx
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
3 |
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
4 |
+
|
5 |
+
import { cn } from "./../../lib/utils";
|
6 |
+
|
7 |
+
function Select({
|
8 |
+
...props
|
9 |
+
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
10 |
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
11 |
+
}
|
12 |
+
|
13 |
+
function SelectGroup({
|
14 |
+
...props
|
15 |
+
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
16 |
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
17 |
+
}
|
18 |
+
|
19 |
+
function SelectValue({
|
20 |
+
...props
|
21 |
+
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
22 |
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
23 |
+
}
|
24 |
+
|
25 |
+
function SelectTrigger({
|
26 |
+
className,
|
27 |
+
size = "default",
|
28 |
+
children,
|
29 |
+
...props
|
30 |
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
31 |
+
size?: "sm" | "default";
|
32 |
+
}) {
|
33 |
+
return (
|
34 |
+
<SelectPrimitive.Trigger
|
35 |
+
data-slot="select-trigger"
|
36 |
+
data-size={size}
|
37 |
+
className={cn(
|
38 |
+
"border-neutral-200 data-[placeholder]:text-neutral-500 [&_svg:not([class*='text-'])]:text-neutral-500 focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 aria-invalid:ring-red-500/20 dark:aria-invalid:ring-red-500/40 aria-invalid:border-red-500 dark:bg-neutral-200/30 dark:hover:bg-neutral-200/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 dark:border-neutral-800 dark:data-[placeholder]:text-neutral-400 dark:[&_svg:not([class*='text-'])]:text-neutral-400 dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50 dark:aria-invalid:ring-red-900/20 dark:dark:aria-invalid:ring-red-900/40 dark:aria-invalid:border-red-900 dark:dark:bg-neutral-800/30 dark:dark:hover:bg-neutral-800/50",
|
39 |
+
className
|
40 |
+
)}
|
41 |
+
{...props}
|
42 |
+
>
|
43 |
+
{children}
|
44 |
+
<SelectPrimitive.Icon asChild>
|
45 |
+
<ChevronDownIcon className="size-4 opacity-50" />
|
46 |
+
</SelectPrimitive.Icon>
|
47 |
+
</SelectPrimitive.Trigger>
|
48 |
+
);
|
49 |
+
}
|
50 |
+
|
51 |
+
function SelectContent({
|
52 |
+
className,
|
53 |
+
children,
|
54 |
+
position = "popper",
|
55 |
+
...props
|
56 |
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
57 |
+
return (
|
58 |
+
<SelectPrimitive.Portal>
|
59 |
+
<SelectPrimitive.Content
|
60 |
+
data-slot="select-content"
|
61 |
+
className={cn(
|
62 |
+
"bg-white text-neutral-950 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-neutral-200 shadow-md dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800",
|
63 |
+
position === "popper" &&
|
64 |
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
65 |
+
className
|
66 |
+
)}
|
67 |
+
position={position}
|
68 |
+
{...props}
|
69 |
+
>
|
70 |
+
<SelectScrollUpButton />
|
71 |
+
<SelectPrimitive.Viewport
|
72 |
+
className={cn(
|
73 |
+
"p-1",
|
74 |
+
position === "popper" &&
|
75 |
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
76 |
+
)}
|
77 |
+
>
|
78 |
+
{children}
|
79 |
+
</SelectPrimitive.Viewport>
|
80 |
+
<SelectScrollDownButton />
|
81 |
+
</SelectPrimitive.Content>
|
82 |
+
</SelectPrimitive.Portal>
|
83 |
+
);
|
84 |
+
}
|
85 |
+
|
86 |
+
function SelectLabel({
|
87 |
+
className,
|
88 |
+
...props
|
89 |
+
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
90 |
+
return (
|
91 |
+
<SelectPrimitive.Label
|
92 |
+
data-slot="select-label"
|
93 |
+
className={cn(
|
94 |
+
"text-neutral-500 px-2 py-1.5 text-xs dark:text-neutral-400",
|
95 |
+
className
|
96 |
+
)}
|
97 |
+
{...props}
|
98 |
+
/>
|
99 |
+
);
|
100 |
+
}
|
101 |
+
|
102 |
+
function SelectItem({
|
103 |
+
className,
|
104 |
+
children,
|
105 |
+
...props
|
106 |
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
107 |
+
return (
|
108 |
+
<SelectPrimitive.Item
|
109 |
+
data-slot="select-item"
|
110 |
+
className={cn(
|
111 |
+
"focus:bg-neutral-100 focus:text-neutral-900 [&_svg:not([class*='text-'])]:text-neutral-500 relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:[&_svg:not([class*='text-'])]:text-neutral-400",
|
112 |
+
className
|
113 |
+
)}
|
114 |
+
{...props}
|
115 |
+
>
|
116 |
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
117 |
+
<SelectPrimitive.ItemIndicator>
|
118 |
+
<CheckIcon className="size-4" />
|
119 |
+
</SelectPrimitive.ItemIndicator>
|
120 |
+
</span>
|
121 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
122 |
+
</SelectPrimitive.Item>
|
123 |
+
);
|
124 |
+
}
|
125 |
+
|
126 |
+
function SelectSeparator({
|
127 |
+
className,
|
128 |
+
...props
|
129 |
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
130 |
+
return (
|
131 |
+
<SelectPrimitive.Separator
|
132 |
+
data-slot="select-separator"
|
133 |
+
className={cn(
|
134 |
+
"bg-neutral-200 pointer-events-none -mx-1 my-1 h-px dark:bg-neutral-800",
|
135 |
+
className
|
136 |
+
)}
|
137 |
+
{...props}
|
138 |
+
/>
|
139 |
+
);
|
140 |
+
}
|
141 |
+
|
142 |
+
function SelectScrollUpButton({
|
143 |
+
className,
|
144 |
+
...props
|
145 |
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
146 |
+
return (
|
147 |
+
<SelectPrimitive.ScrollUpButton
|
148 |
+
data-slot="select-scroll-up-button"
|
149 |
+
className={cn(
|
150 |
+
"flex cursor-default items-center justify-center py-1",
|
151 |
+
className
|
152 |
+
)}
|
153 |
+
{...props}
|
154 |
+
>
|
155 |
+
<ChevronUpIcon className="size-4" />
|
156 |
+
</SelectPrimitive.ScrollUpButton>
|
157 |
+
);
|
158 |
+
}
|
159 |
+
|
160 |
+
function SelectScrollDownButton({
|
161 |
+
className,
|
162 |
+
...props
|
163 |
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
164 |
+
return (
|
165 |
+
<SelectPrimitive.ScrollDownButton
|
166 |
+
data-slot="select-scroll-down-button"
|
167 |
+
className={cn(
|
168 |
+
"flex cursor-default items-center justify-center py-1",
|
169 |
+
className
|
170 |
+
)}
|
171 |
+
{...props}
|
172 |
+
>
|
173 |
+
<ChevronDownIcon className="size-4" />
|
174 |
+
</SelectPrimitive.ScrollDownButton>
|
175 |
+
);
|
176 |
+
}
|
177 |
+
|
178 |
+
export {
|
179 |
+
Select,
|
180 |
+
SelectContent,
|
181 |
+
SelectGroup,
|
182 |
+
SelectItem,
|
183 |
+
SelectLabel,
|
184 |
+
SelectScrollDownButton,
|
185 |
+
SelectScrollUpButton,
|
186 |
+
SelectSeparator,
|
187 |
+
SelectTrigger,
|
188 |
+
SelectValue,
|
189 |
+
};
|
src/components/ui/sonner.tsx
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useTheme } from "next-themes"
|
2 |
+
import { Toaster as Sonner, ToasterProps } from "sonner"
|
3 |
+
|
4 |
+
const Toaster = ({ ...props }: ToasterProps) => {
|
5 |
+
const { theme = "system" } = useTheme()
|
6 |
+
|
7 |
+
return (
|
8 |
+
<Sonner
|
9 |
+
theme={theme as ToasterProps["theme"]}
|
10 |
+
className="toaster group"
|
11 |
+
style={
|
12 |
+
{
|
13 |
+
"--normal-bg": "var(--popover)",
|
14 |
+
"--normal-text": "var(--popover-foreground)",
|
15 |
+
"--normal-border": "var(--border)",
|
16 |
+
} as React.CSSProperties
|
17 |
+
}
|
18 |
+
{...props}
|
19 |
+
/>
|
20 |
+
)
|
21 |
+
}
|
22 |
+
|
23 |
+
export { Toaster }
|
src/components/ui/tabs.tsx
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
3 |
+
|
4 |
+
import { cn } from "./../../lib/utils";
|
5 |
+
|
6 |
+
function Tabs({
|
7 |
+
className,
|
8 |
+
...props
|
9 |
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
10 |
+
return (
|
11 |
+
<TabsPrimitive.Root
|
12 |
+
data-slot="tabs"
|
13 |
+
className={cn("flex flex-col gap-2", className)}
|
14 |
+
{...props}
|
15 |
+
/>
|
16 |
+
);
|
17 |
+
}
|
18 |
+
|
19 |
+
function TabsList({
|
20 |
+
className,
|
21 |
+
...props
|
22 |
+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
23 |
+
return (
|
24 |
+
<TabsPrimitive.List
|
25 |
+
data-slot="tabs-list"
|
26 |
+
className={cn(
|
27 |
+
"bg-neutral-100 text-neutral-500 inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px] dark:bg-neutral-800 dark:text-neutral-400",
|
28 |
+
className
|
29 |
+
)}
|
30 |
+
{...props}
|
31 |
+
/>
|
32 |
+
);
|
33 |
+
}
|
34 |
+
|
35 |
+
function TabsTrigger({
|
36 |
+
className,
|
37 |
+
...props
|
38 |
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
39 |
+
return (
|
40 |
+
<TabsPrimitive.Trigger
|
41 |
+
data-slot="tabs-trigger"
|
42 |
+
className={cn(
|
43 |
+
"data-[state=active]:bg-white dark:data-[state=active]:text-neutral-950 focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 focus-visible:outline-ring dark:data-[state=active]:border-neutral-200 dark:data-[state=active]:bg-neutral-200/30 text-neutral-950 dark:text-neutral-500 inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-neutral-200 border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 dark:data-[state=active]:bg-neutral-950 dark:dark:data-[state=active]:text-neutral-50 dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50 dark:dark:data-[state=active]:border-neutral-800 dark:dark:data-[state=active]:bg-neutral-800/30 dark:text-neutral-50 dark:dark:text-neutral-400 dark:border-neutral-800",
|
44 |
+
className
|
45 |
+
)}
|
46 |
+
{...props}
|
47 |
+
/>
|
48 |
+
);
|
49 |
+
}
|
50 |
+
|
51 |
+
function TabsContent({
|
52 |
+
className,
|
53 |
+
...props
|
54 |
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
55 |
+
return (
|
56 |
+
<TabsPrimitive.Content
|
57 |
+
data-slot="tabs-content"
|
58 |
+
className={cn("flex-1 outline-none", className)}
|
59 |
+
{...props}
|
60 |
+
/>
|
61 |
+
);
|
62 |
+
}
|
63 |
+
|
64 |
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
src/components/ui/toggle-group.tsx
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react";
|
2 |
+
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
3 |
+
import { type VariantProps } from "class-variance-authority";
|
4 |
+
|
5 |
+
import { cn } from "./../../lib/utils";
|
6 |
+
import { toggleVariants } from "./../ui/toggle";
|
7 |
+
|
8 |
+
const ToggleGroupContext = React.createContext<
|
9 |
+
VariantProps<typeof toggleVariants>
|
10 |
+
>({
|
11 |
+
size: "default",
|
12 |
+
variant: "default",
|
13 |
+
});
|
14 |
+
|
15 |
+
function ToggleGroup({
|
16 |
+
className,
|
17 |
+
variant,
|
18 |
+
size,
|
19 |
+
children,
|
20 |
+
...props
|
21 |
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
22 |
+
VariantProps<typeof toggleVariants>) {
|
23 |
+
return (
|
24 |
+
<ToggleGroupPrimitive.Root
|
25 |
+
data-slot="toggle-group"
|
26 |
+
data-variant={variant}
|
27 |
+
data-size={size}
|
28 |
+
className={cn(
|
29 |
+
"group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
|
30 |
+
className
|
31 |
+
)}
|
32 |
+
{...props}
|
33 |
+
>
|
34 |
+
<ToggleGroupContext.Provider value={{ variant, size }}>
|
35 |
+
{children}
|
36 |
+
</ToggleGroupContext.Provider>
|
37 |
+
</ToggleGroupPrimitive.Root>
|
38 |
+
);
|
39 |
+
}
|
40 |
+
|
41 |
+
function ToggleGroupItem({
|
42 |
+
className,
|
43 |
+
children,
|
44 |
+
variant,
|
45 |
+
size,
|
46 |
+
...props
|
47 |
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
48 |
+
VariantProps<typeof toggleVariants>) {
|
49 |
+
const context = React.useContext(ToggleGroupContext);
|
50 |
+
|
51 |
+
return (
|
52 |
+
<ToggleGroupPrimitive.Item
|
53 |
+
data-slot="toggle-group-item"
|
54 |
+
data-variant={context.variant || variant}
|
55 |
+
data-size={context.size || size}
|
56 |
+
className={cn(
|
57 |
+
toggleVariants({
|
58 |
+
variant: context.variant || variant,
|
59 |
+
size: context.size || size,
|
60 |
+
}),
|
61 |
+
"min-w-0 !pl-3 flex-1 cursor-pointer shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
62 |
+
className
|
63 |
+
)}
|
64 |
+
{...props}
|
65 |
+
>
|
66 |
+
{children}
|
67 |
+
</ToggleGroupPrimitive.Item>
|
68 |
+
);
|
69 |
+
}
|
70 |
+
|
71 |
+
export { ToggleGroup, ToggleGroupItem };
|
src/components/ui/toggle.tsx
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import * as React from "react";
|
4 |
+
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
5 |
+
import { cva, type VariantProps } from "class-variance-authority";
|
6 |
+
|
7 |
+
import { cn } from "./../../lib/utils";
|
8 |
+
|
9 |
+
const toggleVariants = cva(
|
10 |
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-neutral-100 hover:text-neutral-500 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-neutral-100 data-[state=on]:text-neutral-900 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-red-500/20 dark:aria-invalid:ring-red-500/40 aria-invalid:border-red-500 whitespace-nowrap dark:hover:bg-neutral-800 dark:hover:text-neutral-400 dark:data-[state=on]:bg-neutral-800 dark:data-[state=on]:text-neutral-50 dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50 dark:aria-invalid:ring-red-900/20 dark:dark:aria-invalid:ring-red-900/40 dark:aria-invalid:border-red-900",
|
11 |
+
{
|
12 |
+
variants: {
|
13 |
+
variant: {
|
14 |
+
default: "bg-transparent",
|
15 |
+
outline:
|
16 |
+
"border border-neutral-200 bg-transparent shadow-xs hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
|
17 |
+
},
|
18 |
+
size: {
|
19 |
+
default: "h-9 px-2 min-w-9",
|
20 |
+
sm: "h-8 px-1.5 min-w-8",
|
21 |
+
lg: "h-10 px-2.5 min-w-10",
|
22 |
+
},
|
23 |
+
},
|
24 |
+
defaultVariants: {
|
25 |
+
variant: "default",
|
26 |
+
size: "default",
|
27 |
+
},
|
28 |
+
}
|
29 |
+
);
|
30 |
+
|
31 |
+
function Toggle({
|
32 |
+
className,
|
33 |
+
variant,
|
34 |
+
size,
|
35 |
+
...props
|
36 |
+
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
37 |
+
VariantProps<typeof toggleVariants>) {
|
38 |
+
return (
|
39 |
+
<TogglePrimitive.Root
|
40 |
+
data-slot="toggle"
|
41 |
+
className={cn(toggleVariants({ variant, size, className }))}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
);
|
45 |
+
}
|
46 |
+
|
47 |
+
export { Toggle, toggleVariants };
|
src/lib/utils.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { clsx, type ClassValue } from "clsx"
|
2 |
+
import { twMerge } from "tailwind-merge"
|
3 |
+
|
4 |
+
export function cn(...inputs: ClassValue[]) {
|
5 |
+
return twMerge(clsx(inputs))
|
6 |
+
}
|
src/main.tsx
CHANGED
@@ -1,12 +1,10 @@
|
|
1 |
import { StrictMode } from "react";
|
2 |
import { createRoot } from "react-dom/client";
|
3 |
-
import { ToastContainer } from "react-toastify";
|
4 |
import "./assets/index.css";
|
5 |
-
import App from "./
|
6 |
|
7 |
createRoot(document.getElementById("root")!).render(
|
8 |
<StrictMode>
|
9 |
<App />
|
10 |
-
<ToastContainer className="pt-11 max-md:p-4" />
|
11 |
</StrictMode>
|
12 |
);
|
|
|
1 |
import { StrictMode } from "react";
|
2 |
import { createRoot } from "react-dom/client";
|
|
|
3 |
import "./assets/index.css";
|
4 |
+
import App from "./views/App.tsx";
|
5 |
|
6 |
createRoot(document.getElementById("root")!).render(
|
7 |
<StrictMode>
|
8 |
<App />
|
|
|
9 |
</StrictMode>
|
10 |
);
|
src/{components → views}/App.tsx
RENAMED
@@ -1,45 +1,48 @@
|
|
1 |
import { useRef, useState } from "react";
|
2 |
-
import Editor from "@monaco-editor/react";
|
3 |
-
import classNames from "classnames";
|
4 |
-
import { editor } from "monaco-editor";
|
5 |
import {
|
6 |
-
|
7 |
-
useUnmount,
|
8 |
useEvent,
|
9 |
useLocalStorage,
|
|
|
10 |
useSearchParam,
|
|
|
|
|
11 |
} from "react-use";
|
12 |
-
import
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
import
|
15 |
-
import
|
16 |
-
import {
|
17 |
-
import
|
18 |
-
import
|
19 |
-
import
|
20 |
-
import
|
|
|
21 |
|
22 |
-
function App() {
|
23 |
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
24 |
const remix = useSearchParam("remix");
|
|
|
|
|
25 |
|
26 |
const preview = useRef<HTMLDivElement>(null);
|
27 |
const editor = useRef<HTMLDivElement>(null);
|
28 |
const resizer = useRef<HTMLDivElement>(null);
|
29 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
|
|
30 |
|
31 |
-
const [isResizing, setIsResizing] = useState(false);
|
32 |
-
const [error, setError] = useState(false);
|
33 |
const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML);
|
34 |
const [isAiWorking, setisAiWorking] = useState(false);
|
35 |
const [auth, setAuth] = useState<Auth | undefined>(undefined);
|
36 |
-
const [currentView, setCurrentView] = useState<"editor" | "preview">(
|
37 |
-
"editor"
|
38 |
-
);
|
39 |
const [prompts, setPrompts] = useState<string[]>([]);
|
40 |
-
const [
|
41 |
-
|
42 |
-
|
|
|
43 |
|
44 |
const fetchMe = async () => {
|
45 |
const res = await fetch("/api/@me");
|
@@ -127,36 +130,20 @@ function App() {
|
|
127 |
document.removeEventListener("mouseup", handleMouseUp);
|
128 |
};
|
129 |
|
130 |
-
// Prevent accidental navigation away when AI is working or content has changed
|
131 |
-
useEvent("beforeunload", (e) => {
|
132 |
-
if (isAiWorking || html !== defaultHTML) {
|
133 |
-
e.preventDefault();
|
134 |
-
return "";
|
135 |
-
}
|
136 |
-
});
|
137 |
-
|
138 |
-
// Initialize component on mount
|
139 |
useMount(() => {
|
140 |
-
// Fetch user data
|
141 |
fetchMe();
|
142 |
fetchRemix();
|
143 |
|
144 |
-
// Restore content from storage if available
|
145 |
if (htmlStorage) {
|
146 |
removeHtmlStorage();
|
147 |
-
toast.
|
148 |
}
|
149 |
|
150 |
-
// Set initial layout based on window size
|
151 |
resetLayout();
|
152 |
-
|
153 |
-
// Attach event listeners
|
154 |
if (!resizer.current) return;
|
155 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
156 |
window.addEventListener("resize", resetLayout);
|
157 |
});
|
158 |
-
|
159 |
-
// Clean up event listeners on unmount
|
160 |
useUnmount(() => {
|
161 |
document.removeEventListener("mousemove", handleResize);
|
162 |
document.removeEventListener("mouseup", handleMouseUp);
|
@@ -166,118 +153,143 @@ function App() {
|
|
166 |
window.removeEventListener("resize", resetLayout);
|
167 |
});
|
168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
return (
|
170 |
-
<
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
}
|
177 |
-
if (
|
178 |
-
window.confirm("You're about to reset the editor. Are you sure?")
|
179 |
-
) {
|
180 |
-
setHtml(defaultHTML);
|
181 |
-
setError(false);
|
182 |
-
removeHtmlStorage();
|
183 |
-
editorRef.current?.revealLine(
|
184 |
-
editorRef.current?.getModel()?.getLineCount() ?? 0
|
185 |
-
);
|
186 |
-
}
|
187 |
-
}}
|
188 |
-
>
|
189 |
<DeployButton
|
190 |
html={html}
|
191 |
-
error={error}
|
192 |
auth={auth}
|
193 |
setHtml={setHtml}
|
194 |
prompts={prompts}
|
195 |
/>
|
196 |
</Header>
|
197 |
-
<main className="max-lg:flex-col flex w-full">
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
/>
|
239 |
-
|
240 |
-
|
241 |
-
html={html}
|
242 |
-
setHtml={(newHtml: string) => {
|
243 |
-
setHtml(newHtml);
|
244 |
-
}}
|
245 |
-
onSuccess={(finalHtml: string, p: string) => {
|
246 |
-
const currentHistory = [...htmlHistory];
|
247 |
-
currentHistory.unshift({
|
248 |
-
html: finalHtml,
|
249 |
-
createdAt: new Date(),
|
250 |
-
prompt: p,
|
251 |
-
});
|
252 |
-
setHtmlHistory(currentHistory);
|
253 |
-
}}
|
254 |
-
isAiWorking={isAiWorking}
|
255 |
-
setisAiWorking={setisAiWorking}
|
256 |
-
setView={setCurrentView}
|
257 |
-
onNewPrompt={(prompt) => {
|
258 |
-
setPrompts((prev) => [...prev, prompt]);
|
259 |
-
}}
|
260 |
-
onScrollToBottom={() => {
|
261 |
-
editorRef.current?.revealLine(
|
262 |
-
editorRef.current?.getModel()?.getLineCount() ?? 0
|
263 |
-
);
|
264 |
-
}}
|
265 |
-
/>
|
266 |
-
</div>
|
267 |
-
<div
|
268 |
-
ref={resizer}
|
269 |
-
className="bg-gray-700 hover:bg-blue-500 w-2 cursor-col-resize h-[calc(100dvh-53px)] max-lg:hidden"
|
270 |
-
/>
|
271 |
<Preview
|
272 |
html={html}
|
273 |
isResizing={isResizing}
|
274 |
isAiWorking={isAiWorking}
|
275 |
ref={preview}
|
276 |
-
|
|
|
|
|
277 |
/>
|
278 |
</main>
|
279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
280 |
);
|
281 |
}
|
282 |
-
|
283 |
-
export default App;
|
|
|
1 |
import { useRef, useState } from "react";
|
|
|
|
|
|
|
2 |
import {
|
3 |
+
useCopyToClipboard,
|
|
|
4 |
useEvent,
|
5 |
useLocalStorage,
|
6 |
+
useMount,
|
7 |
useSearchParam,
|
8 |
+
useUnmount,
|
9 |
+
useUpdateEffect,
|
10 |
} from "react-use";
|
11 |
+
import Editor from "@monaco-editor/react";
|
12 |
+
import { editor } from "monaco-editor";
|
13 |
+
import { toast, Toaster } from "sonner";
|
14 |
+
import classNames from "classnames";
|
15 |
+
import { CopyIcon } from "lucide-react";
|
16 |
|
17 |
+
import { ThemeProvider } from "../components/theme/theme-provider";
|
18 |
+
import Header from "../components/header/header";
|
19 |
+
import { Auth, HtmlHistory } from "../../utils/types";
|
20 |
+
import { defaultHTML } from "../../utils/consts";
|
21 |
+
import DeployButton from "../components/deploy-button/deploy-button";
|
22 |
+
import Preview from "../components/preview/preview";
|
23 |
+
import Footer from "../components/footer/footer";
|
24 |
+
import AskAI from "../components/ask-ai/ask-ai";
|
25 |
|
26 |
+
export default function App() {
|
27 |
const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
|
28 |
const remix = useSearchParam("remix");
|
29 |
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
30 |
+
const [_, copyToClipboard] = useCopyToClipboard();
|
31 |
|
32 |
const preview = useRef<HTMLDivElement>(null);
|
33 |
const editor = useRef<HTMLDivElement>(null);
|
34 |
const resizer = useRef<HTMLDivElement>(null);
|
35 |
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
36 |
+
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
37 |
|
|
|
|
|
38 |
const [html, setHtml] = useState((htmlStorage as string) ?? defaultHTML);
|
39 |
const [isAiWorking, setisAiWorking] = useState(false);
|
40 |
const [auth, setAuth] = useState<Auth | undefined>(undefined);
|
|
|
|
|
|
|
41 |
const [prompts, setPrompts] = useState<string[]>([]);
|
42 |
+
const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
|
43 |
+
const [htmlHistory, setHtmlHistory] = useState<HtmlHistory[]>([]);
|
44 |
+
const [currentTab, setCurrentTab] = useState("chat");
|
45 |
+
const [isResizing, setIsResizing] = useState(false);
|
46 |
|
47 |
const fetchMe = async () => {
|
48 |
const res = await fetch("/api/@me");
|
|
|
130 |
document.removeEventListener("mouseup", handleMouseUp);
|
131 |
};
|
132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
useMount(() => {
|
|
|
134 |
fetchMe();
|
135 |
fetchRemix();
|
136 |
|
|
|
137 |
if (htmlStorage) {
|
138 |
removeHtmlStorage();
|
139 |
+
toast.warning("Previous HTML content restored from local storage.");
|
140 |
}
|
141 |
|
|
|
142 |
resetLayout();
|
|
|
|
|
143 |
if (!resizer.current) return;
|
144 |
resizer.current.addEventListener("mousedown", handleMouseDown);
|
145 |
window.addEventListener("resize", resetLayout);
|
146 |
});
|
|
|
|
|
147 |
useUnmount(() => {
|
148 |
document.removeEventListener("mousemove", handleResize);
|
149 |
document.removeEventListener("mouseup", handleMouseUp);
|
|
|
153 |
window.removeEventListener("resize", resetLayout);
|
154 |
});
|
155 |
|
156 |
+
// Prevent accidental navigation away when AI is working or content has changed
|
157 |
+
useEvent("beforeunload", (e) => {
|
158 |
+
if (isAiWorking || html !== defaultHTML) {
|
159 |
+
e.preventDefault();
|
160 |
+
return "";
|
161 |
+
}
|
162 |
+
});
|
163 |
+
|
164 |
+
useUpdateEffect(() => {
|
165 |
+
if (currentTab === "chat") {
|
166 |
+
// Reset editor width when switching to reasoning tab
|
167 |
+
resetLayout();
|
168 |
+
} else {
|
169 |
+
if (preview.current) {
|
170 |
+
// Reset preview width when switching to preview tab
|
171 |
+
preview.current.style.width = "100%";
|
172 |
+
}
|
173 |
+
}
|
174 |
+
}, [currentTab]);
|
175 |
+
|
176 |
return (
|
177 |
+
<ThemeProvider
|
178 |
+
defaultTheme="dark"
|
179 |
+
storageKey="deepsite-ui-theme"
|
180 |
+
className="h-screen bg-slate-100 dark:bg-neutral-950 flex flex-col"
|
181 |
+
>
|
182 |
+
<Header tab={currentTab} onNewTab={setCurrentTab}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
183 |
<DeployButton
|
184 |
html={html}
|
|
|
185 |
auth={auth}
|
186 |
setHtml={setHtml}
|
187 |
prompts={prompts}
|
188 |
/>
|
189 |
</Header>
|
190 |
+
<main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full">
|
191 |
+
{currentTab === "chat" && (
|
192 |
+
<>
|
193 |
+
<div ref={editor} className="relative">
|
194 |
+
<CopyIcon
|
195 |
+
className="size-4 absolute top-2 right-5 text-neutral-500 hover:text-neutral-300 z-2 cursor-pointer"
|
196 |
+
onClick={() => {
|
197 |
+
copyToClipboard(html);
|
198 |
+
toast.success("HTML copied to clipboard!");
|
199 |
+
}}
|
200 |
+
/>
|
201 |
+
<Editor
|
202 |
+
language="html"
|
203 |
+
theme="vs-dark"
|
204 |
+
className={classNames(
|
205 |
+
"h-[calc(100dvh-98px)] lg:h-full bg-neutral-900 transition-all duration-200 ",
|
206 |
+
{
|
207 |
+
"pointer-events-none": isAiWorking,
|
208 |
+
}
|
209 |
+
)}
|
210 |
+
options={{
|
211 |
+
colorDecorators: true,
|
212 |
+
fontLigatures: true,
|
213 |
+
theme: "vs-dark",
|
214 |
+
minimap: { enabled: false },
|
215 |
+
}}
|
216 |
+
value={html}
|
217 |
+
onChange={(value) => {
|
218 |
+
const newValue = value ?? "";
|
219 |
+
setHtml(newValue);
|
220 |
+
}}
|
221 |
+
onMount={(editor) => (editorRef.current = editor)}
|
222 |
+
/>
|
223 |
+
<AskAI
|
224 |
+
html={html}
|
225 |
+
setHtml={(newHtml: string) => {
|
226 |
+
setHtml(newHtml);
|
227 |
+
}}
|
228 |
+
onSuccess={(finalHtml: string, p: string) => {
|
229 |
+
const currentHistory = [...htmlHistory];
|
230 |
+
currentHistory.unshift({
|
231 |
+
html: finalHtml,
|
232 |
+
createdAt: new Date(),
|
233 |
+
prompt: p,
|
234 |
+
});
|
235 |
+
setHtmlHistory(currentHistory);
|
236 |
+
// if xs or sm
|
237 |
+
if (window.innerWidth <= 1024) {
|
238 |
+
setCurrentTab("preview");
|
239 |
+
}
|
240 |
+
}}
|
241 |
+
isAiWorking={isAiWorking}
|
242 |
+
setisAiWorking={setisAiWorking}
|
243 |
+
onNewPrompt={(prompt: string) => {
|
244 |
+
setPrompts((prev) => [...prev, prompt]);
|
245 |
+
}}
|
246 |
+
onScrollToBottom={() => {
|
247 |
+
editorRef.current?.revealLine(
|
248 |
+
editorRef.current?.getModel()?.getLineCount() ?? 0
|
249 |
+
);
|
250 |
+
}}
|
251 |
+
/>
|
252 |
+
</div>
|
253 |
+
<div
|
254 |
+
ref={resizer}
|
255 |
+
className="bg-neutral-800 hover:bg-sky-500 active:bg-sky-500 w-1.5 cursor-col-resize h-full max-lg:hidden"
|
256 |
/>
|
257 |
+
</>
|
258 |
+
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
<Preview
|
260 |
html={html}
|
261 |
isResizing={isResizing}
|
262 |
isAiWorking={isAiWorking}
|
263 |
ref={preview}
|
264 |
+
device={device}
|
265 |
+
currentTab={currentTab}
|
266 |
+
iframeRef={iframeRef}
|
267 |
/>
|
268 |
</main>
|
269 |
+
<Footer
|
270 |
+
onReset={() => {
|
271 |
+
if (isAiWorking) {
|
272 |
+
toast.warning("Please wait for the AI to finish working.");
|
273 |
+
return;
|
274 |
+
}
|
275 |
+
if (
|
276 |
+
window.confirm("You're about to reset the editor. Are you sure?")
|
277 |
+
) {
|
278 |
+
setHtml(defaultHTML);
|
279 |
+
removeHtmlStorage();
|
280 |
+
editorRef.current?.revealLine(
|
281 |
+
editorRef.current?.getModel()?.getLineCount() ?? 0
|
282 |
+
);
|
283 |
+
}
|
284 |
+
}}
|
285 |
+
htmlHistory={htmlHistory}
|
286 |
+
setHtml={setHtml}
|
287 |
+
iframeRef={iframeRef}
|
288 |
+
auth={auth}
|
289 |
+
device={device}
|
290 |
+
setDevice={setDevice}
|
291 |
+
/>
|
292 |
+
<Toaster richColors position="bottom-center" />
|
293 |
+
</ThemeProvider>
|
294 |
);
|
295 |
}
|
|
|
|
tsconfig.app.json
CHANGED
@@ -6,6 +6,7 @@
|
|
6 |
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
7 |
"module": "ESNext",
|
8 |
"skipLibCheck": true,
|
|
|
9 |
|
10 |
/* Bundler mode */
|
11 |
"moduleResolution": "bundler",
|
|
|
6 |
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
7 |
"module": "ESNext",
|
8 |
"skipLibCheck": true,
|
9 |
+
"composite": true,
|
10 |
|
11 |
/* Bundler mode */
|
12 |
"moduleResolution": "bundler",
|
tsconfig.json
CHANGED
@@ -1,13 +1,17 @@
|
|
1 |
{
|
2 |
"files": [],
|
3 |
"references": [
|
4 |
-
{
|
5 |
-
|
|
|
|
|
|
|
|
|
6 |
],
|
7 |
"compilerOptions": {
|
8 |
"baseUrl": ".",
|
9 |
"paths": {
|
10 |
-
"@/*": ["src/*"]
|
11 |
}
|
12 |
}
|
13 |
}
|
|
|
1 |
{
|
2 |
"files": [],
|
3 |
"references": [
|
4 |
+
{
|
5 |
+
"path": "./tsconfig.app.json"
|
6 |
+
},
|
7 |
+
{
|
8 |
+
"path": "./tsconfig.node.json"
|
9 |
+
}
|
10 |
],
|
11 |
"compilerOptions": {
|
12 |
"baseUrl": ".",
|
13 |
"paths": {
|
14 |
+
"@/*": ["./src/*"]
|
15 |
}
|
16 |
}
|
17 |
}
|
utils/providers.js
CHANGED
@@ -19,9 +19,26 @@ export const PROVIDERS = {
|
|
19 |
max_tokens: 16_000,
|
20 |
id: "novita",
|
21 |
},
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
max_tokens: 16_000,
|
20 |
id: "novita",
|
21 |
},
|
22 |
+
hyperbolic: {
|
23 |
+
name: "Hyperbolic",
|
24 |
+
max_tokens: 131_000,
|
25 |
+
id: "hyperbolic",
|
26 |
+
},
|
27 |
};
|
28 |
+
|
29 |
+
export const MODELS = [
|
30 |
+
{
|
31 |
+
value: "deepseek-ai/DeepSeek-V3-0324",
|
32 |
+
label: "DeepSeek V3 O324",
|
33 |
+
providers: ["fireworks-ai", "nebius", "sambanova", "novita", "hyperbolic"],
|
34 |
+
autoProvider: "sambanova",
|
35 |
+
},
|
36 |
+
{
|
37 |
+
value: "deepseek-ai/DeepSeek-R1-0528",
|
38 |
+
label: "DeepSeek R1 0528",
|
39 |
+
providers: ["fireworks-ai", "novita", "hyperbolic", "nebius"],
|
40 |
+
autoProvider: "novita",
|
41 |
+
isNew: true,
|
42 |
+
isThinker: true,
|
43 |
+
},
|
44 |
+
];
|
utils/types.ts
CHANGED
@@ -4,3 +4,9 @@ export interface Auth {
|
|
4 |
name: string;
|
5 |
isLocalUse?: boolean;
|
6 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
name: string;
|
5 |
isLocalUse?: boolean;
|
6 |
}
|
7 |
+
|
8 |
+
export interface HtmlHistory {
|
9 |
+
html: string;
|
10 |
+
createdAt: Date;
|
11 |
+
prompt: string;
|
12 |
+
}
|
vite.config.ts
CHANGED
@@ -1,11 +1,14 @@
|
|
1 |
-
import
|
2 |
-
import react from "@vitejs/plugin-react";
|
3 |
import tailwindcss from "@tailwindcss/vite";
|
|
|
|
|
4 |
|
5 |
// https://vite.dev/config/
|
6 |
export default defineConfig({
|
7 |
plugins: [react(), tailwindcss()],
|
8 |
resolve: {
|
9 |
-
alias:
|
|
|
|
|
10 |
},
|
11 |
});
|
|
|
1 |
+
import path from "path";
|
|
|
2 |
import tailwindcss from "@tailwindcss/vite";
|
3 |
+
import react from "@vitejs/plugin-react";
|
4 |
+
import { defineConfig } from "vite";
|
5 |
|
6 |
// https://vite.dev/config/
|
7 |
export default defineConfig({
|
8 |
plugins: [react(), tailwindcss()],
|
9 |
resolve: {
|
10 |
+
alias: {
|
11 |
+
"@": path.resolve(__dirname, "./src"),
|
12 |
+
},
|
13 |
},
|
14 |
});
|