From fa7e1c2bdb00bf778f96ed6c0674efec94bbd7d0 Mon Sep 17 00:00:00 2001 From: 0xunion23 <0ckhush@gmail.com> Date: Mon, 1 Jun 2026 15:03:30 +0530 Subject: [PATCH 01/56] Initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..43ebb7b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# vled-project \ No newline at end of file From f3fa19eae8a5c1628f2793ef4643f5d2be81e76d Mon Sep 17 00:00:00 2001 From: 0xkhush <0xkhush@gmail.com> Date: Mon, 1 Jun 2026 18:20:56 +0530 Subject: [PATCH 02/56] initial commit --- .gitignore | 4 + README.md | 104 + chatbot-preview.png | Bin 0 -> 60678 bytes client/.env.example | 1 + client/index.html | 12 + client/package-lock.json | 1716 +++++++++++++++++ client/package.json | 19 + client/src/main.jsx | 186 ++ client/src/styles.css | 518 +++++ package.json | 15 + redesign-preview.png | Bin 0 -> 38072 bytes server/.env.example | 9 + server/package-lock.json | 1102 +++++++++++ server/package.json | 19 + server/requirements.txt | 2 + server/src/config/env.js | 15 + server/src/data/seedFaqs.js | 44 + server/src/db/mongoose.js | 7 + server/src/index.js | 31 + server/src/models/Faq.js | 47 + .../__pycache__/embed_flag.cpython-312.pyc | Bin 0 -> 1213 bytes server/src/python/embed_flag.py | 27 + server/src/routes/chatRoutes.js | 13 + server/src/routes/faqRoutes.js | 24 + server/src/scripts/importSamagamaFaqs.js | 135 ++ server/src/scripts/reindexFaqs.js | 8 + server/src/scripts/seedFaqs.js | 19 + server/src/services/embeddingService.js | 64 + server/src/services/ollamaService.js | 44 + server/src/services/ragService.js | 152 ++ 30 files changed, 4337 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 chatbot-preview.png create mode 100644 client/.env.example create mode 100644 client/index.html create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/src/main.jsx create mode 100644 client/src/styles.css create mode 100644 package.json create mode 100644 redesign-preview.png create mode 100644 server/.env.example create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/requirements.txt create mode 100644 server/src/config/env.js create mode 100644 server/src/data/seedFaqs.js create mode 100644 server/src/db/mongoose.js create mode 100644 server/src/index.js create mode 100644 server/src/models/Faq.js create mode 100644 server/src/python/__pycache__/embed_flag.cpython-312.pyc create mode 100644 server/src/python/embed_flag.py create mode 100644 server/src/routes/chatRoutes.js create mode 100644 server/src/routes/faqRoutes.js create mode 100644 server/src/scripts/importSamagamaFaqs.js create mode 100644 server/src/scripts/reindexFaqs.js create mode 100644 server/src/scripts/seedFaqs.js create mode 100644 server/src/services/embeddingService.js create mode 100644 server/src/services/ollamaService.js create mode 100644 server/src/services/ragService.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9145c03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..05d2a62 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# FAQ VLED RAG Chatbot + +MERN-based RAG chatbot matching the provided architecture, with escalation intentionally left out for now. + +The chatbot is fully local: + +- MongoDB stores FAQ documents and their embeddings. +- FlagEmbedding builds local BGE embeddings. +- The retriever ranks FAQ vectors with cosine similarity. +- Ollama runs the local chat model that writes the final answer. +- React provides the user chat page. + +## Flow + +1. User asks a question in the React page. +2. Express embeds the query with FlagEmbedding. +3. The retriever compares the query vector against FAQ vectors in MongoDB. +4. The top contexts are passed to Ollama. +5. Ollama answers only from retrieved context. +6. If confidence is below `MIN_CONFIDENCE`, the API returns a fallback message without escalation. + +## Requirements + +- Node.js and npm +- MongoDB running locally +- Python 3.10+ +- Ollama running locally +- A pulled Ollama chat model, for example: + +```bash +ollama pull gemma3:4b +``` + +## Setup + +```bash +cp server/.env.example server/.env +cp client/.env.example client/.env +npm run install:all +python3 -m pip install -r server/requirements.txt +npm run seed +npm run dev +``` + +Client: `http://localhost:5173` + +Server: `http://localhost:5001` + +MongoDB defaults to `mongodb://127.0.0.1:27017/faq_vled_rag`. + +Ollama defaults to `http://127.0.0.1:11434` using `gemma3:4b`. + +## Environment + +`server/.env`: + +```bash +PORT=5001 +MONGODB_URI=mongodb://127.0.0.1:27017/faq_vled_rag +CLIENT_ORIGIN=http://localhost:5173 +MIN_CONFIDENCE=0.45 +TOP_K=4 +OLLAMA_BASE_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=gemma3:4b +FLAG_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 +PYTHON_BIN=python3 +``` + +## Reindexing + +Run this after editing FAQ text directly in MongoDB: + +```bash +npm run reindex --prefix server +``` + +## API + +```http +POST /api/chat +Content-Type: application/json + +{ + "message": "Where is FAQ data stored?" +} +``` + +Response: + +```json +{ + "answer": "FAQ data is stored in MongoDB...", + "answerFound": true, + "confidence": 0.71, + "sources": [ + { + "id": "...", + "question": "Where is FAQ data stored?", + "category": "Storage", + "score": 0.7123 + } + ] +} +``` diff --git a/chatbot-preview.png b/chatbot-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..c331ceb4d0757a84f89e744035dfad81e70c377f GIT binary patch literal 60678 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 literal 0 HcmV?d00001 diff --git a/client/.env.example b/client/.env.example new file mode 100644 index 0000000..8ed0e6b --- /dev/null +++ b/client/.env.example @@ -0,0 +1 @@ +VITE_API_URL=http://localhost:5000 diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..61e844f --- /dev/null +++ b/client/index.html @@ -0,0 +1,12 @@ + + + + + + FAQ RAG Chatbot + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..aa40e8b --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,1716 @@ +{ + "name": "faq-vled-rag-client", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "faq-vled-rag-client", + "version": "1.0.0", + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "lucide-react": "^0.468.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "vite": "^6.0.7" + }, + "devDependencies": {} + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.0.tgz", + "integrity": "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.0.tgz", + "integrity": "sha512-Bp3JpGP00Vu3f238ivRrjf7z3xSzVPXqCmaJYA9t2c+c8vKYvOzmXF7LkkeUalTEGd6cZcSWe+PFIP3Vy48fRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.0.tgz", + "integrity": "sha512-zaYIpr670mUmmZ1tVzUFplbQbG7h3Gugx3L5FoqhsC2m/YnLlR1a7zVLmXNPy+iY1tFPEbNG+HHBXZGyId0G5w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.0.tgz", + "integrity": "sha512-+P49fvkv2dSoeevUW+lgZ/I2JHSsJCK1Lyjj7Cu6E4UHG4tS9XIefzIjo5qhgELjAclnen1rLzK2PMKJdo+Dyg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.0.tgz", + "integrity": "sha512-l3FAAOyKJXH2ea6KNFN+MMgC/rnE94YGLXs2ehYqDcCoHt1DpvgWX75BhUJxN38XojP7Ul+4H8PRn7EdyqSDrw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.0.tgz", + "integrity": "sha512-VokPN3TSctKj65cyCNPaUh4vMFA8awxOot/0sp+4J7ZlNRKQEhXhawqPwajoi8H5ZFt61i0ugZJuTKXBjGJ17Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.0.tgz", + "integrity": "sha512-DxH0P3wxm+Yzs/p3zrk9dw1rURu8p0Nv5+MRK/L7OtnLNg5rLZraSBFZ8iUXOd9f2BlhJyEpIZUH/emjq4UJ4g==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.0.tgz", + "integrity": "sha512-T6ZvMNe84kAz6TBWHC7hGAoEtzP1LWYw/AqayGWEF6uISt3Abk/st06LqRD9THd7Xz3NxzurUpzAuEAUbZf+nw==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.0.tgz", + "integrity": "sha512-q/4hzvQkDs8b4jIBab1pnLiiM0ayTZsN2amBFPDzuyZxjEd4wDwx0UJFYM3cOZzSf5Kw8fnWSprJzIBMkcR44Q==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.0.tgz", + "integrity": "sha512-vvYWX3akdEAY6km+9wAqFDnk6pQsbJKVnj7xawcvs/+fdlYBGp+U+Qq/lLfpIxYIZvZLHMAKD9HLdacSx/r3dw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.0.tgz", + "integrity": "sha512-DePa5cqOxDP/Zp0VOXpeWaGew5iIv5DXp9NYbzkX5PFQyWVX9184WCTh3hvr/7lhXo8ZVlbFLkz8+o/q1dU6gA==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.0.tgz", + "integrity": "sha512-LV8aWMB8UChglMCEzs7RkN0GsH29RJaLLqwm9fCIjlqwxQTiWAqNcc7wjBkH31hV0PU/yVxGYvrYsgfea2qw6g==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.0.tgz", + "integrity": "sha512-QoNSnwQtaeNu5grdBbsL0tt1uyl5EnS8DA8Mr3nluMXbhdQNyhN+G4tBax7VCdxLKj8YJ0/4OO9Ho84jMnJtKA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.0.tgz", + "integrity": "sha512-/zZp5MKapIIApE8trN8qLGNSiRN9TUoaUZ1cmVu4XnVdd5LQLOXTtyi+vtfUbNnT3iyjzpPqYeKXmvJ+gJGYWw==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.0.tgz", + "integrity": "sha512-RbrzcD3aJ1k3UbtMRRBNwojdVVyXjuVAFTfn/xPa6EEl6GE9Sm/akPgFTb9aAC9pMKGJ6CtWxaGrqWcabH+ySg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.0.tgz", + "integrity": "sha512-ZF+onDsBso8PJf1XaG9lB+O9RnBpKGnY6OrzC4CSHrtC1jb6jWLTKK4bRqdoCXHd22gyr2hiYmEAm8Wns/BOCw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.0.tgz", + "integrity": "sha512-Atk0aSIk5Zx2Wuh9dgRQgLP0Koc8hOeYpbWryMXyk8G8/HmPkwPPkMqIIDhrXHHYqfUzSJA/I7IWSBv8xSmRBA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.0.tgz", + "integrity": "sha512-0uMOcf3eZ5K+K4cYHkdxShFMPlPXCOdfDFEFn9dNYAEEd2cVvmOfH7zFgRVoDgmtQ1m9k5q7qfrHzyMAubKYUA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.0.tgz", + "integrity": "sha512-mvFtE4A/t/7hRJ7X8Ozmu8FsIkAUat2nzl12pgU337BRmq87AQUJztwHz2Zv5/tjo9/C95E66CK03SI/ToEDJw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.0.tgz", + "integrity": "sha512-z9b9+aTxvt8n2rNltMPvyaUfB8NJ+CVyOrGK/MdIKHx7B+lXmZpm/XbRsU7Rpf3fRqJ2uS6mBJiJveCtq8LHDg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.0.tgz", + "integrity": "sha512-jXaXFqKMehsOc+g8R6oo33RRC6w07G9jDBxAE5eAKX7mOcCbZloYIPNhfG9Wl+P9O9IWHFO4OJgPi1Ml2qkt7w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.0.tgz", + "integrity": "sha512-OXNWVFocS2IA4+QplhTZZ2a+8hPZR7T8KuozsNmJKK8y7cp83StHvGksfHzPG3wczWTczyWHVQuqeiTUbjiyBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.0.tgz", + "integrity": "sha512-AlAbNtBO637LxSldqV43z0FfXoGfl2TW1DgAg/bs7aQswFbDewz2SJm3BUhiGfbOVtW571xbc9p+REdxhyN/Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.0.tgz", + "integrity": "sha512-QRSrQXyJ1M4tjNXdR0/G/IgV6lzfQQJYBjlWIEYkY2Xs86DRl/iEpQ4blMDjJxSl7n19eDKKXMg0AmuBVYy8pQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.0.tgz", + "integrity": "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.61.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.0.tgz", + "integrity": "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.61.0", + "@rollup/rollup-android-arm64": "4.61.0", + "@rollup/rollup-darwin-arm64": "4.61.0", + "@rollup/rollup-darwin-x64": "4.61.0", + "@rollup/rollup-freebsd-arm64": "4.61.0", + "@rollup/rollup-freebsd-x64": "4.61.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", + "@rollup/rollup-linux-arm-musleabihf": "4.61.0", + "@rollup/rollup-linux-arm64-gnu": "4.61.0", + "@rollup/rollup-linux-arm64-musl": "4.61.0", + "@rollup/rollup-linux-loong64-gnu": "4.61.0", + "@rollup/rollup-linux-loong64-musl": "4.61.0", + "@rollup/rollup-linux-ppc64-gnu": "4.61.0", + "@rollup/rollup-linux-ppc64-musl": "4.61.0", + "@rollup/rollup-linux-riscv64-gnu": "4.61.0", + "@rollup/rollup-linux-riscv64-musl": "4.61.0", + "@rollup/rollup-linux-s390x-gnu": "4.61.0", + "@rollup/rollup-linux-x64-gnu": "4.61.0", + "@rollup/rollup-linux-x64-musl": "4.61.0", + "@rollup/rollup-openbsd-x64": "4.61.0", + "@rollup/rollup-openharmony-arm64": "4.61.0", + "@rollup/rollup-win32-arm64-msvc": "4.61.0", + "@rollup/rollup-win32-ia32-msvc": "4.61.0", + "@rollup/rollup-win32-x64-gnu": "4.61.0", + "@rollup/rollup-win32-x64-msvc": "4.61.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..60c4040 --- /dev/null +++ b/client/package.json @@ -0,0 +1,19 @@ +{ + "name": "faq-vled-rag-client", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview --host 0.0.0.0" + }, + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "lucide-react": "^0.468.0" + }, + "devDependencies": {} +} diff --git a/client/src/main.jsx b/client/src/main.jsx new file mode 100644 index 0000000..0661a4d --- /dev/null +++ b/client/src/main.jsx @@ -0,0 +1,186 @@ +import React, { useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Bot, Circle, Database, Loader2, MessageSquare, Send, UserRound } from 'lucide-react'; +import './styles.css'; + +const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001'; +const QUICK_PROMPTS = [ + 'Who can sign the NOC?', + 'Is there a stipend?', + 'How long is the internship?', + 'How do I log in to ViBe?' +]; + +function ConfidenceBadge({ found, confidence }) { + return ( + + + {found ? `${Math.round(confidence * 100)}% match` : 'Low confidence'} + + ); +} + +function Message({ message }) { + const isUser = message.role === 'user'; + + return ( +
+
{isUser ? : }
+
+
+ {isUser ? 'You' : 'OxEngine'} + {!isUser && message.confidence !== undefined && ( + + )} +
+

{message.text}

+ {!isUser && message.sources?.length > 0 && ( +
+ {message.sources.map((source) => ( + + + {source.category}: {source.question} + + ))} +
+ )} +
+
+ ); +} + +function App() { + const [input, setInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [messages, setMessages] = useState([ + { + id: crypto.randomUUID(), + role: 'assistant', + text: "Hi there! I'm OxEngine, your FAQ assistant. Ask me anything and I'll find the best answer for you.", + answerFound: true, + confidence: 1, + sources: [] + } + ]); + + async function sendMessage(event) { + event.preventDefault(); + const text = input.trim(); + if (!text || isLoading) return; + + setMessages((current) => [...current, { id: crypto.randomUUID(), role: 'user', text }]); + setInput(''); + setIsLoading(true); + + try { + const response = await fetch(`${API_URL}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: text }) + }); + + if (!response.ok) { + throw new Error('Request failed'); + } + + const data = await response.json(); + setMessages((current) => [ + ...current, + { + id: crypto.randomUUID(), + role: 'assistant', + text: data.answer, + answerFound: data.answerFound, + confidence: data.confidence, + sources: data.sources + } + ]); + } catch (_error) { + setMessages((current) => [ + ...current, + { + id: crypto.randomUUID(), + role: 'assistant', + text: 'The chatbot API is not reachable. Check that Express, MongoDB, and Ollama are running.', + answerFound: false, + confidence: 0, + sources: [] + } + ]); + } finally { + setIsLoading(false); + } + } + + function useQuickPrompt(prompt) { + setInput(prompt); + } + + return ( +
+
+
+
+
+ +
+
+
+

FAQ OxEngine

+ v2.1 +
+

Answers from the FAQ knowledge base

+
+
+
+ + Escalation off +
+
+ +
+ {messages.map((message) => ( + + ))} + {isLoading && ( +
+
+ +
+
+ + Retrieving context +
+
+ )} +
+ +
+ {QUICK_PROMPTS.map((prompt) => ( + + ))} +
+ +
+
+ + setInput(event.target.value)} + placeholder="Ask a question..." + aria-label="Question" + /> + +
+

Press Enter to send · Shift+Enter for new line

+
+
+
+ ); +} + +createRoot(document.getElementById('root')).render(); diff --git a/client/src/styles.css b/client/src/styles.css new file mode 100644 index 0000000..8ffdba2 --- /dev/null +++ b/client/src/styles.css @@ -0,0 +1,518 @@ +:root { + color: #172238; + background: #eef3fb; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { + box-sizing: border-box; +} + +html { + min-width: 320px; + background: #eef3fb; +} + +body { + margin: 0; +} + +button, +input { + font: inherit; +} + +button { + -webkit-tap-highlight-color: transparent; +} + +.appShell { + min-height: 100vh; + display: grid; + place-items: center; + padding: 46px 24px; + background: + radial-gradient(circle at 22% 14%, rgba(255, 255, 255, 0.9), transparent 24%), + radial-gradient(circle at 78% 92%, rgba(219, 247, 239, 0.78), transparent 30%), + #eef3fb; +} + +.chatPanel { + width: min(1120px, 100%); + height: min(820px, calc(100vh - 92px)); + min-height: 620px; + display: grid; + grid-template-rows: auto 1fr auto auto; + overflow: hidden; + border: 1px solid rgba(219, 226, 238, 0.9); + border-radius: 32px; + background: #ffffff; + box-shadow: + 0 34px 80px rgba(42, 56, 75, 0.18), + 0 10px 26px rgba(66, 76, 95, 0.08); +} + +.topBar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 22px; + min-height: 120px; + padding: 28px 40px; + border-bottom: 1px solid #e5ebf4; + background: #ffffff; +} + +.brandBlock { + display: flex; + align-items: center; + min-width: 0; + gap: 20px; +} + +.brandIcon, +.messageAvatar { + flex: 0 0 auto; + display: grid; + place-items: center; + color: #ffffff; + background: linear-gradient(145deg, #345df7 0%, #7140ea 100%); +} + +.brandIcon { + width: 64px; + height: 64px; + border-radius: 23px; + box-shadow: 0 18px 36px rgba(87, 76, 231, 0.28); +} + +.titleRow { + display: flex; + align-items: center; + gap: 14px; + min-width: 0; +} + +.topBar h1 { + margin: 0; + color: #101a2f; + font-size: 1.48rem; + font-weight: 850; + line-height: 1.15; + letter-spacing: 0; +} + +.versionTag { + display: inline-flex; + align-items: center; + min-height: 34px; + padding: 0 13px; + border-radius: 999px; + color: #16a55a; + background: #eafaf1; + font-size: 0; +} + +.versionTag::before { + content: ""; + width: 12px; + height: 12px; + margin-right: 7px; + border-radius: 50%; + background: #22c765; + box-shadow: 0 0 0 4px rgba(34, 199, 101, 0.14); +} + +.versionTag::after { + content: "Online"; + font-size: 1rem; + font-weight: 800; +} + +.topBar p { + margin: 10px 0 0; + color: #8d9ab1; + font-size: 1.06rem; + font-weight: 700; +} + +.statusPill { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + min-height: 52px; + min-width: 198px; + padding: 0 24px; + border: 1px solid #dce4ef; + border-radius: 999px; + color: #91a0b5; + background: #fbfcff; + font-size: 1rem; + font-weight: 800; + white-space: nowrap; +} + +.statusPill svg { + color: #c6d2e1; +} + +.messages { + display: flex; + flex-direction: column; + gap: 22px; + overflow-y: auto; + min-height: 0; + padding: 34px 40px 18px; + background: #ffffff; + scrollbar-color: #d8e1ef transparent; +} + +.message { + display: grid; + grid-template-columns: 52px minmax(0, 1fr); + gap: 18px; + max-width: 82%; +} + +.botMessage { + align-self: start; +} + +.userMessage { + align-self: end; + grid-template-columns: minmax(0, 1fr) 52px; +} + +.userMessage .messageAvatar { + grid-column: 2; + grid-row: 1; +} + +.userMessage .bubble { + grid-column: 1; + grid-row: 1; +} + +.messageAvatar { + width: 52px; + height: 52px; + border-radius: 22px; + background: #e2e8ff; + color: #6270f4; +} + +.userMessage .messageAvatar { + background: #edf4ff; + color: #5a7ceb; +} + +.bubble { + min-width: 0; + max-width: 780px; + padding: 24px 28px; + border: 1px solid #e8edf5; + border-radius: 8px 26px 26px 8px; + color: #1f293d; + background: #fafbfe; +} + +.userMessage .bubble { + border-radius: 26px 8px 8px 26px; + background: #f7faff; +} + +.messageHeader { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; +} + +.userMessage .messageHeader { + justify-content: flex-end; +} + +.messageHeader strong { + color: #655af5; + font-size: 1.02rem; + font-weight: 850; +} + +.userMessage .messageHeader strong { + color: #345df7; +} + +.bubble p { + margin: 0; + color: #1f293d; + font-size: 1.15rem; + font-weight: 500; + line-height: 1.72; +} + +.badge { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 30px; + padding: 0 11px; + border-radius: 999px; + font-size: 0.86rem; + font-weight: 800; + white-space: nowrap; +} + +.badgeFound { + border: 1px solid #d6f5e2; + color: #18a85e; + background: #eefcf4; +} + +.badgeLow { + border: 1px solid #ffe0d8; + color: #e35b45; + background: #fff4f0; +} + +.sources { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 16px; +} + +.sources span { + display: inline-flex; + align-items: center; + gap: 6px; + max-width: 100%; + min-height: 30px; + padding: 0 10px; + border: 1px solid #dde6f2; + border-radius: 999px; + color: #536279; + background: #ffffff; + font-size: 0.78rem; + font-weight: 700; +} + +.loadingBubble { + display: inline-flex; + align-items: center; + gap: 10px; + color: #8190a6; + font-weight: 700; +} + +.spin { + animation: spin 0.9s linear infinite; +} + +.quickPrompts { + display: flex; + align-content: end; + flex-wrap: wrap; + gap: 14px; + padding: 16px 40px 24px; + background: #ffffff; +} + +.quickPrompts button { + min-height: 48px; + max-width: 100%; + padding: 0 20px; + border: 1px solid #dbe4ef; + border-radius: 999px; + color: #49566c; + background: #fbfcff; + font-size: 1rem; + font-weight: 800; + cursor: pointer; + transition: + border-color 160ms ease, + color 160ms ease, + background 160ms ease, + transform 160ms ease, + box-shadow 160ms ease; +} + +.quickPrompts button:hover { + border-color: #b9c7ff; + color: #3659db; + background: #f6f8ff; + box-shadow: 0 8px 20px rgba(52, 93, 247, 0.08); + transform: translateY(-1px); +} + +.composer { + padding: 20px 34px 30px; + border-top: 1px solid #e5ebf4; + background: #ffffff; +} + +.inputShell { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 20px; + min-height: 82px; + padding: 0 26px; + border: 1px solid #dce4ef; + border-radius: 24px; + color: #c4cfde; + background: #fbfcff; +} + +.inputShell:focus-within { + border-color: #a9bcff; + box-shadow: 0 0 0 4px rgba(91, 116, 245, 0.12); +} + +.composer input { + width: 100%; + min-width: 0; + min-height: 50px; + border: 0; + color: #1f293d; + background: transparent; + outline: none; + font-size: 1.12rem; + font-weight: 600; +} + +.composer input::placeholder { + color: #858d9c; +} + +.composer button { + width: 58px; + height: 58px; + display: grid; + place-items: center; + border: 0; + border-radius: 20px; + color: #ffffff; + background: #9db4ff; + cursor: pointer; + transition: + opacity 160ms ease, + transform 160ms ease, + background 160ms ease; +} + +.composer button:not(:disabled):hover { + background: #7e99f8; + transform: translateY(-1px); +} + +.composer button:disabled { + cursor: not-allowed; + opacity: 0.55; +} + +.composer p { + margin: 16px 0 0; + color: #c7d2e2; + text-align: center; + font-size: 0.94rem; + font-weight: 800; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@media (max-width: 820px) { + .appShell { + padding: 0; + } + + .chatPanel { + width: 100%; + height: 100vh; + min-height: 100vh; + border: 0; + border-radius: 0; + } + + .topBar { + min-height: auto; + align-items: flex-start; + flex-direction: column; + padding: 22px; + } + + .brandIcon { + width: 54px; + height: 54px; + border-radius: 18px; + } + + .statusPill { + min-width: 0; + min-height: 42px; + } + + .messages { + padding: 24px 22px; + } + + .message, + .userMessage { + max-width: 100%; + grid-template-columns: 44px minmax(0, 1fr); + gap: 12px; + } + + .userMessage .messageAvatar { + grid-column: 1; + } + + .userMessage .bubble { + grid-column: 2; + border-radius: 8px 22px 22px 8px; + } + + .messageAvatar { + width: 44px; + height: 44px; + border-radius: 18px; + } + + .bubble { + padding: 18px 20px; + } + + .bubble p { + font-size: 1rem; + } + + .quickPrompts { + padding: 12px 22px 18px; + } + + .quickPrompts button { + min-height: 42px; + font-size: 0.9rem; + } + + .composer { + padding: 14px 16px 18px; + } + + .inputShell { + min-height: 68px; + padding: 0 14px; + border-radius: 18px; + } + + .composer button { + width: 50px; + height: 50px; + border-radius: 16px; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c2c5894 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "faq-vled-rag-chatbot", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "install:all": "npm install --prefix server && npm install --prefix client", + "dev": "concurrently \"npm run dev --prefix server\" \"npm run dev --prefix client\"", + "seed": "npm run seed --prefix server", + "start": "npm run start --prefix server" + }, + "devDependencies": { + "concurrently": "^9.1.2" + } +} diff --git a/redesign-preview.png b/redesign-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..d49c858600348c6781c1395e35c21b0a14352155 GIT binary patch literal 38072 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 L{I{b_KNtQ7!$-ne literal 0 HcmV?d00001 diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..a50982a --- /dev/null +++ b/server/.env.example @@ -0,0 +1,9 @@ +PORT=5000 +MONGODB_URI=mongodb://127.0.0.1:27017/faq_vled_rag +CLIENT_ORIGIN=http://localhost:5173 +MIN_CONFIDENCE=0.45 +TOP_K=4 +OLLAMA_BASE_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=gemma3:4b +FLAG_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 +PYTHON_BIN=python3 diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..155e0ba --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1102 @@ +{ + "name": "faq-vled-rag-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "faq-vled-rag-server", + "version": "1.0.0", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.9.5" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz", + "integrity": "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mongodb": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.24.0.tgz", + "integrity": "sha512-EEZwOibDPZ5uZN3bFapfnRskEbdljAf6sP9ln6u+P4e5IfkOAh6Tqw2g8/Tag++KHOAJ095WXT/c0uqRq4Vckg==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.20.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..c7448cb --- /dev/null +++ b/server/package.json @@ -0,0 +1,19 @@ +{ + "name": "faq-vled-rag-server", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "node --watch src/index.js", + "start": "node src/index.js", + "seed": "node src/scripts/seedFaqs.js", + "reindex": "node src/scripts/reindexFaqs.js", + "import:samagama": "node src/scripts/importSamagamaFaqs.js" + }, + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.9.5" + } +} diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..f196112 --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,2 @@ +FlagEmbedding>=1.3.4 +torch>=2.2.0 diff --git a/server/src/config/env.js b/server/src/config/env.js new file mode 100644 index 0000000..547e440 --- /dev/null +++ b/server/src/config/env.js @@ -0,0 +1,15 @@ +import dotenv from 'dotenv'; + +dotenv.config(); + +export const env = { + port: Number(process.env.PORT || 5000), + mongoUri: process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/faq_vled_rag', + clientOrigin: process.env.CLIENT_ORIGIN || 'http://localhost:5173', + minConfidence: Number(process.env.MIN_CONFIDENCE || 0.45), + topK: Number(process.env.TOP_K || 4), + ollamaBaseUrl: process.env.OLLAMA_BASE_URL || 'http://127.0.0.1:11434', + ollamaModel: process.env.OLLAMA_MODEL || 'gemma3:4b', + flagEmbeddingModel: process.env.FLAG_EMBEDDING_MODEL || 'BAAI/bge-small-en-v1.5', + pythonBin: process.env.PYTHON_BIN || 'python3' +}; diff --git a/server/src/data/seedFaqs.js b/server/src/data/seedFaqs.js new file mode 100644 index 0000000..d5ef3fc --- /dev/null +++ b/server/src/data/seedFaqs.js @@ -0,0 +1,44 @@ +export const seedFaqs = [ + { + question: 'What is the purpose of the RAG chatbot?', + answer: + 'The RAG chatbot answers user questions by retrieving relevant FAQ context from the knowledge base and generating a grounded response from that context.', + category: 'Architecture', + tags: ['rag', 'chatbot', 'overview'] + }, + { + question: 'How does the chatbot decide whether an answer was found?', + answer: + 'The chatbot compares the user question with retrieved FAQ context and returns an answer only when the confidence score meets the configured MIN_CONFIDENCE threshold.', + category: 'Confidence', + tags: ['confidence', 'threshold'] + }, + { + question: 'What happens when confidence is low?', + answer: + 'For the current build, low-confidence questions return a clear fallback message. The escalation system is intentionally not implemented yet.', + category: 'Fallback', + tags: ['low confidence', 'fallback', 'escalation'] + }, + { + question: 'Where is FAQ data stored?', + answer: + 'FAQ data is stored in MongoDB. The Express server saves FlagEmbedding vectors on FAQ records and retrieves relevant answers with cosine similarity.', + category: 'Storage', + tags: ['mongodb', 'retriever', 'vector store'] + }, + { + question: 'Which local model generates chatbot answers?', + answer: + 'The chatbot uses a local Ollama chat model to generate final answers from retrieved FAQ context. No external API key is required.', + category: 'Models', + tags: ['ollama', 'local model', 'llm'] + }, + { + question: 'Which embedding model does retrieval use?', + answer: + 'Retrieval uses the FlagEmbedding package with a BGE embedding model. FAQ embeddings are stored in MongoDB and query embeddings are compared against them with cosine similarity.', + category: 'Models', + tags: ['flagembedding', 'bge', 'retrieval'] + } +]; diff --git a/server/src/db/mongoose.js b/server/src/db/mongoose.js new file mode 100644 index 0000000..7cb278b --- /dev/null +++ b/server/src/db/mongoose.js @@ -0,0 +1,7 @@ +import mongoose from 'mongoose'; +import { env } from '../config/env.js'; + +export async function connectMongo() { + mongoose.set('strictQuery', true); + await mongoose.connect(env.mongoUri); +} diff --git a/server/src/index.js b/server/src/index.js new file mode 100644 index 0000000..472434f --- /dev/null +++ b/server/src/index.js @@ -0,0 +1,31 @@ +import cors from 'cors'; +import express from 'express'; +import { env } from './config/env.js'; +import { connectMongo } from './db/mongoose.js'; +import { chatRouter } from './routes/chatRoutes.js'; +import { faqRouter } from './routes/faqRoutes.js'; + +const app = express(); + +app.use(cors({ origin: env.clientOrigin })); +app.use(express.json({ limit: '1mb' })); + +app.get('/health', (_req, res) => { + res.json({ ok: true }); +}); + +app.use('/api/chat', chatRouter); +app.use('/api/faqs', faqRouter); + +app.use((error, _req, res, _next) => { + console.error(error); + res.status(500).json({ + message: 'Something went wrong while processing the request.' + }); +}); + +await connectMongo(); + +app.listen(env.port, () => { + console.log(`RAG server listening on http://localhost:${env.port}`); +}); diff --git a/server/src/models/Faq.js b/server/src/models/Faq.js new file mode 100644 index 0000000..7798bc9 --- /dev/null +++ b/server/src/models/Faq.js @@ -0,0 +1,47 @@ +import mongoose from 'mongoose'; + +const faqSchema = new mongoose.Schema( + { + question: { + type: String, + required: true, + trim: true + }, + answer: { + type: String, + required: true, + trim: true + }, + category: { + type: String, + default: 'General', + trim: true + }, + tags: { + type: [String], + default: [] + }, + sourceId: { + type: String, + trim: true + }, + sourceUrl: { + type: String, + trim: true + }, + embedding: { + type: [Number], + default: [] + }, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } +); + +faqSchema.index({ question: 'text', answer: 'text', category: 'text', tags: 'text' }); +faqSchema.index({ sourceId: 1 }, { unique: true, sparse: true }); + +export const Faq = mongoose.model('Faq', faqSchema); diff --git a/server/src/python/__pycache__/embed_flag.cpython-312.pyc b/server/src/python/__pycache__/embed_flag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d5990aa04fac67b218a59ed9264984dfbf305be GIT binary patch literal 1213 zcmZ`&%}*0S6rb6zZcB@4MN~j+v?i7Wi3b$XXdn~>0TW|l(gT~NJCv>4-8wTRl$4|g zO(Zcv5)&{n(JO((tN()+v@zDfoAKa{h$m0ZEG+e)FWL8dZ|1!>?`z-3ViCY;X68&` zP6FVYPy*t*Py4qytOE;J&;i*4gjwk8QdZ(9IdW0SDiB!mV~|ymW+@LZ0)TZ1Aa(07 z3MfNqAWs^gn;@D6A=p0AWjqi`HE~QN3TfqSYTb1EY;Gs zLtCO+PRnvtF2oPyzj+l}`gZszf1Be9xzgn;_xS>~xgO?k+e^qm#aU!oJTPIArksv4r9eEF zDUN4aOePgx6rtRX$$3PXsOQ*(rd1Xy znH3QWtHL_sH^^|!BEn+*a0_QdTqOnlK1&c@K)8?KTwkd|=RLR2PtKU*n}4ZPVM@`o zUEIMPbm94evl{r?npm}0>{s&Z@XK($^;~VN5k0#4jDh+8cVJadEa0Z~qliVp=V;7X^uv z>s@PI8|Yo?c~?Dtt2Xgf2|r7%_N??gciv2VPW69C_19BZcTNn{BiBADgZn%$z;we9 z=^92lgvIJ(v8%XMT*bt%oC#*&x6-dl_=xTp#wNg>!u+%_*KhE~l^t{wC%GzqQqsfO ro(v(}1ueTEybB`V!TFzBE4;pU4k*#5OCROaUljex=$;Ie4nNi(PF)i_ literal 0 HcmV?d00001 diff --git a/server/src/python/embed_flag.py b/server/src/python/embed_flag.py new file mode 100644 index 0000000..0b5e3a4 --- /dev/null +++ b/server/src/python/embed_flag.py @@ -0,0 +1,27 @@ +import json +import os +import sys + +from FlagEmbedding import FlagModel + + +def main(): + payload = json.load(sys.stdin) + texts = payload.get("texts", []) + model_name = payload.get("model") or os.environ.get( + "FLAG_EMBEDDING_MODEL", "BAAI/bge-small-en-v1.5" + ) + + model = FlagModel( + model_name, + normalize_embeddings=True, + query_instruction_for_retrieval="Represent this sentence for searching relevant passages:", + use_fp16=False, + ) + + embeddings = model.encode(texts) + print(json.dumps({"embeddings": embeddings.tolist()})) + + +if __name__ == "__main__": + main() diff --git a/server/src/routes/chatRoutes.js b/server/src/routes/chatRoutes.js new file mode 100644 index 0000000..4b4cefc --- /dev/null +++ b/server/src/routes/chatRoutes.js @@ -0,0 +1,13 @@ +import express from 'express'; +import { answerQuestion } from '../services/ragService.js'; + +export const chatRouter = express.Router(); + +chatRouter.post('/', async (req, res, next) => { + try { + const result = await answerQuestion(req.body?.message); + res.json(result); + } catch (error) { + next(error); + } +}); diff --git a/server/src/routes/faqRoutes.js b/server/src/routes/faqRoutes.js new file mode 100644 index 0000000..d0b7f22 --- /dev/null +++ b/server/src/routes/faqRoutes.js @@ -0,0 +1,24 @@ +import express from 'express'; +import { Faq } from '../models/Faq.js'; +import { embedAndSaveFaq } from '../services/ragService.js'; + +export const faqRouter = express.Router(); + +faqRouter.get('/', async (_req, res, next) => { + try { + const faqs = await Faq.find({ isActive: true }).sort({ updatedAt: -1 }); + res.json(faqs); + } catch (error) { + next(error); + } +}); + +faqRouter.post('/', async (req, res, next) => { + try { + const faq = new Faq(req.body); + await embedAndSaveFaq(faq); + res.status(201).json(faq); + } catch (error) { + next(error); + } +}); diff --git a/server/src/scripts/importSamagamaFaqs.js b/server/src/scripts/importSamagamaFaqs.js new file mode 100644 index 0000000..6da27c7 --- /dev/null +++ b/server/src/scripts/importSamagamaFaqs.js @@ -0,0 +1,135 @@ +import { connectMongo } from '../db/mongoose.js'; +import { Faq } from '../models/Faq.js'; +import { buildFaqText, embedTexts } from '../services/embeddingService.js'; + +const FAQ_URL = 'https://samagama.in/internship/faq'; + +function decodeHtml(value) { + return String(value) + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&#(\d+);/g, (_match, code) => String.fromCharCode(Number(code))); +} + +function stripTags(value) { + return decodeHtml( + String(value) + .replace(//gi, '') + .replace(//gi, '') + .replace(/]*>[\s\S]*?<\/a>/gi, '') + .replace(//gi, '\n') + .replace(/<\/p>/gi, '\n') + .replace(/<\/li>/gi, '\n') + .replace(/<\/h[1-6]>/gi, '\n') + .replace(/]*>/gi, '- ') + .replace(/<[^>]+>/g, '') + ) + .replace(/[ \t]+\n/g, '\n') + .replace(/\n{3,}/g, '\n\n') + .replace(/[ \t]{2,}/g, ' ') + .trim(); +} + +function removeNumberPrefix(value) { + return value.replace(/^\d+(?:\.\d+)*\s+/, '').trim(); +} + +function parseSections(html) { + const sections = []; + const sectionRegex = /]*id="(s-\d+)"[^>]*>([\s\S]*?)<\/h2>/gi; + let match; + + while ((match = sectionRegex.exec(html)) !== null) { + sections.push({ + id: match[1], + index: match.index, + title: removeNumberPrefix(stripTags(match[2])) + }); + } + + return sections; +} + +function sectionForIndex(sections, index) { + let current = 'Vicharanashala Internship'; + + for (const section of sections) { + if (section.index > index) break; + current = section.title; + } + + return current; +} + +function parseFaqs(html) { + const sections = parseSections(html); + const faqRegex = + /]*class="[^"]*\bfaq-q\b[^"]*"[^>]*id="([^"]+)"[^>]*>\s*([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/gi; + const faqs = []; + let match; + + while ((match = faqRegex.exec(html)) !== null) { + const sourceId = match[1]; + const rawQuestion = stripTags(match[2]); + const question = removeNumberPrefix(rawQuestion); + const answer = stripTags(match[3]); + const category = sectionForIndex(sections, match.index); + + if (!question || !answer) continue; + + faqs.push({ + question, + answer, + category, + sourceId, + sourceUrl: `${FAQ_URL}#${sourceId}`, + tags: ['samagama', 'vicharanashala', 'internship', category.toLowerCase()] + }); + } + + return faqs; +} + +const response = await fetch(FAQ_URL); + +if (!response.ok) { + throw new Error(`Failed to fetch ${FAQ_URL}: ${response.status}`); +} + +const html = await response.text(); +const faqs = parseFaqs(html); + +if (faqs.length === 0) { + throw new Error('No FAQ records were parsed from the Samagama page.'); +} + +await connectMongo(); + +const embeddings = await embedTexts(faqs.map(buildFaqText)); +let upserted = 0; +let modified = 0; + +for (const [index, faq] of faqs.entries()) { + const result = await Faq.updateOne( + { sourceId: faq.sourceId }, + { + $set: { + ...faq, + embedding: embeddings[index], + isActive: true + } + }, + { upsert: true } + ); + + upserted += result.upsertedCount || 0; + modified += result.modifiedCount || 0; +} + +console.log(`Imported ${faqs.length} Samagama FAQ records.`); +console.log(`Inserted: ${upserted}, updated: ${modified}.`); +process.exit(0); diff --git a/server/src/scripts/reindexFaqs.js b/server/src/scripts/reindexFaqs.js new file mode 100644 index 0000000..5db5f87 --- /dev/null +++ b/server/src/scripts/reindexFaqs.js @@ -0,0 +1,8 @@ +import { connectMongo } from '../db/mongoose.js'; +import { reindexFaqs } from '../services/ragService.js'; + +await connectMongo(); + +const count = await reindexFaqs(); +console.log(`Reindexed ${count} FAQ records with FlagEmbedding.`); +process.exit(0); diff --git a/server/src/scripts/seedFaqs.js b/server/src/scripts/seedFaqs.js new file mode 100644 index 0000000..aabffc1 --- /dev/null +++ b/server/src/scripts/seedFaqs.js @@ -0,0 +1,19 @@ +import { connectMongo } from '../db/mongoose.js'; +import { Faq } from '../models/Faq.js'; +import { seedFaqs } from '../data/seedFaqs.js'; +import { embedTexts, buildFaqText } from '../services/embeddingService.js'; + +await connectMongo(); + +await Faq.deleteMany({}); + +const embeddings = await embedTexts(seedFaqs.map(buildFaqText)); +const records = seedFaqs.map((faq, index) => ({ + ...faq, + embedding: embeddings[index] +})); + +await Faq.insertMany(records); + +console.log(`Seeded and embedded ${seedFaqs.length} FAQ records.`); +process.exit(0); diff --git a/server/src/services/embeddingService.js b/server/src/services/embeddingService.js new file mode 100644 index 0000000..a969b85 --- /dev/null +++ b/server/src/services/embeddingService.js @@ -0,0 +1,64 @@ +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { env } from '../config/env.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const embedScriptPath = path.resolve(__dirname, '../python/embed_flag.py'); + +export function buildFaqText(faq) { + const tags = Array.isArray(faq.tags) && faq.tags.length > 0 ? `\nTags: ${faq.tags.join(', ')}` : ''; + return `Category: ${faq.category || 'General'}\nQuestion: ${faq.question}\nAnswer: ${faq.answer}${tags}`; +} + +export async function embedTexts(texts) { + if (!Array.isArray(texts) || texts.length === 0) { + return []; + } + + return new Promise((resolve, reject) => { + const child = spawn(env.pythonBin, [embedScriptPath], { + stdio: ['pipe', 'pipe', 'pipe'], + env: { + ...process.env, + FLAG_EMBEDDING_MODEL: env.flagEmbeddingModel + } + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (chunk) => { + stdout += chunk.toString(); + }); + + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); + + child.on('error', reject); + + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`FlagEmbedding process failed: ${stderr || `exit code ${code}`}`)); + return; + } + + try { + const parsed = JSON.parse(stdout); + resolve(parsed.embeddings); + } catch (error) { + reject(new Error(`Could not parse FlagEmbedding output: ${error.message}`)); + } + }); + + child.stdin.write(JSON.stringify({ texts, model: env.flagEmbeddingModel })); + child.stdin.end(); + }); +} + +export async function embedFaq(faq) { + const [embedding] = await embedTexts([buildFaqText(faq)]); + return embedding; +} diff --git a/server/src/services/ollamaService.js b/server/src/services/ollamaService.js new file mode 100644 index 0000000..71d046a --- /dev/null +++ b/server/src/services/ollamaService.js @@ -0,0 +1,44 @@ +import { env } from '../config/env.js'; + +export async function generateWithOllama({ query, contexts }) { + const contextText = contexts + .map( + (context, index) => + `Context ${index + 1}\nCategory: ${context.category}\nQuestion: ${context.question}\nAnswer: ${context.answer}` + ) + .join('\n\n'); + + const prompt = `You are a FAQ support chatbot. +Answer using only the retrieved context. +If the context does not contain the answer, say: "I do not have enough information in the FAQ knowledge base to answer that." +Keep the answer concise and helpful. + +Retrieved context: +${contextText} + +User question: ${query} + +Answer:`; + + const response = await fetch(`${env.ollamaBaseUrl}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: env.ollamaModel, + prompt, + stream: false, + options: { + temperature: 0.1, + num_ctx: 4096 + } + }) + }); + + if (!response.ok) { + const body = await response.text(); + throw new Error(`Ollama request failed: ${response.status} ${body}`); + } + + const data = await response.json(); + return String(data.response || '').trim(); +} diff --git a/server/src/services/ragService.js b/server/src/services/ragService.js new file mode 100644 index 0000000..2134159 --- /dev/null +++ b/server/src/services/ragService.js @@ -0,0 +1,152 @@ +import { env } from '../config/env.js'; +import { Faq } from '../models/Faq.js'; +import { buildFaqText, embedFaq, embedTexts } from './embeddingService.js'; +import { generateWithOllama } from './ollamaService.js'; + +let cachedKnowledgeBase = null; + +function dotProduct(left, right) { + const length = Math.min(left.length, right.length); + let score = 0; + + for (let index = 0; index < length; index += 1) { + score += left[index] * right[index]; + } + + return score; +} + +function uniqueByQuestion(results) { + const seen = new Set(); + const uniqueResults = []; + + for (const result of results) { + const key = result.faq.question.toLowerCase(); + if (!seen.has(key)) { + seen.add(key); + uniqueResults.push(result); + } + } + + return uniqueResults; +} + +async function getKnowledgeBase() { + if (cachedKnowledgeBase) { + return cachedKnowledgeBase; + } + + const faqs = await Faq.find({ isActive: true, embedding: { $exists: true, $ne: [] } }).lean(); + cachedKnowledgeBase = faqs.map((faq) => ({ + ...faq, + id: String(faq._id) + })); + + return cachedKnowledgeBase; +} + +export function invalidateRetriever() { + cachedKnowledgeBase = null; +} + +export async function embedAndSaveFaq(faq) { + const embedding = await embedFaq(faq); + faq.embedding = embedding; + await faq.save(); + invalidateRetriever(); + return faq; +} + +export async function reindexFaqs() { + const faqs = await Faq.find({ isActive: true }); + + for (const faq of faqs) { + faq.embedding = await embedFaq(faq); + await faq.save(); + } + + invalidateRetriever(); + return faqs.length; +} + +export async function retrieveContext(query) { + const [queryEmbedding] = await embedTexts([query]); + const knowledgeBase = await getKnowledgeBase(); + + const ranked = knowledgeBase + .filter((faq) => Array.isArray(faq.embedding) && faq.embedding.length > 0) + .map((faq) => ({ + faq, + score: dotProduct(queryEmbedding, faq.embedding) + })) + .sort((left, right) => right.score - left.score); + + return uniqueByQuestion(ranked).slice(0, env.topK); +} + +function toSource(result) { + return { + id: result.faq.id, + question: result.faq.question, + category: result.faq.category, + score: Number(result.score.toFixed(4)) + }; +} + +function toContext(result) { + return { + question: result.faq.question, + answer: result.faq.answer, + category: result.faq.category, + text: buildFaqText(result.faq), + score: result.score + }; +} + +export async function answerQuestion(query) { + const normalizedQuery = String(query || '').trim(); + + if (!normalizedQuery) { + return { + answer: 'Please enter a question.', + answerFound: false, + confidence: 0, + sources: [] + }; + } + + const results = await retrieveContext(normalizedQuery); + const bestScore = results[0]?.score || 0; + const answerFound = bestScore >= env.minConfidence; + + if (results.length === 0) { + return { + answer: 'No indexed FAQ knowledge base has been loaded yet. Seed or add FAQs, then run reindexing.', + answerFound: false, + confidence: 0, + sources: [] + }; + } + + if (!answerFound) { + return { + answer: "I don't have enough information in the FAQ knowledge base to answer that.", + answerFound: false, + confidence: bestScore, + sources: results.map(toSource) + }; + } + + const contexts = results.map(toContext); + const answer = await generateWithOllama({ + query: normalizedQuery, + contexts + }); + + return { + answer, + answerFound: true, + confidence: bestScore, + sources: results.map(toSource) + }; +} From 19a3c3c823aaeb2a1288e985dc1ae8d7813e6445 Mon Sep 17 00:00:00 2001 From: ARKA KANJI Date: Mon, 1 Jun 2026 22:32:39 +0530 Subject: [PATCH 03/56] Added like, dislike, copy to clipboard, auto scroll, refresh page, message timestamps, regenerate response, hover tooltips features to UI --- client/src/main.jsx | 197 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 182 insertions(+), 15 deletions(-) diff --git a/client/src/main.jsx b/client/src/main.jsx index 0661a4d..abe1016 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,6 +1,6 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; -import { Bot, Circle, Database, Loader2, MessageSquare, Send, UserRound } from 'lucide-react'; +import { Bot, Circle, Database, Loader2, MessageSquare, Send, UserRound, ThumbsUp, ThumbsDown, RefreshCw, RotateCcw, Copy, Check } from 'lucide-react'; import './styles.css'; const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001'; @@ -11,6 +11,8 @@ const QUICK_PROMPTS = [ 'How do I log in to ViBe?' ]; +const getTime = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + function ConfidenceBadge({ found, confidence }) { return ( @@ -20,20 +22,81 @@ function ConfidenceBadge({ found, confidence }) { ); } -function Message({ message }) { +function Message({ message, isLatestBotMessage, onRegenerate }) { const isUser = message.role === 'user'; + const [vote, setVote] = useState(null); + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(message.text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; return (
{isUser ? : }
-
+
{isUser ? 'You' : 'OxEngine'} + {message.timestamp} {!isUser && message.confidence !== undefined && ( )}

{message.text}

+ + {!isUser && ( +
+ + + + + + + {isLatestBotMessage && ( + + )} +
+ )} + {!isUser && message.sources?.length > 0 && (
{message.sources.map((source) => ( @@ -52,6 +115,8 @@ function Message({ message }) { function App() { const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); + const messagesEndRef = useRef(null); + const [messages, setMessages] = useState([ { id: crypto.randomUUID(), @@ -59,16 +124,91 @@ function App() { text: "Hi there! I'm OxEngine, your FAQ assistant. Ask me anything and I'll find the best answer for you.", answerFound: true, confidence: 1, - sources: [] + sources: [], + timestamp: getTime() } ]); + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages, isLoading]); + + function handleRefresh() { + setMessages([ + { + id: crypto.randomUUID(), + role: 'assistant', + text: "Hi there! I'm OxEngine, your FAQ assistant. Ask me anything and I'll find the best answer for you.", + answerFound: true, + confidence: 1, + sources: [], + timestamp: getTime() + } + ]); + setInput(''); + setIsLoading(false); + } + + async function handleRegenerate() { + if (isLoading) return; + + const userMessages = messages.filter(m => m.role === 'user'); + if (userMessages.length === 0) return; + const lastUserText = userMessages[userMessages.length - 1].text; + + const newMessages = [...messages]; + if (newMessages[newMessages.length - 1].role === 'assistant') { + newMessages.pop(); + } + setMessages(newMessages); + setIsLoading(true); + + try { + const response = await fetch(`${API_URL}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: lastUserText }) + }); + + if (!response.ok) throw new Error('Request failed'); + + const data = await response.json(); + setMessages((current) => [ + ...current, + { + id: crypto.randomUUID(), + role: 'assistant', + text: data.answer, + answerFound: data.answerFound, + confidence: data.confidence, + sources: data.sources, + timestamp: getTime() + } + ]); + } catch (_error) { + setMessages((current) => [ + ...current, + { + id: crypto.randomUUID(), + role: 'assistant', + text: 'The chatbot API is not reachable. Check that Express, MongoDB, and Ollama are running.', + answerFound: false, + confidence: 0, + sources: [], + timestamp: getTime() + } + ]); + } finally { + setIsLoading(false); + } + } + async function sendMessage(event) { event.preventDefault(); const text = input.trim(); if (!text || isLoading) return; - setMessages((current) => [...current, { id: crypto.randomUUID(), role: 'user', text }]); + setMessages((current) => [...current, { id: crypto.randomUUID(), role: 'user', text, timestamp: getTime() }]); setInput(''); setIsLoading(true); @@ -92,7 +232,8 @@ function App() { text: data.answer, answerFound: data.answerFound, confidence: data.confidence, - sources: data.sources + sources: data.sources, + timestamp: getTime() } ]); } catch (_error) { @@ -104,7 +245,8 @@ function App() { text: 'The chatbot API is not reachable. Check that Express, MongoDB, and Ollama are running.', answerFound: false, confidence: 0, - sources: [] + sources: [], + timestamp: getTime() } ]); } finally { @@ -132,16 +274,40 @@ function App() {

Answers from the FAQ knowledge base

-
- - Escalation off +
+ + +
+ + Escalation off +
- {messages.map((message) => ( - - ))} + {messages.map((message, index) => { + const isLatestBotMessage = message.role === 'assistant' && index === messages.length - 1; + + return ( + + ); + })} {isLoading && (
@@ -153,6 +319,7 @@ function App() {
)} +
@@ -183,4 +350,4 @@ function App() { ); } -createRoot(document.getElementById('root')).render(); +createRoot(document.getElementById('root')).render(); \ No newline at end of file From 08cbcc09c105925070c29e2298fc8f780eacb35e Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 2 Jun 2026 02:28:30 +0530 Subject: [PATCH 04/56] Fix embed_flag.py crash on transformers 5.x, add DESIGN.md, fix port in .env.example - Rewrite embed_flag.py to use AutoTokenizer/AutoModel with per-text encoding to fix TypeError crash caused by breaking change in transformers 5.x fast tokenizer batch API (encode_batch incompatibility) - Strengthen empty-text filter in importSamagamaFaqs.js to also reject texts shorter than 5 chars, preventing malformed entries reaching the embedder - Fix PORT mismatch in server/.env.example (5000 -> 5001) to match README - Add DESIGN.md with full system architecture, request flow, component breakdown, API reference, knowledge base details, and production roadmap --- DESIGN.md | 284 +++++++++++++++++++++++ server/.env.example | 2 +- server/src/python/embed_flag.py | 45 +++- server/src/scripts/importSamagamaFaqs.js | 2 +- 4 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..c75e4ff --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,284 @@ +# FAQ OxEngine — System Design + +## Overview + +FAQ OxEngine is a fully local Retrieval-Augmented Generation (RAG) chatbot built for the Vicharanashala internship program at IIT Ropar. It allows users to ask natural language questions and receive accurate, grounded answers sourced from the official FAQ knowledge base — without any external API dependencies. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Browser │ +│ React Frontend (Vite) │ +│ localhost:5173 │ +└─────────────────────────┬───────────────────────────────────┘ + │ POST /api/chat { message } + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Express API Server │ +│ localhost:5001 │ +│ │ +│ 1. Receives user query │ +│ 2. Calls Python subprocess to embed the query │ +│ 3. Retrieves top-K similar FAQs from MongoDB │ +│ 4. Checks confidence threshold │ +│ 5. Sends context + query to Ollama │ +│ 6. Returns structured response │ +└────────┬──────────────────────┬──────────────────┬──────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Python Process │ │ MongoDB │ │ Ollama │ +│ (FlagEmbedding)│ │ faq_vled_rag │ │ gemma3:1b │ +│ │ │ │ │ │ +│ Embeds query │ │ Stores FAQs │ │ Generates │ +│ into 384-dim │ │ + 384-dim │ │ final answer │ +│ vector │ │ embedding │ │ from context │ +│ │ │ vectors │ │ only │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ +``` + +--- + +## Request Flow + +``` +User: "Is there a stipend?" + │ + ▼ + [1] Express receives message + │ + ▼ + [2] Python embeds query + "Is there a stipend?" → [0.023, -0.041, 0.187, ...] (384 numbers) + │ + ▼ + [3] Load all FAQ vectors from MongoDB (cached in memory) + Compute dot product similarity against all 148 FAQ vectors + Rank by score, pick top 4 + │ + ▼ + [4] Check confidence + best_score >= MIN_CONFIDENCE (0.45)? + │ + ┌──┴──┐ + YES NO + │ │ + │ ▼ + │ Return fallback: + │ "I don't have enough information..." + │ + ▼ + [5] Build prompt for Ollama: + "Answer using ONLY the retrieved context. + Context 1: [FAQ text] + Context 2: [FAQ text] + ... + User question: Is there a stipend?" + │ + ▼ + [6] Ollama generates grounded answer + │ + ▼ + [7] Return to client: + { + answer: "Yes, interns receive...", + answerFound: true, + confidence: 0.81, + sources: [{ question, category, score }, ...] + } +``` + +--- + +## Components + +### Frontend — `client/` + +| File | Purpose | +|------|---------| +| `src/main.jsx` | Single-file React app — chat UI, message history, confidence badges, source chips, quick prompts | +| `src/styles.css` | Custom CSS — responsive layout, mobile support | +| `index.html` | HTML entry point | +| `.env` | `VITE_API_URL` — points to Express server | + +### Backend — `server/src/` + +| File | Purpose | +|------|---------| +| `index.js` | Express app entry — CORS, routes, error handler, MongoDB connect | +| `config/env.js` | All config from environment variables with defaults | +| `models/Faq.js` | Mongoose schema — question, answer, category, tags, embedding, isActive | +| `routes/chatRoutes.js` | `POST /api/chat` — accepts message, returns answer | +| `routes/faqRoutes.js` | `GET/POST /api/faqs` — list and add FAQs | +| `services/ragService.js` | Core RAG logic — retrieval, confidence check, answer generation | +| `services/embeddingService.js` | Spawns Python subprocess, sends texts, receives vectors | +| `services/ollamaService.js` | Calls Ollama `/api/generate` with grounded prompt | +| `python/embed_flag.py` | Loads BGE model, encodes texts, returns JSON embeddings | + +### Scripts — `server/src/scripts/` + +| Script | Command | Purpose | +|--------|---------|---------| +| `seedFaqs.js` | `npm run seed` | Wipes DB, inserts 6 built-in FAQs with embeddings | +| `reindexFaqs.js` | `npm run reindex` | Re-embeds all existing FAQs (run after manual edits) | +| `importSamagamaFaqs.js` | `npm run import:samagama` | Scrapes samagama.in/internship/faq, embeds and upserts all FAQs | + +--- + +## Knowledge Base + +The knowledge base lives in MongoDB (`faq_vled_rag.faqs`). It is populated once and queried at runtime — Samagama is never called during a user query. + +| Source | FAQs | Topics | +|--------|------|--------| +| Samagama import | 142 | NOC, stipend, ViBe platform, team formation, Rosetta journal, Spurti points, certificates, code of conduct, interviews | +| Built-in seed | 6 | System architecture, confidence, storage, models | +| **Total** | **148** | | + +Each FAQ document stores: +- `question` — the FAQ question text +- `answer` — the full answer text +- `category` — section heading from the source page +- `tags` — searchable labels +- `sourceId` / `sourceUrl` — link back to original source +- `embedding` — 384-dimensional BGE vector for semantic search +- `isActive` — soft delete flag + +--- + +## Embedding & Retrieval + +**Model:** `BAAI/bge-small-en-v1.5` (384 dimensions, ~130MB, runs locally) + +**How similarity works:** +- All FAQ embeddings are L2-normalized at index time +- Query embedding is also normalized at query time +- Dot product of two normalized vectors = cosine similarity +- Score range: 0.0 (unrelated) → 1.0 (identical) + +**Retrieval config (via `.env`):** + +| Variable | Default | Meaning | +|----------|---------|---------| +| `MIN_CONFIDENCE` | `0.45` | Minimum score to attempt an answer | +| `TOP_K` | `4` | Number of FAQ contexts passed to Ollama | + +**In-memory cache:** FAQ vectors are loaded from MongoDB once and cached. Cache is invalidated whenever a new FAQ is added or reindexed. + +--- + +## LLM — Ollama + +The LLM only sees the retrieved FAQ context — it has no access to the internet or its own training knowledge for answering. The prompt explicitly instructs it: + +``` +You are a FAQ support chatbot. +Answer using only the retrieved context. +If the context does not contain the answer, say: +"I do not have enough information in the FAQ knowledge base to answer that." +``` + +**Config:** + +| Variable | Default | +|----------|---------| +| `OLLAMA_MODEL` | `gemma3:1b` | +| `OLLAMA_BASE_URL` | `http://127.0.0.1:11434` | +| Temperature | `0.1` (deterministic) | +| Context window | `4096` tokens | + +--- + +## Environment Variables + +### `server/.env` + +```env +PORT=5001 +MONGODB_URI=mongodb://127.0.0.1:27017/faq_vled_rag +CLIENT_ORIGIN=http://localhost:5173 +MIN_CONFIDENCE=0.45 +TOP_K=4 +OLLAMA_BASE_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=gemma3:1b +FLAG_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 +PYTHON_BIN=python +``` + +### `client/.env` + +```env +VITE_API_URL=http://localhost:5001 +``` + +--- + +## API + +### `POST /api/chat` + +**Request:** +```json +{ "message": "Is there a stipend?" } +``` + +**Response:** +```json +{ + "answer": "Yes, interns receive a monthly honorarium...", + "answerFound": true, + "confidence": 0.81, + "sources": [ + { + "id": "...", + "question": "What is the stipend amount?", + "category": "4. Selection, offer letter, and certificate", + "score": 0.8123 + } + ] +} +``` + +**Low confidence response:** +```json +{ + "answer": "I don't have enough information in the FAQ knowledge base to answer that.", + "answerFound": false, + "confidence": 0.31, + "sources": [...] +} +``` + +### `GET /api/faqs` +Returns all active FAQs. + +### `POST /api/faqs` +Adds a new FAQ and auto-embeds it. + +--- + +## Current Limitations + +| Limitation | Detail | +|------------|--------| +| Local only | MongoDB, Ollama, and the embedding model all run on the developer's machine. Not accessible to external users. | +| No escalation | Low-confidence queries return a fallback message. No handoff to a human agent is implemented. | +| Static knowledge base | FAQs are not auto-updated. If Samagama content changes, `npm run import:samagama` must be re-run manually. | +| Memory constraint | `gemma3:1b` requires ~1GB RAM. Larger models need more memory. | +| No auth | The API has no authentication. Anyone with network access to port 5001 can query it. | + +--- + +## Path to Production + +To make this accessible to real users, the following changes are needed: + +1. **Host the server** on a cloud VM (AWS, GCP, DigitalOcean) or containerize with Docker +2. **Use MongoDB Atlas** (free tier) instead of local MongoDB — accessible from anywhere +3. **Replace Ollama** with an API-based LLM (OpenAI, Groq, Gemini) to avoid hosting a local model +4. **Deploy the frontend** to Vercel or Netlify — free, instant +5. **Add authentication** if the chatbot should be restricted to registered interns +6. **Automate FAQ sync** — schedule `import:samagama` to run periodically so the knowledge base stays current diff --git a/server/.env.example b/server/.env.example index a50982a..4ef0027 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,4 +1,4 @@ -PORT=5000 +PORT=5001 MONGODB_URI=mongodb://127.0.0.1:27017/faq_vled_rag CLIENT_ORIGIN=http://localhost:5173 MIN_CONFIDENCE=0.45 diff --git a/server/src/python/embed_flag.py b/server/src/python/embed_flag.py index 0b5e3a4..bc063e6 100644 --- a/server/src/python/embed_flag.py +++ b/server/src/python/embed_flag.py @@ -2,25 +2,52 @@ import os import sys -from FlagEmbedding import FlagModel +os.environ["TOKENIZERS_PARALLELISM"] = "false" def main(): payload = json.load(sys.stdin) texts = payload.get("texts", []) + # Ensure all inputs are clean non-empty strings, encode/decode to strip bad chars + texts = [str(t).encode("utf-8", errors="ignore").decode("utf-8").strip() for t in texts] + texts = [t for t in texts if len(t) >= 5] + + if not texts: + print(json.dumps({"embeddings": []})) + return + model_name = payload.get("model") or os.environ.get( "FLAG_EMBEDDING_MODEL", "BAAI/bge-small-en-v1.5" ) - model = FlagModel( - model_name, - normalize_embeddings=True, - query_instruction_for_retrieval="Represent this sentence for searching relevant passages:", - use_fp16=False, - ) + from transformers import AutoTokenizer, AutoModel + import torch + + tokenizer = AutoTokenizer.from_pretrained(model_name) + model = AutoModel.from_pretrained(model_name) + model.eval() + + all_embeddings = [] + + # Process one at a time to avoid batch encoding issues + for text in texts: + encoded = tokenizer( + text, + padding=True, + truncation=True, + max_length=512, + return_tensors="pt" + ) + with torch.no_grad(): + output = model(**encoded) + token_embeddings = output.last_hidden_state + attention_mask = encoded["attention_mask"] + mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() + embedding = torch.sum(token_embeddings * mask_expanded, 1) / torch.clamp(mask_expanded.sum(1), min=1e-9) + embedding = torch.nn.functional.normalize(embedding, p=2, dim=1) + all_embeddings.append(embedding[0].numpy().tolist()) - embeddings = model.encode(texts) - print(json.dumps({"embeddings": embeddings.tolist()})) + print(json.dumps({"embeddings": all_embeddings})) if __name__ == "__main__": diff --git a/server/src/scripts/importSamagamaFaqs.js b/server/src/scripts/importSamagamaFaqs.js index 6da27c7..9c09a56 100644 --- a/server/src/scripts/importSamagamaFaqs.js +++ b/server/src/scripts/importSamagamaFaqs.js @@ -79,7 +79,7 @@ function parseFaqs(html) { const answer = stripTags(match[3]); const category = sectionForIndex(sections, match.index); - if (!question || !answer) continue; + if (!question || !answer || question.trim().length < 5 || answer.trim().length < 5) continue; faqs.push({ question, From 9d7ee7b462e93ab8da50d33858c8c9c48d173e2f Mon Sep 17 00:00:00 2001 From: 0xkhush <0xkhush@gmail.com> Date: Tue, 2 Jun 2026 13:35:08 +0530 Subject: [PATCH 05/56] changed ollama model info --- DESIGN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index c75e4ff..c6e6746 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -185,7 +185,7 @@ If the context does not contain the answer, say: | Variable | Default | |----------|---------| -| `OLLAMA_MODEL` | `gemma3:1b` | +| `OLLAMA_MODEL` | `gemma3:4b` | | `OLLAMA_BASE_URL` | `http://127.0.0.1:11434` | | Temperature | `0.1` (deterministic) | | Context window | `4096` tokens | @@ -203,7 +203,7 @@ CLIENT_ORIGIN=http://localhost:5173 MIN_CONFIDENCE=0.45 TOP_K=4 OLLAMA_BASE_URL=http://127.0.0.1:11434 -OLLAMA_MODEL=gemma3:1b +OLLAMA_MODEL=gemma3:4b FLAG_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5 PYTHON_BIN=python ``` @@ -267,7 +267,7 @@ Adds a new FAQ and auto-embeds it. | Local only | MongoDB, Ollama, and the embedding model all run on the developer's machine. Not accessible to external users. | | No escalation | Low-confidence queries return a fallback message. No handoff to a human agent is implemented. | | Static knowledge base | FAQs are not auto-updated. If Samagama content changes, `npm run import:samagama` must be re-run manually. | -| Memory constraint | `gemma3:1b` requires ~1GB RAM. Larger models need more memory. | +| Memory constraint | `gemma3:4b` requires ~4GB RAM. Larger models need more memory. | | No auth | The API has no authentication. Anyone with network access to port 5001 can query it. | --- From 71184ae63da6d4825fda55a9ed441dc8fe5403c2 Mon Sep 17 00:00:00 2001 From: 0xkhush <0xkhush@gmail.com> Date: Tue, 2 Jun 2026 13:36:14 +0530 Subject: [PATCH 06/56] added transformers --- server/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/requirements.txt b/server/requirements.txt index f196112..79cc4de 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1,2 +1,2 @@ -FlagEmbedding>=1.3.4 torch>=2.2.0 +transformers>=4.44.2 From 622ce7d44128bccf4d7c8a345afd89d3d2ca0465 Mon Sep 17 00:00:00 2001 From: 0xkhush <0xkhush@gmail.com> Date: Tue, 2 Jun 2026 13:37:06 +0530 Subject: [PATCH 07/56] fix: handle short query embeddings safely --- server/src/python/embed_flag.py | 2 +- server/src/services/ragService.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/server/src/python/embed_flag.py b/server/src/python/embed_flag.py index bc063e6..68ee4da 100644 --- a/server/src/python/embed_flag.py +++ b/server/src/python/embed_flag.py @@ -10,7 +10,7 @@ def main(): texts = payload.get("texts", []) # Ensure all inputs are clean non-empty strings, encode/decode to strip bad chars texts = [str(t).encode("utf-8", errors="ignore").decode("utf-8").strip() for t in texts] - texts = [t for t in texts if len(t) >= 5] + texts = [t if len(t) >= 5 else "empty faq text" for t in texts] if not texts: print(json.dumps({"embeddings": []})) diff --git a/server/src/services/ragService.js b/server/src/services/ragService.js index a7a0b55..931a73f 100644 --- a/server/src/services/ragService.js +++ b/server/src/services/ragService.js @@ -6,6 +6,10 @@ import { generateWithOllama } from './ollamaService.js'; let cachedKnowledgeBase = null; function dotProduct(left, right) { + if (!Array.isArray(left) || !Array.isArray(right)) { + return 0; + } + const length = Math.min(left.length, right.length); let score = 0; @@ -71,6 +75,10 @@ export async function reindexFaqs() { export async function retrieveContext(query) { const [queryEmbedding] = await embedTexts([query]); + if (!Array.isArray(queryEmbedding) || queryEmbedding.length === 0) { + return []; + } + const knowledgeBase = await getKnowledgeBase(); const ranked = knowledgeBase From 5ddecc190e09c78318a04e7adbfbaabf63f58af3 Mon Sep 17 00:00:00 2001 From: ARKA KANJI Date: Tue, 2 Jun 2026 13:54:01 +0530 Subject: [PATCH 08/56] Feature: Added Auto/Light/Dark theme toggle, Copy & Edit Prompt, and Scroll-to-bottom UI --- client/src/main.jsx | 203 ++++++++++++++++++++++++++++++++++++++++-- client/src/styles.css | 115 +++++++++++++++++++++++- 2 files changed, 309 insertions(+), 9 deletions(-) diff --git a/client/src/main.jsx b/client/src/main.jsx index abe1016..3d1d422 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,6 +1,9 @@ import React, { useState, useRef, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; -import { Bot, Circle, Database, Loader2, MessageSquare, Send, UserRound, ThumbsUp, ThumbsDown, RefreshCw, RotateCcw, Copy, Check } from 'lucide-react'; +import { + Bot, Circle, Database, Loader2, MessageSquare, Send, UserRound, + ThumbsUp, ThumbsDown, RefreshCw, RotateCcw, Copy, Check, Pencil, ArrowDown +} from 'lucide-react'; import './styles.css'; const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5001'; @@ -22,10 +25,12 @@ function ConfidenceBadge({ found, confidence }) { ); } -function Message({ message, isLatestBotMessage, onRegenerate }) { +function Message({ message, isLatestBotMessage, onRegenerate, onEditPrompt }) { const isUser = message.role === 'user'; const [vote, setVote] = useState(null); const [copied, setCopied] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [editText, setEditText] = useState(message.text); const handleCopy = () => { navigator.clipboard.writeText(message.text); @@ -35,7 +40,7 @@ function Message({ message, isLatestBotMessage, onRegenerate }) { return (
-
{isUser ? : }
+
{isUser ? : }
{isUser ? 'You' : 'OxEngine'} @@ -44,9 +49,68 @@ function Message({ message, isLatestBotMessage, onRegenerate }) { )}
-

{message.text}

- {!isUser && ( + {isUser && isEditing ? ( +
+