enzostvs HF Staff commited on
Commit
f6f8d55
·
1 Parent(s): 50e5742

DeepSite v2 🐳

Browse files
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
- Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
 
 
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.13.13",
1706
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
1707
- "integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
 
1708
  "dependencies": {
1709
- "undici-types": "~6.20.0"
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
- "dev": true,
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.20.0",
6223
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
6224
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
 
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: MODEL_ID,
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
- if (provider !== "sambanova") {
322
- res.write(chunk);
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 "react-toastify";
7
- import { useCopyToClipboard, useLocalStorage } from "react-use";
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 "./../../../utils/consts";
13
  import SuccessSound from "./../../assets/success.mp3";
14
  import Settings from "../settings/settings";
15
  import ProModal from "../pro-modal/pro-modal";
16
- // import SpeechPrompt from "../speech-prompt/speech-prompt";
 
 
 
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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
46
- const [_, copyToClipboard] = useCopyToClipboard();
 
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
- // Force-close the HTML tag so the iframe doesn't render half-finished markup
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
- className={`bg-gray-950 rounded-xl py-2 lg:py-2.5 pl-3.5 lg:pl-4 pr-2 lg:pr-2.5 absolute lg:sticky bottom-3 left-3 lg:bottom-4 lg:left-4 w-[calc(100%-1.5rem)] lg:w-[calc(100%-2rem)] z-10 group ${
154
- isAiWorking ? "animate-pulse" : ""
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
- <IoCopy />
166
- </p>
167
- )}
168
- {defaultHTML !== html && (
169
- <button
170
- className="bg-white lg:hidden -translate-y-[calc(100%+8px)] absolute left-0 top-0 shadow-md text-gray-950 text-xs font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center gap-2 border border-gray-100 hover:brightness-150 transition-all duration-100 cursor-pointer"
171
- onClick={() => setView("preview")}
172
- >
173
- <MdPreview className="text-sm" />
174
- View Preview
175
- </button>
 
 
 
 
 
 
 
 
176
  )}
177
- <div className="w-full relative flex items-center justify-between">
178
- <RiSparkling2Fill className="text-lg lg:text-xl text-gray-500 group-focus-within:text-pink-500" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-gray-500 font-code"
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
- <button
204
- disabled={isAiWorking}
205
- className="relative overflow-hidden cursor-pointer flex-none flex items-center justify-center rounded-full text-sm font-semibold size-8 text-center bg-pink-500 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
 
206
  onClick={callAi}
207
  >
208
  <GrSend className="-translate-x-[1px]" />
209
- </button>
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 classNames from "classnames";
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
- {auth &&
91
- (auth.isLocalUse ? (
92
- <>
93
- <div className="bg-amber-500/10 border border-amber-10 text-amber-500 font-semibold leading-5 lg:leading-6 py-1 px-5 text-xs lg:text-sm rounded-md mr-4 select-none">
94
- Local Usage
95
- </div>
96
- </>
97
- ) : (
98
- <>
99
- <button
100
- className="mr-2 cursor-pointer"
101
- onClick={() => {
102
- if (confirm("Are you sure you want to log out?")) {
103
- // go to /auth/logout page
104
- window.location.href = "/auth/logout";
105
- }
106
- }}
107
- >
108
- <FaPowerOff className="text-lg text-red-500" />
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
- {!path && (
185
- <label className="block">
186
- <p className="text-gray-600 text-sm font-medium mb-1.5">
187
- Space Title
188
- </p>
189
- <input
190
- type="text"
191
- value={config.title}
192
- className="mr-2 border rounded-md px-3 py-1.5 border-gray-300 w-full text-sm"
193
- placeholder="My Awesome Space"
194
- onChange={(e) =>
195
- setConfig({ ...config, title: e.target.value })
196
- }
197
- />
198
- </label>
199
- )}
200
- {error && (
201
- <p className="text-red-500 text-xs bg-red-500/10 rounded-md p-2">
202
- Your code has errors. Fix them before deploying.
 
 
 
 
 
 
 
 
203
  </p>
204
- )}
205
- <div className="pt-2 text-right">
206
- <button
207
- disabled={error || loading || (!path && !config.title)}
208
- 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"
209
- onClick={createSpace}
210
- >
211
- {path ? "Update Space" : "Create Space"}
212
- {loading && <Loading />}
213
- </button>
214
- </div>
215
- </main>
216
- </>
217
- )}
218
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 { MdAdd } from "react-icons/md";
3
 
4
  import Logo from "@/assets/logo.svg";
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  function Header({
7
- onReset,
 
8
  children,
9
  }: {
10
- onReset: () => void;
 
11
  children?: ReactNode;
12
  }) {
13
  return (
14
- <header className="border-b border-gray-900 bg-gray-950 px-3 lg:px-6 py-2 flex justify-between items-center sticky top-0 z-20">
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
- {children}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 "react-toastify";
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
- <p
58
- className="underline hover:text-white cursor-pointer text-xs lg:text-sm text-gray-300"
59
- onClick={() => setOpen(!open)}
60
- >
61
- Load Space
62
- </p>
63
- <div
64
- className={classNames(
65
- "h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
66
- {
67
- "opacity-0 pointer-events-none": !open,
68
- }
69
- )}
70
- onClick={() => setOpen(false)}
71
- ></div>
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-pink-600 bg-pink-100 rounded-md px-3 py-2">
90
  Load an existing DeepSite Space to continue working on it.
91
  </p>
92
  <label className="block">
93
- <p className="text-gray-600 text-sm font-medium mb-1.5">
94
- Space URL
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
- onFocus={() => setError(false)}
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
- <button
121
- disabled={error || loading || !url}
122
- 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"
 
123
  onClick={loadSpace}
124
  >
125
- Load Space
126
  {loading && <Loading />}
127
- </button>
128
  </div>
129
  </main>
130
- </>
131
- </div>
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-2 border-b border-gray-200 gap-2 bg-gray-100 font-semibold text-gray-700">
27
- <span className="text-xs bg-red-500/10 text-red-500 rounded-full pl-1.5 pr-2.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}>
35
  <img
36
- src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
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 { TbReload } from "react-icons/tb";
7
- import { toast } from "react-toastify";
8
- import { defaultHTML } from "../../../utils/consts";
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 bg-white h-[calc(100dvh-49px)] lg:h-[calc(100dvh-53px)] relative transition-all duration-200",
42
  {
43
- "flex items-center justify-center": device === "mobile",
44
  }
45
  )}
46
  onClick={(e) => {
47
  if (isAiWorking) {
48
  e.preventDefault();
49
  e.stopPropagation();
50
- toast.warn("Please wait for the AI to finish working.");
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-black shadow-2xl":
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
- <button
24
- className="relative overflow-hidden cursor-pointer flex-none flex items-center justify-center rounded-full text-base font-semibold size-8 text-center bg-gray-800 hover:bg-gray-700 text-gray-100 shadow-sm dark:shadow-highlight/20"
25
- onClick={() => {
26
- onClose((prev) => !prev);
27
- }}
28
- >
29
- <PiGearSixFill />
30
- </button>
31
- <div
32
- className={classNames(
33
- "h-screen w-screen bg-black/20 fixed left-0 top-0 z-40",
34
- {
35
- "opacity-0 pointer-events-none": !open,
36
- }
37
- )}
38
- onClick={() => onClose(false)}
39
- ></div>
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-gray-600 bg-gray-50 border border-gray-100 px-2 py-2 rounded-lg mb-3 text-sm font-medium hover:brightness-95"
61
  >
62
  How to use it locally?
63
- <button className="bg-black text-white rounded-md px-3 py-1.5 text-xs font-semibold cursor-pointer">
64
- See the guide
65
- </button>
66
  </a>
67
- <div className="flex items-center justify-between">
68
- <p className="text-gray-800 text-sm font-medium flex items-center justify-between">
69
- Use auto-provider
70
  </p>
71
- <div
72
- className={classNames(
73
- "bg-gray-200 rounded-full w-10 h-6 flex items-center justify-between p-1 cursor-pointer transition-all duration-200",
74
- {
75
- "!bg-blue-500": provider === "auto",
76
- }
77
- )}
78
- onClick={() => {
79
- onChange(provider === "auto" ? "fireworks-ai" : "auto");
80
- }}
81
- >
82
- <div
83
- className={classNames(
84
- "w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-white",
85
- {
86
- "translate-x-4": provider === "auto",
87
- }
88
- )}
89
- />
90
- </div>
91
- </div>
92
- <p className="text-xs text-gray-500 mt-2">
93
- We'll automatically select the best provider for you based on your
94
- prompt.
95
- </p>
96
- </div>
97
- {error !== "" && (
98
- <p className="text-red-500 text-sm font-medium mb-2 flex items-center justify-between bg-red-500/10 p-2 rounded-md">
99
- {error}
100
- </p>
101
- )}
102
- <label className="block">
103
- <p className="text-gray-800 text-sm font-medium mb-2 flex items-center justify-between">
104
- Inference Provider
105
- </p>
106
- <div className="grid grid-cols-2 gap-1.5">
107
- {Object.keys(PROVIDERS).map((id: string) => (
 
 
 
 
 
 
 
 
 
 
108
  <div
109
- key={id}
110
  className={classNames(
111
- "text-gray-600 text-sm font-medium cursor-pointer border p-2 rounded-md flex items-center justify-start gap-2",
112
  {
113
- "bg-blue-500/10 border-blue-500/15 text-blue-500":
114
- id === provider,
115
- "hover:bg-gray-100 border-gray-100": id !== provider,
116
  }
117
  )}
118
  onClick={() => {
119
- onChange(id);
 
 
 
 
 
 
 
120
  }}
121
  >
122
- <img
123
- src={`/providers/${id}.svg`}
124
- alt={PROVIDERS[id].name}
125
- className="size-5"
 
 
 
126
  />
127
- {PROVIDERS[id].name}
128
  </div>
129
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
- </label>
132
- </main>
133
- </div>
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 "./components/App.tsx";
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
- useMount,
7
- useUnmount,
8
  useEvent,
9
  useLocalStorage,
 
10
  useSearchParam,
 
 
11
  } from "react-use";
12
- import { toast } from "react-toastify";
 
 
 
 
13
 
14
- import Header from "./header/header";
15
- import DeployButton from "./deploy-button/deploy-button";
16
- import { defaultHTML } from "./../../utils/consts";
17
- import Tabs from "./tabs/tabs";
18
- import AskAI from "./ask-ai/ask-ai";
19
- import { Auth } from "./../../utils/types";
20
- import Preview from "./preview/preview";
 
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 [htmlHistory, setHtmlHistory] = useState<
41
- { html: string; createdAt: Date; prompt: string }[]
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.warn("Previous HTML content restored from local storage.");
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
- <div className="h-screen bg-gray-950 font-sans overflow-hidden">
171
- <Header
172
- onReset={() => {
173
- if (isAiWorking) {
174
- toast.warn("Please wait for the AI to finish working.");
175
- return;
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
- <div
199
- ref={editor}
200
- className={classNames(
201
- "w-full h-[calc(100dvh-49px)] lg:h-[calc(100dvh-54px)] relative overflow-hidden max-lg:transition-all max-lg:duration-200 select-none",
202
- {
203
- "max-lg:h-0": currentView === "preview",
204
- }
205
- )}
206
- >
207
- <Tabs htmlHistory={htmlHistory} setHtml={setHtml} />
208
- <div
209
- onClick={(e) => {
210
- if (isAiWorking) {
211
- e.preventDefault();
212
- e.stopPropagation();
213
- toast.warn("Please wait for the AI to finish working.");
214
- }
215
- }}
216
- >
217
- <Editor
218
- language="html"
219
- theme="vs-dark"
220
- className={classNames(
221
- "h-[calc(100dvh-90px)] lg:h-[calc(100dvh-96px)]",
222
- {
223
- "pointer-events-none": isAiWorking,
224
- }
225
- )}
226
- value={html}
227
- onValidate={(markers) => {
228
- if (markers?.length > 0) {
229
- setError(true);
230
- }
231
- }}
232
- onChange={(value) => {
233
- const newValue = value ?? "";
234
- setHtml(newValue);
235
- setError(false);
236
- }}
237
- onMount={(editor) => (editorRef.current = editor)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  />
239
- </div>
240
- <AskAI
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
- setView={setCurrentView}
 
 
277
  />
278
  </main>
279
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
 
 
 
 
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
- // hyperbolic: {
23
- // name: "Hyperbolic",
24
- // max_tokens: 131_000,
25
- // id: "hyperbolic",
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 { defineConfig } from "vite";
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: [{ find: "@", replacement: "/src" }],
 
 
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
  });