├── .gitignore ├── LICENSE ├── README.md ├── Screenshot 2025-01-14 090700.png ├── Screenshot 2025-01-14 093931.png ├── backend ├── .env.example ├── package-lock.json ├── package.json └── src │ ├── ai.js │ ├── env.js │ ├── search.js │ └── server.js └── frontend ├── .gitignore ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.tsx ├── components │ ├── SearchInput.tsx │ ├── SearchResults.tsx │ └── ui │ │ └── Tooltip.tsx ├── index.css ├── main.tsx └── types │ └── index.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.app.tsbuildinfo ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.node.tsbuildinfo └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | *.local 4 | .DS_Store 5 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Yash Kamble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **AI Search Agent** 2 | 3 | A simple yet powerful AI-powered search agent inspired by [Perplexity](https://perplexity.ai). This project leverages cutting-edge AI models and a robust search engine to provide intelligent, context-aware search results. 4 | 5 | ![Project Workflow](https://github.com/hackice20/perplexity-clone/raw/main/Screenshot%202025-01-14%20093931.png) 6 | 7 | 8 | ## **Overview** 9 | 10 | This project integrates: 11 | - [Llama-3.3-70b-versatile](https://github.com/meta-llama/llama-models/blob/main/models/llama3_3/MODEL_CARD.md) (hosted on [Groq](https://console.groq.com/docs/models)) for natural language understanding and processing. 12 | - [Google Custom Search JSON API](https://developers.google.com/custom-search/v1/overview) for retrieving high-quality web search results. 13 | 14 | With a clean interface and a powerful backend, the AI Search Agent combines the best of AI and traditional search engines. 15 | 16 | ![Project Screenshot](https://github.com/hackice20/perplexity-clone/raw/main/Screenshot%202025-01-14%20090700.png) 17 | 18 | --- 19 | 20 | ## **Features** 21 | 22 | - 🌟 **AI-Powered Search:** Get accurate, contextual answers powered by Llama-3.3 AI. 23 | - 🔍 **Web Search Integration:** Use Google Custom Search for retrieving reliable information. 24 | - 🖥️ **Responsive UI:** A user-friendly and responsive interface built with modern frontend tools. 25 | 26 | --- 27 | 28 | ## **Tech Stack** 29 | 30 | ### **Backend** 31 | - **Node.js** – Scalable server-side JavaScript runtime. 32 | - **Express.js** – Minimalist web framework for routing and API development. 33 | 34 | ### **Frontend** 35 | - **React** – A JavaScript library for building user interfaces. 36 | - **TypeScript** – Adds type safety and improves developer productivity. 37 | - **Tailwind CSS** – Utility-first framework for building custom UI. 38 | 39 | --- 40 | 41 | ## **License** 42 | 43 | This project is licensed under the [MIT License](LICENSE). 44 | 45 | --- 46 | -------------------------------------------------------------------------------- /Screenshot 2025-01-14 090700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackice20/perplexity-clone/5a263d5d4ecb48d44fb938b889301c0f079c6674/Screenshot 2025-01-14 090700.png -------------------------------------------------------------------------------- /Screenshot 2025-01-14 093931.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackice20/perplexity-clone/5a263d5d4ecb48d44fb938b889301c0f079c6674/Screenshot 2025-01-14 093931.png -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | GOOGLE_API_KEY 3 | GOOGLE_SEARCH_ENGINE_ID 4 | GROQ_API_KEY -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aisearch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aisearch", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^5.0.1", 13 | "jsdom": "^25.0.1", 14 | "zod": "^3.24.1" 15 | }, 16 | "devDependencies": { 17 | "dotenv": "^16.4.7", 18 | "nodemon": "^3.1.9" 19 | } 20 | }, 21 | "node_modules/accepts": { 22 | "version": "2.0.0", 23 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 24 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 25 | "dependencies": { 26 | "mime-types": "^3.0.0", 27 | "negotiator": "^1.0.0" 28 | }, 29 | "engines": { 30 | "node": ">= 0.6" 31 | } 32 | }, 33 | "node_modules/agent-base": { 34 | "version": "7.1.3", 35 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", 36 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", 37 | "license": "MIT", 38 | "engines": { 39 | "node": ">= 14" 40 | } 41 | }, 42 | "node_modules/anymatch": { 43 | "version": "3.1.3", 44 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 45 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 46 | "dev": true, 47 | "license": "ISC", 48 | "dependencies": { 49 | "normalize-path": "^3.0.0", 50 | "picomatch": "^2.0.4" 51 | }, 52 | "engines": { 53 | "node": ">= 8" 54 | } 55 | }, 56 | "node_modules/array-flatten": { 57 | "version": "3.0.0", 58 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", 59 | "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" 60 | }, 61 | "node_modules/asynckit": { 62 | "version": "0.4.0", 63 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 64 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 65 | "license": "MIT" 66 | }, 67 | "node_modules/balanced-match": { 68 | "version": "1.0.2", 69 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 70 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 71 | "dev": true, 72 | "license": "MIT" 73 | }, 74 | "node_modules/binary-extensions": { 75 | "version": "2.3.0", 76 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 77 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 78 | "dev": true, 79 | "license": "MIT", 80 | "engines": { 81 | "node": ">=8" 82 | }, 83 | "funding": { 84 | "url": "https://github.com/sponsors/sindresorhus" 85 | } 86 | }, 87 | "node_modules/body-parser": { 88 | "version": "2.0.2", 89 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.2.tgz", 90 | "integrity": "sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==", 91 | "dependencies": { 92 | "bytes": "3.1.2", 93 | "content-type": "~1.0.5", 94 | "debug": "3.1.0", 95 | "destroy": "1.2.0", 96 | "http-errors": "2.0.0", 97 | "iconv-lite": "0.5.2", 98 | "on-finished": "2.4.1", 99 | "qs": "6.13.0", 100 | "raw-body": "^3.0.0", 101 | "type-is": "~1.6.18" 102 | }, 103 | "engines": { 104 | "node": ">=18" 105 | } 106 | }, 107 | "node_modules/body-parser/node_modules/debug": { 108 | "version": "3.1.0", 109 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 110 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 111 | "dependencies": { 112 | "ms": "2.0.0" 113 | } 114 | }, 115 | "node_modules/body-parser/node_modules/media-typer": { 116 | "version": "0.3.0", 117 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 118 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 119 | "engines": { 120 | "node": ">= 0.6" 121 | } 122 | }, 123 | "node_modules/body-parser/node_modules/mime-db": { 124 | "version": "1.52.0", 125 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 126 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 127 | "engines": { 128 | "node": ">= 0.6" 129 | } 130 | }, 131 | "node_modules/body-parser/node_modules/mime-types": { 132 | "version": "2.1.35", 133 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 134 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 135 | "dependencies": { 136 | "mime-db": "1.52.0" 137 | }, 138 | "engines": { 139 | "node": ">= 0.6" 140 | } 141 | }, 142 | "node_modules/body-parser/node_modules/ms": { 143 | "version": "2.0.0", 144 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 145 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 146 | }, 147 | "node_modules/body-parser/node_modules/type-is": { 148 | "version": "1.6.18", 149 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 150 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 151 | "dependencies": { 152 | "media-typer": "0.3.0", 153 | "mime-types": "~2.1.24" 154 | }, 155 | "engines": { 156 | "node": ">= 0.6" 157 | } 158 | }, 159 | "node_modules/brace-expansion": { 160 | "version": "1.1.11", 161 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 162 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 163 | "dev": true, 164 | "license": "MIT", 165 | "dependencies": { 166 | "balanced-match": "^1.0.0", 167 | "concat-map": "0.0.1" 168 | } 169 | }, 170 | "node_modules/braces": { 171 | "version": "3.0.3", 172 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 173 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 174 | "dev": true, 175 | "license": "MIT", 176 | "dependencies": { 177 | "fill-range": "^7.1.1" 178 | }, 179 | "engines": { 180 | "node": ">=8" 181 | } 182 | }, 183 | "node_modules/bytes": { 184 | "version": "3.1.2", 185 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 186 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 187 | "engines": { 188 | "node": ">= 0.8" 189 | } 190 | }, 191 | "node_modules/call-bind-apply-helpers": { 192 | "version": "1.0.1", 193 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 194 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 195 | "dependencies": { 196 | "es-errors": "^1.3.0", 197 | "function-bind": "^1.1.2" 198 | }, 199 | "engines": { 200 | "node": ">= 0.4" 201 | } 202 | }, 203 | "node_modules/call-bound": { 204 | "version": "1.0.3", 205 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", 206 | "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", 207 | "dependencies": { 208 | "call-bind-apply-helpers": "^1.0.1", 209 | "get-intrinsic": "^1.2.6" 210 | }, 211 | "engines": { 212 | "node": ">= 0.4" 213 | }, 214 | "funding": { 215 | "url": "https://github.com/sponsors/ljharb" 216 | } 217 | }, 218 | "node_modules/chokidar": { 219 | "version": "3.6.0", 220 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 221 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 222 | "dev": true, 223 | "license": "MIT", 224 | "dependencies": { 225 | "anymatch": "~3.1.2", 226 | "braces": "~3.0.2", 227 | "glob-parent": "~5.1.2", 228 | "is-binary-path": "~2.1.0", 229 | "is-glob": "~4.0.1", 230 | "normalize-path": "~3.0.0", 231 | "readdirp": "~3.6.0" 232 | }, 233 | "engines": { 234 | "node": ">= 8.10.0" 235 | }, 236 | "funding": { 237 | "url": "https://paulmillr.com/funding/" 238 | }, 239 | "optionalDependencies": { 240 | "fsevents": "~2.3.2" 241 | } 242 | }, 243 | "node_modules/combined-stream": { 244 | "version": "1.0.8", 245 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 246 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 247 | "license": "MIT", 248 | "dependencies": { 249 | "delayed-stream": "~1.0.0" 250 | }, 251 | "engines": { 252 | "node": ">= 0.8" 253 | } 254 | }, 255 | "node_modules/concat-map": { 256 | "version": "0.0.1", 257 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 258 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 259 | "dev": true, 260 | "license": "MIT" 261 | }, 262 | "node_modules/content-disposition": { 263 | "version": "1.0.0", 264 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 265 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 266 | "dependencies": { 267 | "safe-buffer": "5.2.1" 268 | }, 269 | "engines": { 270 | "node": ">= 0.6" 271 | } 272 | }, 273 | "node_modules/content-type": { 274 | "version": "1.0.5", 275 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 276 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 277 | "engines": { 278 | "node": ">= 0.6" 279 | } 280 | }, 281 | "node_modules/cookie": { 282 | "version": "0.7.1", 283 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 284 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 285 | "engines": { 286 | "node": ">= 0.6" 287 | } 288 | }, 289 | "node_modules/cookie-signature": { 290 | "version": "1.2.2", 291 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 292 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 293 | "engines": { 294 | "node": ">=6.6.0" 295 | } 296 | }, 297 | "node_modules/cssstyle": { 298 | "version": "4.1.0", 299 | "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", 300 | "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", 301 | "license": "MIT", 302 | "dependencies": { 303 | "rrweb-cssom": "^0.7.1" 304 | }, 305 | "engines": { 306 | "node": ">=18" 307 | } 308 | }, 309 | "node_modules/data-urls": { 310 | "version": "5.0.0", 311 | "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", 312 | "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", 313 | "license": "MIT", 314 | "dependencies": { 315 | "whatwg-mimetype": "^4.0.0", 316 | "whatwg-url": "^14.0.0" 317 | }, 318 | "engines": { 319 | "node": ">=18" 320 | } 321 | }, 322 | "node_modules/debug": { 323 | "version": "4.3.6", 324 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 325 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 326 | "dependencies": { 327 | "ms": "2.1.2" 328 | }, 329 | "engines": { 330 | "node": ">=6.0" 331 | }, 332 | "peerDependenciesMeta": { 333 | "supports-color": { 334 | "optional": true 335 | } 336 | } 337 | }, 338 | "node_modules/decimal.js": { 339 | "version": "10.4.3", 340 | "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", 341 | "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", 342 | "license": "MIT" 343 | }, 344 | "node_modules/delayed-stream": { 345 | "version": "1.0.0", 346 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 347 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 348 | "license": "MIT", 349 | "engines": { 350 | "node": ">=0.4.0" 351 | } 352 | }, 353 | "node_modules/depd": { 354 | "version": "2.0.0", 355 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 356 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 357 | "engines": { 358 | "node": ">= 0.8" 359 | } 360 | }, 361 | "node_modules/destroy": { 362 | "version": "1.2.0", 363 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 364 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 365 | "engines": { 366 | "node": ">= 0.8", 367 | "npm": "1.2.8000 || >= 1.4.16" 368 | } 369 | }, 370 | "node_modules/dotenv": { 371 | "version": "16.4.7", 372 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 373 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 374 | "dev": true, 375 | "license": "BSD-2-Clause", 376 | "engines": { 377 | "node": ">=12" 378 | }, 379 | "funding": { 380 | "url": "https://dotenvx.com" 381 | } 382 | }, 383 | "node_modules/dunder-proto": { 384 | "version": "1.0.1", 385 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 386 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 387 | "dependencies": { 388 | "call-bind-apply-helpers": "^1.0.1", 389 | "es-errors": "^1.3.0", 390 | "gopd": "^1.2.0" 391 | }, 392 | "engines": { 393 | "node": ">= 0.4" 394 | } 395 | }, 396 | "node_modules/ee-first": { 397 | "version": "1.1.1", 398 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 399 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 400 | }, 401 | "node_modules/encodeurl": { 402 | "version": "2.0.0", 403 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 404 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 405 | "engines": { 406 | "node": ">= 0.8" 407 | } 408 | }, 409 | "node_modules/entities": { 410 | "version": "4.5.0", 411 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 412 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 413 | "license": "BSD-2-Clause", 414 | "engines": { 415 | "node": ">=0.12" 416 | }, 417 | "funding": { 418 | "url": "https://github.com/fb55/entities?sponsor=1" 419 | } 420 | }, 421 | "node_modules/es-define-property": { 422 | "version": "1.0.1", 423 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 424 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 425 | "engines": { 426 | "node": ">= 0.4" 427 | } 428 | }, 429 | "node_modules/es-errors": { 430 | "version": "1.3.0", 431 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 432 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 433 | "engines": { 434 | "node": ">= 0.4" 435 | } 436 | }, 437 | "node_modules/es-object-atoms": { 438 | "version": "1.0.0", 439 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", 440 | "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", 441 | "dependencies": { 442 | "es-errors": "^1.3.0" 443 | }, 444 | "engines": { 445 | "node": ">= 0.4" 446 | } 447 | }, 448 | "node_modules/escape-html": { 449 | "version": "1.0.3", 450 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 451 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 452 | }, 453 | "node_modules/etag": { 454 | "version": "1.8.1", 455 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 456 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 457 | "engines": { 458 | "node": ">= 0.6" 459 | } 460 | }, 461 | "node_modules/express": { 462 | "version": "5.0.1", 463 | "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", 464 | "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", 465 | "dependencies": { 466 | "accepts": "^2.0.0", 467 | "body-parser": "^2.0.1", 468 | "content-disposition": "^1.0.0", 469 | "content-type": "~1.0.4", 470 | "cookie": "0.7.1", 471 | "cookie-signature": "^1.2.1", 472 | "debug": "4.3.6", 473 | "depd": "2.0.0", 474 | "encodeurl": "~2.0.0", 475 | "escape-html": "~1.0.3", 476 | "etag": "~1.8.1", 477 | "finalhandler": "^2.0.0", 478 | "fresh": "2.0.0", 479 | "http-errors": "2.0.0", 480 | "merge-descriptors": "^2.0.0", 481 | "methods": "~1.1.2", 482 | "mime-types": "^3.0.0", 483 | "on-finished": "2.4.1", 484 | "once": "1.4.0", 485 | "parseurl": "~1.3.3", 486 | "proxy-addr": "~2.0.7", 487 | "qs": "6.13.0", 488 | "range-parser": "~1.2.1", 489 | "router": "^2.0.0", 490 | "safe-buffer": "5.2.1", 491 | "send": "^1.1.0", 492 | "serve-static": "^2.1.0", 493 | "setprototypeof": "1.2.0", 494 | "statuses": "2.0.1", 495 | "type-is": "^2.0.0", 496 | "utils-merge": "1.0.1", 497 | "vary": "~1.1.2" 498 | }, 499 | "engines": { 500 | "node": ">= 18" 501 | } 502 | }, 503 | "node_modules/fill-range": { 504 | "version": "7.1.1", 505 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 506 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 507 | "dev": true, 508 | "license": "MIT", 509 | "dependencies": { 510 | "to-regex-range": "^5.0.1" 511 | }, 512 | "engines": { 513 | "node": ">=8" 514 | } 515 | }, 516 | "node_modules/finalhandler": { 517 | "version": "2.0.0", 518 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.0.0.tgz", 519 | "integrity": "sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==", 520 | "dependencies": { 521 | "debug": "2.6.9", 522 | "encodeurl": "~1.0.2", 523 | "escape-html": "~1.0.3", 524 | "on-finished": "2.4.1", 525 | "parseurl": "~1.3.3", 526 | "statuses": "2.0.1", 527 | "unpipe": "~1.0.0" 528 | }, 529 | "engines": { 530 | "node": ">= 0.8" 531 | } 532 | }, 533 | "node_modules/finalhandler/node_modules/debug": { 534 | "version": "2.6.9", 535 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 536 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 537 | "dependencies": { 538 | "ms": "2.0.0" 539 | } 540 | }, 541 | "node_modules/finalhandler/node_modules/encodeurl": { 542 | "version": "1.0.2", 543 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 544 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 545 | "engines": { 546 | "node": ">= 0.8" 547 | } 548 | }, 549 | "node_modules/finalhandler/node_modules/ms": { 550 | "version": "2.0.0", 551 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 552 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 553 | }, 554 | "node_modules/form-data": { 555 | "version": "4.0.1", 556 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 557 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 558 | "license": "MIT", 559 | "dependencies": { 560 | "asynckit": "^0.4.0", 561 | "combined-stream": "^1.0.8", 562 | "mime-types": "^2.1.12" 563 | }, 564 | "engines": { 565 | "node": ">= 6" 566 | } 567 | }, 568 | "node_modules/form-data/node_modules/mime-db": { 569 | "version": "1.52.0", 570 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 571 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 572 | "license": "MIT", 573 | "engines": { 574 | "node": ">= 0.6" 575 | } 576 | }, 577 | "node_modules/form-data/node_modules/mime-types": { 578 | "version": "2.1.35", 579 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 580 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 581 | "license": "MIT", 582 | "dependencies": { 583 | "mime-db": "1.52.0" 584 | }, 585 | "engines": { 586 | "node": ">= 0.6" 587 | } 588 | }, 589 | "node_modules/forwarded": { 590 | "version": "0.2.0", 591 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 592 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 593 | "engines": { 594 | "node": ">= 0.6" 595 | } 596 | }, 597 | "node_modules/fresh": { 598 | "version": "2.0.0", 599 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 600 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 601 | "engines": { 602 | "node": ">= 0.8" 603 | } 604 | }, 605 | "node_modules/fsevents": { 606 | "version": "2.3.3", 607 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 608 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 609 | "dev": true, 610 | "hasInstallScript": true, 611 | "license": "MIT", 612 | "optional": true, 613 | "os": [ 614 | "darwin" 615 | ], 616 | "engines": { 617 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 618 | } 619 | }, 620 | "node_modules/function-bind": { 621 | "version": "1.1.2", 622 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 623 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 624 | "funding": { 625 | "url": "https://github.com/sponsors/ljharb" 626 | } 627 | }, 628 | "node_modules/get-intrinsic": { 629 | "version": "1.2.7", 630 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", 631 | "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", 632 | "dependencies": { 633 | "call-bind-apply-helpers": "^1.0.1", 634 | "es-define-property": "^1.0.1", 635 | "es-errors": "^1.3.0", 636 | "es-object-atoms": "^1.0.0", 637 | "function-bind": "^1.1.2", 638 | "get-proto": "^1.0.0", 639 | "gopd": "^1.2.0", 640 | "has-symbols": "^1.1.0", 641 | "hasown": "^2.0.2", 642 | "math-intrinsics": "^1.1.0" 643 | }, 644 | "engines": { 645 | "node": ">= 0.4" 646 | }, 647 | "funding": { 648 | "url": "https://github.com/sponsors/ljharb" 649 | } 650 | }, 651 | "node_modules/get-proto": { 652 | "version": "1.0.1", 653 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 654 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 655 | "dependencies": { 656 | "dunder-proto": "^1.0.1", 657 | "es-object-atoms": "^1.0.0" 658 | }, 659 | "engines": { 660 | "node": ">= 0.4" 661 | } 662 | }, 663 | "node_modules/glob-parent": { 664 | "version": "5.1.2", 665 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 666 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 667 | "dev": true, 668 | "license": "ISC", 669 | "dependencies": { 670 | "is-glob": "^4.0.1" 671 | }, 672 | "engines": { 673 | "node": ">= 6" 674 | } 675 | }, 676 | "node_modules/gopd": { 677 | "version": "1.2.0", 678 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 679 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 680 | "engines": { 681 | "node": ">= 0.4" 682 | }, 683 | "funding": { 684 | "url": "https://github.com/sponsors/ljharb" 685 | } 686 | }, 687 | "node_modules/has-flag": { 688 | "version": "3.0.0", 689 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 690 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 691 | "dev": true, 692 | "license": "MIT", 693 | "engines": { 694 | "node": ">=4" 695 | } 696 | }, 697 | "node_modules/has-symbols": { 698 | "version": "1.1.0", 699 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 700 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 701 | "engines": { 702 | "node": ">= 0.4" 703 | }, 704 | "funding": { 705 | "url": "https://github.com/sponsors/ljharb" 706 | } 707 | }, 708 | "node_modules/hasown": { 709 | "version": "2.0.2", 710 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 711 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 712 | "dependencies": { 713 | "function-bind": "^1.1.2" 714 | }, 715 | "engines": { 716 | "node": ">= 0.4" 717 | } 718 | }, 719 | "node_modules/html-encoding-sniffer": { 720 | "version": "4.0.0", 721 | "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", 722 | "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", 723 | "license": "MIT", 724 | "dependencies": { 725 | "whatwg-encoding": "^3.1.1" 726 | }, 727 | "engines": { 728 | "node": ">=18" 729 | } 730 | }, 731 | "node_modules/http-errors": { 732 | "version": "2.0.0", 733 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 734 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 735 | "dependencies": { 736 | "depd": "2.0.0", 737 | "inherits": "2.0.4", 738 | "setprototypeof": "1.2.0", 739 | "statuses": "2.0.1", 740 | "toidentifier": "1.0.1" 741 | }, 742 | "engines": { 743 | "node": ">= 0.8" 744 | } 745 | }, 746 | "node_modules/http-proxy-agent": { 747 | "version": "7.0.2", 748 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", 749 | "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", 750 | "license": "MIT", 751 | "dependencies": { 752 | "agent-base": "^7.1.0", 753 | "debug": "^4.3.4" 754 | }, 755 | "engines": { 756 | "node": ">= 14" 757 | } 758 | }, 759 | "node_modules/https-proxy-agent": { 760 | "version": "7.0.6", 761 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 762 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 763 | "license": "MIT", 764 | "dependencies": { 765 | "agent-base": "^7.1.2", 766 | "debug": "4" 767 | }, 768 | "engines": { 769 | "node": ">= 14" 770 | } 771 | }, 772 | "node_modules/iconv-lite": { 773 | "version": "0.5.2", 774 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", 775 | "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", 776 | "dependencies": { 777 | "safer-buffer": ">= 2.1.2 < 3" 778 | }, 779 | "engines": { 780 | "node": ">=0.10.0" 781 | } 782 | }, 783 | "node_modules/ignore-by-default": { 784 | "version": "1.0.1", 785 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 786 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 787 | "dev": true, 788 | "license": "ISC" 789 | }, 790 | "node_modules/inherits": { 791 | "version": "2.0.4", 792 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 793 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 794 | }, 795 | "node_modules/ipaddr.js": { 796 | "version": "1.9.1", 797 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 798 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 799 | "engines": { 800 | "node": ">= 0.10" 801 | } 802 | }, 803 | "node_modules/is-binary-path": { 804 | "version": "2.1.0", 805 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 806 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 807 | "dev": true, 808 | "license": "MIT", 809 | "dependencies": { 810 | "binary-extensions": "^2.0.0" 811 | }, 812 | "engines": { 813 | "node": ">=8" 814 | } 815 | }, 816 | "node_modules/is-extglob": { 817 | "version": "2.1.1", 818 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 819 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 820 | "dev": true, 821 | "license": "MIT", 822 | "engines": { 823 | "node": ">=0.10.0" 824 | } 825 | }, 826 | "node_modules/is-glob": { 827 | "version": "4.0.3", 828 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 829 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 830 | "dev": true, 831 | "license": "MIT", 832 | "dependencies": { 833 | "is-extglob": "^2.1.1" 834 | }, 835 | "engines": { 836 | "node": ">=0.10.0" 837 | } 838 | }, 839 | "node_modules/is-number": { 840 | "version": "7.0.0", 841 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 842 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 843 | "dev": true, 844 | "license": "MIT", 845 | "engines": { 846 | "node": ">=0.12.0" 847 | } 848 | }, 849 | "node_modules/is-potential-custom-element-name": { 850 | "version": "1.0.1", 851 | "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", 852 | "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", 853 | "license": "MIT" 854 | }, 855 | "node_modules/is-promise": { 856 | "version": "4.0.0", 857 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 858 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" 859 | }, 860 | "node_modules/jsdom": { 861 | "version": "25.0.1", 862 | "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", 863 | "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", 864 | "license": "MIT", 865 | "dependencies": { 866 | "cssstyle": "^4.1.0", 867 | "data-urls": "^5.0.0", 868 | "decimal.js": "^10.4.3", 869 | "form-data": "^4.0.0", 870 | "html-encoding-sniffer": "^4.0.0", 871 | "http-proxy-agent": "^7.0.2", 872 | "https-proxy-agent": "^7.0.5", 873 | "is-potential-custom-element-name": "^1.0.1", 874 | "nwsapi": "^2.2.12", 875 | "parse5": "^7.1.2", 876 | "rrweb-cssom": "^0.7.1", 877 | "saxes": "^6.0.0", 878 | "symbol-tree": "^3.2.4", 879 | "tough-cookie": "^5.0.0", 880 | "w3c-xmlserializer": "^5.0.0", 881 | "webidl-conversions": "^7.0.0", 882 | "whatwg-encoding": "^3.1.1", 883 | "whatwg-mimetype": "^4.0.0", 884 | "whatwg-url": "^14.0.0", 885 | "ws": "^8.18.0", 886 | "xml-name-validator": "^5.0.0" 887 | }, 888 | "engines": { 889 | "node": ">=18" 890 | }, 891 | "peerDependencies": { 892 | "canvas": "^2.11.2" 893 | }, 894 | "peerDependenciesMeta": { 895 | "canvas": { 896 | "optional": true 897 | } 898 | } 899 | }, 900 | "node_modules/math-intrinsics": { 901 | "version": "1.1.0", 902 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 903 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 904 | "engines": { 905 | "node": ">= 0.4" 906 | } 907 | }, 908 | "node_modules/media-typer": { 909 | "version": "1.1.0", 910 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 911 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 912 | "engines": { 913 | "node": ">= 0.8" 914 | } 915 | }, 916 | "node_modules/merge-descriptors": { 917 | "version": "2.0.0", 918 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 919 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 920 | "engines": { 921 | "node": ">=18" 922 | }, 923 | "funding": { 924 | "url": "https://github.com/sponsors/sindresorhus" 925 | } 926 | }, 927 | "node_modules/methods": { 928 | "version": "1.1.2", 929 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 930 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 931 | "engines": { 932 | "node": ">= 0.6" 933 | } 934 | }, 935 | "node_modules/mime-db": { 936 | "version": "1.53.0", 937 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", 938 | "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", 939 | "engines": { 940 | "node": ">= 0.6" 941 | } 942 | }, 943 | "node_modules/mime-types": { 944 | "version": "3.0.0", 945 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", 946 | "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", 947 | "dependencies": { 948 | "mime-db": "^1.53.0" 949 | }, 950 | "engines": { 951 | "node": ">= 0.6" 952 | } 953 | }, 954 | "node_modules/minimatch": { 955 | "version": "3.1.2", 956 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 957 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 958 | "dev": true, 959 | "license": "ISC", 960 | "dependencies": { 961 | "brace-expansion": "^1.1.7" 962 | }, 963 | "engines": { 964 | "node": "*" 965 | } 966 | }, 967 | "node_modules/ms": { 968 | "version": "2.1.2", 969 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 970 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 971 | }, 972 | "node_modules/negotiator": { 973 | "version": "1.0.0", 974 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 975 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 976 | "engines": { 977 | "node": ">= 0.6" 978 | } 979 | }, 980 | "node_modules/nodemon": { 981 | "version": "3.1.9", 982 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", 983 | "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", 984 | "dev": true, 985 | "license": "MIT", 986 | "dependencies": { 987 | "chokidar": "^3.5.2", 988 | "debug": "^4", 989 | "ignore-by-default": "^1.0.1", 990 | "minimatch": "^3.1.2", 991 | "pstree.remy": "^1.1.8", 992 | "semver": "^7.5.3", 993 | "simple-update-notifier": "^2.0.0", 994 | "supports-color": "^5.5.0", 995 | "touch": "^3.1.0", 996 | "undefsafe": "^2.0.5" 997 | }, 998 | "bin": { 999 | "nodemon": "bin/nodemon.js" 1000 | }, 1001 | "engines": { 1002 | "node": ">=10" 1003 | }, 1004 | "funding": { 1005 | "type": "opencollective", 1006 | "url": "https://opencollective.com/nodemon" 1007 | } 1008 | }, 1009 | "node_modules/normalize-path": { 1010 | "version": "3.0.0", 1011 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1012 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1013 | "dev": true, 1014 | "license": "MIT", 1015 | "engines": { 1016 | "node": ">=0.10.0" 1017 | } 1018 | }, 1019 | "node_modules/nwsapi": { 1020 | "version": "2.2.16", 1021 | "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", 1022 | "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", 1023 | "license": "MIT" 1024 | }, 1025 | "node_modules/object-inspect": { 1026 | "version": "1.13.3", 1027 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 1028 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 1029 | "engines": { 1030 | "node": ">= 0.4" 1031 | }, 1032 | "funding": { 1033 | "url": "https://github.com/sponsors/ljharb" 1034 | } 1035 | }, 1036 | "node_modules/on-finished": { 1037 | "version": "2.4.1", 1038 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1039 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1040 | "dependencies": { 1041 | "ee-first": "1.1.1" 1042 | }, 1043 | "engines": { 1044 | "node": ">= 0.8" 1045 | } 1046 | }, 1047 | "node_modules/once": { 1048 | "version": "1.4.0", 1049 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1050 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1051 | "dependencies": { 1052 | "wrappy": "1" 1053 | } 1054 | }, 1055 | "node_modules/parse5": { 1056 | "version": "7.2.1", 1057 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", 1058 | "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", 1059 | "license": "MIT", 1060 | "dependencies": { 1061 | "entities": "^4.5.0" 1062 | }, 1063 | "funding": { 1064 | "url": "https://github.com/inikulin/parse5?sponsor=1" 1065 | } 1066 | }, 1067 | "node_modules/parseurl": { 1068 | "version": "1.3.3", 1069 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1070 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1071 | "engines": { 1072 | "node": ">= 0.8" 1073 | } 1074 | }, 1075 | "node_modules/path-to-regexp": { 1076 | "version": "8.2.0", 1077 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 1078 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 1079 | "engines": { 1080 | "node": ">=16" 1081 | } 1082 | }, 1083 | "node_modules/picomatch": { 1084 | "version": "2.3.1", 1085 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1086 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1087 | "dev": true, 1088 | "license": "MIT", 1089 | "engines": { 1090 | "node": ">=8.6" 1091 | }, 1092 | "funding": { 1093 | "url": "https://github.com/sponsors/jonschlinkert" 1094 | } 1095 | }, 1096 | "node_modules/proxy-addr": { 1097 | "version": "2.0.7", 1098 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1099 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1100 | "dependencies": { 1101 | "forwarded": "0.2.0", 1102 | "ipaddr.js": "1.9.1" 1103 | }, 1104 | "engines": { 1105 | "node": ">= 0.10" 1106 | } 1107 | }, 1108 | "node_modules/pstree.remy": { 1109 | "version": "1.1.8", 1110 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 1111 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 1112 | "dev": true, 1113 | "license": "MIT" 1114 | }, 1115 | "node_modules/punycode": { 1116 | "version": "2.3.1", 1117 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1118 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1119 | "license": "MIT", 1120 | "engines": { 1121 | "node": ">=6" 1122 | } 1123 | }, 1124 | "node_modules/qs": { 1125 | "version": "6.13.0", 1126 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1127 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1128 | "dependencies": { 1129 | "side-channel": "^1.0.6" 1130 | }, 1131 | "engines": { 1132 | "node": ">=0.6" 1133 | }, 1134 | "funding": { 1135 | "url": "https://github.com/sponsors/ljharb" 1136 | } 1137 | }, 1138 | "node_modules/range-parser": { 1139 | "version": "1.2.1", 1140 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1141 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1142 | "engines": { 1143 | "node": ">= 0.6" 1144 | } 1145 | }, 1146 | "node_modules/raw-body": { 1147 | "version": "3.0.0", 1148 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 1149 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 1150 | "dependencies": { 1151 | "bytes": "3.1.2", 1152 | "http-errors": "2.0.0", 1153 | "iconv-lite": "0.6.3", 1154 | "unpipe": "1.0.0" 1155 | }, 1156 | "engines": { 1157 | "node": ">= 0.8" 1158 | } 1159 | }, 1160 | "node_modules/raw-body/node_modules/iconv-lite": { 1161 | "version": "0.6.3", 1162 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1163 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1164 | "dependencies": { 1165 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1166 | }, 1167 | "engines": { 1168 | "node": ">=0.10.0" 1169 | } 1170 | }, 1171 | "node_modules/readdirp": { 1172 | "version": "3.6.0", 1173 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1174 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1175 | "dev": true, 1176 | "license": "MIT", 1177 | "dependencies": { 1178 | "picomatch": "^2.2.1" 1179 | }, 1180 | "engines": { 1181 | "node": ">=8.10.0" 1182 | } 1183 | }, 1184 | "node_modules/router": { 1185 | "version": "2.0.0", 1186 | "resolved": "https://registry.npmjs.org/router/-/router-2.0.0.tgz", 1187 | "integrity": "sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==", 1188 | "dependencies": { 1189 | "array-flatten": "3.0.0", 1190 | "is-promise": "4.0.0", 1191 | "methods": "~1.1.2", 1192 | "parseurl": "~1.3.3", 1193 | "path-to-regexp": "^8.0.0", 1194 | "setprototypeof": "1.2.0", 1195 | "utils-merge": "1.0.1" 1196 | }, 1197 | "engines": { 1198 | "node": ">= 0.10" 1199 | } 1200 | }, 1201 | "node_modules/rrweb-cssom": { 1202 | "version": "0.7.1", 1203 | "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", 1204 | "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", 1205 | "license": "MIT" 1206 | }, 1207 | "node_modules/safe-buffer": { 1208 | "version": "5.2.1", 1209 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1210 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1211 | "funding": [ 1212 | { 1213 | "type": "github", 1214 | "url": "https://github.com/sponsors/feross" 1215 | }, 1216 | { 1217 | "type": "patreon", 1218 | "url": "https://www.patreon.com/feross" 1219 | }, 1220 | { 1221 | "type": "consulting", 1222 | "url": "https://feross.org/support" 1223 | } 1224 | ] 1225 | }, 1226 | "node_modules/safer-buffer": { 1227 | "version": "2.1.2", 1228 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1229 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1230 | }, 1231 | "node_modules/saxes": { 1232 | "version": "6.0.0", 1233 | "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", 1234 | "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", 1235 | "license": "ISC", 1236 | "dependencies": { 1237 | "xmlchars": "^2.2.0" 1238 | }, 1239 | "engines": { 1240 | "node": ">=v12.22.7" 1241 | } 1242 | }, 1243 | "node_modules/semver": { 1244 | "version": "7.6.3", 1245 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 1246 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 1247 | "dev": true, 1248 | "license": "ISC", 1249 | "bin": { 1250 | "semver": "bin/semver.js" 1251 | }, 1252 | "engines": { 1253 | "node": ">=10" 1254 | } 1255 | }, 1256 | "node_modules/send": { 1257 | "version": "1.1.0", 1258 | "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", 1259 | "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", 1260 | "dependencies": { 1261 | "debug": "^4.3.5", 1262 | "destroy": "^1.2.0", 1263 | "encodeurl": "^2.0.0", 1264 | "escape-html": "^1.0.3", 1265 | "etag": "^1.8.1", 1266 | "fresh": "^0.5.2", 1267 | "http-errors": "^2.0.0", 1268 | "mime-types": "^2.1.35", 1269 | "ms": "^2.1.3", 1270 | "on-finished": "^2.4.1", 1271 | "range-parser": "^1.2.1", 1272 | "statuses": "^2.0.1" 1273 | }, 1274 | "engines": { 1275 | "node": ">= 18" 1276 | } 1277 | }, 1278 | "node_modules/send/node_modules/fresh": { 1279 | "version": "0.5.2", 1280 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1281 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1282 | "engines": { 1283 | "node": ">= 0.6" 1284 | } 1285 | }, 1286 | "node_modules/send/node_modules/mime-db": { 1287 | "version": "1.52.0", 1288 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1289 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1290 | "engines": { 1291 | "node": ">= 0.6" 1292 | } 1293 | }, 1294 | "node_modules/send/node_modules/mime-types": { 1295 | "version": "2.1.35", 1296 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1297 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1298 | "dependencies": { 1299 | "mime-db": "1.52.0" 1300 | }, 1301 | "engines": { 1302 | "node": ">= 0.6" 1303 | } 1304 | }, 1305 | "node_modules/send/node_modules/ms": { 1306 | "version": "2.1.3", 1307 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1308 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1309 | }, 1310 | "node_modules/serve-static": { 1311 | "version": "2.1.0", 1312 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", 1313 | "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", 1314 | "dependencies": { 1315 | "encodeurl": "^2.0.0", 1316 | "escape-html": "^1.0.3", 1317 | "parseurl": "^1.3.3", 1318 | "send": "^1.0.0" 1319 | }, 1320 | "engines": { 1321 | "node": ">= 18" 1322 | } 1323 | }, 1324 | "node_modules/setprototypeof": { 1325 | "version": "1.2.0", 1326 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1327 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1328 | }, 1329 | "node_modules/side-channel": { 1330 | "version": "1.1.0", 1331 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1332 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1333 | "dependencies": { 1334 | "es-errors": "^1.3.0", 1335 | "object-inspect": "^1.13.3", 1336 | "side-channel-list": "^1.0.0", 1337 | "side-channel-map": "^1.0.1", 1338 | "side-channel-weakmap": "^1.0.2" 1339 | }, 1340 | "engines": { 1341 | "node": ">= 0.4" 1342 | }, 1343 | "funding": { 1344 | "url": "https://github.com/sponsors/ljharb" 1345 | } 1346 | }, 1347 | "node_modules/side-channel-list": { 1348 | "version": "1.0.0", 1349 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1350 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1351 | "dependencies": { 1352 | "es-errors": "^1.3.0", 1353 | "object-inspect": "^1.13.3" 1354 | }, 1355 | "engines": { 1356 | "node": ">= 0.4" 1357 | }, 1358 | "funding": { 1359 | "url": "https://github.com/sponsors/ljharb" 1360 | } 1361 | }, 1362 | "node_modules/side-channel-map": { 1363 | "version": "1.0.1", 1364 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1365 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1366 | "dependencies": { 1367 | "call-bound": "^1.0.2", 1368 | "es-errors": "^1.3.0", 1369 | "get-intrinsic": "^1.2.5", 1370 | "object-inspect": "^1.13.3" 1371 | }, 1372 | "engines": { 1373 | "node": ">= 0.4" 1374 | }, 1375 | "funding": { 1376 | "url": "https://github.com/sponsors/ljharb" 1377 | } 1378 | }, 1379 | "node_modules/side-channel-weakmap": { 1380 | "version": "1.0.2", 1381 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1382 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1383 | "dependencies": { 1384 | "call-bound": "^1.0.2", 1385 | "es-errors": "^1.3.0", 1386 | "get-intrinsic": "^1.2.5", 1387 | "object-inspect": "^1.13.3", 1388 | "side-channel-map": "^1.0.1" 1389 | }, 1390 | "engines": { 1391 | "node": ">= 0.4" 1392 | }, 1393 | "funding": { 1394 | "url": "https://github.com/sponsors/ljharb" 1395 | } 1396 | }, 1397 | "node_modules/simple-update-notifier": { 1398 | "version": "2.0.0", 1399 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1400 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1401 | "dev": true, 1402 | "license": "MIT", 1403 | "dependencies": { 1404 | "semver": "^7.5.3" 1405 | }, 1406 | "engines": { 1407 | "node": ">=10" 1408 | } 1409 | }, 1410 | "node_modules/statuses": { 1411 | "version": "2.0.1", 1412 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1413 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1414 | "engines": { 1415 | "node": ">= 0.8" 1416 | } 1417 | }, 1418 | "node_modules/supports-color": { 1419 | "version": "5.5.0", 1420 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1421 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1422 | "dev": true, 1423 | "license": "MIT", 1424 | "dependencies": { 1425 | "has-flag": "^3.0.0" 1426 | }, 1427 | "engines": { 1428 | "node": ">=4" 1429 | } 1430 | }, 1431 | "node_modules/symbol-tree": { 1432 | "version": "3.2.4", 1433 | "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", 1434 | "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", 1435 | "license": "MIT" 1436 | }, 1437 | "node_modules/tldts": { 1438 | "version": "6.1.71", 1439 | "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.71.tgz", 1440 | "integrity": "sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==", 1441 | "license": "MIT", 1442 | "dependencies": { 1443 | "tldts-core": "^6.1.71" 1444 | }, 1445 | "bin": { 1446 | "tldts": "bin/cli.js" 1447 | } 1448 | }, 1449 | "node_modules/tldts-core": { 1450 | "version": "6.1.71", 1451 | "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.71.tgz", 1452 | "integrity": "sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==", 1453 | "license": "MIT" 1454 | }, 1455 | "node_modules/to-regex-range": { 1456 | "version": "5.0.1", 1457 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1458 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1459 | "dev": true, 1460 | "license": "MIT", 1461 | "dependencies": { 1462 | "is-number": "^7.0.0" 1463 | }, 1464 | "engines": { 1465 | "node": ">=8.0" 1466 | } 1467 | }, 1468 | "node_modules/toidentifier": { 1469 | "version": "1.0.1", 1470 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1471 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1472 | "engines": { 1473 | "node": ">=0.6" 1474 | } 1475 | }, 1476 | "node_modules/touch": { 1477 | "version": "3.1.1", 1478 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", 1479 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", 1480 | "dev": true, 1481 | "license": "ISC", 1482 | "bin": { 1483 | "nodetouch": "bin/nodetouch.js" 1484 | } 1485 | }, 1486 | "node_modules/tough-cookie": { 1487 | "version": "5.0.0", 1488 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", 1489 | "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", 1490 | "license": "BSD-3-Clause", 1491 | "dependencies": { 1492 | "tldts": "^6.1.32" 1493 | }, 1494 | "engines": { 1495 | "node": ">=16" 1496 | } 1497 | }, 1498 | "node_modules/tr46": { 1499 | "version": "5.0.0", 1500 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", 1501 | "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", 1502 | "license": "MIT", 1503 | "dependencies": { 1504 | "punycode": "^2.3.1" 1505 | }, 1506 | "engines": { 1507 | "node": ">=18" 1508 | } 1509 | }, 1510 | "node_modules/type-is": { 1511 | "version": "2.0.0", 1512 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", 1513 | "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", 1514 | "dependencies": { 1515 | "content-type": "^1.0.5", 1516 | "media-typer": "^1.1.0", 1517 | "mime-types": "^3.0.0" 1518 | }, 1519 | "engines": { 1520 | "node": ">= 0.6" 1521 | } 1522 | }, 1523 | "node_modules/undefsafe": { 1524 | "version": "2.0.5", 1525 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1526 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1527 | "dev": true, 1528 | "license": "MIT" 1529 | }, 1530 | "node_modules/unpipe": { 1531 | "version": "1.0.0", 1532 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1533 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1534 | "engines": { 1535 | "node": ">= 0.8" 1536 | } 1537 | }, 1538 | "node_modules/utils-merge": { 1539 | "version": "1.0.1", 1540 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1541 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1542 | "engines": { 1543 | "node": ">= 0.4.0" 1544 | } 1545 | }, 1546 | "node_modules/vary": { 1547 | "version": "1.1.2", 1548 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1549 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1550 | "engines": { 1551 | "node": ">= 0.8" 1552 | } 1553 | }, 1554 | "node_modules/w3c-xmlserializer": { 1555 | "version": "5.0.0", 1556 | "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", 1557 | "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", 1558 | "license": "MIT", 1559 | "dependencies": { 1560 | "xml-name-validator": "^5.0.0" 1561 | }, 1562 | "engines": { 1563 | "node": ">=18" 1564 | } 1565 | }, 1566 | "node_modules/webidl-conversions": { 1567 | "version": "7.0.0", 1568 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1569 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 1570 | "license": "BSD-2-Clause", 1571 | "engines": { 1572 | "node": ">=12" 1573 | } 1574 | }, 1575 | "node_modules/whatwg-encoding": { 1576 | "version": "3.1.1", 1577 | "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", 1578 | "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", 1579 | "license": "MIT", 1580 | "dependencies": { 1581 | "iconv-lite": "0.6.3" 1582 | }, 1583 | "engines": { 1584 | "node": ">=18" 1585 | } 1586 | }, 1587 | "node_modules/whatwg-encoding/node_modules/iconv-lite": { 1588 | "version": "0.6.3", 1589 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1590 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1591 | "license": "MIT", 1592 | "dependencies": { 1593 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1594 | }, 1595 | "engines": { 1596 | "node": ">=0.10.0" 1597 | } 1598 | }, 1599 | "node_modules/whatwg-mimetype": { 1600 | "version": "4.0.0", 1601 | "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", 1602 | "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", 1603 | "license": "MIT", 1604 | "engines": { 1605 | "node": ">=18" 1606 | } 1607 | }, 1608 | "node_modules/whatwg-url": { 1609 | "version": "14.1.0", 1610 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", 1611 | "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", 1612 | "license": "MIT", 1613 | "dependencies": { 1614 | "tr46": "^5.0.0", 1615 | "webidl-conversions": "^7.0.0" 1616 | }, 1617 | "engines": { 1618 | "node": ">=18" 1619 | } 1620 | }, 1621 | "node_modules/wrappy": { 1622 | "version": "1.0.2", 1623 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1624 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1625 | }, 1626 | "node_modules/ws": { 1627 | "version": "8.18.0", 1628 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", 1629 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", 1630 | "license": "MIT", 1631 | "engines": { 1632 | "node": ">=10.0.0" 1633 | }, 1634 | "peerDependencies": { 1635 | "bufferutil": "^4.0.1", 1636 | "utf-8-validate": ">=5.0.2" 1637 | }, 1638 | "peerDependenciesMeta": { 1639 | "bufferutil": { 1640 | "optional": true 1641 | }, 1642 | "utf-8-validate": { 1643 | "optional": true 1644 | } 1645 | } 1646 | }, 1647 | "node_modules/xml-name-validator": { 1648 | "version": "5.0.0", 1649 | "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", 1650 | "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", 1651 | "license": "Apache-2.0", 1652 | "engines": { 1653 | "node": ">=18" 1654 | } 1655 | }, 1656 | "node_modules/xmlchars": { 1657 | "version": "2.2.0", 1658 | "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", 1659 | "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", 1660 | "license": "MIT" 1661 | }, 1662 | "node_modules/zod": { 1663 | "version": "3.24.1", 1664 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 1665 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 1666 | "license": "MIT", 1667 | "funding": { 1668 | "url": "https://github.com/sponsors/colinhacks" 1669 | } 1670 | } 1671 | } 1672 | } 1673 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aisearch", 3 | "version": "1.0.0", 4 | "main": "src/server.js", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "nodemon src/server.js", 8 | "start": "node src/server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "express": "^5.0.1", 16 | "jsdom": "^25.0.1", 17 | "zod": "^3.24.1" 18 | }, 19 | "devDependencies": { 20 | "dotenv": "^16.4.7", 21 | "nodemon": "^3.1.9" 22 | } 23 | } -------------------------------------------------------------------------------- /backend/src/ai.js: -------------------------------------------------------------------------------- 1 | import { Readable } from "node:stream"; 2 | 3 | import { env } from "./env.js"; 4 | 5 | const GROQ_API = "https://api.groq.com/openai/v1/chat/completions"; 6 | const MODEL = "llama-3.3-70b-versatile"; 7 | 8 | /** 9 | * Gets a response from the OpenRouter API. 10 | * 11 | * @param {{ role: string, content: string }[]} messages 12 | * @param {boolean} [stream=true] - Whether to stream the response or not, defaults to `true` 13 | * 14 | * @returns {Promise} - Returns a `Promise` resolving to a string when `stream` is `false`, 15 | * and a `Readable` when `stream` is `true`. 16 | */ 17 | async function getResponse(messages, stream = true) { 18 | const response = await fetch(GROQ_API, { 19 | method: "POST", 20 | headers: { 21 | Authorization: `Bearer ${env.GROQ_API_KEY}`, 22 | "Content-Type": "application/json", 23 | }, 24 | body: JSON.stringify({ 25 | model: MODEL, 26 | stream, 27 | messages, 28 | }), 29 | }); 30 | 31 | if (!response.ok) { 32 | const errMsg = (await response.text()).slice(0, 100); 33 | console.error(response.status, errMsg); 34 | throw new Error("Network response was not ok"); 35 | } 36 | 37 | if (!stream) { 38 | const data = await response.json(); 39 | return data.choices[0].message.content; 40 | } 41 | 42 | return Readable.from(response.body); 43 | } 44 | 45 | /** 46 | * Gets an AI answer for the given query. 47 | * 48 | * @param {string} query 49 | * @param {string} searchResults 50 | * @param {boolean} [stream=true] - Whether to stream the response or not, defaults to `true` 51 | * 52 | */ 53 | export async function getAiAnswer(query, searchResults, stream = true) { 54 | const messages = [ 55 | { 56 | role: "system", 57 | content: systemPromptWithSearchResults(searchResults), 58 | }, 59 | { 60 | role: "user", 61 | content: query, 62 | }, 63 | ]; 64 | 65 | return getResponse(messages, stream); 66 | } 67 | 68 | const SP_SEARCH_RESULTS = `You are an AI assistant providing answers based on search results. 69 | 70 | CRITICAL RULE: 71 | - You can ONLY cite numbers that match the exact search result numbers (1-5 if there are 5 results) 72 | - Citations MUST use format: [N](url) where N is the search result number 73 | - NEVER cite numbers higher than the total number of provided search results 74 | 75 | FORMAT: 76 | 1. MUST cite every fact with corresponding search result number 77 | 2. Example format: "The Earth orbits the Sun [1](url1). This takes 365 days [2](url2)." 78 | 3. If unsure about source number, DO NOT cite 79 | 80 | For insufficient/no results: 81 | "The search results don't contain enough information to answer this query." 82 | 83 | Remember: Only use citation numbers that match actual search result numbers.`; 84 | 85 | /** 86 | * 87 | * @param {string} searchResults 88 | * @returns 89 | */ 90 | function systemPromptWithSearchResults(searchResults) { 91 | return `${SP_SEARCH_RESULTS} 92 | 93 | Search results: 94 | ${searchResults.slice(0, 6000)}`; 95 | } 96 | 97 | const SP_SEARCH_QUERY = `Intelligently convert user's message into a Google search query 98 | that will get the most relevant results. 99 | 100 | - Match human Google search patterns 101 | - Return only the search query 102 | - No explanations or context 103 | - Don't wrap the query in quotes 104 | 105 | Example: 106 | User: What is the capital of France? 107 | Query: capital of France 108 | 109 | User: how did vercel start 110 | Query: vercel history 111 | `; 112 | 113 | /** 114 | * 115 | * @param {string} query 116 | * @returns 117 | */ 118 | function systemPromptWithSearchQuery(query) { 119 | return `${SP_SEARCH_QUERY} 120 | 121 | User's message: 122 | ${query}`; 123 | } 124 | 125 | export async function getAiSearchQuery(query) { 126 | const messages = [ 127 | { 128 | role: "system", 129 | content: systemPromptWithSearchQuery(query), 130 | }, 131 | ]; 132 | 133 | const response = await getResponse(messages, false); 134 | if (typeof response !== "string") { 135 | throw Error("getSearchQuery(): response is unexpectedly not a string"); 136 | } 137 | 138 | return response; 139 | } -------------------------------------------------------------------------------- /backend/src/env.js: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | 6 | const envSchema = z.object({ 7 | PORT: z.string().regex(/^\d+$/).transform(Number), 8 | GOOGLE_API_KEY: z.string(), 9 | GOOGLE_SEARCH_ENGINE_ID: z.string(), 10 | GROQ_API_KEY: z.string(), 11 | }); 12 | 13 | const parsedEnv = envSchema.safeParse(process.env); 14 | 15 | if (!parsedEnv.success) { 16 | console.error("Invalid environment variables:", parsedEnv.error.format()); 17 | process.exit(1); 18 | } 19 | 20 | export const env = parsedEnv.data; -------------------------------------------------------------------------------- /backend/src/search.js: -------------------------------------------------------------------------------- 1 | import jsdom, { JSDOM } from "jsdom"; 2 | 3 | import { env } from "./env.js"; 4 | import { getAiSearchQuery } from "./ai.js"; 5 | 6 | const virtualConsole = new jsdom.VirtualConsole(); 7 | virtualConsole.on("error", () => { 8 | // No-op to skip console errors. 9 | }); 10 | 11 | const SEARCH_API = "https://www.googleapis.com/customsearch/v1"; 12 | 13 | /** 14 | * @typedef {Object} SearchResult 15 | * @property {string} title - Title of the source. 16 | * @property {string} link - Link to the source. 17 | * @property {string} snippet - Snippet of the source. 18 | * @property {string} displayLink - Display link of the source. 19 | * @property {string | null} image - Image URL of the source. 20 | */ 21 | 22 | /** 23 | * Searches Google Custom Search API for the given query. 24 | * 25 | * @param {string} query 26 | * @return {Promise} 27 | */ 28 | export async function search(query) { 29 | query = await getAiSearchQuery(query); 30 | console.log(`log google search query: '${query}'`); 31 | 32 | const url = new URL(SEARCH_API); 33 | url.searchParams.append("key", env.GOOGLE_API_KEY); 34 | url.searchParams.append("cx", env.GOOGLE_SEARCH_ENGINE_ID); 35 | url.searchParams.append("q", query); 36 | url.searchParams.append("num", "5"); 37 | 38 | const response = await fetch(url.toString()); 39 | 40 | if (!response.ok) { 41 | const errMsg = (await res.text()).slice(0, 100); 42 | console.error(response.status, errMsg); 43 | throw new Error(`HTTP error! status: ${response.status}`); 44 | } 45 | 46 | const data = await response.json(); 47 | 48 | if (!Array.isArray(data.items)) { 49 | return []; 50 | } 51 | 52 | return data.items.map((result) => ({ 53 | title: result.title, 54 | link: result.link, 55 | snippet: result.snippet, 56 | displayLink: result.displayLink, 57 | image: 58 | result.pagemap?.metatags?.length && "og:image" in result.pagemap.metatags[0] 59 | ? result.pagemap.metatags[0]["og:image"] 60 | : null, 61 | })); 62 | } 63 | 64 | /** 65 | * @typedef {SearchResult & {text: string}} SearchResultWithContent 66 | */ 67 | 68 | /** 69 | * Fetches the text content from an array of URLs concurrently. 70 | * @param {SearchResult[]} results - Array of search results to fetch the texts from. 71 | * @returns {Promise} 72 | */ 73 | export async function fetchFromSearchResults(results) { 74 | const fetchPromises = results.map(async (source) => { 75 | let text = ""; 76 | try { 77 | const response = await fetch(source.link, { 78 | headers: { 79 | "User-Agent": 80 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 81 | }, 82 | }); 83 | if (!response.ok) { 84 | throw new Error(`HTTP error! status: ${response.status}`); 85 | } 86 | 87 | const contentType = response.headers.get("content-type"); 88 | if (!contentType) { 89 | return { ...source, text }; 90 | } else if (contentType.includes("application/json")) { 91 | text = await response.json(); 92 | } else if (contentType.includes("text/html")) { 93 | const html = await response.text(); 94 | const dom = new JSDOM(html, { virtualConsole }); 95 | const body = dom.window.document.body; 96 | 97 | // delete unnecessary tags from the body 98 | body.querySelectorAll("script,style,noscript").forEach((el) => el.remove()); 99 | 100 | text = body.textContent?.replace(/\s+/g, " ").trim() ?? ""; 101 | } else if (contentType.includes("text/plain") || contentType.includes("application/xml")) { 102 | text = await response.text(); 103 | } 104 | } catch (error) { 105 | console.error(`Error fetching text from ${source.link}:`, error); 106 | } 107 | 108 | return { ...source, text }; 109 | }); 110 | 111 | return await Promise.all(fetchPromises); 112 | } -------------------------------------------------------------------------------- /backend/src/server.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import express from "express"; 3 | 4 | import { getAiAnswer } from "./ai.js"; 5 | import { env } from "./env.js"; 6 | import { search, fetchFromSearchResults } from "./search.js"; 7 | 8 | dotenv.config(); 9 | 10 | const app = express(); 11 | 12 | // cors 13 | app.use(function (req, res, next) { 14 | res.header("Access-Control-Allow-Origin", "*"); 15 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 16 | next(); 17 | }); 18 | 19 | app.get("/search", async (req, res) => { 20 | const userQuery = req.query.q; 21 | if (typeof userQuery !== "string") { 22 | res.status(400).json({ error: "No query provided" }); 23 | return; 24 | } 25 | 26 | const results = await search(userQuery); 27 | 28 | res.setHeader("Content-Type", "text/event-stream"); 29 | res.setHeader("Cache-Control", "no-cache"); 30 | res.setHeader("Connection", "keep-alive"); 31 | 32 | // send sources first 33 | res.write(`data: ${JSON.stringify(results)}\n\n`); 34 | 35 | // fetch text for each source 36 | const searchResultsWithText = (await fetchFromSearchResults(results)).filter(({ text }) => text); 37 | const context = searchResultsWithText 38 | .map( 39 | (result, i) => ` 40 | [${i + 1}] ${result.link} 41 | Title: ${result.title} 42 | ${result.text || result.snippet}` 43 | ) 44 | .join("\n\n"); 45 | 46 | console.log("context:", context); 47 | 48 | const answer = await getAiAnswer(userQuery, context); 49 | if (typeof answer === "string") { 50 | throw Error("/search: answer is unexpectedly a string"); 51 | } 52 | 53 | // stream response 54 | answer.pipe(res); 55 | }); 56 | 57 | app.listen(env.PORT, () => { 58 | console.log(`🏃‍♂️ Server is running on port ${env.PORT}`); 59 | }); 60 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 23 | }, 24 | } 25 | ); -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AI Search 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-search-frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "lucide-react": "^0.469.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-markdown": "^9.0.3" 17 | }, 18 | "devDependencies": { 19 | "@eslint/js": "^9.17.0", 20 | "@types/node": "^22.10.5", 21 | "@types/react": "^19.0.2", 22 | "@types/react-dom": "^19.0.2", 23 | "@vitejs/plugin-react-swc": "^3.5.0", 24 | "autoprefixer": "^10.4.20", 25 | "eslint": "^9.17.0", 26 | "eslint-plugin-react": "^7.37.2", 27 | "eslint-plugin-react-hooks": "^5.0.0", 28 | "eslint-plugin-react-refresh": "^0.4.16", 29 | "globals": "^15.14.0", 30 | "postcss": "^8.4.49", 31 | "tailwindcss": "^3.4.17", 32 | "typescript": "^5.7.3", 33 | "typescript-eslint": "^8.19.1", 34 | "vite": "^6.0.5" 35 | } 36 | } -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Brain } from "lucide-react"; 2 | import { useState } from "react"; 3 | 4 | import SearchInput from "./components/SearchInput"; 5 | import SearchResults from "./components/SearchResults"; 6 | import { Source } from "./types"; 7 | 8 | // we only have one endpoint for now so let's keep it simple 9 | const SEARCH_ENDPOINT = "https://perplexity-clone-6te8.onrender.com/search"; 10 | 11 | function App() { 12 | const [sources, setSources] = useState([]); 13 | const [content, setContent] = useState(""); 14 | const [isStreaming, setIsStreaming] = useState(false); 15 | 16 | const handleSearch = async (query: string) => { 17 | setSources([]); 18 | setContent(""); 19 | setIsStreaming(true); 20 | 21 | await streamResult( 22 | query, 23 | (newContent) => { 24 | setContent((prev) => prev + newContent); 25 | }, 26 | setSources 27 | ); 28 | 29 | setIsStreaming(false); 30 | }; 31 | 32 | return ( 33 |
34 |
35 |
36 | 37 |

38 | AI Search Assistant 39 |

40 |
41 | 42 |
43 | 44 | 45 |
46 |
47 |
48 | ); 49 | } 50 | 51 | export default App; 52 | 53 | const streamResult = async ( 54 | query: string, 55 | onAnswer: (newContent: string) => void, 56 | onSources: (sources: Source[]) => void 57 | ) => { 58 | const url = new URL(SEARCH_ENDPOINT); 59 | url.searchParams.set("q", query); 60 | 61 | // first thing would be sources 62 | let sourcesReceived = false; 63 | 64 | const buffer: string[] = []; 65 | 66 | const evtSource = new EventSource(url.toString()); 67 | evtSource.onmessage = async (event) => { 68 | try { 69 | if (event.data === "[DONE]") { 70 | const rest = buffer.join(""); 71 | if (rest) { 72 | onAnswer(rest); 73 | } 74 | 75 | evtSource.close(); 76 | return; 77 | } 78 | 79 | const data = JSON.parse(event.data); 80 | if (!sourcesReceived) { 81 | onSources(data); 82 | sourcesReceived = true; 83 | return; 84 | } 85 | 86 | const content: string | undefined = data.choices[0].delta.content; 87 | if (!content) { 88 | return; 89 | } 90 | 91 | const joined = buffer.join("") + content; 92 | if (joined.split(/\s+/).length > 4) { 93 | onAnswer(joined); 94 | buffer.length = 0; 95 | } else { 96 | buffer.push(content); 97 | } 98 | } catch (error) { 99 | console.error("error while streaming response:", error); 100 | evtSource.close(); 101 | } 102 | }; 103 | }; 104 | -------------------------------------------------------------------------------- /frontend/src/components/SearchInput.tsx: -------------------------------------------------------------------------------- 1 | import { Search } from "lucide-react"; 2 | import { useState } from "react"; 3 | 4 | interface SearchInputProps { 5 | onSearch: (query: string) => void; 6 | isLoading: boolean; 7 | } 8 | 9 | const SearchInput: React.FC = ({ onSearch, isLoading }) => { 10 | const [query, setQuery] = useState(""); 11 | 12 | const handleSubmit: React.FormEventHandler = (e) => { 13 | e.preventDefault(); 14 | if (query.trim() && !isLoading) { 15 | onSearch(query); 16 | } 17 | }; 18 | 19 | return ( 20 |
21 |
22 | setQuery(e.target.value)} 26 | placeholder="Ask anything..." 27 | className="w-full px-6 py-4 bg-neutral-900 border border-neutral-800 rounded-2xl 28 | text-white placeholder-neutral-400 focus:outline-none focus:ring-2 29 | focus:ring-purple-500/50 focus:border-purple-500/50 transition-all duration-200" 30 | disabled={isLoading} 31 | /> 32 | 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default SearchInput; -------------------------------------------------------------------------------- /frontend/src/components/SearchResults.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import Markdown from "react-markdown"; 3 | import { Globe, Sparkles } from "lucide-react"; 4 | 5 | import { Source } from "@/types"; 6 | import Tooltip from "./ui/Tooltip"; 7 | 8 | interface SearchResultsProps { 9 | sources: Source[]; 10 | content: string; 11 | isStreaming: boolean; 12 | } 13 | 14 | const SearchResults: React.FC = ({ sources, content, isStreaming }) => { 15 | const responseRef = useRef(null); 16 | 17 | useEffect(() => { 18 | if (responseRef.current && isStreaming) { 19 | responseRef.current.scrollIntoView({ behavior: "smooth" }); 20 | } 21 | }, [content, isStreaming]); 22 | 23 | if (!content && !isStreaming && !sources.length) { 24 | return null; 25 | } 26 | 27 | return ( 28 |
29 |

30 | 31 | Sources 32 |

33 | 59 | 60 |

61 | 62 | Answer 63 |

64 |
65 | { 68 | return
; 69 | }, 70 | 71 | a: ({ node, children, ...props }) => { 72 | if (!node) { 73 | throw new Error("node is undefined"); 74 | } 75 | 76 | // @ts-ignore (probably their types are wrong idk) 77 | const index = Number(node.children[0].value); 78 | if (isNaN(index) || index < 0 || index >= sources.length) { 79 | return ( 80 | 81 | {children} 82 | 83 | ); 84 | } 85 | 86 | const source = sources[index - 1]; 87 | props.href = source.link; 88 | 89 | return ( 90 | 93 |
94 | {source.image && ( 95 | { 99 | e.currentTarget.remove(); 100 | }} 101 | /> 102 | )} 103 | {source.displayLink} 104 |
105 |

{source.title}

106 |

{source.snippet}

107 |
108 | } 109 | > 110 | 111 | {index} 112 | 113 | 114 | ); 115 | }, 116 | }} 117 | > 118 | {content} 119 |
120 | {isStreaming && } 121 |
122 |
123 | ); 124 | }; 125 | 126 | export default SearchResults; -------------------------------------------------------------------------------- /frontend/src/components/ui/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | 3 | interface TooltipProps { 4 | content: React.ReactNode; 5 | children: React.ReactNode; 6 | position?: "top" | "bottom" | "left" | "right"; 7 | } 8 | 9 | export const Tooltip: React.FC = ({ content, children, position = "top" }) => { 10 | const [isVisible, setIsVisible] = useState(false); 11 | const [coords, setCoords] = useState({ x: 0, y: 0 }); 12 | const tooltipRef = useRef(null); 13 | const containerRef = useRef(null); 14 | 15 | useEffect(() => { 16 | const updatePosition = () => { 17 | if (isVisible && tooltipRef.current && containerRef.current) { 18 | const container = containerRef.current.getBoundingClientRect(); 19 | const tooltip = tooltipRef.current.getBoundingClientRect(); 20 | const gap = container.height + 8; 21 | 22 | let x = 0; 23 | let y = 0; 24 | 25 | switch (position) { 26 | case "top": 27 | x = container.width / 2 - tooltip.width / 2; 28 | y = -(tooltip.height + gap); 29 | break; 30 | case "bottom": 31 | x = container.width / 2 - tooltip.width / 2; 32 | y = container.height + gap; 33 | break; 34 | case "left": 35 | x = -(tooltip.width + gap); 36 | y = container.height / 2 - tooltip.height / 2; 37 | break; 38 | case "right": 39 | x = container.width + gap; 40 | y = container.height / 2 - tooltip.height / 2; 41 | break; 42 | } 43 | 44 | setCoords({ x, y }); 45 | } 46 | }; 47 | 48 | if (isVisible) { 49 | updatePosition(); 50 | window.addEventListener("resize", updatePosition); 51 | window.addEventListener("scroll", updatePosition); 52 | } 53 | 54 | return () => { 55 | window.removeEventListener("resize", updatePosition); 56 | window.removeEventListener("scroll", updatePosition); 57 | }; 58 | }, [isVisible, position]); 59 | 60 | return ( 61 |
setIsVisible(true)} 65 | onMouseLeave={() => setIsVisible(false)} 66 | > 67 | {children} 68 | 69 |
83 | {content} 84 |
94 |
95 |
96 | ); 97 | }; 98 | 99 | export default Tooltip; -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | color-scheme: dark; 7 | } 8 | 9 | #root { 10 | max-width: 1280px; 11 | margin: 0 auto; 12 | padding: 2rem; 13 | } 14 | 15 | .nunito-font { 16 | font-family: "Nunito", sans-serif; 17 | font-optical-sizing: auto; 18 | font-weight: 400; 19 | font-style: italic; 20 | } 21 | 22 | body { 23 | margin: 0; 24 | min-width: 320px; 25 | min-height: 100vh; 26 | font-family: "Nunito", serif; 27 | background-color: black; 28 | } 29 | 30 | /* Custom scrollbar for Webkit browsers */ 31 | ::-webkit-scrollbar { 32 | width: 8px; 33 | height: 8px; 34 | } 35 | 36 | ::-webkit-scrollbar-track { 37 | background: rgba(0, 0, 0, 0.1); 38 | } 39 | 40 | ::-webkit-scrollbar-thumb { 41 | background: rgba(255, 255, 255, 0.1); 42 | border-radius: 4px; 43 | } 44 | 45 | ::-webkit-scrollbar-thumb:hover { 46 | background: rgba(255, 255, 255, 0.2); 47 | } 48 | 49 | #search-response ol { 50 | @apply list-decimal pl-4 my-2; 51 | } 52 | #search-response ul { 53 | @apply list-disc pl-4 my-2; 54 | } 55 | #search-response p { 56 | @apply whitespace-pre-wrap mt-1; 57 | } 58 | #search-response a { 59 | @apply text-purple-400 underline; 60 | } 61 | #search-response .citation { 62 | @apply text-purple-400 bg-neutral-700/30 text-xs rounded p-1 mx-1; 63 | } 64 | #search-response h1, 65 | #search-response h2, 66 | #search-response h3, 67 | #search-response h4, 68 | #search-response h5, 69 | #search-response h6 { 70 | @apply font-bold; 71 | } 72 | #search-response .paragraph { 73 | @apply whitespace-pre-wrap mt-1 mb-4; 74 | } -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import "./index.css"; 5 | 6 | import App from "./App.jsx"; 7 | 8 | createRoot(document.getElementById("root")!).render( 9 | 10 | 11 | 12 | ); -------------------------------------------------------------------------------- /frontend/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface Source { 2 | title: string; 3 | link: string; 4 | snippet: string; 5 | displayLink: string; 6 | image: string | null; 7 | } -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./src/**/*.tsx"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Path aliases */ 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | }, 22 | 23 | /* Linting */ 24 | "strict": true, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "noFallthroughCasesInSwitch": true 28 | }, 29 | "include": ["src"] 30 | } -------------------------------------------------------------------------------- /frontend/tsconfig.app.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./src/app.tsx","./src/main.tsx","./src/components/searchinput.tsx","./src/components/searchresults.tsx","./src/components/ui/tooltip.tsx","./src/types/index.ts"],"version":"5.7.3"} -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } -------------------------------------------------------------------------------- /frontend/tsconfig.node.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"root":["./vite.config.ts"],"version":"5.7.3"} -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { defineConfig } from "vite"; 4 | import react from "@vitejs/plugin-react-swc"; 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | plugins: [react()], 14 | }); --------------------------------------------------------------------------------