From 65ade24e1b6245aa4411c6046b36c1574ddbbd07 Mon Sep 17 00:00:00 2001 From: balex Date: Fri, 27 Feb 2026 20:50:03 +0100 Subject: [PATCH] add rag-view frontend --- .gitlab-ci.yml | 1 - rag-view/.gitignore | 26 + rag-view/README.md | 12 + rag-view/docker/Dockerfile | 11 + rag-view/docker/nginx.conf | 8 + rag-view/eslint.config.js | 29 + rag-view/index.html | 13 + rag-view/package-lock.json | 4601 +++++++++++++++++ rag-view/package.json | 37 + rag-view/postcss.config.js | 5 + rag-view/public/context-questions.txt | 31 + rag-view/public/guest-example.txt | 30 + rag-view/src/App.jsx | 50 + rag-view/src/features/constants.js | 54 + .../fetch-async/fetchAddNewUserEntry.js | 44 + .../fetch-async/fetchCreateNewChat.js | 43 + .../features/fetch-async/fetchDeleteChat.js | 50 + .../fetch-async/fetchDeleteUploaded.js | 33 + .../src/features/fetch-async/fetchGetChat.js | 40 + .../features/fetch-async/fetchGetChatList.js | 40 + .../features/fetch-async/fetchLoginUser.js | 54 + .../features/fetch-async/fetchRefreshToken.js | 54 + .../features/fetch-async/fetchRegisterUser.js | 52 + .../features/fetch-async/fetchUserProfile.js | 35 + .../src/features/fetch-async/fetchWithAuth.js | 46 + .../chat-slice/deleteChatExtraReducers.js | 24 + .../slices/chat-slice/getChatExtraReducers.js | 15 + .../chat-slice/getChatListExtraReducers.js | 16 + .../src/features/slices/chat-slice/index.js | 26 + .../slices/chat-slice/initialState.js | 9 + .../slices/chat-slice/newChatExtraReducers.js | 18 + .../chat-slice/newEntryExtraReducers.js | 18 + .../features/slices/chat-slice/reducers.js | 8 + .../slices/details-slice/authExtraReducers.js | 59 + .../details-slice/deleteExtraReducers.js | 25 + .../features/slices/details-slice/index.js | 7 + .../slices/details-slice/initialState.js | 36 + .../details-slice/profileExtraReducers.js | 27 + .../features/slices/details-slice/reducers.js | 26 + .../slices/details-slice/tokenHelpers.js | 33 + .../slices/details-slice/userDetailsSlice.js | 24 + .../src/features/slices/rag-slice/index.js | 13 + .../features/slices/rag-slice/initialState.js | 9 + .../src/features/slices/rag-slice/reducers.js | 9 + .../slices/upload-slice/abortController.js | 19 + .../src/features/slices/upload-slice/index.js | 48 + .../slices/upload-slice/initialState.js | 18 + .../features/slices/upload-slice/reducers.js | 62 + .../features/slices/upload-slice/selectors.js | 74 + .../features/slices/upload-slice/thunks.js | 190 + rag-view/src/features/uploadFilesSliceOld.js | 333 ++ rag-view/src/features/userDetailsSliceOld.js | 199 + rag-view/src/index.css | 1 + rag-view/src/main.jsx | 15 + rag-view/src/pages/HomePage.jsx | 300 ++ rag-view/src/pages/LoginPage.jsx | 136 + .../src/pages/RagPage/components/ChatArea.jsx | 189 + .../src/pages/RagPage/components/ChatList.jsx | 102 + .../RagPage/components/NewChatButton.jsx | 90 + .../RagPage/components/SettingsPanel.jsx | 127 + .../src/pages/RagPage/components/Sidebar.jsx | 15 + .../pages/RagPage/components/UserProfile.jsx | 48 + rag-view/src/pages/RagPage/index.jsx | 44 + .../src/pages/RagPage/utils/formatDate.js | 10 + rag-view/src/pages/RagPage/utils/titleUtils | 23 + rag-view/src/pages/RagPageOld.jsx | 426 ++ rag-view/src/pages/RegisterPage.jsx | 163 + rag-view/src/pages/RootLayout.jsx | 11 + .../UploadPage/components/ErrorState.jsx | 25 + .../pages/UploadPage/components/FileList.jsx | 26 + .../UploadPage/components/FileSelector.jsx | 40 + .../pages/UploadPage/components/QuotaInfo.jsx | 38 + .../UploadPage/components/SuccessState.jsx | 27 + .../UploadPage/components/UploadProgress.jsx | 47 + rag-view/src/pages/UploadPage/index.jsx | 257 + .../pages/UploadPage/styles/uploadStyles.js | 278 + .../pages/UploadPage/utils/statusHelpers.js | 36 + rag-view/src/pages/UploadPageOld.jsx | 683 +++ rag-view/src/store.js | 16 + rag-view/vite.config.js | 7 + 80 files changed, 9923 insertions(+), 1 deletion(-) create mode 100644 rag-view/.gitignore create mode 100644 rag-view/README.md create mode 100644 rag-view/docker/Dockerfile create mode 100644 rag-view/docker/nginx.conf create mode 100644 rag-view/eslint.config.js create mode 100644 rag-view/index.html create mode 100644 rag-view/package-lock.json create mode 100644 rag-view/package.json create mode 100644 rag-view/postcss.config.js create mode 100644 rag-view/public/context-questions.txt create mode 100644 rag-view/public/guest-example.txt create mode 100644 rag-view/src/App.jsx create mode 100644 rag-view/src/features/constants.js create mode 100644 rag-view/src/features/fetch-async/fetchAddNewUserEntry.js create mode 100644 rag-view/src/features/fetch-async/fetchCreateNewChat.js create mode 100644 rag-view/src/features/fetch-async/fetchDeleteChat.js create mode 100644 rag-view/src/features/fetch-async/fetchDeleteUploaded.js create mode 100644 rag-view/src/features/fetch-async/fetchGetChat.js create mode 100644 rag-view/src/features/fetch-async/fetchGetChatList.js create mode 100644 rag-view/src/features/fetch-async/fetchLoginUser.js create mode 100644 rag-view/src/features/fetch-async/fetchRefreshToken.js create mode 100644 rag-view/src/features/fetch-async/fetchRegisterUser.js create mode 100644 rag-view/src/features/fetch-async/fetchUserProfile.js create mode 100644 rag-view/src/features/fetch-async/fetchWithAuth.js create mode 100644 rag-view/src/features/slices/chat-slice/deleteChatExtraReducers.js create mode 100644 rag-view/src/features/slices/chat-slice/getChatExtraReducers.js create mode 100644 rag-view/src/features/slices/chat-slice/getChatListExtraReducers.js create mode 100644 rag-view/src/features/slices/chat-slice/index.js create mode 100644 rag-view/src/features/slices/chat-slice/initialState.js create mode 100644 rag-view/src/features/slices/chat-slice/newChatExtraReducers.js create mode 100644 rag-view/src/features/slices/chat-slice/newEntryExtraReducers.js create mode 100644 rag-view/src/features/slices/chat-slice/reducers.js create mode 100644 rag-view/src/features/slices/details-slice/authExtraReducers.js create mode 100644 rag-view/src/features/slices/details-slice/deleteExtraReducers.js create mode 100644 rag-view/src/features/slices/details-slice/index.js create mode 100644 rag-view/src/features/slices/details-slice/initialState.js create mode 100644 rag-view/src/features/slices/details-slice/profileExtraReducers.js create mode 100644 rag-view/src/features/slices/details-slice/reducers.js create mode 100644 rag-view/src/features/slices/details-slice/tokenHelpers.js create mode 100644 rag-view/src/features/slices/details-slice/userDetailsSlice.js create mode 100644 rag-view/src/features/slices/rag-slice/index.js create mode 100644 rag-view/src/features/slices/rag-slice/initialState.js create mode 100644 rag-view/src/features/slices/rag-slice/reducers.js create mode 100644 rag-view/src/features/slices/upload-slice/abortController.js create mode 100644 rag-view/src/features/slices/upload-slice/index.js create mode 100644 rag-view/src/features/slices/upload-slice/initialState.js create mode 100644 rag-view/src/features/slices/upload-slice/reducers.js create mode 100644 rag-view/src/features/slices/upload-slice/selectors.js create mode 100644 rag-view/src/features/slices/upload-slice/thunks.js create mode 100644 rag-view/src/features/uploadFilesSliceOld.js create mode 100644 rag-view/src/features/userDetailsSliceOld.js create mode 100644 rag-view/src/index.css create mode 100644 rag-view/src/main.jsx create mode 100644 rag-view/src/pages/HomePage.jsx create mode 100644 rag-view/src/pages/LoginPage.jsx create mode 100644 rag-view/src/pages/RagPage/components/ChatArea.jsx create mode 100644 rag-view/src/pages/RagPage/components/ChatList.jsx create mode 100644 rag-view/src/pages/RagPage/components/NewChatButton.jsx create mode 100644 rag-view/src/pages/RagPage/components/SettingsPanel.jsx create mode 100644 rag-view/src/pages/RagPage/components/Sidebar.jsx create mode 100644 rag-view/src/pages/RagPage/components/UserProfile.jsx create mode 100644 rag-view/src/pages/RagPage/index.jsx create mode 100644 rag-view/src/pages/RagPage/utils/formatDate.js create mode 100644 rag-view/src/pages/RagPage/utils/titleUtils create mode 100644 rag-view/src/pages/RagPageOld.jsx create mode 100644 rag-view/src/pages/RegisterPage.jsx create mode 100644 rag-view/src/pages/RootLayout.jsx create mode 100644 rag-view/src/pages/UploadPage/components/ErrorState.jsx create mode 100644 rag-view/src/pages/UploadPage/components/FileList.jsx create mode 100644 rag-view/src/pages/UploadPage/components/FileSelector.jsx create mode 100644 rag-view/src/pages/UploadPage/components/QuotaInfo.jsx create mode 100644 rag-view/src/pages/UploadPage/components/SuccessState.jsx create mode 100644 rag-view/src/pages/UploadPage/components/UploadProgress.jsx create mode 100644 rag-view/src/pages/UploadPage/index.jsx create mode 100644 rag-view/src/pages/UploadPage/styles/uploadStyles.js create mode 100644 rag-view/src/pages/UploadPage/utils/statusHelpers.js create mode 100644 rag-view/src/pages/UploadPageOld.jsx create mode 100644 rag-view/src/store.js create mode 100644 rag-view/vite.config.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7ccf85..749047b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -205,7 +205,6 @@ deploy-rag: ENDSSH deploy-gateway: - <<: *deploy_setup <<: *deploy_setup needs: [publish-gateway] script: diff --git a/rag-view/.gitignore b/rag-view/.gitignore new file mode 100644 index 0000000..0760178 --- /dev/null +++ b/rag-view/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.env + +# Editor directories and files +.vscode/* +.git +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/rag-view/README.md b/rag-view/README.md new file mode 100644 index 0000000..7059a96 --- /dev/null +++ b/rag-view/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/rag-view/docker/Dockerfile b/rag-view/docker/Dockerfile new file mode 100644 index 0000000..26d99eb --- /dev/null +++ b/rag-view/docker/Dockerfile @@ -0,0 +1,11 @@ +FROM node:22-alpine AS build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html/ragview +EXPOSE 80 \ No newline at end of file diff --git a/rag-view/docker/nginx.conf b/rag-view/docker/nginx.conf new file mode 100644 index 0000000..49f01dd --- /dev/null +++ b/rag-view/docker/nginx.conf @@ -0,0 +1,8 @@ +server { + listen 80; + root /usr/share/nginx/html; + + location /ragview/ { + try_files $uri $uri/ /ragview/index.html; + } +} \ No newline at end of file diff --git a/rag-view/eslint.config.js b/rag-view/eslint.config.js new file mode 100644 index 0000000..cee1e2c --- /dev/null +++ b/rag-view/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/rag-view/index.html b/rag-view/index.html new file mode 100644 index 0000000..7ad8f9c --- /dev/null +++ b/rag-view/index.html @@ -0,0 +1,13 @@ + + + + + + + RAG by Balex + + +
+ + + diff --git a/rag-view/package-lock.json b/rag-view/package-lock.json new file mode 100644 index 0000000..5431e4c --- /dev/null +++ b/rag-view/package-lock.json @@ -0,0 +1,4601 @@ +{ + "name": "rag-view", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rag-view", + "version": "0.0.0", + "dependencies": { + "@reduxjs/toolkit": "^2.10.1", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-redux": "^9.2.0", + "react-router-dom": "^7.0.2", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "tailwindcss-textshadow": "^2.1.3" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@tailwindcss/postcss": "^4.1.18", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "autoprefixer": "^10.4.23", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "^7.1.7" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@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.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@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.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fullhuman/postcss-purgecss": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz", + "integrity": "sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw==", + "license": "MIT", + "dependencies": { + "postcss": "7.0.32", + "purgecss": "^2.3.0" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + }, + "node_modules/@fullhuman/postcss-purgecss/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz", + "integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.2.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", + "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "license": "MIT", + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==", + "license": "MIT" + }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "license": "MIT" + }, + "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-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "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.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-functions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz", + "integrity": "sha512-N5yWXWKA+uhpLQ9ZhBRl2bIAdM6oVJYpDojuI1nF2SzXBimJcdjFwiAouBVbO5VuOF3qA6BSFWFc3wXbbj72XQ==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.2", + "object-assign": "^4.1.1", + "postcss": "^6.0.9", + "postcss-value-parser": "^3.3.0" + } + }, + "node_modules/postcss-functions/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-functions/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-functions/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/postcss-functions/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/postcss-functions/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/postcss-functions/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-functions/node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-functions/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "license": "MIT" + }, + "node_modules/postcss-functions/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-2.0.3.tgz", + "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1", + "postcss": "^7.0.18" + } + }, + "node_modules/postcss-js/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/postcss-js/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-nested": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", + "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", + "license": "MIT", + "dependencies": { + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2" + } + }, + "node_modules/postcss-nested/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/postcss-nested/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/purgecss": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.3.0.tgz", + "integrity": "sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ==", + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.0.0", + "postcss": "7.0.32", + "postcss-selector-parser": "^6.0.2" + }, + "bin": { + "purgecss": "bin/purgecss" + } + }, + "node_modules/purgecss/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/purgecss/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/purgecss/node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/purgecss/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/purgecss/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/purgecss/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/purgecss/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/purgecss/node_modules/postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + }, + "node_modules/purgecss/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz", + "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.6" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "license": "MIT", + "dependencies": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + }, + "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "license": "MIT" + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "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==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss-textshadow": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tailwindcss-textshadow/-/tailwindcss-textshadow-2.1.3.tgz", + "integrity": "sha512-FGVHfK+xnV879VSQDeRvY61Aa+b0GDiGaFBPwCOKvqIrK57GyepWJL1GydjtGOLHE9qqphFucRNj9fHramCzNg==", + "license": "MIT", + "dependencies": { + "tailwindcss": "^1.2.0" + } + }, + "node_modules/tailwindcss-textshadow/node_modules/autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + }, + "node_modules/tailwindcss-textshadow/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/tailwindcss-textshadow/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/tailwindcss-textshadow/node_modules/tailwindcss": { + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.9.6.tgz", + "integrity": "sha512-nY8WYM/RLPqGsPEGEV2z63riyQPcHYZUJpAwdyBzVpxQHOHqHE+F/fvbCeXhdF1+TA5l72vSkZrtYCB9hRcwkQ==", + "license": "MIT", + "dependencies": { + "@fullhuman/postcss-purgecss": "^2.1.2", + "autoprefixer": "^9.4.5", + "browserslist": "^4.12.0", + "bytes": "^3.0.0", + "chalk": "^3.0.0 || ^4.0.0", + "color": "^3.1.2", + "detective": "^5.2.0", + "fs-extra": "^8.0.0", + "html-tags": "^3.1.0", + "lodash": "^4.17.20", + "node-emoji": "^1.8.1", + "normalize.css": "^8.0.1", + "object-hash": "^2.0.3", + "postcss": "^7.0.11", + "postcss-functions": "^3.0.0", + "postcss-js": "^2.0.0", + "postcss-nested": "^4.1.1", + "postcss-selector-parser": "^6.0.0", + "postcss-value-parser": "^4.1.0", + "pretty-hrtime": "^1.0.3", + "reduce-css-calc": "^2.1.6", + "resolve": "^1.14.2" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", + "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/rag-view/package.json b/rag-view/package.json new file mode 100644 index 0000000..4a39863 --- /dev/null +++ b/rag-view/package.json @@ -0,0 +1,37 @@ +{ + "name": "rag-view", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.10.1", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-redux": "^9.2.0", + "react-router-dom": "^7.0.2", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "tailwindcss-textshadow": "^2.1.3" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@tailwindcss/postcss": "^4.1.18", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "autoprefixer": "^10.4.23", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "^7.1.7" + } +} diff --git a/rag-view/postcss.config.js b/rag-view/postcss.config.js new file mode 100644 index 0000000..c2ddf74 --- /dev/null +++ b/rag-view/postcss.config.js @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/rag-view/public/context-questions.txt b/rag-view/public/context-questions.txt new file mode 100644 index 0000000..ccc9864 --- /dev/null +++ b/rag-view/public/context-questions.txt @@ -0,0 +1,31 @@ +Example Questions and Expected Answers +======================================= + +These questions are based on the default context file (guest-example.txt). +Use them to test RAG query behavior with different settings. + +--- Questions with answers found in context --- + +1. What port does the Notification Service run on? + Expected answer: 8082 + +2. How long are access tokens valid in Project Aurora? + Expected answer: 30 minutes + +3. What is the database naming convention? + Expected answer: aurora_{service_name} + +4. Who is the lead developer? + Expected answer: Maria Chen + +5. What message broker does Aurora use? + Expected answer: RabbitMQ on port 5672 + +6. What happens when CPU goes above 70%? + Expected answer: HPA scales pods, from 2 to 10 replicas + +--- Question to test onlyContext mode --- + +7. What is the capital of France? + With onlyContext=true: should respond that the answer is not found in context + With onlyContext=false: should answer "Paris" diff --git a/rag-view/public/guest-example.txt b/rag-view/public/guest-example.txt new file mode 100644 index 0000000..f1e448a --- /dev/null +++ b/rag-view/public/guest-example.txt @@ -0,0 +1,30 @@ +Project Aurora - Internal Technical Documentation + +Project Aurora is an internal microservices platform developed by NovaTech Solutions +in Q3 2024. The platform consists of five core services: Gateway Service (port 8080), +User Management Service (port 8081), Notification Service (port 8082), Analytics +Service (port 8083), and Billing Service (port 8084). + +The Gateway Service uses Spring Cloud Gateway and requires a minimum of 512MB RAM. +All inter-service communication is handled via RabbitMQ running on port 5672. +The default exchange name is "aurora.exchange" and the dead letter queue is +"aurora.dlq". + +Database configuration: each service has its own PostgreSQL schema. The naming +convention is aurora_{service_name}. For example, the User Management Service +uses the schema aurora_user_management. Connection pooling is managed by HikariCP +with a maximum pool size of 15 connections per service. + +Authentication is handled by the User Management Service using JWT tokens with +RS256 algorithm. Access tokens expire after 30 minutes, refresh tokens after 7 days. +The public key for token verification is available at +http://user-service:8081/.well-known/jwks.json. + +Deployment: all services are containerized using Docker and orchestrated with +Kubernetes. The production cluster runs on 3 nodes with a minimum of 8GB RAM each. +Horizontal Pod Autoscaler is configured to scale between 2 and 10 replicas based +on CPU utilization threshold of 70%. + +The lead developer is Maria Chen (maria.chen@novatech.example.com). +The project manager is Alex Kumar (alex.kumar@novatech.example.com). +Weekly sync meetings are held every Wednesday at 14:00 CET. \ No newline at end of file diff --git a/rag-view/src/App.jsx b/rag-view/src/App.jsx new file mode 100644 index 0000000..2b82c0d --- /dev/null +++ b/rag-view/src/App.jsx @@ -0,0 +1,50 @@ +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { useEffect, useRef } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import fetchUserProfile from "./features/fetch-async/fetchUserProfile"; +import { TOKEN_UNDEFINED } from "./features/constants"; + +import RootLayout from "./pages/RootLayout"; +import HomePage from "./pages/HomePage"; +import RegisterPage from "./pages/RegisterPage"; +import LoginPage from "./pages/LoginPage"; +import UploadPage from "./pages/UploadPage"; +import RagPage from "./pages/RagPage"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + children: [ + { index: true, element: }, + { path: "register", element: }, + { path: "login", element: }, + { path: "upload", element: }, + { path: "rag", element: }, + ], + }, +]); + +function App() { + const dispatch = useDispatch(); + const token = useSelector((state) => state.userDetails.token); + const bootstrappedRef = useRef(false); + + const auth = + typeof token === "string" && + token !== TOKEN_UNDEFINED && + token.trim() !== ""; + + useEffect(() => { + if (!auth) { + bootstrappedRef.current = false; + return; + } + if (bootstrappedRef.current) return; + bootstrappedRef.current = true; + dispatch(fetchUserProfile()); + }, [auth, dispatch]); + return ; +} + +export default App; diff --git a/rag-view/src/features/constants.js b/rag-view/src/features/constants.js new file mode 100644 index 0000000..fe3d7f0 --- /dev/null +++ b/rag-view/src/features/constants.js @@ -0,0 +1,54 @@ +export const BASE_URL = `${import.meta.env.VITE_API_BASE_URL}/api/rag`; +export const METHOD_POST_QUERY = "POST"; +export const METHOD_GET_QUERY = "GET"; +export const METHOD_DELETE_QUERY = "DELETE"; +export const HEADER_CONTENT_TYPE = "Content-Type"; +export const JWT_TOKEN = "jwt"; +export const JWT_REFRESH_TOKEN = "jwt-refresh"; +export const APPLICATION_JSON = "application/json"; +export const USER_NAME_UNDEFINED = "userNameUndefined"; +export const USER_EMAIL_UNDEFINED = "userEmailUndefined"; +export const GUEST_EMAIL = "guest@gmail.com"; +export const GUEST_PASSWORD = "Guest123!"; +export const TOKEN_EXPIRED = "Token expired."; +export const ERROR_RESPONSE_NOT_OK = "Response not ok"; +export const ERROR_NO_RESPONSE = "No response from server"; +export const PREFIX_AUTH = "/auth"; +export const PREFIX_USERS = "/users"; +export const PREFIX_CHAT = "/chat"; +export const PREFIX_ENTRY = "/entry"; +export const PREFIX_CREATE_NEW_CHAT = "/new"; +export const PREFIX_USERINFO = "/userinfo"; +export const PREFIX_REGISTER = "/register"; +export const PREFIX_LOGIN = "/login"; +export const PREFIX_REFRESH_TOKEN = "/refresh/token"; +export const PREFIX_DOCUMENT = "/documents"; +export const PREFIX_UPLOAD = "/upload"; +export const PREFIX_UPLOAD_STREAM = "/upload-stream"; +export const FORM_DATA_FILES = "files"; +export const TYPE_WINDOW_UNDEFINED = "undefined"; +export const TOKEN_UNDEFINED = "undefined"; +export const MAX_FILE_SIZE_KB = 10; +export const MAX_FILES_TO_UPLOAD = 3; +export const DEFAULT_ERROR_STATUS = 500; +export const FULFILLED_BUT_NOT_SUCCESS_ERROR_STATUS = 999; +export const UNKNOWN_ERROR_AFTER_FULFILLED_QUERY = + "Unknown error after fulfilled query"; +export const UNKNOWN_ERROR = "Unknown error"; +export const STATUS_UNAUTHORIZED = "unauthorized"; +export const HTTP_STATUS_UNAUTHORIZED_CODE = 401; +export const NO_REFRESH_TOKEN = "No refresh token"; +export const PROGRESS_STATUS_PROCESSING = "processing"; +export const PROGRESS_STATUS_COMPLETED = "completed"; +export const PROGRESS_STATUS_ERROR = "error"; +export const PROGRESS_STATUS_SKIPPED = "skipped"; +export const IS_SEARCH_RESULT_ONLY_WITH_CONTEXT_DEFAULT = true; +export const TOP_P_FAST_VALUE = 0.5; +export const TOP_P_DEFAULT_VALUE = 0.6; +export const TOP_P_SLOW_VALUE = 0.9; +export const NO_ACTIVE_CHAT_ID = 0; +export const MAX_TITLE_LENGTH = 30; +export const MESSAGE_ROLE = { + USER: "user", + ASSISTANT: "assistant", +}; diff --git a/rag-view/src/features/fetch-async/fetchAddNewUserEntry.js b/rag-view/src/features/fetch-async/fetchAddNewUserEntry.js new file mode 100644 index 0000000..703df8f --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchAddNewUserEntry.js @@ -0,0 +1,44 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_ENTRY, + METHOD_POST_QUERY, + STATUS_UNAUTHORIZED, + UNKNOWN_ERROR, + HTTP_STATUS_UNAUTHORIZED_CODE, +} from "../constants"; + +const fetchAddNewUserEntry = createAsyncThunk( + "chat/fetchAddNewUserEntry", + async ({ chatId, content, onlyContext, topP }, thunkAPI) => { + const { rejectWithValue } = thunkAPI; + + try { + const res = await fetchWithAuth( + `${BASE_URL}${PREFIX_ENTRY}/${chatId}`, + { + method: METHOD_POST_QUERY, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content, onlyContext, topP }), + }, + thunkAPI, + ); + + if (res.status === HTTP_STATUS_UNAUTHORIZED_CODE) { + return rejectWithValue({ status: STATUS_UNAUTHORIZED }); + } + + if (!res.ok) { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + + const data = await res.json(); + return data; + } catch { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + }, +); + +export default fetchAddNewUserEntry; diff --git a/rag-view/src/features/fetch-async/fetchCreateNewChat.js b/rag-view/src/features/fetch-async/fetchCreateNewChat.js new file mode 100644 index 0000000..943577c --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchCreateNewChat.js @@ -0,0 +1,43 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_CHAT, + PREFIX_CREATE_NEW_CHAT, + METHOD_POST_QUERY, + STATUS_UNAUTHORIZED, + UNKNOWN_ERROR, + HTTP_STATUS_UNAUTHORIZED_CODE, +} from "../constants"; + +const fetchCreateNewChat = createAsyncThunk( + "chat/fetchCreateNewChat", + async (title, thunkAPI) => { + const { rejectWithValue } = thunkAPI; + + try { + const res = await fetchWithAuth( + `${BASE_URL}${PREFIX_CHAT}${PREFIX_CREATE_NEW_CHAT}?title=${encodeURIComponent( + title, + )}`, + { method: METHOD_POST_QUERY }, + thunkAPI, + ); + + if (res.status === HTTP_STATUS_UNAUTHORIZED_CODE) { + return rejectWithValue({ status: STATUS_UNAUTHORIZED }); + } + + if (!res.ok) { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + + const data = await res.json(); + return data; + } catch { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + }, +); + +export default fetchCreateNewChat; diff --git a/rag-view/src/features/fetch-async/fetchDeleteChat.js b/rag-view/src/features/fetch-async/fetchDeleteChat.js new file mode 100644 index 0000000..d5cf3e1 --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchDeleteChat.js @@ -0,0 +1,50 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_CHAT, + METHOD_DELETE_QUERY, + STATUS_UNAUTHORIZED, + UNKNOWN_ERROR, + HTTP_STATUS_UNAUTHORIZED_CODE, +} from "../constants"; + +const fetchDeleteChat = createAsyncThunk( + "chat/fetchDeleteChat", + async (chatId, thunkAPI) => { + const { rejectWithValue } = thunkAPI; + + try { + const res = await fetchWithAuth( + `${BASE_URL}${PREFIX_CHAT}/${chatId}`, + { method: METHOD_DELETE_QUERY }, + thunkAPI, + ); + + if (res.status === HTTP_STATUS_UNAUTHORIZED_CODE) { + return rejectWithValue({ status: STATUS_UNAUTHORIZED }); + } + + if (!res.ok) { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + + // DELETE requests may return 204 No Content without a body + if (res.status === 204) { + return { success: true, chatId }; + } + + try { + const data = await res.json(); + return data; + } catch { + // If JSON parsing fails, still consider it a success + return { success: true, chatId }; + } + } catch { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + }, +); + +export default fetchDeleteChat; diff --git a/rag-view/src/features/fetch-async/fetchDeleteUploaded.js b/rag-view/src/features/fetch-async/fetchDeleteUploaded.js new file mode 100644 index 0000000..c53050a --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchDeleteUploaded.js @@ -0,0 +1,33 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_USERS, + PREFIX_USERINFO, + METHOD_DELETE_QUERY, + ERROR_RESPONSE_NOT_OK, +} from "../constants"; + +const fetchDeleteUploaded = createAsyncThunk( + "userDetails/fetchDeleteUploaded", + async (_, thunkAPI) => { + const res = await fetchWithAuth( + BASE_URL + PREFIX_USERS + PREFIX_USERINFO, + { method: METHOD_DELETE_QUERY }, + thunkAPI + ); + + if (!res.ok) { + const text = await res.text().catch(() => ""); + return thunkAPI.rejectWithValue({ + status: res.status, + message: text || ERROR_RESPONSE_NOT_OK, + }); + } + + const data = await res.json(); + return data; + } +); + +export default fetchDeleteUploaded; diff --git a/rag-view/src/features/fetch-async/fetchGetChat.js b/rag-view/src/features/fetch-async/fetchGetChat.js new file mode 100644 index 0000000..7caea33 --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchGetChat.js @@ -0,0 +1,40 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_CHAT, + METHOD_GET_QUERY, + STATUS_UNAUTHORIZED, + UNKNOWN_ERROR, + HTTP_STATUS_UNAUTHORIZED_CODE, +} from "../constants"; + +const fetchGetChat = createAsyncThunk( + "chat/fetchGetChat", + async (chatId, thunkAPI) => { + const { rejectWithValue } = thunkAPI; + + try { + const res = await fetchWithAuth( + `${BASE_URL}${PREFIX_CHAT}/${chatId}`, + { method: METHOD_GET_QUERY }, + thunkAPI, + ); + + if (res.status === HTTP_STATUS_UNAUTHORIZED_CODE) { + return rejectWithValue({ status: STATUS_UNAUTHORIZED }); + } + + if (!res.ok) { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + + const data = await res.json(); + return data; + } catch { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + }, +); + +export default fetchGetChat; diff --git a/rag-view/src/features/fetch-async/fetchGetChatList.js b/rag-view/src/features/fetch-async/fetchGetChatList.js new file mode 100644 index 0000000..2b906aa --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchGetChatList.js @@ -0,0 +1,40 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { + BASE_URL, + PREFIX_CHAT, + METHOD_GET_QUERY, + STATUS_UNAUTHORIZED, + UNKNOWN_ERROR, + HTTP_STATUS_UNAUTHORIZED_CODE, +} from "../constants"; + +const fetchGetChatList = createAsyncThunk( + "chat/fetchGetChatList", + async (_, thunkAPI) => { + const { rejectWithValue } = thunkAPI; + + try { + const res = await fetchWithAuth( + `${BASE_URL}${PREFIX_CHAT}`, + { method: METHOD_GET_QUERY }, + thunkAPI, + ); + + if (res.status === HTTP_STATUS_UNAUTHORIZED_CODE) { + return rejectWithValue({ status: STATUS_UNAUTHORIZED }); + } + + if (!res.ok) { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + + const data = await res.json(); + return data; + } catch { + return rejectWithValue({ status: UNKNOWN_ERROR }); + } + }, +); + +export default fetchGetChatList; diff --git a/rag-view/src/features/fetch-async/fetchLoginUser.js b/rag-view/src/features/fetch-async/fetchLoginUser.js new file mode 100644 index 0000000..57e3874 --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchLoginUser.js @@ -0,0 +1,54 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { + BASE_URL, + METHOD_POST_QUERY, + HEADER_CONTENT_TYPE, + PREFIX_AUTH, + PREFIX_LOGIN, + APPLICATION_JSON, + ERROR_RESPONSE_NOT_OK, + ERROR_NO_RESPONSE, +} from "../constants"; + +const fetchLoginUser = createAsyncThunk( + "userDetails/fetchLoginUser", + async (payload, { rejectWithValue }) => { + try { + const res = await fetch(BASE_URL + PREFIX_AUTH + PREFIX_LOGIN, { + method: METHOD_POST_QUERY, + headers: { [HEADER_CONTENT_TYPE]: APPLICATION_JSON }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + // Проверяем Content-Type ответа + const contentType = res.headers.get("Content-Type") || ""; + let errorMessage = ERROR_RESPONSE_NOT_OK; + + if (contentType.includes("application/json")) { + const errorData = await res.json().catch(() => ({})); + errorMessage = errorData.message || ERROR_RESPONSE_NOT_OK; + } else { + // text/plain ответ + const textError = await res.text().catch(() => ""); + errorMessage = textError || ERROR_RESPONSE_NOT_OK; + } + + return rejectWithValue({ + status: res.status, + message: errorMessage, + }); + } + + const data = await res.json(); + return data; + } catch (err) { + return rejectWithValue({ + status: 500, + message: `${ERROR_NO_RESPONSE}: ${err}`, + }); + } + } +); + +export default fetchLoginUser; diff --git a/rag-view/src/features/fetch-async/fetchRefreshToken.js b/rag-view/src/features/fetch-async/fetchRefreshToken.js new file mode 100644 index 0000000..3425eba --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchRefreshToken.js @@ -0,0 +1,54 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { + BASE_URL, + METHOD_GET_QUERY, + PREFIX_AUTH, + PREFIX_REFRESH_TOKEN, + TOKEN_UNDEFINED, + JWT_REFRESH_TOKEN, + ERROR_RESPONSE_NOT_OK, + ERROR_NO_RESPONSE, + NO_REFRESH_TOKEN, +} from "../constants"; + +const fetchRefreshToken = createAsyncThunk( + "userDetails/fetchRefreshToken", + async (_, { getState, rejectWithValue }) => { + try { + const state = getState(); + const refreshToken = + state.userDetails?.refreshToken || + localStorage.getItem(JWT_REFRESH_TOKEN); + + if (!refreshToken || refreshToken === TOKEN_UNDEFINED) { + return rejectWithValue({ status: 401, message: NO_REFRESH_TOKEN }); + } + + const url = + BASE_URL + + PREFIX_AUTH + + PREFIX_REFRESH_TOKEN + + `?token=${encodeURIComponent(refreshToken)}`; + + const res = await fetch(url, { method: METHOD_GET_QUERY }); + + if (!res.ok) { + const text = await res.text().catch(() => ""); + return rejectWithValue({ + status: res.status, + message: text || ERROR_RESPONSE_NOT_OK, + }); + } + + const data = await res.json(); + return data; + } catch (err) { + return rejectWithValue({ + status: 500, + message: `${ERROR_NO_RESPONSE}: ${err}`, + }); + } + } +); + +export default fetchRefreshToken; diff --git a/rag-view/src/features/fetch-async/fetchRegisterUser.js b/rag-view/src/features/fetch-async/fetchRegisterUser.js new file mode 100644 index 0000000..1c0809c --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchRegisterUser.js @@ -0,0 +1,52 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { + BASE_URL, + METHOD_POST_QUERY, + HEADER_CONTENT_TYPE, + PREFIX_AUTH, + PREFIX_REGISTER, + APPLICATION_JSON, + ERROR_RESPONSE_NOT_OK, + ERROR_NO_RESPONSE, +} from "../constants"; + +const fetchRegisterUser = createAsyncThunk( + "userDetails/fetchRegisterUser", + async (payload, { rejectWithValue }) => { + try { + const res = await fetch(BASE_URL + PREFIX_AUTH + PREFIX_REGISTER, { + method: METHOD_POST_QUERY, + headers: { [HEADER_CONTENT_TYPE]: APPLICATION_JSON }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + const contentType = res.headers.get("Content-Type") || ""; + let errorMessage = ERROR_RESPONSE_NOT_OK; + + if (contentType.includes("application/json")) { + const errorData = await res.json().catch(() => ({})); + errorMessage = errorData.message || ERROR_RESPONSE_NOT_OK; + } else { + const textError = await res.text().catch(() => ""); + errorMessage = textError || ERROR_RESPONSE_NOT_OK; + } + + return rejectWithValue({ + status: res.status, + message: errorMessage, + }); + } + + const data = await res.json(); + return data; + } catch (err) { + return rejectWithValue({ + status: 500, + message: `${ERROR_NO_RESPONSE}: ${err}`, + }); + } + } +); + +export default fetchRegisterUser; diff --git a/rag-view/src/features/fetch-async/fetchUserProfile.js b/rag-view/src/features/fetch-async/fetchUserProfile.js new file mode 100644 index 0000000..bd2f6e7 --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchUserProfile.js @@ -0,0 +1,35 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchWithAuth } from "./fetchWithAuth"; +import { logoutUser } from "../slices/details-slice"; +import { + BASE_URL, + PREFIX_USERS, + PREFIX_USERINFO, + METHOD_GET_QUERY, +} from "../constants"; + +const fetchUserProfile = createAsyncThunk( + "userDetails/fetchUserProfile", + async (_, thunkAPI) => { + try { + const res = await fetchWithAuth( + BASE_URL + PREFIX_USERS + PREFIX_USERINFO, + { method: METHOD_GET_QUERY }, + thunkAPI, + ); + + const text = await res.text().catch(() => ""); + if (!res.ok) { + thunkAPI.dispatch(logoutUser()); + return; + } + + const data = JSON.parse(text); + return data; + } catch { + thunkAPI.dispatch(logoutUser()); + } + }, +); + +export default fetchUserProfile; diff --git a/rag-view/src/features/fetch-async/fetchWithAuth.js b/rag-view/src/features/fetch-async/fetchWithAuth.js new file mode 100644 index 0000000..ca9f764 --- /dev/null +++ b/rag-view/src/features/fetch-async/fetchWithAuth.js @@ -0,0 +1,46 @@ +import fetchRefreshToken from "./fetchRefreshToken"; +import { JWT_TOKEN } from "../constants"; + +const SESSION_INVALIDATED = "SESSION_INVALIDATED"; + +export const fetchWithAuth = async ( + url, + options = {}, + { dispatch, getState }, +) => { + const state = getState(); + const token = state.userDetails?.token || localStorage.getItem(JWT_TOKEN); + + const headers = { + ...(options.headers || {}), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }; + + const firstRes = await fetch(url, { ...options, headers }); + + if (firstRes.status !== 401) return firstRes; + + const responseText = await firstRes.clone().text(); + + if (responseText === SESSION_INVALIDATED) { + localStorage.clear(); + window.location.href = "/login"; + return firstRes; + } + + try { + await dispatch(fetchRefreshToken()).unwrap(); + } catch { + return firstRes; + } + + const newToken = + getState().userDetails?.token || localStorage.getItem(JWT_TOKEN); + + const retryHeaders = { + ...(options.headers || {}), + ...(newToken ? { Authorization: `Bearer ${newToken}` } : {}), + }; + + return fetch(url, { ...options, headers: retryHeaders }); +}; diff --git a/rag-view/src/features/slices/chat-slice/deleteChatExtraReducers.js b/rag-view/src/features/slices/chat-slice/deleteChatExtraReducers.js new file mode 100644 index 0000000..3c23a37 --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/deleteChatExtraReducers.js @@ -0,0 +1,24 @@ +import fetchDeleteChat from "../../fetch-async/fetchDeleteChat"; + +export const buildDeleteChatExtraReducers = (builder) => { + builder + .addCase(fetchDeleteChat.fulfilled, (state, action) => { + const deletedChatId = action.meta.arg; + + state.chatList = state.chatList.filter((chat) => chat.id !== deletedChatId); + + if (state.activeChatId === deletedChatId) { + if (state.chatList.length > 0) { + state.activeChatId = state.chatList[0].id; + state.activeTitle = state.chatList[0].title; + } else { + state.activeChatId = null; + state.activeTitle = ""; + state.messages = []; + } + } + }) + .addCase(fetchDeleteChat.rejected, (_state, action) => { + console.log("fetchDeleteChat error:", action.payload?.status); + }); +}; diff --git a/rag-view/src/features/slices/chat-slice/getChatExtraReducers.js b/rag-view/src/features/slices/chat-slice/getChatExtraReducers.js new file mode 100644 index 0000000..b9d49ce --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/getChatExtraReducers.js @@ -0,0 +1,15 @@ +import fetchGetChat from "../../fetch-async/fetchGetChat"; + +export const buildGetChatExtraReducers = (builder) => { + builder + .addCase(fetchGetChat.fulfilled, (state, action) => { + const chat = action.payload; + + if (chat) { + state.messages = chat.history || []; + } + }) + .addCase(fetchGetChat.rejected, (_state, action) => { + console.log("fetchGetChat error:", action.payload?.status); + }); +}; diff --git a/rag-view/src/features/slices/chat-slice/getChatListExtraReducers.js b/rag-view/src/features/slices/chat-slice/getChatListExtraReducers.js new file mode 100644 index 0000000..24e217a --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/getChatListExtraReducers.js @@ -0,0 +1,16 @@ +import fetchGetChatList from "../../fetch-async/fetchGetChatList"; + +export const buildChatListExtraReducers = (builder) => { + builder + .addCase(fetchGetChatList.fulfilled, (state, action) => { + const chatList = action.payload; + + if (chatList && chatList.length > 0) { + state.chatList = chatList; + state.messages = []; + } + }) + .addCase(fetchGetChatList.rejected, (state, action) => { + console.log("fetchGetChatList error:", action.payload?.status); + }); +}; diff --git a/rag-view/src/features/slices/chat-slice/index.js b/rag-view/src/features/slices/chat-slice/index.js new file mode 100644 index 0000000..d3277fd --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/index.js @@ -0,0 +1,26 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { reducers } from "./reducers"; + +import { initialState } from "./initialState"; +import { buildNewChatExtraReducers } from "./newChatExtraReducers"; +import { buildChatListExtraReducers } from "./getChatListExtraReducers"; +import { buildDeleteChatExtraReducers } from "./deleteChatExtraReducers"; +import { buildNewEntryExtraReducers } from "./newEntryExtraReducers"; +import { buildGetChatExtraReducers } from "./getChatExtraReducers"; + +const chatSlice = createSlice({ + name: "chats", + initialState, + reducers, + extraReducers: (builder) => { + buildNewChatExtraReducers(builder); + buildChatListExtraReducers(builder); + buildDeleteChatExtraReducers(builder); + buildNewEntryExtraReducers(builder); + buildGetChatExtraReducers(builder); + }, +}); + +export const { setActiveChat } = chatSlice.actions; + +export default chatSlice.reducer; diff --git a/rag-view/src/features/slices/chat-slice/initialState.js b/rag-view/src/features/slices/chat-slice/initialState.js new file mode 100644 index 0000000..0e5c4fe --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/initialState.js @@ -0,0 +1,9 @@ +import { NO_ACTIVE_CHAT_ID } from "../../constants"; + +export const initialState = { + chatList: [], + activeChatId: NO_ACTIVE_CHAT_ID, + activeTitle: "", + messages: [], + isWaitingResponse: false, +}; diff --git a/rag-view/src/features/slices/chat-slice/newChatExtraReducers.js b/rag-view/src/features/slices/chat-slice/newChatExtraReducers.js new file mode 100644 index 0000000..fe8e910 --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/newChatExtraReducers.js @@ -0,0 +1,18 @@ +import fetchCreateNewChat from "../../fetch-async/fetchCreateNewChat"; + +export const buildNewChatExtraReducers = (builder) => { + builder + .addCase(fetchCreateNewChat.fulfilled, (state, action) => { + const chat = action.payload; + + if (chat) { + state.chatList.push(chat); + state.activeChatId = chat.id; + state.activeTitle = chat.title; + state.messages = []; + } + }) + .addCase(fetchCreateNewChat.rejected, (state, action) => { + console.log("fetchCreateNewChat error:", action.payload?.status); + }); +}; diff --git a/rag-view/src/features/slices/chat-slice/newEntryExtraReducers.js b/rag-view/src/features/slices/chat-slice/newEntryExtraReducers.js new file mode 100644 index 0000000..05cda61 --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/newEntryExtraReducers.js @@ -0,0 +1,18 @@ +import fetchAddNewUserEntry from "../../fetch-async/fetchAddNewUserEntry"; +import { MESSAGE_ROLE } from "../../constants"; + +export const buildNewEntryExtraReducers = (builder) => { + builder + .addCase(fetchAddNewUserEntry.pending, (state, action) => { + const { content } = action.meta.arg; + state.messages.push({ role: MESSAGE_ROLE.USER, content }); + state.isWaitingResponse = true; + }) + .addCase(fetchAddNewUserEntry.fulfilled, (state) => { + state.isWaitingResponse = false; + }) + .addCase(fetchAddNewUserEntry.rejected, (state, action) => { + state.isWaitingResponse = false; + console.log("fetchAddNewUserEntry error:", action.payload?.status); + }); +}; diff --git a/rag-view/src/features/slices/chat-slice/reducers.js b/rag-view/src/features/slices/chat-slice/reducers.js new file mode 100644 index 0000000..93bddef --- /dev/null +++ b/rag-view/src/features/slices/chat-slice/reducers.js @@ -0,0 +1,8 @@ +export const reducers = { + setActiveChat(state, action) { + const chat = action.payload; + state.activeChatId = chat.id; + state.activeTitle = chat.title; + state.messages = chat.history || []; + }, +}; diff --git a/rag-view/src/features/slices/details-slice/authExtraReducers.js b/rag-view/src/features/slices/details-slice/authExtraReducers.js new file mode 100644 index 0000000..ce945f9 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/authExtraReducers.js @@ -0,0 +1,59 @@ +import fetchLoginUser from "../../fetch-async/fetchLoginUser"; +import fetchRegisterUser from "../../fetch-async/fetchRegisterUser"; +import fetchRefreshToken from "../../fetch-async/fetchRefreshToken"; +import { updateTokens } from "./tokenHelpers"; +import { UNKNOWN_ERROR } from "../../constants"; + +export const buildAuthExtraReducers = (builder) => { + // Register + builder.addCase(fetchRegisterUser.pending, (state) => { + state.loading = true; + state.error = null; + }); + + builder.addCase(fetchRegisterUser.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + + const p = action.payload.payload; + state.userName = p.username; + state.userEmail = p.email; + updateTokens(state, p.token, p.refreshToken); + }); + + builder.addCase(fetchRegisterUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload?.status ?? 500; + }); + + // Login + builder.addCase(fetchLoginUser.pending, (state) => { + state.loading = true; + state.error = null; + }); + + builder.addCase(fetchLoginUser.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + + const p = action.payload.payload; + state.userName = p.username; + state.userEmail = p.email; + updateTokens(state, p.token, p.refreshToken); + }); + + builder.addCase(fetchLoginUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; + }); + + // Refresh Token + builder.addCase(fetchRefreshToken.fulfilled, (state, action) => { + const p = action.payload?.payload; + updateTokens(state, p?.token, p?.refreshToken); + }); + + builder.addCase(fetchRefreshToken.rejected, (state, action) => { + state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; + }); +}; diff --git a/rag-view/src/features/slices/details-slice/deleteExtraReducers.js b/rag-view/src/features/slices/details-slice/deleteExtraReducers.js new file mode 100644 index 0000000..950afb6 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/deleteExtraReducers.js @@ -0,0 +1,25 @@ +import fetchDeleteUploaded from "../../fetch-async/fetchDeleteUploaded"; +import { UNKNOWN_ERROR } from "../../constants"; + +export const buildDeleteExtraReducers = (builder) => { + builder.addCase(fetchDeleteUploaded.pending, (state) => { + state.deleting = true; + state.deleteSuccess = false; + state.deletedCount = 0; + state.error = null; + }); + + builder.addCase(fetchDeleteUploaded.fulfilled, (state, action) => { + state.deleting = false; + state.deleteSuccess = true; + state.deletedCount = action.payload?.payload ?? 0; + state.loadedFiles = []; + state.error = null; + }); + + builder.addCase(fetchDeleteUploaded.rejected, (state, action) => { + state.deleting = false; + state.deleteSuccess = false; + state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; + }); +}; diff --git a/rag-view/src/features/slices/details-slice/index.js b/rag-view/src/features/slices/details-slice/index.js new file mode 100644 index 0000000..c68de67 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/index.js @@ -0,0 +1,7 @@ +export { + logoutUser, + clearError, + clearDeleteStatus, + //addLoadedFiles, + default, +} from "./userDetailsSlice"; diff --git a/rag-view/src/features/slices/details-slice/initialState.js b/rag-view/src/features/slices/details-slice/initialState.js new file mode 100644 index 0000000..377616e --- /dev/null +++ b/rag-view/src/features/slices/details-slice/initialState.js @@ -0,0 +1,36 @@ +import { + USER_NAME_UNDEFINED, + USER_EMAIL_UNDEFINED, + JWT_TOKEN, + JWT_REFRESH_TOKEN, + TYPE_WINDOW_UNDEFINED, + TOKEN_UNDEFINED, +} from "../../constants"; + +const getInitialToken = () => { + if (typeof window === TYPE_WINDOW_UNDEFINED) return null; + + const token = localStorage.getItem(JWT_TOKEN); + if (!token || token === TOKEN_UNDEFINED) return null; + return token; +}; + +const getInitialRefreshToken = () => { + if (typeof window === TYPE_WINDOW_UNDEFINED) return null; + return localStorage.getItem(JWT_REFRESH_TOKEN); +}; + +export const initialState = { + userId: null, + userName: USER_NAME_UNDEFINED, + userEmail: USER_EMAIL_UNDEFINED, + loadedFiles: [], + maxFilesToLoad: 0, + token: getInitialToken(), + refreshToken: getInitialRefreshToken(), + loading: false, + deleting: false, + deleteSuccess: false, + deletedCount: 0, + error: null, +}; diff --git a/rag-view/src/features/slices/details-slice/profileExtraReducers.js b/rag-view/src/features/slices/details-slice/profileExtraReducers.js new file mode 100644 index 0000000..f5811c0 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/profileExtraReducers.js @@ -0,0 +1,27 @@ +import fetchUserProfile from "../../fetch-async/fetchUserProfile"; +import { UNKNOWN_ERROR } from "../../constants"; + +export const buildProfileExtraReducers = (builder) => { + builder.addCase(fetchUserProfile.pending, (state) => { + state.loading = true; + state.error = null; + }); + + builder.addCase(fetchUserProfile.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + + const p = action.payload?.payload; + + if (p?.id) state.userId = p.id; + if (p?.username) state.userName = p.username; + if (p?.email) state.userEmail = p.email; + if (p?.loadedFiles) state.loadedFiles = p.loadedFiles; + if (p?.maxLoadedFiles != null) state.maxFilesToLoad = p.maxLoadedFiles; + }); + + builder.addCase(fetchUserProfile.rejected, (state, action) => { + state.loading = false; + state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; + }); +}; diff --git a/rag-view/src/features/slices/details-slice/reducers.js b/rag-view/src/features/slices/details-slice/reducers.js new file mode 100644 index 0000000..ea66471 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/reducers.js @@ -0,0 +1,26 @@ +import { JWT_TOKEN, JWT_REFRESH_TOKEN } from "../../constants"; + +import { initialState } from "./initialState"; + +export const reducers = { + logoutUser(state) { + Object.assign(state, initialState); + state.token = null; + state.refreshToken = null; + localStorage.removeItem(JWT_TOKEN); + localStorage.removeItem(JWT_REFRESH_TOKEN); + }, + + clearError(state) { + state.error = null; + }, + + clearDeleteStatus(state) { + state.deleteSuccess = false; + state.deletedCount = 0; + }, + + // addLoadedFiles(state, action) { + // state.loadedFiles = [...state.loadedFiles, ...action.payload]; + // }, +}; diff --git a/rag-view/src/features/slices/details-slice/tokenHelpers.js b/rag-view/src/features/slices/details-slice/tokenHelpers.js new file mode 100644 index 0000000..8dbf390 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/tokenHelpers.js @@ -0,0 +1,33 @@ +import { JWT_TOKEN, JWT_REFRESH_TOKEN } from "../../constants"; + +/** + * Helper to update token in state and localStorage + */ +export const updateToken = (state, token) => { + state.token = token || null; + if (token) { + localStorage.setItem(JWT_TOKEN, token); + } else { + localStorage.removeItem(JWT_TOKEN); + } +}; + +/** + * Helper to update refresh token in state and localStorage + */ +export const updateRefreshToken = (state, refreshToken) => { + state.refreshToken = refreshToken || null; + if (refreshToken) { + localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); + } else { + localStorage.removeItem(JWT_REFRESH_TOKEN); + } +}; + +/** + * Helper to update both tokens + */ +export const updateTokens = (state, token, refreshToken) => { + updateToken(state, token); + updateRefreshToken(state, refreshToken); +}; diff --git a/rag-view/src/features/slices/details-slice/userDetailsSlice.js b/rag-view/src/features/slices/details-slice/userDetailsSlice.js new file mode 100644 index 0000000..4f2d476 --- /dev/null +++ b/rag-view/src/features/slices/details-slice/userDetailsSlice.js @@ -0,0 +1,24 @@ +import { createSlice } from "@reduxjs/toolkit"; + +import { initialState } from "./initialState"; +import { reducers } from "./reducers"; +import { buildAuthExtraReducers } from "./authExtraReducers"; +import { buildProfileExtraReducers } from "./profileExtraReducers"; +import { buildDeleteExtraReducers } from "./deleteExtraReducers"; + +const userDetailsSlice = createSlice({ + name: "userDetails", + initialState, + reducers, + extraReducers: (builder) => { + buildAuthExtraReducers(builder); + buildProfileExtraReducers(builder); + buildDeleteExtraReducers(builder); + }, +}); + +//export const { logoutUser, clearError, clearDeleteStatus, addLoadedFiles } = +export const { logoutUser, clearError, clearDeleteStatus } = + userDetailsSlice.actions; + +export default userDetailsSlice.reducer; diff --git a/rag-view/src/features/slices/rag-slice/index.js b/rag-view/src/features/slices/rag-slice/index.js new file mode 100644 index 0000000..5103268 --- /dev/null +++ b/rag-view/src/features/slices/rag-slice/index.js @@ -0,0 +1,13 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { initialState } from "./initialState"; +import { reducers } from "./reducers"; + +const ragSlice = createSlice({ + name: "ragConfig", + initialState, + reducers, +}); + +export const { switchSearchMode, setTopP } = ragSlice.actions; + +export default ragSlice.reducer; diff --git a/rag-view/src/features/slices/rag-slice/initialState.js b/rag-view/src/features/slices/rag-slice/initialState.js new file mode 100644 index 0000000..f9df897 --- /dev/null +++ b/rag-view/src/features/slices/rag-slice/initialState.js @@ -0,0 +1,9 @@ +import { + IS_SEARCH_RESULT_ONLY_WITH_CONTEXT_DEFAULT, + TOP_P_DEFAULT_VALUE, +} from "../../constants"; + +export const initialState = { + isUseOnlyContextSearch: IS_SEARCH_RESULT_ONLY_WITH_CONTEXT_DEFAULT, + topP: TOP_P_DEFAULT_VALUE, +}; diff --git a/rag-view/src/features/slices/rag-slice/reducers.js b/rag-view/src/features/slices/rag-slice/reducers.js new file mode 100644 index 0000000..801302f --- /dev/null +++ b/rag-view/src/features/slices/rag-slice/reducers.js @@ -0,0 +1,9 @@ +export const reducers = { + switchSearchMode(state) { + state.isUseOnlyContextSearch = !state.isUseOnlyContextSearch; + }, + + setTopP(state, action) { + state.topP = action.payload; + }, +}; diff --git a/rag-view/src/features/slices/upload-slice/abortController.js b/rag-view/src/features/slices/upload-slice/abortController.js new file mode 100644 index 0000000..647b29d --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/abortController.js @@ -0,0 +1,19 @@ +let currentAbortController = null; + +export const getAbortController = () => currentAbortController; + +export const createAbortController = () => { + currentAbortController = new AbortController(); + return currentAbortController; +}; + +export const abortAndClear = () => { + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + } +}; + +export const clearAbortController = () => { + currentAbortController = null; +}; diff --git a/rag-view/src/features/slices/upload-slice/index.js b/rag-view/src/features/slices/upload-slice/index.js new file mode 100644 index 0000000..dff0dd9 --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/index.js @@ -0,0 +1,48 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { initialState } from "./initialState"; +import { reducers } from "./reducers"; +import { createUploadThunks } from "./thunks"; +import { + validateFilesQuota as createValidateFilesQuota, + canUploadFiles, + getRemainingSlots, +} from "./selectors"; + +const uploadFilesSlice = createSlice({ + name: "uploadFiles", + initialState, + reducers, +}); + +export const { + resetUploadState, + setAreFilesValid, + setQuotaError, + clearQuotaError, + uploadStarted, + progressUpdated, + uploadCompleted, + uploadFailed, + uploadCancelled, +} = uploadFilesSlice.actions; + +const { cancelUpload, uploadFilesWithProgress } = createUploadThunks( + uploadFilesSlice.actions +); + +const validateFilesQuota = (files) => + createValidateFilesQuota(files, { + setQuotaError, + setAreFilesValid, + clearQuotaError, + }); + +export { + cancelUpload, + uploadFilesWithProgress, + validateFilesQuota, + canUploadFiles, + getRemainingSlots, +}; + +export default uploadFilesSlice.reducer; diff --git a/rag-view/src/features/slices/upload-slice/initialState.js b/rag-view/src/features/slices/upload-slice/initialState.js new file mode 100644 index 0000000..c417f20 --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/initialState.js @@ -0,0 +1,18 @@ +export const initialState = { + uploading: false, + error: { + status: null, + message: "", + }, + success: false, + areFilesValid: true, + quotaError: null, + uploadedFiles: [], + progress: { + percent: 0, + processedFiles: 0, + totalFiles: 0, + currentFile: "", + status: "", + }, +}; diff --git a/rag-view/src/features/slices/upload-slice/reducers.js b/rag-view/src/features/slices/upload-slice/reducers.js new file mode 100644 index 0000000..54b7305 --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/reducers.js @@ -0,0 +1,62 @@ +import { initialState } from "./initialState"; +import { abortAndClear } from "./abortController"; +import { DEFAULT_ERROR_STATUS, UNKNOWN_ERROR } from "../../constants"; + +export const reducers = { + resetUploadState() { + abortAndClear(); + return initialState; + }, + + setAreFilesValid(state, action) { + state.areFilesValid = action.payload; + }, + + setQuotaError(state, action) { + state.quotaError = action.payload; + }, + + clearQuotaError(state) { + state.quotaError = null; + }, + + uploadStarted(state, action) { + state.uploading = true; + state.error = { status: null, message: "" }; + state.success = false; + state.quotaError = null; + state.progress = { + percent: 0, + processedFiles: 0, + totalFiles: action.payload.totalFiles, + currentFile: "", + status: "starting", + }; + }, + + progressUpdated(state, action) { + state.progress = action.payload; + }, + + uploadCompleted(state, action) { + state.uploading = false; + state.success = true; + state.uploadedFiles = action.payload.fileNames; + }, + + uploadFailed(state, action) { + state.uploading = false; + state.success = false; + state.error = { + status: action.payload.status ?? DEFAULT_ERROR_STATUS, + message: action.payload.message ?? UNKNOWN_ERROR, + }; + }, + + uploadCancelled(state) { + state.uploading = false; + state.success = false; + state.error = { status: null, message: "" }; + state.progress = initialState.progress; + }, +}; diff --git a/rag-view/src/features/slices/upload-slice/selectors.js b/rag-view/src/features/slices/upload-slice/selectors.js new file mode 100644 index 0000000..b665dee --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/selectors.js @@ -0,0 +1,74 @@ +import { MAX_FILES_TO_UPLOAD } from "../../constants"; + +const getQuotaInfo = (state) => { + const loadedFiles = state.userDetails?.loadedFiles || []; + const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; + const loadedCount = loadedFiles.length; + const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); + + return { loadedFiles, maxFilesToLoad, loadedCount, remainingSlots }; +}; + +export const validateFilesQuota = + (files, { setQuotaError, setAreFilesValid, clearQuotaError }) => + (dispatch, getState) => { + const state = getState(); + const { maxFilesToLoad, loadedCount, remainingSlots } = getQuotaInfo(state); + const filesCount = files.length; + + if (filesCount > MAX_FILES_TO_UPLOAD) { + const errorMsg = `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`; + dispatch(setQuotaError(errorMsg)); + dispatch(setAreFilesValid(false)); + return { valid: false, error: errorMsg }; + } + + if (filesCount > remainingSlots) { + const errorMsg = + remainingSlots === 0 + ? `You have reached the maximum number of files (${maxFilesToLoad})` + : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`; + dispatch(setQuotaError(errorMsg)); + dispatch(setAreFilesValid(false)); + return { valid: false, error: errorMsg }; + } + + dispatch(clearQuotaError()); + dispatch(setAreFilesValid(true)); + return { valid: true, error: null }; + }; + +export const canUploadFiles = () => (dispatch, getState) => { + const state = getState(); + const { remainingSlots } = getQuotaInfo(state); + return remainingSlots > 0; +}; + +export const getRemainingSlots = () => (dispatch, getState) => { + const state = getState(); + const { remainingSlots } = getQuotaInfo(state); + return remainingSlots; +}; + +export const validateQuotaBeforeUpload = (state, filesCount) => { + const { maxFilesToLoad, loadedCount, remainingSlots } = getQuotaInfo(state); + + if (filesCount > MAX_FILES_TO_UPLOAD) { + return { + status: 400, + message: `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`, + }; + } + + if (filesCount > remainingSlots) { + return { + status: 400, + message: + remainingSlots === 0 + ? `You have reached the maximum number of files (${maxFilesToLoad})` + : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`, + }; + } + + return null; +}; diff --git a/rag-view/src/features/slices/upload-slice/thunks.js b/rag-view/src/features/slices/upload-slice/thunks.js new file mode 100644 index 0000000..c8c7b45 --- /dev/null +++ b/rag-view/src/features/slices/upload-slice/thunks.js @@ -0,0 +1,190 @@ +import fetchUserProfile from "../../fetch-async/fetchUserProfile"; +import { + BASE_URL, + METHOD_POST_QUERY, + PREFIX_DOCUMENT, + PREFIX_UPLOAD_STREAM, + FORM_DATA_FILES, + DEFAULT_ERROR_STATUS, + PROGRESS_STATUS_COMPLETED, + PROGRESS_STATUS_ERROR, + ERROR_NO_RESPONSE, +} from "../../constants"; +import { + createAbortController, + abortAndClear, + clearAbortController, +} from "./abortController"; +import { validateQuotaBeforeUpload } from "./selectors"; + +export const createUploadThunks = (actions) => { + const { + uploadStarted, + progressUpdated, + uploadCompleted, + uploadFailed, + uploadCancelled, + } = actions; + + const cancelUpload = () => (dispatch) => { + abortAndClear(); + dispatch(uploadCancelled()); + }; + + const uploadFilesWithProgress = (files) => async (dispatch, getState) => { + const filesArray = Array.from(files); + const fileNames = filesArray.map((f) => f.name); + const state = getState(); + const quotaError = validateQuotaBeforeUpload(state, filesArray.length); + + if (quotaError) { + dispatch(uploadFailed(quotaError)); + return; + } + + const abortController = createAbortController(); + const signal = abortController.signal; + + dispatch(uploadStarted({ totalFiles: filesArray.length })); + + const formData = new FormData(); + filesArray.forEach((file) => { + formData.append(FORM_DATA_FILES, file); + }); + + const token = state.userDetails?.token; + const headers = {}; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + try { + const response = await fetch( + `${BASE_URL}${PREFIX_DOCUMENT}${PREFIX_UPLOAD_STREAM}`, + { + method: METHOD_POST_QUERY, + headers, + body: formData, + signal, + } + ); + + if (!response.ok) { + dispatch( + uploadFailed({ + status: response.status, + message: `Server error: ${response.statusText}`, + }) + ); + return; + } + + await processSSEStream(response, signal, dispatch, fileNames, { + progressUpdated, + uploadCompleted, + uploadFailed, + }); + + const currentState = getState(); + if (currentState.uploadFiles.uploading) { + dispatch(uploadCompleted({ fileNames })); + dispatch(fetchUserProfile()); + } + } catch (err) { + if (err.name === "AbortError") { + console.log("Upload cancelled by user"); + return; + } + + console.error("Upload stream error:", err); + dispatch( + uploadFailed({ + status: DEFAULT_ERROR_STATUS, + message: `${ERROR_NO_RESPONSE}: ${err.message}`, + }) + ); + } finally { + clearAbortController(); + } + }; + + return { + cancelUpload, + uploadFilesWithProgress, + }; +}; + +async function processSSEStream( + response, + signal, + dispatch, + fileNames, + actions +) { + const { progressUpdated, uploadCompleted, uploadFailed } = actions; + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + try { + while (true) { + if (signal.aborted) { + reader.cancel(); + return; + } + + const { done, value } = await reader.read(); + + if (done) { + break; + } + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + + for (const line of lines) { + processSSELine(line, dispatch, fileNames, { + progressUpdated, + uploadCompleted, + uploadFailed, + }); + } + } + } finally { + reader.releaseLock(); + } +} + +function processSSELine(line, dispatch, fileNames, actions) { + const { progressUpdated, uploadCompleted, uploadFailed } = actions; + + if (!line.startsWith("data:")) { + return; + } + + const jsonStr = line.slice(5).trim(); + if (!jsonStr || !jsonStr.startsWith("{")) { + return; + } + + try { + const progressData = JSON.parse(jsonStr); + dispatch(progressUpdated(progressData)); + + if (progressData.status === PROGRESS_STATUS_COMPLETED) { + dispatch(uploadCompleted({ fileNames })); + dispatch(fetchUserProfile()); + } else if (progressData.status === PROGRESS_STATUS_ERROR) { + dispatch( + uploadFailed({ + status: DEFAULT_ERROR_STATUS, + message: `Error processing file: ${progressData.currentFile}`, + }) + ); + } + } catch (parseError) { + console.error("Failed to parse SSE data:", parseError); + } +} diff --git a/rag-view/src/features/uploadFilesSliceOld.js b/rag-view/src/features/uploadFilesSliceOld.js new file mode 100644 index 0000000..8d305a0 --- /dev/null +++ b/rag-view/src/features/uploadFilesSliceOld.js @@ -0,0 +1,333 @@ +import { createSlice } from "@reduxjs/toolkit"; +//import { addLoadedFiles } from "./slices/details-slice"; +import fetchUserProfile from "./fetch-async/fetchUserProfile"; +import { + BASE_URL, + METHOD_POST_QUERY, + PREFIX_DOCUMENT, + PREFIX_UPLOAD_STREAM, + FORM_DATA_FILES, + DEFAULT_ERROR_STATUS, + PROGRESS_STATUS_COMPLETED, + PROGRESS_STATUS_ERROR, + ERROR_NO_RESPONSE, + UNKNOWN_ERROR, + MAX_FILES_TO_UPLOAD, +} from "./constants"; + +const initialState = { + uploading: false, + error: { + status: null, + message: "", + }, + success: false, + areFilesValid: true, + quotaError: null, + uploadedFiles: [], + progress: { + percent: 0, + processedFiles: 0, + totalFiles: 0, + currentFile: "", + status: "", + }, +}; + +// Store AbortController outside Redux state +let currentAbortController = null; + +const uploadFilesSlice = createSlice({ + name: "uploadFiles", + initialState, + reducers: { + resetUploadState() { + // Abort any ongoing upload when resetting + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + } + return initialState; + }, + setAreFilesValid(state, action) { + state.areFilesValid = action.payload; + }, + setQuotaError(state, action) { + state.quotaError = action.payload; + }, + clearQuotaError(state) { + state.quotaError = null; + }, + uploadStarted(state, action) { + state.uploading = true; + state.error = { status: null, message: "" }; + state.success = false; + state.quotaError = null; + state.progress = { + percent: 0, + processedFiles: 0, + totalFiles: action.payload.totalFiles, + currentFile: "", + status: "starting", + }; + }, + progressUpdated(state, action) { + state.progress = action.payload; + }, + uploadCompleted(state, action) { + state.uploading = false; + state.success = true; + state.uploadedFiles = action.payload.fileNames; + }, + uploadFailed(state, action) { + state.uploading = false; + state.success = false; + state.error = { + status: action.payload.status ?? DEFAULT_ERROR_STATUS, + message: action.payload.message ?? UNKNOWN_ERROR, + }; + }, + uploadCancelled(state) { + state.uploading = false; + state.success = false; + state.error = { status: null, message: "" }; + state.progress = initialState.progress; + }, + }, +}); + +export const { + resetUploadState, + setAreFilesValid, + setQuotaError, + clearQuotaError, + uploadStarted, + progressUpdated, + uploadCompleted, + uploadFailed, + uploadCancelled, +} = uploadFilesSlice.actions; + +export const cancelUpload = () => (dispatch) => { + if (currentAbortController) { + currentAbortController.abort(); + currentAbortController = null; + } + dispatch(uploadCancelled()); +}; + +/** + * Validate files against quota limits + * Returns { valid: boolean, error: string | null } + */ +export const validateFilesQuota = (files) => (dispatch, getState) => { + const state = getState(); + const loadedFiles = state.userDetails?.loadedFiles || []; + const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; + + const loadedCount = loadedFiles.length; + const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); + const filesCount = files.length; + + // Check max files per upload + if (filesCount > MAX_FILES_TO_UPLOAD) { + const errorMsg = `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`; + dispatch(setQuotaError(errorMsg)); + dispatch(setAreFilesValid(false)); + return { valid: false, error: errorMsg }; + } + + // Check remaining slots + if (filesCount > remainingSlots) { + const errorMsg = + remainingSlots === 0 + ? `You have reached the maximum number of files (${maxFilesToLoad})` + : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`; + dispatch(setQuotaError(errorMsg)); + dispatch(setAreFilesValid(false)); + return { valid: false, error: errorMsg }; + } + + dispatch(clearQuotaError()); + dispatch(setAreFilesValid(true)); + return { valid: true, error: null }; +}; + +/** + * Check if user can upload files (has remaining slots) + */ +export const canUploadFiles = () => (dispatch, getState) => { + const state = getState(); + const loadedFiles = state.userDetails?.loadedFiles || []; + const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; + + const remainingSlots = Math.max(0, maxFilesToLoad - loadedFiles.length); + return remainingSlots > 0; +}; + +/** + * Get remaining upload slots + */ +export const getRemainingSlots = () => (dispatch, getState) => { + const state = getState(); + const loadedFiles = state.userDetails?.loadedFiles || []; + const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; + + return Math.max(0, maxFilesToLoad - loadedFiles.length); +}; + +/** + * Thunk for uploading files with SSE progress streaming. + */ +export const uploadFilesWithProgress = + (files) => async (dispatch, getState) => { + const filesArray = Array.from(files); + const fileNames = filesArray.map((f) => f.name); + + // Validate quota before upload + const state = getState(); + const loadedFiles = state.userDetails?.loadedFiles || []; + const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; + const loadedCount = loadedFiles.length; + const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); + + // Check max files per upload + if (filesArray.length > MAX_FILES_TO_UPLOAD) { + dispatch( + uploadFailed({ + status: 400, + message: `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`, + }) + ); + return; + } + + // Check remaining slots + if (filesArray.length > remainingSlots) { + dispatch( + uploadFailed({ + status: 400, + message: + remainingSlots === 0 + ? `You have reached the maximum number of files (${maxFilesToLoad})` + : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`, + }) + ); + return; + } + + // Create new AbortController for this upload + currentAbortController = new AbortController(); + const signal = currentAbortController.signal; + + dispatch(uploadStarted({ totalFiles: filesArray.length })); + + const formData = new FormData(); + filesArray.forEach((file) => { + formData.append(FORM_DATA_FILES, file); + }); + + const token = state.userDetails?.token; + + const headers = {}; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + + try { + const response = await fetch( + `${BASE_URL}${PREFIX_DOCUMENT}${PREFIX_UPLOAD_STREAM}`, + { + method: METHOD_POST_QUERY, + headers, + body: formData, + signal, + } + ); + + if (!response.ok) { + dispatch( + uploadFailed({ + status: response.status, + message: `Server error: ${response.statusText}`, + }) + ); + return; + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + while (true) { + // Check if aborted before reading + if (signal.aborted) { + reader.cancel(); + return; + } + + const { done, value } = await reader.read(); + + if (done) { + break; + } + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + + for (const line of lines) { + if (line.startsWith("data:")) { + const jsonStr = line.slice(5).trim(); + if (jsonStr && jsonStr.startsWith("{")) { + try { + const progressData = JSON.parse(jsonStr); + dispatch(progressUpdated(progressData)); + + if (progressData.status === PROGRESS_STATUS_COMPLETED) { + dispatch(uploadCompleted({ fileNames })); + dispatch(fetchUserProfile()); + //dispatch(addLoadedFiles(fileNames)); + } else if (progressData.status === PROGRESS_STATUS_ERROR) { + dispatch( + uploadFailed({ + status: DEFAULT_ERROR_STATUS, + message: `Error processing file: ${progressData.currentFile}`, + }) + ); + } + } catch (parseError) { + console.error("Failed to parse SSE data:", parseError); + } + } + } + } + } + + // Ensure completion if stream ended without explicit completed status + const currentState = getState(); + if (currentState.uploadFiles.uploading) { + dispatch(uploadCompleted({ fileNames })); + dispatch(addLoadedFiles(fileNames)); + } + } catch (err) { + // Don't dispatch error if it was an intentional abort + if (err.name === "AbortError") { + console.log("Upload cancelled by user"); + return; + } + + console.error("Upload stream error:", err); + dispatch( + uploadFailed({ + status: DEFAULT_ERROR_STATUS, + message: `${ERROR_NO_RESPONSE}: ${err.message}`, + }) + ); + } finally { + currentAbortController = null; + } + }; + +//export default uploadFilesSlice.reducer; diff --git a/rag-view/src/features/userDetailsSliceOld.js b/rag-view/src/features/userDetailsSliceOld.js new file mode 100644 index 0000000..555d36f --- /dev/null +++ b/rag-view/src/features/userDetailsSliceOld.js @@ -0,0 +1,199 @@ +import { createSlice } from "@reduxjs/toolkit"; +import fetchLoginUser from "./fetch-async/fetchLoginUser"; +import fetchRegisterUser from "./fetch-async/fetchRegisterUser"; +import fetchRefreshToken from "./fetch-async/fetchRefreshToken"; +import fetchUserProfile from "./fetch-async/fetchUserProfile"; +import fetchDeleteUploaded from "./fetch-async/fetchDeleteUploaded"; + +import { + USER_NAME_UNDEFINED, + USER_EMAIL_UNDEFINED, + JWT_TOKEN, + JWT_REFRESH_TOKEN, + TYPE_WINDOW_UNDEFINED, + TOKEN_UNDEFINED, + UNKNOWN_ERROR, +} from "./constants"; + +const getInitialToken = () => { + if (typeof window === TYPE_WINDOW_UNDEFINED) return null; + + const token = localStorage.getItem(JWT_TOKEN); + if (!token || token === TOKEN_UNDEFINED) return null; + return token; +}; + +const initialState = { + userId: null, + userName: USER_NAME_UNDEFINED, + userEmail: USER_EMAIL_UNDEFINED, + loadedFiles: [], + maxFilesToLoad: 0, + token: getInitialToken(), + refreshToken: localStorage.getItem(JWT_REFRESH_TOKEN), + loading: false, + deleting: false, + deleteSuccess: false, + deletedCount: 0, + error: null, +}; + +const userDetailsSlice = createSlice({ + name: "userDetails", + initialState, + reducers: { + logoutUser(state) { + state.userId = null; + state.userName = USER_NAME_UNDEFINED; + state.userEmail = USER_EMAIL_UNDEFINED; + state.loadedFiles = []; + state.maxFilesToLoad = 0; + state.token = null; + state.refreshToken = null; + state.loading = false; + state.deleting = false; + state.deleteSuccess = false; + state.deletedCount = 0; + state.error = null; + localStorage.removeItem(JWT_TOKEN); + localStorage.removeItem(JWT_REFRESH_TOKEN); + }, + clearError(state) { + state.error = null; + }, + clearDeleteStatus(state) { + state.deleteSuccess = false; + state.deletedCount = 0; + }, + addLoadedFiles(state, action) { + state.loadedFiles = [...state.loadedFiles, ...action.payload]; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetchRegisterUser.pending, (state) => { + state.loading = true; + state.error = null; + }); + builder.addCase(fetchRegisterUser.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + state.userName = action.payload.payload.username; + state.userEmail = action.payload.payload.email; + const token = action.payload.payload.token; + state.token = token || null; + if (token) { + localStorage.setItem(JWT_TOKEN, token); + } else { + localStorage.removeItem(JWT_TOKEN); + } + const refreshToken = action.payload.payload.refreshToken; + state.refreshToken = refreshToken || null; + if (refreshToken) { + localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); + } else { + localStorage.removeItem(JWT_REFRESH_TOKEN); + } + }); + builder.addCase(fetchRegisterUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload?.status ?? 500; + }); + + builder.addCase(fetchLoginUser.pending, (state) => { + state.loading = true; + state.error = null; + }); + builder.addCase(fetchLoginUser.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + state.userName = action.payload.payload.username; + state.userEmail = action.payload.payload.email; + + const token = action.payload.payload.token; + state.token = token || null; + if (token) { + localStorage.setItem(JWT_TOKEN, token); + } else { + localStorage.removeItem(JWT_TOKEN); + } + + const refreshToken = action.payload.payload.refreshToken; + state.refreshToken = refreshToken || null; + if (refreshToken) { + localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); + } else { + localStorage.removeItem(JWT_REFRESH_TOKEN); + } + }); + builder.addCase(fetchLoginUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload ?? { status: 500, message: "Unknown error" }; + }); + + builder.addCase(fetchRefreshToken.fulfilled, (state, action) => { + const token = action.payload?.payload?.token; + const refreshToken = action.payload?.payload?.refreshToken; + + state.token = token || null; + state.refreshToken = refreshToken || null; + + if (token) localStorage.setItem(JWT_TOKEN, token); + else localStorage.removeItem(JWT_TOKEN); + + if (refreshToken) localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); + else localStorage.removeItem(JWT_REFRESH_TOKEN); + }); + + builder.addCase(fetchRefreshToken.rejected, (state, action) => { + state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; + }); + + builder.addCase(fetchUserProfile.pending, (state) => { + state.loading = true; + state.error = null; + }); + + builder.addCase(fetchUserProfile.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + + const p = action.payload?.payload; + + if (p?.id) state.userId = p.id; + if (p?.username) state.userName = p.username; + if (p?.email) state.userEmail = p.email; + if (p?.loadedFiles) state.loadedFiles = p.loadedFiles; + if (p?.maxLoadedFiles != null) state.maxFilesToLoad = p.maxLoadedFiles; + }); + + builder.addCase(fetchUserProfile.rejected, (state, action) => { + state.loading = false; + state.error = action.payload ?? { status: 500, message: "Unknown error" }; + }); + + // Delete uploaded files + builder.addCase(fetchDeleteUploaded.pending, (state) => { + state.deleting = true; + state.deleteSuccess = false; + state.deletedCount = 0; + state.error = null; + }); + + builder.addCase(fetchDeleteUploaded.fulfilled, (state, action) => { + state.deleting = false; + state.deleteSuccess = true; + state.deletedCount = action.payload?.payload ?? 0; + state.loadedFiles = []; + state.error = null; + }); + + builder.addCase(fetchDeleteUploaded.rejected, (state, action) => { + state.deleting = false; + state.deleteSuccess = false; + state.error = action.payload ?? { status: 500, message: "Unknown error" }; + }); + }, +}); + +//export const { logoutUser, clearError, clearDeleteStatus, addLoadedFiles } = userDetailsSlice.actions; +//export default userDetailsSlice.reducer; diff --git a/rag-view/src/index.css b/rag-view/src/index.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/rag-view/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/rag-view/src/main.jsx b/rag-view/src/main.jsx new file mode 100644 index 0000000..5123f93 --- /dev/null +++ b/rag-view/src/main.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Provider } from "react-redux"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App.jsx"; +import store from "./store"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/rag-view/src/pages/HomePage.jsx b/rag-view/src/pages/HomePage.jsx new file mode 100644 index 0000000..b94b07e --- /dev/null +++ b/rag-view/src/pages/HomePage.jsx @@ -0,0 +1,300 @@ +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { + logoutUser, + clearDeleteStatus, +} from "../features/slices/details-slice"; +import { uploadFilesWithProgress } from "../features/slices/upload-slice"; +import fetchDeleteUploaded from "../features/fetch-async/fetchDeleteUploaded"; +import fetchLoginUser from "../features/fetch-async/fetchLoginUser"; +import { + TOKEN_UNDEFINED, + GUEST_EMAIL, + GUEST_PASSWORD, +} from "../features/constants"; + +const GUEST_USERNAME = "Guest"; +const DEFAULT_CONTEXT_FILE = "/guest-example.txt"; +const EXAMPLE_QUESTIONS_FILE = "/context-questions.txt"; + +function HomePage() { + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const [view, setView] = useState(null); + const [fileContent, setFileContent] = useState(""); + const [uploadingDefault, setUploadingDefault] = useState(false); + + const token = useSelector((state) => state.userDetails.token); + const userId = useSelector((state) => state.userDetails.userId); + const userName = useSelector((state) => state.userDetails.userName); + const userEmail = useSelector((state) => state.userDetails.userEmail); + const loadedFiles = useSelector((state) => state.userDetails.loadedFiles); + const loading = useSelector((state) => state.userDetails.loading); + const deleting = useSelector((state) => state.userDetails.deleting); + const deleteSuccess = useSelector((state) => state.userDetails.deleteSuccess); + const deletedCount = useSelector((state) => state.userDetails.deletedCount); + + const auth = + typeof token === "string" && + token !== TOKEN_UNDEFINED && + token.trim() !== ""; + + const isGuest = + userEmail === GUEST_EMAIL && userName === GUEST_USERNAME; + const hasDefaultFile = loadedFiles.some( + (f) => f.fileName === "guest-example.txt", + ); + const showUploadDefault = isGuest && !hasDefaultFile; + const showViewExamples = isGuest && hasDefaultFile; + + const handleGuestLogin = () => { + dispatch(fetchLoginUser({ email: GUEST_EMAIL, password: GUEST_PASSWORD })); + }; + + const handleLogout = () => { + dispatch(logoutUser()); + }; + + const handleDeleteUploaded = () => { + if (window.confirm("Are you sure you want to delete all uploaded files?")) { + dispatch(fetchDeleteUploaded()); + } + }; + + const handleClearDeleteStatus = () => { + dispatch(clearDeleteStatus()); + }; + + const handleUploadDefaultFile = async () => { + setUploadingDefault(true); + try { + const res = await fetch(DEFAULT_CONTEXT_FILE); + const text = await res.text(); + const file = new File([text], "guest-example.txt", { + type: "text/plain", + }); + dispatch(uploadFilesWithProgress([file])); + navigate("/upload"); + } finally { + setUploadingDefault(false); + } + }; + + const handleViewFile = async (filePath, viewName) => { + const res = await fetch(filePath); + const text = await res.text(); + setFileContent(text); + setView(viewName); + }; + + // File viewer screen + if (view) { + const title = + view === "context" ? "Example Context File" : "Example Questions"; + return ( +
+
+

{title}

+
+            {fileContent}
+          
+ +
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+

RAG Viewer

+

+ Choose an option to start working with your text files and RAG + queries. +

+
+ + {!auth ? ( +
+ + + +
+ ) : ( +
+ {/* User Info Section */} +
+

+ User Profile +

+ {loading ? ( +

Loading...

+ ) : ( +
+
+ ID + {userId ?? "—"} +
+
+ Username + {userName} +
+
+ Email + + {userEmail} + +
+
+ )} +
+ + {/* Delete Success Message */} + {deleteSuccess && ( +
+
+

+ Successfully deleted {deletedCount} file(s) +

+ +
+
+ )} + + {/* Loaded Files Section */} +
+

+ Loaded Documents ({loadedFiles.length}) +

+ {loading ? ( +

Loading...

+ ) : loadedFiles.length === 0 ? ( +

+ No documents uploaded yet +

+ ) : ( +
    + {loadedFiles.map((file) => ( +
  • + + + + + {file.fileName} + + + #{file.id} + +
  • + ))} +
+ )} +
+ + {/* Action Buttons */} +
+ + {showUploadDefault && ( + + )} + + {showViewExamples && ( + <> + + + + )} + + +
+
+ )} +
+
+ ); +} + +export default HomePage; diff --git a/rag-view/src/pages/LoginPage.jsx b/rag-view/src/pages/LoginPage.jsx new file mode 100644 index 0000000..16d180a --- /dev/null +++ b/rag-view/src/pages/LoginPage.jsx @@ -0,0 +1,136 @@ +import { useState, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { clearError } from "../features/slices/details-slice"; +import fetchLoginUser from "../features/fetch-async/fetchLoginUser"; + +function LoginPage() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { loading, error, token } = useSelector((state) => state.userDetails); + + // Redirect if already logged in + useEffect(() => { + if (token) { + navigate("/"); + } + }, [token, navigate]); + + // Clear error on unmount + useEffect(() => { + return () => { + dispatch(clearError()); + }; + }, [dispatch]); + + const handleSubmit = async (e) => { + e.preventDefault(); + + const result = await dispatch(fetchLoginUser({ email, password })); + + if (fetchLoginUser.fulfilled.match(result)) { + navigate("/"); + } + }; + + return ( +
+
+

+ Login +

+

+ Enter your email and password, then press the button to continue. +

+ + {error && ( +
+ {error.message || `Error: ${error.status}`} +
+ )} + +
+
+ + setEmail(e.target.value)} + placeholder="Your email" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + required + disabled={loading} + /> +
+ +
+ + setPassword(e.target.value)} + placeholder="Your password" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + required + disabled={loading} + /> +
+ + +
+ +

+ Don't have an account?{" "} + + Register + +

+
+
+ ); +} + +export default LoginPage; diff --git a/rag-view/src/pages/RagPage/components/ChatArea.jsx b/rag-view/src/pages/RagPage/components/ChatArea.jsx new file mode 100644 index 0000000..553f681 --- /dev/null +++ b/rag-view/src/pages/RagPage/components/ChatArea.jsx @@ -0,0 +1,189 @@ +import { useRef, useEffect, useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import fetchCreateNewChat from "../../../features/fetch-async/fetchCreateNewChat"; +import fetchAddNewUserEntry from "../../../features/fetch-async/fetchAddNewUserEntry"; +import fetchGetChat from "../../../features/fetch-async/fetchGetChat"; +import { generateTitleFromMessage } from "../utils/titleUtils"; +import { MESSAGE_ROLE } from "../../../features/constants"; + +const ChatArea = () => { + const dispatch = useDispatch(); + const [inputValue, setInputValue] = useState(""); + const { loadedFiles } = useSelector((state) => state.userDetails); + const activeChatId = useSelector((state) => state.chats.activeChatId); + const messages = useSelector((state) => state.chats.messages); + const isWaitingResponse = useSelector( + (state) => state.chats.isWaitingResponse, + ); + const messagesEndRef = useRef(null); + const { isUseOnlyContextSearch, topP } = useSelector( + (state) => state.ragConfig, + ); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const handleSendMessage = () => { + const trimmedInput = inputValue.trim(); + if (!trimmedInput) return; + + if (!activeChatId) { + // No active chat - create a new one, then send the entry + const title = generateTitleFromMessage(trimmedInput); + dispatch(fetchCreateNewChat(title)).then((result) => { + if (fetchCreateNewChat.fulfilled.match(result)) { + const newChatId = result.payload.id; + dispatch( + fetchAddNewUserEntry({ + chatId: newChatId, + content: trimmedInput, + onlyContext: isUseOnlyContextSearch, + topP, + }), + ).then((entryResult) => { + if (fetchAddNewUserEntry.fulfilled.match(entryResult)) { + dispatch(fetchGetChat(newChatId)); + } + }); + } + }); + } else { + // Active chat exists - send message to it + dispatch( + fetchAddNewUserEntry({ + chatId: activeChatId, + content: trimmedInput, + onlyContext: isUseOnlyContextSearch, + topP, + }), + ).then((result) => { + if (fetchAddNewUserEntry.fulfilled.match(result)) { + dispatch(fetchGetChat(activeChatId)); + } + }); + } + + setInputValue(""); + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + if (!loadedFiles || loadedFiles.length === 0) { + return ( +
+ + + +

Unable to make RAG query

+

+ Please upload at least one context file first +

+
+ ); + } + + return ( +
+ {/* Messages area */} +
+ {!activeChatId || messages.length === 0 ? ( +
+

+ {!activeChatId + ? "Start a new conversation..." + : "No messages yet"} +

+
+ ) : ( + messages.map((msg, index) => { + const isUser = msg.role === MESSAGE_ROLE.USER; + return ( +
+
+ {msg.content} +
+
+ ); + }) + )} + {isWaitingResponse && ( +
+
+
+ + + +
+
+
+ )} +
+
+ + {/* Input area */} +
+ setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={ + activeChatId ? "Type your message..." : "Start a conversation..." + } + className="flex-1 px-4 py-3 rounded-xl bg-slate-900/60 border border-slate-600/50 text-white placeholder-slate-400 text-sm focus:outline-none focus:border-indigo-500 transition-colors" + /> + +
+
+ ); +}; + +export default ChatArea; diff --git a/rag-view/src/pages/RagPage/components/ChatList.jsx b/rag-view/src/pages/RagPage/components/ChatList.jsx new file mode 100644 index 0000000..fa55f3a --- /dev/null +++ b/rag-view/src/pages/RagPage/components/ChatList.jsx @@ -0,0 +1,102 @@ +import { useSelector, useDispatch } from "react-redux"; +import { setActiveChat } from "../../../features/slices/chat-slice"; +import { formatDate } from "../utils/formatDate"; +import fetchDeleteChat from "../../../features/fetch-async/fetchDeleteChat"; + +const ChatList = ({ chats }) => { + const dispatch = useDispatch(); + const activeChatId = useSelector((state) => state.chats.activeChatId); + + const handleChatClick = (chat) => { + dispatch(setActiveChat(chat)); + }; + + const handleDeleteChat = (e, chatId) => { + e.stopPropagation(); + dispatch(fetchDeleteChat(chatId)); + }; + + // Sort chats: active chat first, then by date + const sortedChats = [...chats].sort((a, b) => { + if (a.id === activeChatId) return -1; + if (b.id === activeChatId) return 1; + return new Date(b.createdAt) - new Date(a.createdAt); + }); + + return ( +
+
+ + + + Chats +
+ +
+ {sortedChats.map((chat) => { + const isActive = chat.id === activeChatId; + + return ( + + + ); + })} +
+
+ ); +}; + +export default ChatList; diff --git a/rag-view/src/pages/RagPage/components/NewChatButton.jsx b/rag-view/src/pages/RagPage/components/NewChatButton.jsx new file mode 100644 index 0000000..9058b03 --- /dev/null +++ b/rag-view/src/pages/RagPage/components/NewChatButton.jsx @@ -0,0 +1,90 @@ +import { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import fetchCreateNewChat from "../../../features/fetch-async/fetchCreateNewChat"; +import { NO_ACTIVE_CHAT_ID } from "../../../features/constants"; + +const NewChatButton = () => { + const dispatch = useDispatch(); + const activeChatId = useSelector((state) => state.chats.activeChatId); + const isDisabled = activeChatId === NO_ACTIVE_CHAT_ID; + const [isCreating, setIsCreating] = useState(false); + const [title, setTitle] = useState(""); + + const handleCreate = () => { + if (title.trim()) { + // Create new chat with title only (no initial message) + dispatch(fetchCreateNewChat(title.trim())); + setIsCreating(false); + setTitle(""); + } + }; + + const handleCancel = () => { + setIsCreating(false); + setTitle(""); + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" && title.trim()) handleCreate(); + if (e.key === "Escape") handleCancel(); + }; + + if (isCreating) { + return ( +
+
+ setTitle(e.target.value)} + onKeyDown={handleKeyDown} + onBlur={handleCancel} + placeholder="Title" + autoFocus + className="flex-1 px-3 py-2.5 rounded-lg bg-slate-800 border border-slate-600 text-white placeholder-slate-400 text-sm focus:outline-none focus:border-indigo-500" + /> + {title.trim() && ( + + )} +
+
+ ); + } + + return ( +
+ +
+ ); +}; + +export default NewChatButton; diff --git a/rag-view/src/pages/RagPage/components/SettingsPanel.jsx b/rag-view/src/pages/RagPage/components/SettingsPanel.jsx new file mode 100644 index 0000000..33c61f5 --- /dev/null +++ b/rag-view/src/pages/RagPage/components/SettingsPanel.jsx @@ -0,0 +1,127 @@ +import { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { + switchSearchMode, + setTopP, +} from "../../../features/slices/rag-slice"; +import { + TOP_P_FAST_VALUE, + TOP_P_DEFAULT_VALUE, + TOP_P_SLOW_VALUE, +} from "../../../features/constants"; + +const SettingsPanel = () => { + const dispatch = useDispatch(); + const [isOpen, setIsOpen] = useState(false); + const { isUseOnlyContextSearch, topP } = useSelector( + (state) => state.ragConfig, + ); + + return ( +
+ + + {isOpen && ( +
+ {/* Search Mode */} +
+

+ Search Mode +

+
+ + +
+
+ + {/* Top P */} +
+

+ Top P +

+
+ {[ + { value: TOP_P_FAST_VALUE, label: "Fast" }, + { value: TOP_P_DEFAULT_VALUE, label: "Default" }, + { value: TOP_P_SLOW_VALUE, label: "Large scan" }, + ].map(({ value, label }) => ( + + ))} +
+
+
+ )} +
+ ); +}; + +export default SettingsPanel; diff --git a/rag-view/src/pages/RagPage/components/Sidebar.jsx b/rag-view/src/pages/RagPage/components/Sidebar.jsx new file mode 100644 index 0000000..d15f210 --- /dev/null +++ b/rag-view/src/pages/RagPage/components/Sidebar.jsx @@ -0,0 +1,15 @@ +import NewChatButton from "./NewChatButton"; +import ChatList from "./ChatList"; +import SettingsPanel from "./SettingsPanel"; + +const Sidebar = ({ chats }) => { + return ( + + ); +}; + +export default Sidebar; diff --git a/rag-view/src/pages/RagPage/components/UserProfile.jsx b/rag-view/src/pages/RagPage/components/UserProfile.jsx new file mode 100644 index 0000000..7ff0ae9 --- /dev/null +++ b/rag-view/src/pages/RagPage/components/UserProfile.jsx @@ -0,0 +1,48 @@ +import { useSelector } from "react-redux"; + +const UserProfile = () => { + const { userName, userEmail, loading } = useSelector( + (state) => state.userDetails, + ); + + return ( +
+

+ + + + User Profile +

+ {loading ? ( +

Loading...

+ ) : ( +
+
+ Username + + {userName || "—"} + +
+
+ Email + + {userEmail || "—"} + +
+
+ )} +
+ ); +}; + +export default UserProfile; diff --git a/rag-view/src/pages/RagPage/index.jsx b/rag-view/src/pages/RagPage/index.jsx new file mode 100644 index 0000000..81d1bd3 --- /dev/null +++ b/rag-view/src/pages/RagPage/index.jsx @@ -0,0 +1,44 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import fetchGetChatList from "../../features/fetch-async/fetchGetChatList"; + +import Sidebar from "./components/Sidebar"; +import UserProfile from "./components/UserProfile"; +import ChatArea from "./components/ChatArea"; + +const RagPage = () => { + const dispatch = useDispatch(); + const chatList = useSelector((state) => state.chats.chatList); + + const sortedChats = [...chatList].sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt), + ); + + useEffect(() => { + if (chatList.length === 0) { + dispatch(fetchGetChatList()); + } + }, [dispatch, chatList.length]); + + return ( +
+ + +
+
+
+

+ RAG Query +

+
+
+ + + +
+
+
+ ); +}; + +export default RagPage; diff --git a/rag-view/src/pages/RagPage/utils/formatDate.js b/rag-view/src/pages/RagPage/utils/formatDate.js new file mode 100644 index 0000000..38d11e0 --- /dev/null +++ b/rag-view/src/pages/RagPage/utils/formatDate.js @@ -0,0 +1,10 @@ +export const formatDate = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return "Today"; + if (diffDays === 1) return "Yesterday"; + + return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); +}; diff --git a/rag-view/src/pages/RagPage/utils/titleUtils b/rag-view/src/pages/RagPage/utils/titleUtils new file mode 100644 index 0000000..e093aa1 --- /dev/null +++ b/rag-view/src/pages/RagPage/utils/titleUtils @@ -0,0 +1,23 @@ +import { MAX_TITLE_LENGTH } from "../../../features/constants"; +export const generateTitleFromMessage = (message) => { + const trimmed = message.trim(); + const words = trimmed.split(/\s+/); + const firstThreeWords = words.slice(0, 3).join(" "); + + if (firstThreeWords.length <= MAX_TITLE_LENGTH) { + return firstThreeWords; + } + + // First 3 words exceed MAX_TITLE_LENGTH, truncate to MAX_TITLE_LENGTH + return trimmed.slice(0, MAX_TITLE_LENGTH); +}; + +/** + * Format title for display - add "..." if it was truncated + */ +export const formatTitleForDisplay = (title) => { + if (title.length === MAX_TITLE_LENGTH) { + return `${title}...`; + } + return title; +}; diff --git a/rag-view/src/pages/RagPageOld.jsx b/rag-view/src/pages/RagPageOld.jsx new file mode 100644 index 0000000..7d49d19 --- /dev/null +++ b/rag-view/src/pages/RagPageOld.jsx @@ -0,0 +1,426 @@ +import { useState, useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import fetchGetChatList from "../features/fetch-async/fetchGetChatList"; +import { + switchSearchMode, + setTopK, + setTopP, +} from "../features/slices/rag-slice"; +import fetchCreateNewChat from "../features/fetch-async/fetchCreateNewChat"; +import { + TOP_K_MIN_VALUE, + TOP_K_DEFAULT_VALUE, + TOP_K_MAX_VALUE, + TOP_P_FAST_VALUE, + TOP_P_DEFAULT_VALUE, + TOP_P_SLOW_VALUE, +} from "../features/constants"; + +// Helper function to format date +const formatDate = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return "Today"; + if (diffDays === 1) return "Yesterday"; + + return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); +}; + +const RagPage = () => { + const dispatch = useDispatch(); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [isCreatingChat, setIsCreatingChat] = useState(false); + const [newChatTitle, setNewChatTitle] = useState(""); + const chatList = useSelector((state) => state.chats.chatList); + + // Sort chats: newest first (by createdAt descending) + const sortedChats = [...chatList].sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt), + ); + + useEffect(() => { + if (chatList.length === 0) { + dispatch(fetchGetChatList()); + } + }, [dispatch, chatList.length]); + + // User details selectors + const { userName, userEmail, loadedFiles, loading } = useSelector( + (state) => state.userDetails, + ); + + // RAG config selectors + const { isUseOnlyContextSearch, topK, topP } = useSelector( + (state) => state.ragConfig, + ); + + const handleSearchModeChange = () => { + dispatch(switchSearchMode()); + }; + + const handleTopKChange = (value) => { + dispatch(setTopK(value)); + }; + + const handleTopPChange = (value) => { + dispatch(setTopP(value)); + }; + + const handleNewChatClick = () => { + setIsCreatingChat(true); + setNewChatTitle(""); + }; + + const handleCreateChat = () => { + if (newChatTitle.trim()) { + dispatch(fetchCreateNewChat(newChatTitle.trim())); + setIsCreatingChat(false); + setNewChatTitle(""); + } + }; + + const handleCancelCreate = () => { + setIsCreatingChat(false); + setNewChatTitle(""); + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" && newChatTitle.trim()) { + handleCreateChat(); + } else if (e.key === "Escape") { + handleCancelCreate(); + } + }; + + return ( +
+ {/* Left Sidebar */} + + + {/* Main Content Area */} +
+
+ {/* Header */} +
+

+ RAG Query +

+
+
+ + {/* User Info Section */} +
+

+ + + + User Profile +

+ {loading ? ( +

Loading...

+ ) : ( +
+
+ Username + + {userName || "—"} + +
+
+ Email + + {userEmail || "—"} + +
+
+ )} +
+ + {/* Warning if no files */} + {(!loadedFiles || loadedFiles.length === 0) && ( +
+ + + +

+ Unable to make RAG query +

+

+ Please upload at least one context file first +

+
+ )} + + {/* Chat area placeholder */} + {loadedFiles && loadedFiles.length > 0 && ( +
+

Start a conversation...

+
+ )} +
+
+
+ ); +}; + +export default RagPage; diff --git a/rag-view/src/pages/RegisterPage.jsx b/rag-view/src/pages/RegisterPage.jsx new file mode 100644 index 0000000..eb15a1f --- /dev/null +++ b/rag-view/src/pages/RegisterPage.jsx @@ -0,0 +1,163 @@ +import { useState, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import fetchRegisterUser from "../features/fetch-async/fetchRegisterUser"; +import { + USER_NAME_UNDEFINED, + USER_EMAIL_UNDEFINED, +} from "../features/constants"; + +function RegisterPage() { + const [login, setLogin] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [confirm, setConfirm] = useState(""); + const [validationError, setValidationError] = useState(""); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const { userName, userEmail, loading, error } = useSelector( + (state) => state.userDetails + ); + + useEffect(() => { + if (loading) return; + + if ( + userName !== USER_NAME_UNDEFINED && + userEmail !== USER_EMAIL_UNDEFINED + ) { + navigate("/"); + } + }, [loading, userName, userEmail, navigate]); + + const handleClear = () => { + setLogin(""); + setEmail(""); + setPassword(""); + setConfirm(""); + setValidationError(""); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + setValidationError(""); + + if (login.length < 3) { + setValidationError("Login must be at least 3 characters."); + return; + } + if (password.length < 8) { + setValidationError("Password must be at least 8 characters."); + return; + } + if (password !== confirm) { + setValidationError("Password and Confirm password do not match."); + return; + } + + const payload = { + username: login, + email: email, + password: password, + confirmPassword: confirm, + }; + + dispatch(fetchRegisterUser(payload)); + }; + + return ( +
+
+

+ Register +

+

+ Create a new account by choosing a login, email and a strong password. +

+ + {(validationError || error) && ( +
+ {validationError || error} +
+ )} + +
+
+ + setLogin(e.target.value)} + placeholder="Your login" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + /> +
+ +
+ + setEmail(e.target.value)} + placeholder="Your email" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + /> +
+ +
+ + setPassword(e.target.value)} + placeholder="Your password" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + /> +
+ +
+ + setConfirm(e.target.value)} + placeholder="Repeat your password" + className="w-full rounded-xl bg-slate-800/80 border border-slate-600 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-500/60 focus:outline-none px-4 py-2.5 text-slate-100 placeholder:text-slate-500 text-sm" + /> +
+ +
+ + {(validationError || error) && ( + + )} +
+
+
+
+ ); +} + +export default RegisterPage; diff --git a/rag-view/src/pages/RootLayout.jsx b/rag-view/src/pages/RootLayout.jsx new file mode 100644 index 0000000..f0cebe5 --- /dev/null +++ b/rag-view/src/pages/RootLayout.jsx @@ -0,0 +1,11 @@ +import { Outlet } from "react-router-dom"; + +function RootLayout() { + return ( +
+ +
+ ); +} + +export default RootLayout; diff --git a/rag-view/src/pages/UploadPage/components/ErrorState.jsx b/rag-view/src/pages/UploadPage/components/ErrorState.jsx new file mode 100644 index 0000000..1e6f5ff --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/ErrorState.jsx @@ -0,0 +1,25 @@ +import { styles } from "../styles/uploadStyles"; + +const ErrorState = ({ error, onTryAgain, onGoHome }) => { + return ( + <> +
+ ⚠️ +
+
{error.message}
+
Status: {error.status}
+
+
+
+ + +
+ + ); +}; + +export default ErrorState; diff --git a/rag-view/src/pages/UploadPage/components/FileList.jsx b/rag-view/src/pages/UploadPage/components/FileList.jsx new file mode 100644 index 0000000..b7f0c96 --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/FileList.jsx @@ -0,0 +1,26 @@ +import { styles } from "../styles/uploadStyles"; + +const FileList = ({ files }) => { + if (files.length === 0) return null; + + return ( +
+

+ Selected files ({files.length}): +

+
    + {files.map((file, index) => ( +
  • + 📄 + {file.name} + + {(file.size / 1024).toFixed(1)} KB + +
  • + ))} +
+
+ ); +}; + +export default FileList; diff --git a/rag-view/src/pages/UploadPage/components/FileSelector.jsx b/rag-view/src/pages/UploadPage/components/FileSelector.jsx new file mode 100644 index 0000000..4e17216 --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/FileSelector.jsx @@ -0,0 +1,40 @@ +import { useRef } from "react"; +import { styles } from "../styles/uploadStyles"; + +const FileSelector = ({ disabled, onFilesChange }) => { + const fileInputRef = useRef(null); + + const handleClick = () => { + if (fileInputRef.current) { + fileInputRef.current.value = null; + fileInputRef.current.click(); + } + }; + + return ( + <> + + + + + ); +}; + +export default FileSelector; diff --git a/rag-view/src/pages/UploadPage/components/QuotaInfo.jsx b/rag-view/src/pages/UploadPage/components/QuotaInfo.jsx new file mode 100644 index 0000000..3392414 --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/QuotaInfo.jsx @@ -0,0 +1,38 @@ +import { styles } from "../styles/uploadStyles"; + +const QuotaInfo = ({ loadedCount, maxFilesToLoad, remainingSlots }) => { + const percentage = + maxFilesToLoad > 0 ? (loadedCount / maxFilesToLoad) * 100 : 0; + + return ( +
+
+ Files loaded: + + {loadedCount} / {maxFilesToLoad} + +
+
+ Remaining slots: + 0 ? "#38a169" : "#e53e3e", + }} + > + {remainingSlots} + +
+
+
+
+
+ ); +}; + +export default QuotaInfo; diff --git a/rag-view/src/pages/UploadPage/components/SuccessState.jsx b/rag-view/src/pages/UploadPage/components/SuccessState.jsx new file mode 100644 index 0000000..9fccbf9 --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/SuccessState.jsx @@ -0,0 +1,27 @@ +import { styles } from "../styles/uploadStyles"; + +const SuccessState = ({ filesCount, onUploadAgain, onGoHome }) => { + return ( + <> +
+ +
+
Upload completed!
+
+ {filesCount} of {filesCount} files processed +
+
+
+
+ + +
+ + ); +}; + +export default SuccessState; diff --git a/rag-view/src/pages/UploadPage/components/UploadProgress.jsx b/rag-view/src/pages/UploadPage/components/UploadProgress.jsx new file mode 100644 index 0000000..4518067 --- /dev/null +++ b/rag-view/src/pages/UploadPage/components/UploadProgress.jsx @@ -0,0 +1,47 @@ +import { getStatusIcon, getStatusText } from "../utils/statusHelpers"; +import { styles } from "../styles/uploadStyles"; + +const UploadProgress = ({ progress, onCancel }) => { + return ( +
+
+ 📤 + Uploading files... +
+ +
+
+
+ +
+
+ {progress.percent}% + Complete +
+
+ + {progress.currentFile && ( +
+ {getStatusIcon(progress.status)} + {progress.currentFile} + + {getStatusText(progress.status)} + +
+ )} + +
+ +
+
+ ); +}; + +export default UploadProgress; diff --git a/rag-view/src/pages/UploadPage/index.jsx b/rag-view/src/pages/UploadPage/index.jsx new file mode 100644 index 0000000..4f4ba62 --- /dev/null +++ b/rag-view/src/pages/UploadPage/index.jsx @@ -0,0 +1,257 @@ +import { useState, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { + uploadFilesWithProgress, + resetUploadState, + setAreFilesValid, + setQuotaError, + clearQuotaError, + cancelUpload, +} from "../../features/slices/upload-slice"; +import { + MAX_FILE_SIZE_KB, + MAX_FILES_TO_UPLOAD, +} from "../../features/constants"; + +import UploadProgress from "./components/UploadProgress"; +import ErrorState from "./components/ErrorState"; +import SuccessState from "./components/SuccessState"; +import QuotaInfo from "./components/QuotaInfo"; +import FileSelector from "./components/FileSelector"; +import FileList from "./components/FileList"; +import { styles } from "./styles/uploadStyles"; + +export default function UploadPage() { + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const { + uploading, + error, + success, + areFilesValid, + quotaError, + progress, + uploadedFiles, + } = useSelector((state) => state.uploadFiles); + + const loadedFiles = useSelector((state) => state.userDetails.loadedFiles); + const maxFilesToLoad = useSelector( + (state) => state.userDetails.maxFilesToLoad, + ); + + const loadedCount = loadedFiles?.length || 0; + const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); + + const [selectedFiles, setSelectedFiles] = useState([]); + + useEffect(() => { + if (!uploading && !success) { + dispatch(resetUploadState()); + } + }, [dispatch, uploading, success]); + + useEffect(() => { + if (success) { + const timer = setTimeout(() => { + dispatch(resetUploadState()); + navigate("/"); + }, 1500); + return () => clearTimeout(timer); + } + }, [success, dispatch, navigate]); + + const handleFilesChange = (event) => { + const files = Array.from(event.target.files || []); + dispatch(clearQuotaError()); + + if (files.length > MAX_FILES_TO_UPLOAD) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError( + `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`, + ), + ); + setSelectedFiles([]); + return; + } + + if (files.length > remainingSlots) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError( + `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`, + ), + ); + setSelectedFiles([]); + return; + } + + const isSizeCorrect = files.every( + (file) => file.size <= MAX_FILE_SIZE_KB * 1024, + ); + if (!isSizeCorrect) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError(`Each file must be no more than ${MAX_FILE_SIZE_KB} KB`), + ); + setSelectedFiles([]); + return; + } + + dispatch(setAreFilesValid(true)); + setSelectedFiles(files); + }; + + const handleSend = () => { + if (!selectedFiles.length || uploading) return; + dispatch(uploadFilesWithProgress(selectedFiles)); + }; + + const handleClear = () => { + setSelectedFiles([]); + dispatch(clearQuotaError()); + }; + + const handleCancel = () => { + dispatch(cancelUpload()); + setSelectedFiles([]); + }; + + const handleTryAgain = () => { + dispatch(resetUploadState()); + setSelectedFiles([]); + }; + + const handleGoHome = () => { + dispatch(resetUploadState()); + navigate("/"); + }; + + const handleUploadAgain = () => { + dispatch(resetUploadState()); + setSelectedFiles([]); + }; + + // Loading state + if (uploading) { + return ( +
+

+ Upload text files for context searching in LLM +

+ +
+ ); + } + + // Error state + if (error.status !== undefined && error.status !== null) { + return ( +
+

+ Upload text files for context searching in LLM +

+ +
+ ); + } + + // Success state + if (success) { + return ( +
+

+ Upload text files for context searching in LLM +

+ +
+ ); + } + + // Default state + return ( +
+

+ Upload text files for context searching in LLM +

+

+ Each file must be no more than {MAX_FILE_SIZE_KB} KB. You can upload up + to {MAX_FILES_TO_UPLOAD} files at once. +

+ + + + {!areFilesValid && !quotaError && ( +
+ ⚠️ + File requirements not met. Please adjust your selection. +
+ )} + + {quotaError && ( +
+ ⚠️ + {quotaError} +
+ )} + + {remainingSlots === 0 && !quotaError && ( +
+ 🚫 +
+
+ You have reached the maximum number of files +
+
+
+ )} + + + + + +
+ + +
+
+ ); +} diff --git a/rag-view/src/pages/UploadPage/styles/uploadStyles.js b/rag-view/src/pages/UploadPage/styles/uploadStyles.js new file mode 100644 index 0000000..22200b1 --- /dev/null +++ b/rag-view/src/pages/UploadPage/styles/uploadStyles.js @@ -0,0 +1,278 @@ +export const styles = { + container: { + padding: "2rem", + maxWidth: "600px", + margin: "0 auto", + fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", + }, + title: { + fontSize: "1.5rem", + fontWeight: 600, + color: "#1a1a2e", + marginBottom: "0.5rem", + }, + subtitle: { + color: "#666", + marginBottom: "1.5rem", + lineHeight: 1.5, + }, + quotaContainer: { + backgroundColor: "#f8f9fa", + borderRadius: "8px", + padding: "1rem", + marginBottom: "1rem", + border: "1px solid #e0e0e0", + }, + quotaRow: { + display: "flex", + justifyContent: "space-between", + marginBottom: "0.5rem", + }, + quotaLabel: { + color: "#666", + fontSize: "0.9rem", + }, + quotaValue: { + fontWeight: 500, + color: "#1a1a2e", + }, + quotaProgressContainer: { + width: "100%", + height: "6px", + backgroundColor: "#e0e0e0", + borderRadius: "3px", + overflow: "hidden", + marginTop: "0.5rem", + }, + quotaProgressFill: { + height: "100%", + backgroundColor: "#667eea", + borderRadius: "3px", + transition: "width 0.3s ease", + }, + progressContainer: { + backgroundColor: "#f8f9fa", + borderRadius: "12px", + padding: "1.5rem", + marginTop: "1.5rem", + }, + progressHeader: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + fontSize: "1.1rem", + fontWeight: 500, + marginBottom: "1rem", + }, + progressIcon: { + fontSize: "1.5rem", + }, + progressBarContainer: { + width: "100%", + height: "8px", + backgroundColor: "#e0e0e0", + borderRadius: "4px", + overflow: "hidden", + marginBottom: "1rem", + }, + progressBarFill: { + height: "100%", + backgroundColor: "#4caf50", + borderRadius: "4px", + transition: "width 0.3s ease", + }, + progressStats: { + display: "flex", + justifyContent: "center", + marginBottom: "1rem", + }, + statItem: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + statValue: { + fontSize: "1.5rem", + fontWeight: 600, + color: "#1a1a2e", + }, + statLabel: { + fontSize: "0.85rem", + color: "#666", + }, + currentFile: { + display: "flex", + alignItems: "center", + gap: "0.75rem", + padding: "0.75rem", + backgroundColor: "#fff", + borderRadius: "8px", + border: "1px solid #e0e0e0", + }, + currentFileName: { + flex: 1, + fontWeight: 500, + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + currentFileStatus: { + fontSize: "0.85rem", + color: "#666", + }, + cancelButtonContainer: { + display: "flex", + justifyContent: "center", + marginTop: "1.5rem", + }, + cancelButton: { + padding: "0.75rem 2rem", + fontSize: "1rem", + fontWeight: 500, + color: "#fff", + backgroundColor: "#e53e3e", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + errorBox: { + display: "flex", + alignItems: "center", + gap: "1rem", + padding: "1rem", + backgroundColor: "#fff5f5", + border: "1px solid #feb2b2", + borderRadius: "8px", + marginTop: "1rem", + marginBottom: "1rem", + }, + errorIcon: { + fontSize: "2rem", + }, + errorMessage: { + color: "#c53030", + fontWeight: 500, + }, + errorStatus: { + color: "#666", + fontSize: "0.85rem", + }, + successBox: { + display: "flex", + alignItems: "center", + gap: "1rem", + padding: "1rem", + backgroundColor: "#f0fff4", + border: "1px solid #9ae6b4", + borderRadius: "8px", + marginTop: "1rem", + }, + successIcon: { + fontSize: "2rem", + }, + successMessage: { + color: "#276749", + fontWeight: 500, + fontSize: "1.1rem", + }, + successStats: { + color: "#666", + fontSize: "0.9rem", + }, + warningBox: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + padding: "0.75rem 1rem", + backgroundColor: "#fffaf0", + border: "1px solid #fbd38d", + borderRadius: "8px", + color: "#c05621", + marginBottom: "1rem", + }, + uploadButton: { + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "0.5rem", + width: "100%", + padding: "1rem", + fontSize: "1rem", + backgroundColor: "#f8f9fa", + border: "2px dashed #ccc", + borderRadius: "8px", + cursor: "pointer", + transition: "all 0.2s", + }, + uploadIcon: { + fontSize: "1.25rem", + }, + selectedFilesContainer: { + marginTop: "1.5rem", + padding: "1rem", + backgroundColor: "#f8f9fa", + borderRadius: "8px", + }, + selectedFilesTitle: { + margin: "0 0 0.75rem 0", + fontSize: "1rem", + color: "#333", + }, + fileList: { + listStyle: "none", + margin: 0, + padding: 0, + }, + fileItem: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + padding: "0.5rem 0", + borderBottom: "1px solid #e0e0e0", + }, + fileIcon: { + fontSize: "1rem", + }, + fileName: { + flex: 1, + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + fileSize: { + color: "#666", + fontSize: "0.85rem", + }, + buttonGroup: { + display: "flex", + gap: "0.75rem", + marginTop: "1.5rem", + }, + button: { + padding: "0.75rem 1.5rem", + fontSize: "1rem", + fontWeight: 500, + color: "#fff", + backgroundColor: "#4a5568", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + buttonSecondary: { + padding: "0.75rem 1.5rem", + fontSize: "1rem", + fontWeight: 500, + color: "#4a5568", + backgroundColor: "#e2e8f0", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + buttonDisabled: { + opacity: 0.5, + cursor: "not-allowed", + }, +}; diff --git a/rag-view/src/pages/UploadPage/utils/statusHelpers.js b/rag-view/src/pages/UploadPage/utils/statusHelpers.js new file mode 100644 index 0000000..fa7f1c1 --- /dev/null +++ b/rag-view/src/pages/UploadPage/utils/statusHelpers.js @@ -0,0 +1,36 @@ +import { + PROGRESS_STATUS_PROCESSING, + PROGRESS_STATUS_COMPLETED, + PROGRESS_STATUS_ERROR, + PROGRESS_STATUS_SKIPPED, +} from "../../../features/constants"; + +export const getStatusIcon = (status) => { + switch (status) { + case PROGRESS_STATUS_PROCESSING: + return "⏳"; + case PROGRESS_STATUS_COMPLETED: + return "✅"; + case PROGRESS_STATUS_ERROR: + return "❌"; + case PROGRESS_STATUS_SKIPPED: + return "⏭️"; + default: + return "📄"; + } +}; + +export const getStatusText = (status) => { + switch (status) { + case PROGRESS_STATUS_PROCESSING: + return "Processing..."; + case PROGRESS_STATUS_COMPLETED: + return "Completed"; + case PROGRESS_STATUS_ERROR: + return "Error"; + case PROGRESS_STATUS_SKIPPED: + return "Skipped (duplicate)"; + default: + return "Waiting..."; + } +}; diff --git a/rag-view/src/pages/UploadPageOld.jsx b/rag-view/src/pages/UploadPageOld.jsx new file mode 100644 index 0000000..5f9cb64 --- /dev/null +++ b/rag-view/src/pages/UploadPageOld.jsx @@ -0,0 +1,683 @@ +import { useState, useRef, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { + uploadFilesWithProgress, + resetUploadState, + setAreFilesValid, + setQuotaError, + clearQuotaError, + cancelUpload, +} from "../features/uploadFilesSlice"; +import { + MAX_FILE_SIZE_KB, + MAX_FILES_TO_UPLOAD, + PROGRESS_STATUS_PROCESSING, + PROGRESS_STATUS_COMPLETED, + PROGRESS_STATUS_ERROR, + PROGRESS_STATUS_SKIPPED, +} from "../features/constants"; + +export default function UploadPage() { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { + uploading, + error, + success, + areFilesValid, + quotaError, + progress, + uploadedFiles, + } = useSelector((state) => state.uploadFiles); + + const loadedFiles = useSelector((state) => state.userDetails.loadedFiles); + const maxFilesToLoad = useSelector( + (state) => state.userDetails.maxFilesToLoad + ); + + const loadedCount = loadedFiles?.length || 0; + const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); + + const [selectedFiles, setSelectedFiles] = useState([]); + const fileInputRef = useRef(null); + + useEffect(() => { + dispatch(resetUploadState()); + }, [dispatch]); + + const handleUploadClick = () => { + if (remainingSlots === 0) { + dispatch(setQuotaError("You have reached the maximum number of files")); + return; + } + if (fileInputRef.current) { + fileInputRef.current.value = null; + fileInputRef.current.click(); + } + }; + + const handleFilesChange = (event) => { + const files = Array.from(event.target.files || []); + dispatch(clearQuotaError()); + + // Check max files per upload + if (files.length > MAX_FILES_TO_UPLOAD) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError( + `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once` + ) + ); + setSelectedFiles([]); + return; + } + + // Check remaining slots + if (files.length > remainingSlots) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError( + `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}` + ) + ); + setSelectedFiles([]); + return; + } + + // Check file size + const isSizeCorrect = files.every( + (file) => file.size <= MAX_FILE_SIZE_KB * 1024 + ); + if (!isSizeCorrect) { + dispatch(resetUploadState()); + dispatch(setAreFilesValid(false)); + dispatch( + setQuotaError(`Each file must be no more than ${MAX_FILE_SIZE_KB} KB`) + ); + setSelectedFiles([]); + return; + } + + dispatch(setAreFilesValid(true)); + setSelectedFiles(files); + }; + + const handleSend = () => { + if (!selectedFiles.length || uploading) return; + dispatch(uploadFilesWithProgress(selectedFiles)); + }; + + const handleClear = () => { + setSelectedFiles([]); + dispatch(clearQuotaError()); + }; + + const handleCancel = () => { + dispatch(cancelUpload()); + setSelectedFiles([]); + }; + + const handleTryAgain = () => { + dispatch(resetUploadState()); + setSelectedFiles([]); + }; + + const handleGoHome = () => { + dispatch(resetUploadState()); + navigate("/"); + }; + + const handleUploadAgain = () => { + dispatch(resetUploadState()); + setSelectedFiles([]); + }; + + const getStatusIcon = (status) => { + switch (status) { + case PROGRESS_STATUS_PROCESSING: + return "⏳"; + case PROGRESS_STATUS_COMPLETED: + return "✅"; + case PROGRESS_STATUS_ERROR: + return "❌"; + case PROGRESS_STATUS_SKIPPED: + return "⏭️"; + default: + return "📄"; + } + }; + + const getStatusText = (status) => { + switch (status) { + case PROGRESS_STATUS_PROCESSING: + return "Processing..."; + case PROGRESS_STATUS_COMPLETED: + return "Completed"; + case PROGRESS_STATUS_ERROR: + return "Error"; + case PROGRESS_STATUS_SKIPPED: + return "Skipped (duplicate)"; + default: + return "Waiting..."; + } + }; + + // Loading state with progress + if (uploading) { + return ( +
+

+ Upload text files for context searching in LLM +

+ +
+
+ 📤 + Uploading files... +
+ +
+
+
+ +
+
+ {progress.percent}% + Complete +
+
+ + {progress.currentFile && ( +
+ {getStatusIcon(progress.status)} + {progress.currentFile} + + {getStatusText(progress.status)} + +
+ )} + +
+ +
+
+
+ ); + } + + // Error state + if (error.status !== undefined && error.status !== null) { + return ( +
+

+ Upload text files for context searching in LLM +

+
+ ⚠️ +
+
{error.message}
+
Status: {error.status}
+
+
+
+ + +
+
+ ); + } + + // Success state + if (success) { + const filesCount = uploadedFiles.length; + return ( +
+

+ Upload text files for context searching in LLM +

+
+ +
+
Upload completed!
+
+ {filesCount} of {filesCount} files processed +
+
+
+
+ + +
+
+ ); + } + + // Default state - file selection + return ( +
+

+ Upload text files for context searching in LLM +

+

+ Each file must be no more than {MAX_FILE_SIZE_KB} KB. You can upload up + to {MAX_FILES_TO_UPLOAD} files at once. +

+ + {/* File quota info */} +
+
+ Files loaded: + + {loadedCount} / {maxFilesToLoad} + +
+
+ Remaining slots: + 0 ? "#38a169" : "#e53e3e", + }} + > + {remainingSlots} + +
+
+
0 ? (loadedCount / maxFilesToLoad) * 100 : 0 + }%`, + }} + /> +
+
+ + {!areFilesValid && !quotaError && ( +
+ ⚠️ + File requirements not met. Please adjust your selection. +
+ )} + + {quotaError && ( +
+ ⚠️ + {quotaError} +
+ )} + + {remainingSlots === 0 && !quotaError && ( +
+ 🚫 +
+
+ You have reached the maximum number of files +
+
+
+ )} + + + + + + {selectedFiles.length > 0 && ( +
+

+ Selected files ({selectedFiles.length}): +

+
    + {selectedFiles.map((file, index) => ( +
  • + 📄 + {file.name} + + {(file.size / 1024).toFixed(1)} KB + +
  • + ))} +
+
+ )} + +
+ + +
+
+ ); +} + +const styles = { + container: { + padding: "2rem", + maxWidth: "600px", + margin: "0 auto", + fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif", + }, + title: { + fontSize: "1.5rem", + fontWeight: 600, + color: "#1a1a2e", + marginBottom: "0.5rem", + }, + subtitle: { + color: "#666", + marginBottom: "1.5rem", + lineHeight: 1.5, + }, + quotaContainer: { + backgroundColor: "#f8f9fa", + borderRadius: "8px", + padding: "1rem", + marginBottom: "1rem", + border: "1px solid #e0e0e0", + }, + quotaRow: { + display: "flex", + justifyContent: "space-between", + marginBottom: "0.5rem", + }, + quotaLabel: { + color: "#666", + fontSize: "0.9rem", + }, + quotaValue: { + fontWeight: 500, + color: "#1a1a2e", + }, + quotaProgressContainer: { + width: "100%", + height: "6px", + backgroundColor: "#e0e0e0", + borderRadius: "3px", + overflow: "hidden", + marginTop: "0.5rem", + }, + quotaProgressFill: { + height: "100%", + backgroundColor: "#667eea", + borderRadius: "3px", + transition: "width 0.3s ease", + }, + progressContainer: { + backgroundColor: "#f8f9fa", + borderRadius: "12px", + padding: "1.5rem", + marginTop: "1.5rem", + }, + progressHeader: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + fontSize: "1.1rem", + fontWeight: 500, + marginBottom: "1rem", + }, + progressIcon: { + fontSize: "1.5rem", + }, + progressBarContainer: { + width: "100%", + height: "8px", + backgroundColor: "#e0e0e0", + borderRadius: "4px", + overflow: "hidden", + marginBottom: "1rem", + }, + progressBarFill: { + height: "100%", + backgroundColor: "#4caf50", + borderRadius: "4px", + transition: "width 0.3s ease", + }, + progressStats: { + display: "flex", + justifyContent: "center", + marginBottom: "1rem", + }, + statItem: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + statValue: { + fontSize: "1.5rem", + fontWeight: 600, + color: "#1a1a2e", + }, + statLabel: { + fontSize: "0.85rem", + color: "#666", + }, + currentFile: { + display: "flex", + alignItems: "center", + gap: "0.75rem", + padding: "0.75rem", + backgroundColor: "#fff", + borderRadius: "8px", + border: "1px solid #e0e0e0", + }, + currentFileName: { + flex: 1, + fontWeight: 500, + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + currentFileStatus: { + fontSize: "0.85rem", + color: "#666", + }, + cancelButtonContainer: { + display: "flex", + justifyContent: "center", + marginTop: "1.5rem", + }, + cancelButton: { + padding: "0.75rem 2rem", + fontSize: "1rem", + fontWeight: 500, + color: "#fff", + backgroundColor: "#e53e3e", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + errorBox: { + display: "flex", + alignItems: "center", + gap: "1rem", + padding: "1rem", + backgroundColor: "#fff5f5", + border: "1px solid #feb2b2", + borderRadius: "8px", + marginTop: "1rem", + marginBottom: "1rem", + }, + errorIcon: { + fontSize: "2rem", + }, + errorMessage: { + color: "#c53030", + fontWeight: 500, + }, + errorStatus: { + color: "#666", + fontSize: "0.85rem", + }, + successBox: { + display: "flex", + alignItems: "center", + gap: "1rem", + padding: "1rem", + backgroundColor: "#f0fff4", + border: "1px solid #9ae6b4", + borderRadius: "8px", + marginTop: "1rem", + }, + successIcon: { + fontSize: "2rem", + }, + successMessage: { + color: "#276749", + fontWeight: 500, + fontSize: "1.1rem", + }, + successStats: { + color: "#666", + fontSize: "0.9rem", + }, + warningBox: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + padding: "0.75rem 1rem", + backgroundColor: "#fffaf0", + border: "1px solid #fbd38d", + borderRadius: "8px", + color: "#c05621", + marginBottom: "1rem", + }, + uploadButton: { + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: "0.5rem", + width: "100%", + padding: "1rem", + fontSize: "1rem", + backgroundColor: "#f8f9fa", + border: "2px dashed #ccc", + borderRadius: "8px", + cursor: "pointer", + transition: "all 0.2s", + }, + uploadIcon: { + fontSize: "1.25rem", + }, + selectedFilesContainer: { + marginTop: "1.5rem", + padding: "1rem", + backgroundColor: "#f8f9fa", + borderRadius: "8px", + }, + selectedFilesTitle: { + margin: "0 0 0.75rem 0", + fontSize: "1rem", + color: "#333", + }, + fileList: { + listStyle: "none", + margin: 0, + padding: 0, + }, + fileItem: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + padding: "0.5rem 0", + borderBottom: "1px solid #e0e0e0", + }, + fileIcon: { + fontSize: "1rem", + }, + fileName: { + flex: 1, + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, + fileSize: { + color: "#666", + fontSize: "0.85rem", + }, + buttonGroup: { + display: "flex", + gap: "0.75rem", + marginTop: "1.5rem", + }, + button: { + padding: "0.75rem 1.5rem", + fontSize: "1rem", + fontWeight: 500, + color: "#fff", + backgroundColor: "#4a5568", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + buttonSecondary: { + padding: "0.75rem 1.5rem", + fontSize: "1rem", + fontWeight: 500, + color: "#4a5568", + backgroundColor: "#e2e8f0", + border: "none", + borderRadius: "6px", + cursor: "pointer", + transition: "background-color 0.2s", + }, + buttonDisabled: { + opacity: 0.5, + cursor: "not-allowed", + }, +}; diff --git a/rag-view/src/store.js b/rag-view/src/store.js new file mode 100644 index 0000000..b41b09b --- /dev/null +++ b/rag-view/src/store.js @@ -0,0 +1,16 @@ +import { configureStore } from "@reduxjs/toolkit"; +import userDetailsReducer from "./features/slices/details-slice"; +import uploadFilesReducer from "./features/slices/upload-slice"; +import ragSliceReducer from "./features/slices/rag-slice"; +import chatSliceReducer from "./features/slices/chat-slice"; + +const store = configureStore({ + reducer: { + userDetails: userDetailsReducer, + uploadFiles: uploadFilesReducer, + ragConfig: ragSliceReducer, + chats: chatSliceReducer, + }, +}); + +export default store; diff --git a/rag-view/vite.config.js b/rag-view/vite.config.js new file mode 100644 index 0000000..24b61fe --- /dev/null +++ b/rag-view/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + base: "/ragview/", +});