nomadicsynth commited on
Commit
bf55ced
·
verified ·
2 Parent(s): 1007337 05b2241

Merge branch #enzostvs/deepsite' into 'nomadicsynth/deepsite'

Browse files
README.md CHANGED
@@ -16,10 +16,13 @@ license: mit
16
  short_description: Generate any application with DeepSeek
17
  models:
18
  - deepseek-ai/DeepSeek-V3-0324
 
19
  ---
20
 
21
  # DeepSite 🐳
 
22
  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.
23
 
24
  ## How to use it locally
25
- Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
 
 
16
  short_description: Generate any application with DeepSeek
17
  models:
18
  - deepseek-ai/DeepSeek-V3-0324
19
+ - deepseek-ai/DeepSeek-R1-0528
20
  ---
21
 
22
  # DeepSite 🐳
23
+
24
  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.
25
 
26
  ## How to use it locally
27
+
28
+ 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
 
@@ -263,7 +286,7 @@ app.post("/api/ask-ai", async (req, res) => {
263
  const DEFAULT_PROVIDER = PROVIDERS.novita;
264
  const selectedProvider =
265
  provider === "auto"
266
- ? DEFAULT_PROVIDER
267
  : PROVIDERS[provider] ?? DEFAULT_PROVIDER;
268
 
269
  if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {
@@ -276,12 +299,12 @@ 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
  {
283
  role: "system",
284
- content: `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE`,
285
  },
286
  ...(previousPrompt
287
  ? [
@@ -318,23 +341,39 @@ 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>");
333
- }
334
  completeResponse += newChunk;
335
  res.write(newChunk);
336
- if (newChunk.includes("</html>")) {
337
- break;
 
 
 
 
 
338
  }
339
  }
340
  }
 
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
 
 
286
  const DEFAULT_PROVIDER = PROVIDERS.novita;
287
  const selectedProvider =
288
  provider === "auto"
289
+ ? PROVIDERS[selectedModel.autoProvider]
290
  : PROVIDERS[provider] ?? DEFAULT_PROVIDER;
291
 
292
  if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {
 
299
 
300
  try {
301
  const chatCompletion = client.chatCompletionStream({
302
+ model: selectedModel.value,
303
  provider: selectedProvider.id,
304
  messages: [
305
  {
306
  role: "system",
307
+ content: `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. MAKE IT RESPONSIVE USING TAILWINDCSS. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE`,
308
  },
309
  ...(previousPrompt
310
  ? [
 
341
  }
342
  const chunk = value.choices[0]?.delta?.content;
343
  if (chunk) {
344
+ let newChunk = chunk;
345
+ if (!selectedModel?.isThinker) {
346
+ if (provider !== "sambanova") {
347
+ res.write(chunk);
348
+ completeResponse += chunk;
349
+
350
+ if (completeResponse.includes("</html>")) {
351
+ break;
352
+ }
353
+ } else {
354
+ let newChunk = chunk;
355
+ if (chunk.includes("</html>")) {
356
+ // Replace everything after the last </html> tag with an empty string
357
+ newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");
358
+ }
359
+ completeResponse += newChunk;
360
+ res.write(newChunk);
361
+ if (newChunk.includes("</html>")) {
362
+ break;
363
+ }
364
  }
365
  } else {
366
+ // check if in the completeResponse there is already a </html> tag but after the last </think> tag, if yes break the loop
367
+ const lastThinkTagIndex = completeResponse.lastIndexOf("</think>");
 
 
 
368
  completeResponse += newChunk;
369
  res.write(newChunk);
370
+ if (lastThinkTagIndex !== -1) {
371
+ const afterLastThinkTag = completeResponse.slice(
372
+ lastThinkTagIndex + "</think>".length
373
+ );
374
+ if (afterLastThinkTag.includes("</html>")) {
375
+ break;
376
+ }
377
  }
378
  }
379
  }
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,27 +22,31 @@ function AskAI({
21
  onScrollToBottom,
22
  isAiWorking,
23
  setisAiWorking,
24
- setView,
25
  onNewPrompt,
 
26
  }: {
27
  html: string;
28
  setHtml: (html: string) => void;
29
  onScrollToBottom: () => void;
30
  isAiWorking: boolean;
31
  onNewPrompt: (prompt: string) => void;
32
- setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
33
  setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
 
34
  }) {
 
 
35
  const [open, setOpen] = useState(false);
36
  const [prompt, setPrompt] = useState("");
37
  const [hasAsked, setHasAsked] = useState(false);
38
  const [previousPrompt, setPreviousPrompt] = useState("");
39
  const [provider, setProvider] = useLocalStorage("provider", "auto");
 
40
  const [openProvider, setOpenProvider] = useState(false);
41
  const [providerError, setProviderError] = useState("");
42
  const [openProModal, setOpenProModal] = useState(false);
43
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
44
- const [_, copyToClipboard] = useCopyToClipboard();
 
45
 
46
  const audio = new Audio(SuccessSound);
47
  audio.volume = 0.5;
@@ -50,6 +55,9 @@ function AskAI({
50
  if (isAiWorking || !prompt.trim()) return;
51
  setisAiWorking(true);
52
  setProviderError("");
 
 
 
53
 
54
  let contentResponse = "";
55
  let lastRenderTime = 0;
@@ -60,6 +68,7 @@ function AskAI({
60
  body: JSON.stringify({
61
  prompt,
62
  provider,
 
63
  ...(html === defaultHTML ? {} : { html }),
64
  ...(previousPrompt ? { previousPrompt } : {}),
65
  }),
@@ -85,17 +94,19 @@ function AskAI({
85
  }
86
  const reader = request.body.getReader();
87
  const decoder = new TextDecoder("utf-8");
88
-
 
 
 
89
  const read = async () => {
90
  const { done, value } = await reader.read();
91
  if (done) {
92
  toast.success("AI responded successfully");
93
- setPrompt("");
94
  setPreviousPrompt(prompt);
 
95
  setisAiWorking(false);
96
  setHasAsked(true);
97
  audio.play();
98
- setView("preview");
99
 
100
  // Now we have the complete HTML including </html>, so set it to be sure
101
  const finalDoc = contentResponse.match(
@@ -104,15 +115,27 @@ function AskAI({
104
  if (finalDoc) {
105
  setHtml(finalDoc);
106
  }
 
107
 
108
  return;
109
  }
110
 
111
  const chunk = decoder.decode(value, { stream: true });
112
  contentResponse += chunk;
 
 
 
 
 
 
 
 
 
 
 
113
  const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
114
  if (newHtml) {
115
- // Force-close the HTML tag so the iframe doesn't render half-finished markup
116
  let partialDoc = newHtml;
117
  if (!partialDoc.includes("</html>")) {
118
  partialDoc += "\n</html>";
@@ -134,8 +157,6 @@ function AskAI({
134
 
135
  read();
136
  }
137
-
138
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
  } catch (error: any) {
140
  setisAiWorking(false);
141
  toast.error(error.message);
@@ -145,39 +166,79 @@ function AskAI({
145
  }
146
  };
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  return (
149
- <div
150
- 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 ${
151
- isAiWorking ? "animate-pulse" : ""
152
- }`}
153
- >
154
- {defaultHTML !== html && (
155
- <p
156
- className="text-2xl text-white/50 hover:text-white/80 -translate-y-[calc(100%+8px)] absolute top-0 right-0 cursor-pointer"
157
- onClick={() => {
158
- copyToClipboard(html);
159
- toast.success("HTML copied to clipboard");
160
- audio.play();
161
- }}
162
- >
163
- <IoCopy />
164
- </p>
165
- )}
166
- {defaultHTML !== html && (
167
- <button
168
- 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"
169
- onClick={() => setView("preview")}
170
- >
171
- <MdPreview className="text-sm" />
172
- View Preview
173
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
174
  )}
175
- <div className="w-full relative flex items-center justify-between">
176
- <RiSparkling2Fill className="text-lg lg:text-xl text-gray-500 group-focus-within:text-pink-500" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  <input
178
  type="text"
179
  disabled={isAiWorking}
180
- className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-gray-500 font-code"
181
  placeholder={
182
  hasAsked ? "What do you want to ask AI next?" : "Ask AI anything..."
183
  }
@@ -193,18 +254,21 @@ function AskAI({
193
  {/* <SpeechPrompt setPrompt={setPrompt} /> */}
194
  <Settings
195
  provider={provider as string}
 
196
  onChange={setProvider}
 
197
  open={openProvider}
198
  error={providerError}
199
  onClose={setOpenProvider}
200
  />
201
- <button
202
- disabled={isAiWorking}
203
- 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"
 
204
  onClick={callAi}
205
  >
206
  <GrSend className="-translate-x-[1px]" />
207
- </button>
208
  </div>
209
  </div>
210
  <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
+ import { ChevronDown } from "lucide-react";
 
9
 
10
  import Login from "../login/login";
11
+ import { defaultHTML } from "../../../utils/consts";
12
  import SuccessSound from "./../../assets/success.mp3";
13
  import Settings from "../settings/settings";
14
  import ProModal from "../pro-modal/pro-modal";
15
+ import { Button } from "../ui/button";
16
+ // @ts-expect-error not needed
17
+ import { MODELS } from "./../../../utils/providers";
18
 
19
  function AskAI({
20
  html,
 
22
  onScrollToBottom,
23
  isAiWorking,
24
  setisAiWorking,
 
25
  onNewPrompt,
26
+ onSuccess,
27
  }: {
28
  html: string;
29
  setHtml: (html: string) => void;
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;
 
55
  if (isAiWorking || !prompt.trim()) return;
56
  setisAiWorking(true);
57
  setProviderError("");
58
+ setThink("");
59
+ setOpenThink(false);
60
+ setIsThinking(true);
61
 
62
  let contentResponse = "";
63
  let lastRenderTime = 0;
 
68
  body: JSON.stringify({
69
  prompt,
70
  provider,
71
+ model,
72
  ...(html === defaultHTML ? {} : { html }),
73
  ...(previousPrompt ? { previousPrompt } : {}),
74
  }),
 
94
  }
95
  const reader = request.body.getReader();
96
  const decoder = new TextDecoder("utf-8");
97
+ const selectedModel = MODELS.find(
98
+ (m: { value: string }) => m.value === model
99
+ );
100
+ let contentThink: string | undefined = undefined;
101
  const read = async () => {
102
  const { done, value } = await reader.read();
103
  if (done) {
104
  toast.success("AI responded successfully");
 
105
  setPreviousPrompt(prompt);
106
+ setPrompt("");
107
  setisAiWorking(false);
108
  setHasAsked(true);
109
  audio.play();
 
110
 
111
  // Now we have the complete HTML including </html>, so set it to be sure
112
  const finalDoc = contentResponse.match(
 
115
  if (finalDoc) {
116
  setHtml(finalDoc);
117
  }
118
+ onSuccess(finalDoc ?? contentResponse, prompt);
119
 
120
  return;
121
  }
122
 
123
  const chunk = decoder.decode(value, { stream: true });
124
  contentResponse += chunk;
125
+ if (selectedModel?.isThinker) {
126
+ const thinkMatch = contentResponse.match(/<think>[\s\S]*/)?.[0];
127
+ if (thinkMatch && !contentResponse?.includes("</think>")) {
128
+ if ((contentThink?.length ?? 0) < 3) {
129
+ setOpenThink(true);
130
+ }
131
+ setThink(thinkMatch.replace("<think>", "").trim());
132
+ contentThink += chunk;
133
+ }
134
+ }
135
+
136
  const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
137
  if (newHtml) {
138
+ setIsThinking(false);
139
  let partialDoc = newHtml;
140
  if (!partialDoc.includes("</html>")) {
141
  partialDoc += "\n</html>";
 
157
 
158
  read();
159
  }
 
 
160
  } catch (error: any) {
161
  setisAiWorking(false);
162
  toast.error(error.message);
 
166
  }
167
  };
168
 
169
+ useUpdateEffect(() => {
170
+ if (refThink.current) {
171
+ refThink.current.scrollTop = refThink.current.scrollHeight;
172
+ }
173
+ }, [think]);
174
+
175
+ useUpdateEffect(() => {
176
+ if (!isThinking) {
177
+ setOpenThink(false);
178
+ }
179
+ }, [isThinking]);
180
+
181
+ // TODO: auto scroll is not working properly, fix it
182
+
183
  return (
184
+ <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">
185
+ {think && (
186
+ <div className="w-full border-b border-neutral-700 relative overflow-hidden">
187
+ <header
188
+ className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
189
+ onClick={() => {
190
+ setOpenThink(!openThink);
191
+ }}
192
+ >
193
+ <p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
194
+ {isThinking ? "AI is thinking..." : "AI's plan"}
195
+ </p>
196
+ <ChevronDown
197
+ className={classNames(
198
+ "size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
199
+ {
200
+ "rotate-180": openThink,
201
+ }
202
+ )}
203
+ />
204
+ </header>
205
+ <main
206
+ ref={refThink}
207
+ className={classNames(
208
+ "overflow-y-auto transition-all duration-200 ease-in-out",
209
+ {
210
+ "max-h-[0px]": !openThink,
211
+ "min-h-[250px] max-h-[250px] border-t border-neutral-700":
212
+ openThink,
213
+ }
214
+ )}
215
+ >
216
+ <p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
217
+ {think}
218
+ </p>
219
+ </main>
220
+ </div>
221
  )}
222
+ <div
223
+ className={classNames(
224
+ "w-full relative flex items-center justify-between pl-3.5 p-2 lg:p-2 lg:pl-4",
225
+ {
226
+ "animate-pulse": isAiWorking,
227
+ }
228
+ )}
229
+ >
230
+ {isAiWorking && (
231
+ <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">
232
+ <p className="text-neutral-400 font-code">
233
+ AI is {isThinking ? "thinking" : "coding"}...
234
+ </p>
235
+ </div>
236
+ )}
237
+ <RiSparkling2Fill className="size-5 text-neutral-400 group-focus-within:text-sky-500" />
238
  <input
239
  type="text"
240
  disabled={isAiWorking}
241
+ className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-neutral-400 font-code"
242
  placeholder={
243
  hasAsked ? "What do you want to ask AI next?" : "Ask AI anything..."
244
  }
 
254
  {/* <SpeechPrompt setPrompt={setPrompt} /> */}
255
  <Settings
256
  provider={provider as string}
257
+ model={model as string}
258
  onChange={setProvider}
259
+ onModelChange={setModel}
260
  open={openProvider}
261
  error={providerError}
262
  onClose={setOpenProvider}
263
  />
264
+ <Button
265
+ variant="pink"
266
+ size="icon"
267
+ disabled={isAiWorking || !prompt.trim()}
268
  onClick={callAi}
269
  >
270
  <GrSend className="-translate-x-[1px]" />
271
+ </Button>
272
  </div>
273
  </div>
274
  <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,83 +1,67 @@
1
  import classNames from "classnames";
2
- import { useRef } from "react";
3
- import { TbReload } from "react-icons/tb";
4
- import { toast } from "react-toastify";
5
- import { FaLaptopCode } from "react-icons/fa6";
6
- import { defaultHTML } from "../../../utils/consts";
7
 
8
  function Preview({
9
  html,
10
  isResizing,
11
  isAiWorking,
12
- setView,
13
  ref,
 
 
 
14
  }: {
15
  html: string;
16
  isResizing: boolean;
17
  isAiWorking: boolean;
18
- setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
19
  ref: React.RefObject<HTMLDivElement | null>;
 
 
 
20
  }) {
21
- const iframeRef = useRef<HTMLIFrameElement | null>(null);
22
-
23
- const handleRefreshIframe = () => {
24
- if (iframeRef.current) {
25
- const iframe = iframeRef.current;
26
- const content = iframe.srcdoc;
27
- iframe.srcdoc = "";
28
- setTimeout(() => {
29
- iframe.srcdoc = content;
30
- }, 10);
31
- }
32
- };
33
-
34
  return (
35
  <div
36
  ref={ref}
37
- className="w-full border-l border-gray-900 bg-white h-[calc(100dvh-49px)] lg:h-[calc(100dvh-53px)] relative"
 
 
 
 
 
38
  onClick={(e) => {
39
  if (isAiWorking) {
40
  e.preventDefault();
41
  e.stopPropagation();
42
- toast.warn("Please wait for the AI to finish working.");
43
  }
44
  }}
45
  >
 
 
 
 
 
 
 
 
46
  <iframe
47
  ref={iframeRef}
48
  title="output"
49
- className={classNames("w-full h-full select-none", {
50
- "pointer-events-none": isResizing || isAiWorking,
51
- })}
 
 
 
 
 
 
 
 
52
  srcDoc={html}
53
  />
54
- <div className="flex items-center justify-start gap-3 absolute bottom-3 lg:bottom-5 max-lg:left-3 lg:right-5">
55
- <button
56
- 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"
57
- onClick={() => setView("editor")}
58
- >
59
- <FaLaptopCode className="text-sm" />
60
- Hide preview
61
- </button>
62
- {html === defaultHTML && (
63
- <a
64
- href="https://huggingface.co/spaces/victor/deepsite-gallery"
65
- target="_blank"
66
- 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"
67
- >
68
- 🖼️ <span>DeepSite Gallery</span>
69
- </a>
70
- )}
71
- {!isAiWorking && (
72
- <button
73
- 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"
74
- onClick={handleRefreshIframe}
75
- >
76
- <TbReload className="text-sm" />
77
- Refresh Preview
78
- </button>
79
- )}
80
- </div>
81
  </div>
82
  );
83
  }
 
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 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 (provider !== "auto" && !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 foundModel = MODELS.find(
142
+ (m: { value: string }) => m.value === model
143
+ );
144
+ if (provider === "auto") {
145
+ onChange(foundModel.providers[0]);
146
+ } else {
147
+ onChange("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,29 +0,0 @@
1
- import Deepseek from "./../../assets/deepseek-color.svg";
2
-
3
- function Tabs({ children }: { children?: React.ReactNode }) {
4
- return (
5
- <div className="border-b border-gray-800 pl-4 lg:pl-7 pr-3 flex items-center justify-between">
6
- <div
7
- className="
8
- space-x-6"
9
- >
10
- <button className="rounded-md text-sm cursor-pointer transition-all duration-100 font-medium relative py-2.5 text-white">
11
- index.html
12
- <span className="absolute bottom-0 left-0 h-0.5 w-full transition-all duration-100 bg-white" />
13
- </button>
14
- </div>
15
- <div className="flex items-center justify-end gap-3">
16
- <a
17
- href="https://huggingface.co/deepseek-ai/DeepSeek-V3-0324"
18
- target="_blank"
19
- className="text-[12px] text-gray-300 hover:brightness-120 flex items-center gap-1 font-code"
20
- >
21
- Powered by <img src={Deepseek} className="size-5" /> Deepseek
22
- </a>
23
- {children}
24
- </div>
25
- </div>
26
- );
27
- }
28
-
29
- 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,42 +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
 
41
  const fetchMe = async () => {
42
  const res = await fetch("/api/@me");
@@ -124,36 +130,20 @@ function App() {
124
  document.removeEventListener("mouseup", handleMouseUp);
125
  };
126
 
127
- // Prevent accidental navigation away when AI is working or content has changed
128
- useEvent("beforeunload", (e) => {
129
- if (isAiWorking || html !== defaultHTML) {
130
- e.preventDefault();
131
- return "";
132
- }
133
- });
134
-
135
- // Initialize component on mount
136
  useMount(() => {
137
- // Fetch user data
138
  fetchMe();
139
  fetchRemix();
140
 
141
- // Restore content from storage if available
142
  if (htmlStorage) {
143
  removeHtmlStorage();
144
- toast.warn("Previous HTML content restored from local storage.");
145
  }
146
 
147
- // Set initial layout based on window size
148
  resetLayout();
149
-
150
- // Attach event listeners
151
  if (!resizer.current) return;
152
  resizer.current.addEventListener("mousedown", handleMouseDown);
153
  window.addEventListener("resize", resetLayout);
154
  });
155
-
156
- // Clean up event listeners on unmount
157
  useUnmount(() => {
158
  document.removeEventListener("mousemove", handleResize);
159
  document.removeEventListener("mouseup", handleMouseUp);
@@ -163,107 +153,143 @@ function App() {
163
  window.removeEventListener("resize", resetLayout);
164
  });
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  return (
167
- <div className="h-screen bg-gray-950 font-sans overflow-hidden">
168
- <Header
169
- onReset={() => {
170
- if (isAiWorking) {
171
- toast.warn("Please wait for the AI to finish working.");
172
- return;
173
- }
174
- if (
175
- window.confirm("You're about to reset the editor. Are you sure?")
176
- ) {
177
- setHtml(defaultHTML);
178
- setError(false);
179
- removeHtmlStorage();
180
- editorRef.current?.revealLine(
181
- editorRef.current?.getModel()?.getLineCount() ?? 0
182
- );
183
- }
184
- }}
185
- >
186
  <DeployButton
187
  html={html}
188
- error={error}
189
  auth={auth}
190
  setHtml={setHtml}
191
  prompts={prompts}
192
  />
193
  </Header>
194
- <main className="max-lg:flex-col flex w-full">
195
- <div
196
- ref={editor}
197
- className={classNames(
198
- "w-full h-[calc(100dvh-49px)] lg:h-[calc(100dvh-54px)] relative overflow-hidden max-lg:transition-all max-lg:duration-200 select-none",
199
- {
200
- "max-lg:h-0": currentView === "preview",
201
- }
202
- )}
203
- >
204
- <Tabs />
205
- <div
206
- onClick={(e) => {
207
- if (isAiWorking) {
208
- e.preventDefault();
209
- e.stopPropagation();
210
- toast.warn("Please wait for the AI to finish working.");
211
- }
212
- }}
213
- >
214
- <Editor
215
- language="html"
216
- theme="vs-dark"
217
- className={classNames(
218
- "h-[calc(100dvh-90px)] lg:h-[calc(100dvh-96px)]",
219
- {
220
- "pointer-events-none": isAiWorking,
221
- }
222
- )}
223
- value={html}
224
- onValidate={(markers) => {
225
- if (markers?.length > 0) {
226
- setError(true);
227
- }
228
- }}
229
- onChange={(value) => {
230
- const newValue = value ?? "";
231
- setHtml(newValue);
232
- setError(false);
233
- }}
234
- onMount={(editor) => (editorRef.current = editor)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  />
236
- </div>
237
- <AskAI
238
- html={html}
239
- setHtml={setHtml}
240
- isAiWorking={isAiWorking}
241
- setisAiWorking={setisAiWorking}
242
- setView={setCurrentView}
243
- onNewPrompt={(prompt) => {
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-gray-700 hover:bg-blue-500 w-2 cursor-col-resize h-[calc(100dvh-53px)] max-lg:hidden"
256
- />
257
  <Preview
258
  html={html}
259
  isResizing={isResizing}
260
  isAiWorking={isAiWorking}
261
  ref={preview}
262
- setView={setCurrentView}
 
 
263
  />
264
  </main>
265
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  );
267
  }
268
-
269
- 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/consts.ts CHANGED
@@ -13,6 +13,7 @@ export const defaultHTML = `<!DOCTYPE html>
13
  height: 100dvh;
14
  font-family: "Arial", sans-serif;
15
  text-align: center;
 
16
  }
17
  .arrow {
18
  position: absolute;
 
13
  height: 100dvh;
14
  font-family: "Arial", sans-serif;
15
  text-align: center;
16
+ background-color: #fff;
17
  }
18
  .arrow {
19
  position: absolute;
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: "novita",
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
  });