demeter

Autonomous Hydroponic Intelligence
commit 4c72258ffb76997e6edb25cb256a2914ed29c80d
parent a024e924e5d9b454b1330bbf6495fa1fbc0bc0c5
Author: Arnav Gupta <66205884+arnav0103@users.noreply.github.com>
Date:   Sat, 21 Mar 2026 18:55:25 +0530

Merge branch 'maydayv7:main' into main

Diffstat:
Magent/sub_agents/Supervisor.py | 2+-
Aassets/architecture.png | 0
Dassets/images/Fetcher.png | 0
Dassets/images/README.md | 133-------------------------------------------------------------------------------
Rassets/images/screenshots/agent_control.png -> assets/screenshots/agent_control.png | 0
Rassets/images/screenshots/console_log.png -> assets/screenshots/console_log.png | 0
Rassets/images/demeter_dashboard.png -> assets/screenshots/demeter_dashboard.png | 0
Rassets/images/screenshots/system_overview.png -> assets/screenshots/system_overview.png | 0
Mbackend/node_server/package-lock.json | 94++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mbackend/server/functions.py | 9++++++---
Dfrontend/README.md | 70----------------------------------------------------------------------
Mfrontend/package-lock.json | 911++++++++++++++++++++++++++++++++++++++-----------------------------------------
Dfrontend/public/background.png | 0
Mfrontend/public/index.html | 2+-
Mfrontend/public/manifest.json | 4++--
Mfrontend/src/App.js | 29+++++++++++++++++------------
Dfrontend/src/App.test.js | 8--------
Mfrontend/src/api/agentApi.js | 45+++++++++++++++++++++++++++++++++++++++++++++
Mfrontend/src/api/farmApi.jsx | 21+++++++++++++++++++++
Afrontend/src/components/AgentWidgets.jsx | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfrontend/src/components/Sidebar.jsx | 42+++++++++++++++++++++---------------------
Afrontend/src/data/mockData.js | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afrontend/src/hooks/useSettings.js | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Mfrontend/src/index.css | 336+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mfrontend/src/pages/AgentControl.jsx | 322+++++++++++++++++++------------------------------------------------------------
Mfrontend/src/pages/Alerts.jsx | 20++------------------
Mfrontend/src/pages/Analytics.jsx | 584++++++++++++++++++++++++++++++++++++-------------------------------------------
Mfrontend/src/pages/CropDetails.jsx | 450++++++++++++++++++++++++++++++++++---------------------------------------------
Mfrontend/src/pages/Dashboard.jsx | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Afrontend/src/pages/Settings.jsx | 527+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dfrontend/src/setupTests.js | 5-----
Mfrontend/src/utils/dataUtils.js | 121++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Dpackage-lock.json | 934-------------------------------------------------------------------------------
Dpackage.json | 6------
Mreadme.md | 12++++++------
35 files changed, 2721 insertions(+), 2814 deletions(-)

diff --git a/agent/sub_agents/Supervisor.py b/agent/sub_agents/Supervisor.py @@ -233,7 +233,7 @@ class SupervisorAgent: # --- ADVISORY ONLY (Not Enforced) --- def get_strategic_goal(self, fmu): # (Same as before, but treated as advice now) - sensors = fmu.metadata.get('sensor_data', {}) + sensors = fmu.metadata.get('sensors', {}) fmu_vector = fmu.vector vis_vec1 = np.array(fmu_vector) if isinstance(fmu_vector, list) else fmu_vector vis_vec = vis_vec1[:512] if len(vis_vec1) >= 512 else None diff --git a/assets/architecture.png b/assets/architecture.png Binary files differ. diff --git a/assets/images/Fetcher.png b/assets/images/Fetcher.png Binary files differ. diff --git a/assets/images/README.md b/assets/images/README.md @@ -1,133 +0,0 @@ -# Demeter README Images - -This directory contains images used in the main README.md file. Below are the specifications for each required image. - -## Required Images - -### 1. `demeter-dashboard.png` -**Purpose**: Main dashboard screenshot showing the web interface -**Dimensions**: 800x600px (recommended) -**Content**: Should show the main dashboard with: -- Real-time sensor readings (temperature, pH, EC, etc.) -- Current agent status -- Plant health indicators -- Control buttons for manual intervention - -### 2. `demeter-architecture.png` -**Purpose**: System architecture diagram -**Dimensions**: 800x600px (recommended) -**Content**: Visual representation of the agent hierarchy showing: -- Supervisor Agent at the top -- Researcher and Judge agents -- Atmospheric and Water agents -- Doctor and Historian agents -- Data flow between agents -- Technology stack indicators (LangGraph, Qdrant, etc.) - -### 3. `disease-detection.png` -**Purpose**: Disease detection interface screenshot -**Dimensions**: 600x400px (recommended) -**Content**: Should show: -- Plant image analysis interface -- YOLOv8 detection results -- Disease identification (e.g., leaf spot, powdery mildew) -- Confidence scores -- Treatment recommendations - -### 4. `agent-control.png` -**Purpose**: Agent control and monitoring interface -**Dimensions**: 700x500px (recommended) -**Content**: Display showing: -- Active agent statuses -- Agent communication logs -- Manual override controls -- System health indicators -- Real-time decision making display - -### 5. `project-structure.png` -**Purpose**: Visual representation of project file structure -**Dimensions**: 400x600px (recommended) -**Content**: Clean visualization of the directory structure showing: -- Main folders (agent/, backend/, frontend/) -- Key files and their relationships -- Technology indicators for each component - -## Screenshot Grid Images (1x3 Grid) - -These images appear in a grid at the top of the README for quick visual overview. - -### 6. `screenshots/system-overview.png` -**Purpose**: Hero screenshot showing the main system dashboard -**Dimensions**: 300x200px (optimized for grid display) -**Content**: Primary dashboard view with: -- Key metrics overview (temperature, humidity, pH) -- System status indicators -- Active alerts or notifications -- Quick action buttons - -### 7. `screenshots/agent-control.png` -**Purpose**: Agent orchestration and control interface -**Dimensions**: 300x200px (optimized for grid display) -**Content**: Agent management interface showing: -- Active agent statuses -- Agent communication logs -- Control buttons for agent actions -- Real-time decision indicators - -### 8. `screenshots/console-log.png` -**Purpose**: AI agent decision and logging interface -**Dimensions**: 300x200px (optimized for grid display) -**Content**: Console/logs interface displaying: -- Real-time agent decision logs -- Action execution history -- Error messages and warnings -- System status updates and notifications - -## Image Guidelines - -### Design Requirements -- **Resolution**: High quality (300 DPI minimum) -- **Format**: PNG preferred for screenshots, SVG for diagrams -- **Style**: Clean, professional, modern UI -- **Colors**: Use Demeter brand colors (green theme #4CAF50) -- **Annotations**: Include clear labels and callouts where helpful - -### Technical Specifications -- **File Size**: Optimize for web (under 500KB each) -- **Aspect Ratio**: Maintain proper proportions for responsive display -- **Accessibility**: Ensure good contrast and readable text - -## Creating Images - -### For Screenshots: -1. Set up the application in development mode -2. Navigate to the relevant interface -3. Use browser dev tools or screenshot tools -4. Crop and annotate as needed -5. Save in PNG format - -### For Architecture Diagrams: -1. Use tools like Draw.io, Figma, or Lucidchart -2. Follow the agent hierarchy described in the README -3. Use consistent styling and colors -4. Export as high-quality PNG or SVG - -### For Project Structure: -1. Use directory visualization tools or create manually -2. Show hierarchical relationships clearly -3. Include file type indicators -4. Keep clean and readable - -## Updating Images - -When updating the application or architecture: -1. Regenerate relevant screenshots -2. Update architecture diagrams to reflect changes -3. Ensure all images remain current with the codebase -4. Test image display in the README on GitHub - -## File Naming Convention - -- Use lowercase with hyphens: `image-name.png` -- Be descriptive but concise -- Include version numbers if needed: `dashboard-v2.png` diff --git a/assets/images/screenshots/agent_control.png b/assets/screenshots/agent_control.png Binary files differ. diff --git a/assets/images/screenshots/console_log.png b/assets/screenshots/console_log.png Binary files differ. diff --git a/assets/images/demeter_dashboard.png b/assets/screenshots/demeter_dashboard.png Binary files differ. diff --git a/assets/images/screenshots/system_overview.png b/assets/screenshots/system_overview.png Binary files differ. diff --git a/backend/node_server/package-lock.json b/backend/node_server/package-lock.json @@ -18,13 +18,13 @@ } }, "node_modules/@qdrant/js-client-rest": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.16.2.tgz", - "integrity": "sha512-Zm4wEZURrZ24a+Hmm4l1QQYjiz975Ep3vF0yzWR7ICGcxittNz47YK2iBOk8kb8qseCu8pg7WmO1HOIsO8alvw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@qdrant/js-client-rest/-/js-client-rest-1.17.0.tgz", + "integrity": "sha512-aZFQeirWVqWAa1a8vJ957LMzcXkFHGbsoRhzc8AkGfg6V0jtK8PlG8/eyyc2xhYsR961FDDx1Tx6nyE0K7lS+A==", "license": "Apache-2.0", "dependencies": { "@qdrant/openapi-typescript-fetch": "1.2.6", - "undici": "^6.0.0" + "undici": "^6.23.0" }, "engines": { "node": ">=18.17.0", @@ -71,10 +71,13 @@ } }, "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" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -113,13 +116,15 @@ } }, "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==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -196,12 +201,6 @@ "fsevents": "~2.3.2" } }, - "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/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -243,9 +242,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -253,6 +252,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/debug": { @@ -282,9 +285,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -739,15 +742,18 @@ } }, "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", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -766,15 +772,15 @@ } }, "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -895,9 +901,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -968,9 +974,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1200,9 +1206,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/backend/server/functions.py b/backend/server/functions.py @@ -3,7 +3,7 @@ import shutil import json import traceback import base64 -from fastapi import UploadFile +from fastapi import UploadFile, HTTPException from groq import Groq from qdrant_client.http import models from datetime import datetime @@ -257,13 +257,16 @@ async def process_search(file: UploadFile, sensors_str: str, builder): "new_fmu_id": query_fmu.id, "agent_decision": final_decision_json, "explanation": explanation_log, - "search_results": [{"id": p.id, "score": p.score, "payload": p.payload} for p in points_list], + "search_results": [ + {"id": p.id, "score": p.score, "payload": p.payload} + for p in points_list + ], } except Exception as e: print(f"❌ Pipeline Error: {e}") traceback.print_exc() - return {"status": "error", "message": str(e)} + raise HTTPException(status_code=500, detail=str(e)) finally: # Cleanup temp file diff --git a/frontend/README.md b/frontend/README.md @@ -1,70 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `npm start` - -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. - -The page will reload when you make changes.\ -You may also see any lint errors in the console. - -### `npm test` - -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `npm run build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` - -**Note: this is a one-way operation. Once you `eject`, you can't go back!** - -If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. - -You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `npm run build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/frontend/package-lock.json b/frontend/package-lock.json @@ -45,9 +45,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -59,29 +59,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -143,13 +143,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -252,16 +252,16 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -429,25 +429,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -553,9 +553,9 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.6.tgz", - "integrity": "sha512-RVdFPPyY9fCRAX68haPmOk2iyKW8PKJFthmm8NeSI3paNxKWGZIn99+VbIf0FrtCpFnPgnpF/L48tadi617ULg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", @@ -949,14 +949,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", - "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1128,9 +1128,9 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", - "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1346,15 +1346,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1380,13 +1380,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1650,9 +1650,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", - "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1696,13 +1696,13 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", - "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", @@ -1715,6 +1715,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1883,12 +1896,12 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", - "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", + "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -1902,7 +1915,7 @@ "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", @@ -1913,7 +1926,7 @@ "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", @@ -1926,9 +1939,9 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", @@ -1940,7 +1953,7 @@ "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1953,10 +1966,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -2029,9 +2042,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2052,17 +2065,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -2070,9 +2083,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -3007,9 +3020,9 @@ } }, "node_modules/@reduxjs/toolkit/node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", "license": "MIT", "funding": { "type": "opencollective", @@ -3102,9 +3115,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "license": "MIT" }, "node_modules/@sinclair/typebox": { @@ -3395,15 +3408,6 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", @@ -3430,9 +3434,9 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.1.tgz", - "integrity": "sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" @@ -3481,15 +3485,6 @@ "node": ">= 6" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -3773,12 +3768,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/node-forge": { @@ -3809,9 +3804,9 @@ "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "license": "MIT" }, "node_modules/@types/range-parser": { @@ -4354,9 +4349,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4452,9 +4447,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -4485,9 +4480,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -4576,15 +4571,12 @@ } }, "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==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -4625,12 +4617,12 @@ } }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -4865,9 +4857,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "funding": [ { "type": "opencollective", @@ -4885,7 +4877,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -5048,13 +5040,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -5071,25 +5063,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5194,12 +5186,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.15", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", - "integrity": "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==", + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/batch": { @@ -5515,9 +5510,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", "funding": [ { "type": "opencollective", @@ -5559,6 +5554,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk/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/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -5942,12 +5952,16 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cookie-signature": { @@ -5957,9 +5971,9 @@ "license": "MIT" }, "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -5968,12 +5982,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", - "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "license": "MIT", "dependencies": { - "browserslist": "^4.28.0" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -5981,9 +5995,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", - "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.49.0.tgz", + "integrity": "sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -7008,9 +7022,9 @@ } }, "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==", + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", "license": "ISC" }, "node_modules/emittery": { @@ -7050,13 +7064,13 @@ } }, "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==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -7182,9 +7196,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz", + "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -7202,6 +7216,7 @@ "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" }, "engines": { @@ -7271,9 +7286,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", - "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", "license": "MIT", "workspaces": [ "docs", @@ -7602,6 +7617,15 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.5", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", @@ -7659,18 +7683,24 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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" } @@ -7948,9 +7978,9 @@ } }, "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, "node_modules/events": { @@ -8054,6 +8084,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -8212,9 +8251,9 @@ } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" @@ -8230,9 +8269,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -8340,9 +8379,9 @@ } }, "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==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/follow-redirects": { @@ -8714,7 +8753,7 @@ "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", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -9232,6 +9271,12 @@ } } }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -10727,18 +10772,6 @@ "@types/yargs-parser": "*" } }, - "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-watch-typeahead/node_modules/emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -10909,12 +10942,12 @@ } }, "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -11123,20 +11156,20 @@ } }, "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz", + "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==", "license": "MIT", "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" + "esprima": "1.2.5", + "static-eval": "2.1.1", + "underscore": "1.13.6" } }, "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -11224,9 +11257,9 @@ } }, "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.1.tgz", + "integrity": "sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==", "license": "MIT", "dependencies": { "picocolors": "^1.1.1", @@ -11310,9 +11343,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -11569,9 +11602,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", - "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.1.tgz", + "integrity": "sha512-k7G3Y5QOegl380tXmZ68foBRRjE9Ljavx835ObdvmZjQ639izvZD8CS7BkWw1qKPPzHsGL/JDhl0uyU1zc2rJw==", "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", @@ -11595,9 +11628,9 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -11712,6 +11745,33 @@ "tslib": "^2.0.3" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-forge": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", @@ -11728,9 +11788,9 @@ "license": "MIT" }, "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==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, "node_modules/normalize-path": { @@ -12315,9 +12375,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -13519,6 +13579,15 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "license": "CC0-1.0" }, + "node_modules/postcss-svgo/node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/postcss-svgo/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13529,17 +13598,17 @@ } }, "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz", + "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==", "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^4.1.3", "css-tree": "^1.1.3", "csso": "^4.2.0", "picocolors": "^1.0.0", + "sax": "^1.5.0", "stable": "^0.1.8" }, "bin": { @@ -13615,17 +13684,11 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" }, "node_modules/process-nextick-args": { "version": "2.0.1", @@ -13727,9 +13790,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -13822,9 +13885,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13953,15 +14016,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-error-overlay": { @@ -13971,10 +14034,11 @@ "license": "MIT" }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", @@ -14009,9 +14073,9 @@ } }, "node_modules/react-router": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", - "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -14031,12 +14095,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", - "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", "license": "MIT", "dependencies": { - "react-router": "7.12.0" + "react-router": "7.13.1" }, "engines": { "node": ">=20.0.0" @@ -14046,19 +14110,6 @@ "react-dom": ">=18" } }, - "node_modules/react-router/node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14168,15 +14219,15 @@ } }, "node_modules/recharts": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", - "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz", + "integrity": "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==", "license": "MIT", "workspaces": [ "www" ], "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", @@ -14197,12 +14248,6 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/recharts/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, "node_modules/recharts/node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -14564,9 +14609,9 @@ } }, "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "license": "MIT", "bin": { "rollup": "dist/bin/rollup" @@ -14806,9 +14851,9 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -14859,9 +14904,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14919,21 +14964,25 @@ } }, "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", "license": "MIT", "dependencies": { - "accepts": "~1.3.4", + "accepts": "~1.3.8", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-index/node_modules/debug": { @@ -14955,38 +15004,27 @@ } }, "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", "dependencies": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" - }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -15348,103 +15386,12 @@ "license": "MIT" }, "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", "license": "MIT", "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/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", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "escodegen": "^2.1.0" } }, "node_modules/statuses": { @@ -16131,9 +16078,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -16149,15 +16096,14 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -16557,9 +16503,9 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "peer": true, "bin": { @@ -16567,7 +16513,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -16589,15 +16535,15 @@ } }, "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "license": "MIT" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -16909,9 +16855,9 @@ } }, "node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -16920,11 +16866,11 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", + "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -16936,9 +16882,9 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -17098,9 +17044,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -17393,9 +17339,9 @@ } }, "node_modules/workbox-build/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -17649,6 +17595,21 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/frontend/public/background.png b/frontend/public/background.png Binary files differ. diff --git a/frontend/public/index.html b/frontend/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - <title>React App</title> + <title>Demeter</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "Demeter", + "name": "Demeter", "icons": [ { "src": "favicon.ico", diff --git a/frontend/src/App.js b/frontend/src/App.js @@ -1,5 +1,6 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { FarmDataProvider } from "./hooks/useFarmData"; +import { SettingsProvider } from "./hooks/useSettings"; import LandingPage from "./pages/LandingPage"; import Dashboard from "./pages/Dashboard"; @@ -7,21 +8,25 @@ import CropDetails from "./pages/CropDetails"; import AgentControl from "./pages/AgentControl"; import Analytics from "./pages/Analytics"; import Alerts from "./pages/Alerts"; +import SettingsPage from "./pages/Settings"; function App() { return ( - <FarmDataProvider> - <Router> - <Routes> - <Route path="/" element={<LandingPage />} /> - <Route path="/control" element={<AgentControl />} /> - <Route path="/dashboard" element={<Dashboard />} /> - <Route path="/crop/:cropId" element={<CropDetails />} /> - <Route path="/analytics" element={<Analytics />} /> - <Route path="/alerts" element={<Alerts />} /> - </Routes> - </Router> - </FarmDataProvider> + <SettingsProvider> + <FarmDataProvider> + <Router> + <Routes> + <Route path="/" element={<LandingPage />} /> + <Route path="/control" element={<AgentControl />} /> + <Route path="/dashboard" element={<Dashboard />} /> + <Route path="/crop/:cropId" element={<CropDetails />} /> + <Route path="/analytics" element={<Analytics />} /> + <Route path="/alerts" element={<Alerts />} /> + <Route path="/settings" element={<SettingsPage />} /> + </Routes> + </Router> + </FarmDataProvider> + </SettingsProvider> ); } diff --git a/frontend/src/App.test.js b/frontend/src/App.test.js @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(<App />); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/frontend/src/api/agentApi.js b/frontend/src/api/agentApi.js @@ -1,3 +1,9 @@ +import { + USE_MOCK_DATA, + MOCK_SEARCH_RESULT, + MOCK_DASHBOARD, +} from "../data/mockData"; + const API_URL = "http://localhost:8000"; export const agentService = { @@ -5,6 +11,11 @@ export const agentService = { * Uploads an image + sensors to create a new FMU (Functional Memory Unit) */ async uploadFMU(file, sensors) { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 500)); + return { status: "success", fmu_id: "mock-fmu-ingest-001" }; + } + const formData = new FormData(); formData.append("file", file); formData.append( @@ -37,6 +48,11 @@ export const agentService = { * Searches for similar memories and gets an Agent Decision */ async searchFMU(file, sensors) { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 1200)); + return MOCK_SEARCH_RESULT; + } + const formData = new FormData(); formData.append("file", file); formData.append( @@ -63,6 +79,24 @@ export const agentService = { * Queries the RAG/Agent via Text */ async queryText(text) { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 400)); + const q = text.toLowerCase(); + const filtered = MOCK_DASHBOARD.filter( + (d) => + d.payload.crop?.toLowerCase().includes(q) || + d.payload.stage?.toLowerCase().includes(q) || + d.payload.crop_id?.toLowerCase().includes(q), + ); + return { + status: "success", + results: (filtered.length ? filtered : MOCK_DASHBOARD).map((d) => ({ + id: d.id, + payload: d.payload, + })), + }; + } + const formData = new FormData(); formData.append("query", text); const res = await fetch(`${API_URL}/query-text`, { @@ -76,6 +110,17 @@ export const agentService = { * Queries the RAG/Agent via Audio */ async queryAudio(audioBlob) { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 800)); + return { + status: "success", + transcription: "show all lettuce crops", + results: MOCK_DASHBOARD.filter((d) => d.payload.crop === "Lettuce").map( + (d) => ({ id: d.id, score: 1, payload: d.payload }), + ), + }; + } + const formData = new FormData(); formData.append("file", audioBlob, "recording.webm"); const res = await fetch(`${API_URL}/query-audio`, { diff --git a/frontend/src/api/farmApi.jsx b/frontend/src/api/farmApi.jsx @@ -1,9 +1,16 @@ +import { USE_MOCK_DATA, MOCK_DASHBOARD, MOCK_HISTORY } from "../data/mockData"; + const API_BASE_URL = "http://localhost:3001/api"; /** * Fetches the latest state of all unique crops for the Dashboard. */ export const fetchDashboardData = async () => { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 300)); // simulate latency + return MOCK_DASHBOARD; + } + try { const res = await fetch(`${API_BASE_URL}/dashboard`); if (!res.ok) throw new Error("Network error"); @@ -17,6 +24,11 @@ export const fetchDashboardData = async () => { * Fetches the full history (logs, charts) for a specific crop ID. */ export const fetchCropDetails = async (cropId) => { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 200)); + return MOCK_HISTORY.filter((h) => h.payload?.crop_id === cropId); + } + try { const res = await fetch(`${API_BASE_URL}/crop/${cropId}`); if (!res.ok) throw new Error("Network error"); @@ -31,6 +43,15 @@ export const fetchCropDetails = async (cropId) => { * Returns a flat array of all point objects sorted oldest → newest by timestamp. */ export const fetchAllCropHistories = async (dashboardItems) => { + if (USE_MOCK_DATA) { + await new Promise((r) => setTimeout(r, 200)); + return MOCK_HISTORY.sort((a, b) => { + const ta = new Date(a.payload?.timestamp || 0).getTime(); + const tb = new Date(b.payload?.timestamp || 0).getTime(); + return ta - tb; + }); + } + if (!dashboardItems?.length) return []; const cropIds = [ ...new Set(dashboardItems.map((i) => i.payload?.crop_id).filter(Boolean)), diff --git a/frontend/src/components/AgentWidgets.jsx b/frontend/src/components/AgentWidgets.jsx @@ -0,0 +1,307 @@ +import { Fan, FlaskConical, Sprout, Waves } from "lucide-react"; + +// Shared action metadata +const ACTION_META = { + acid_dosage_ml: { + label: "Acid Dosage", + icon: FlaskConical, + unit: "ml", + color: "var(--red)", + bg: "rgba(248,113,113,0.1)", + desc: "pH Down", + }, + base_dosage_ml: { + label: "Base Dosage", + icon: FlaskConical, + unit: "ml", + color: "#a78bfa", + bg: "rgba(167,139,250,0.1)", + desc: "pH Up", + }, + nutrient_dosage_ml: { + label: "Nutrients", + icon: Sprout, + unit: "ml", + color: "var(--green)", + bg: "rgba(74,222,128,0.1)", + desc: "EC Boost", + }, + fan_speed_pct: { + label: "Fan Speed", + icon: Fan, + unit: "%", + color: "var(--blue)", + bg: "rgba(96,165,250,0.1)", + desc: "Airflow", + }, + water_refill_l: { + label: "Water Refill", + icon: Waves, + unit: "L", + color: "#22d3ee", + bg: "rgba(34,211,238,0.1)", + desc: "Dilution", + }, +}; + +// Parse action JSON safely +function parseAction(raw) { + if (!raw || raw === "PENDING_ACTION") return null; + if (typeof raw === "object") return raw; + try { + return JSON.parse(raw); + } catch { + try { + return JSON.parse( + raw + .replace(/'/g, '"') + .replace(/\bNone\b/g, "null") + .replace(/\bTrue\b/g, "true") + .replace(/\bFalse\b/g, "false"), + ); + } catch { + return null; + } + } +} + +// Show actuator commands as cards +export function AgentActionWidget({ actionTaken, compact = false }) { + const action = parseAction(actionTaken); + if (!action) return null; + + const entries = Object.entries(ACTION_META).map(([key, meta]) => ({ + key, + meta, + value: action[key] ?? 0, + })); + + const active = entries.filter((e) => parseFloat(e.value) > 0); + const display = active.length > 0 ? active : entries; + + if (compact) { + return ( + <div + style={{ + display: "flex", + flexWrap: "wrap", + gap: 6, + }} + > + {display.map(({ key, meta, value }) => { + const Icon = meta.icon; + return ( + <div + key={key} + style={{ + display: "flex", + alignItems: "center", + gap: 5, + padding: "4px 10px", + borderRadius: 20, + background: meta.bg, + border: `1px solid ${meta.color}40`, + }} + > + <Icon size={11} style={{ color: meta.color }} /> + <span + style={{ + fontSize: 12, + fontFamily: "DM Mono, monospace", + color: meta.color, + fontWeight: 600, + }} + > + {value} + {meta.unit} + </span> + <span + style={{ + fontSize: 10, + fontFamily: "DM Mono, monospace", + color: "var(--text-3)", + }} + > + {meta.desc} + </span> + </div> + ); + })} + </div> + ); + } + + return ( + <div + style={{ + display: "grid", + gridTemplateColumns: "repeat(auto-fill, minmax(110px, 1fr))", + gap: 10, + }} + > + {entries.map(({ key, meta, value }) => { + const Icon = meta.icon; + const isActive = parseFloat(value) > 0; + return ( + <div + key={key} + style={{ + borderRadius: 12, + padding: "14px 12px", + textAlign: "center", + background: isActive ? meta.bg : "var(--bg-3)", + border: `1px solid ${isActive ? meta.color + "40" : "var(--border)"}`, + transition: "all 0.2s", + }} + > + <div + style={{ + width: 32, + height: 32, + borderRadius: 8, + background: isActive ? meta.bg : "var(--border)", + display: "flex", + alignItems: "center", + justifyContent: "center", + margin: "0 auto 8px", + }} + > + <Icon + size={14} + style={{ color: isActive ? meta.color : "var(--text-3)" }} + /> + </div> + <div + style={{ + fontWeight: 700, + fontFamily: "DM Mono, monospace", + fontSize: 22, + color: isActive ? meta.color : "var(--text-3)", + lineHeight: 1, + }} + > + {value} + </div> + <div + style={{ + fontSize: 10, + fontFamily: "DM Mono, monospace", + color: "var(--text-3)", + marginTop: 2, + }} + > + {meta.unit} + </div> + <div + style={{ + fontSize: 11, + color: isActive ? "var(--text-2)" : "var(--text-3)", + marginTop: 4, + fontWeight: isActive ? 600 : 400, + }} + > + {meta.label} + </div> + </div> + ); + })} + </div> + ); +} + +// Agent Outcome +export function AgentOutcomeWidget({ outcome, rewardScore, strategicIntent }) { + if (!outcome || outcome === "PENDING_OBSERVATION") return null; + + const raw = outcome.split("| Reward:")[0].trim(); + const reward = + rewardScore != null + ? parseFloat(rewardScore) + : outcome.includes("Reward:") + ? parseFloat(outcome.split("Reward:")[1]) + : null; + + const isPositive = raw === "IMPROVED" || (reward != null && reward > 0.3); + const isNegative = + raw === "DETERIORATED" || (reward != null && reward < -0.1); + + const color = isNegative + ? "var(--red)" + : isPositive + ? "var(--green)" + : "var(--amber)"; + const bg = isNegative + ? "rgba(248,113,113,0.08)" + : isPositive + ? "rgba(74,222,128,0.08)" + : "rgba(245,158,11,0.08)"; + const border = isNegative + ? "rgba(248,113,113,0.25)" + : isPositive + ? "rgba(74,222,128,0.25)" + : "rgba(245,158,11,0.25)"; + + const emoji = isNegative ? "▼" : isPositive ? "▲" : "●"; + + return ( + <div + style={{ + borderRadius: 12, + padding: "12px 16px", + background: bg, + border: `1px solid ${border}`, + display: "flex", + alignItems: "center", + gap: 12, + flexWrap: "wrap", + }} + > + <span + style={{ + fontSize: 18, + fontFamily: "DM Mono, monospace", + color, + fontWeight: 700, + flexShrink: 0, + }} + > + {emoji} {raw} + </span> + + {reward != null && ( + <span + style={{ + fontSize: 13, + fontFamily: "DM Mono, monospace", + padding: "2px 10px", + borderRadius: 20, + background: `${color}20`, + color, + border: `1px solid ${color}40`, + flexShrink: 0, + }} + > + Reward: {reward > 0 ? "+" : ""} + {reward.toFixed(2)} + </span> + )} + + {strategicIntent && ( + <span + style={{ + fontSize: 11, + fontFamily: "DM Mono, monospace", + padding: "2px 10px", + borderRadius: 20, + background: "var(--bg-3)", + color: "var(--text-3)", + border: "1px solid var(--border)", + flexShrink: 0, + }} + > + {strategicIntent.replace(/_/g, " ")} + </span> + )} + </div> + ); +} diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx @@ -12,11 +12,13 @@ import { } from "lucide-react"; import { useFarmData } from "../hooks/useFarmData"; import { deriveCropStatus } from "../utils/dataUtils"; +import { useSettings } from "../hooks/useSettings"; export default function Sidebar() { const [collapsed, setCollapsed] = useState(false); const loc = useLocation(); const { dashboard } = useFarmData(); + const { settings } = useSettings(); const alertCount = useMemo(() => { if (!dashboard?.length) return 0; @@ -30,10 +32,19 @@ export default function Sidebar() { { label: "Crops", icon: LayoutGrid, path: "/dashboard" }, { label: "Analytics", icon: BarChart3, path: "/analytics" }, { label: "Alerts", icon: Bell, path: "/alerts", badge: alertCount || null }, - { label: "Agent AI", icon: Brain, path: "/control" }, - { label: "Settings", icon: Settings, path: "#" }, + { label: "Agent Control", icon: Brain, path: "/control" }, + { label: "Settings", icon: Settings, path: "/settings" }, ]; + const initials = + settings.userInitials || + (settings.userName || "?") + .split(" ") + .map((w) => w[0]) + .join("") + .toUpperCase() + .slice(0, 2); + return ( <aside style={{ @@ -80,7 +91,7 @@ export default function Sidebar() { <div style={{ fontWeight: 700, - fontSize: 14, + fontSize: 15, color: "var(--text)", lineHeight: 1.2, }} @@ -119,13 +130,7 @@ export default function Sidebar() { justifyContent: "center", cursor: "pointer", zIndex: 10, - transition: "background 0.15s", }} - onMouseEnter={(e) => - (e.currentTarget.style.background = "var(--surface-2)") - } - onMouseLeave={(e) => (e.currentTarget.style.background = "var(--bg-3)")} - title={collapsed ? "Expand sidebar" : "Collapse sidebar"} > {collapsed ? <ChevronRight size={11} /> : <ChevronLeft size={11} />} </button> @@ -241,7 +246,7 @@ export default function Sidebar() { })} </nav> - {/* Alert count summary */} + {/* Alert status summary */} {!collapsed && ( <div style={{ padding: "0 12px 12px" }}> <div @@ -253,13 +258,8 @@ export default function Sidebar() { }} > <div - style={{ - fontSize: 9, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 6, - letterSpacing: "0.05em", - }} + className="section-label" + style={{ margin: 0, marginBottom: 6, fontSize: 9 }} > ALERT STATUS </div> @@ -328,7 +328,7 @@ export default function Sidebar() { width: 28, height: 28, borderRadius: "50%", - background: "var(--amber-dim)", + background: "rgba(245,158,11,0.15)", color: "var(--amber)", display: "flex", alignItems: "center", @@ -338,17 +338,17 @@ export default function Sidebar() { flexShrink: 0, }} > - R + {initials} </div> {!collapsed && ( <div> <div style={{ fontSize: 12, fontWeight: 600, color: "var(--text)" }} > - Rajesh Rai + {settings.userName} </div> <div style={{ fontSize: 10, color: "var(--text-3)" }}> - Farm Owner + {settings.userDesignation} </div> </div> )} diff --git a/frontend/src/data/mockData.js b/frontend/src/data/mockData.js @@ -0,0 +1,233 @@ +// MOCK DATA +// -- Set USE_MOCK_DATA = true to use local test data +// -- Set USE_MOCK_DATA = false to connect to the real backend API +export const USE_MOCK_DATA = false; + +// HELPERS +const ts = (daysAgo, hour = 10, min = 0) => { + const d = new Date(); + d.setDate(d.getDate() - daysAgo); + d.setHours(hour, min, 0, 0); + return d.toISOString(); +}; + +// Dashboard snapshots (latest per crop) +export const MOCK_DASHBOARD = [ + { + id: "pt-001", + payload: { + crop_id: "Batch_Lettuce_2025A", + crop: "Lettuce", + stage: "Vegetative", + sequence_number: 14, + timestamp: ts(0, 9, 30), + sensors: { pH: 6.1, EC: 1.4, temp: 23.5, humidity: 68 }, + action_taken: JSON.stringify({ + acid_dosage_ml: 0, + base_dosage_ml: 0, + nutrient_dosage_ml: 2.5, + fan_speed_pct: 45, + water_refill_l: 0, + }), + outcome: "IMPROVED | Reward: 0.8", + strategic_intent: "MAINTAIN_CURRENT", + bandit_action_id: 0, + reward_score: 0.8, + }, + }, + { + id: "pt-002", + payload: { + crop_id: "Batch_Tomato_2025B", + crop: "Tomato", + stage: "Flowering", + sequence_number: 22, + timestamp: ts(0, 8, 15), + sensors: { pH: 5.8, EC: 2.1, temp: 26.0, humidity: 58 }, + action_taken: JSON.stringify({ + acid_dosage_ml: 1.5, + base_dosage_ml: 0, + nutrient_dosage_ml: 4.0, + fan_speed_pct: 60, + water_refill_l: 0, + }), + outcome: "STABLE | Reward: 0.4", + strategic_intent: "INCREASE_EC_BLOOM", + bandit_action_id: 6, + reward_score: 0.4, + }, + }, + { + id: "pt-003", + payload: { + crop_id: "Batch_Basil_2025C", + crop: "Basil", + stage: "Seedling", + sequence_number: 5, + timestamp: ts(0, 11, 0), + sensors: { pH: 7.8, EC: 0.6, temp: 29.5, humidity: 82 }, + action_taken: JSON.stringify({ + acid_dosage_ml: 8.0, + base_dosage_ml: 0, + nutrient_dosage_ml: 3.0, + fan_speed_pct: 80, + water_refill_l: 0, + }), + outcome: "DETERIORATED | Reward: -0.6", + strategic_intent: "AGGRESSIVE_PH_DOWN", + bandit_action_id: 2, + reward_score: -0.6, + }, + }, + { + id: "pt-004", + payload: { + crop_id: "Batch_Spinach_2025D", + crop: "Spinach", + stage: "Vegetative", + sequence_number: 9, + timestamp: ts(1, 14, 45), + sensors: { pH: 6.3, EC: 1.6, temp: 21.0, humidity: 72 }, + action_taken: JSON.stringify({ + acid_dosage_ml: 0, + base_dosage_ml: 0, + nutrient_dosage_ml: 1.0, + fan_speed_pct: 35, + water_refill_l: 2.0, + }), + outcome: "IMPROVED | Reward: 0.7", + strategic_intent: "GENTLE_PH_BALANCING", + bandit_action_id: 4, + reward_score: 0.7, + }, + }, + { + id: "pt-005", + payload: { + crop_id: "Batch_Cucumber_2025E", + crop: "Cucumber", + stage: "Fruiting", + sequence_number: 31, + timestamp: ts(0, 7, 0), + sensors: { pH: 5.5, EC: 2.8, temp: 27.5, humidity: 55 }, + action_taken: JSON.stringify({ + acid_dosage_ml: 0, + base_dosage_ml: 2.0, + nutrient_dosage_ml: 0, + fan_speed_pct: 70, + water_refill_l: 5.0, + }), + outcome: "STABLE | Reward: 0.3", + strategic_intent: "LOWER_EC_FLUSH", + bandit_action_id: 7, + reward_score: 0.3, + }, + }, +]; + +// Detailed history per crop (multiple snapshots) +function makeHistory(cropId, cropName, stage, n, baseVals) { + return Array.from({ length: n }, (_, i) => { + const jitter = (range) => (Math.random() - 0.5) * range; + return { + id: `${cropId}-seq-${i + 1}`, + payload: { + crop_id: cropId, + crop: cropName, + stage, + sequence_number: i + 1, + timestamp: ts(Math.floor((n - i) / 3), (i * 2) % 24, (i * 7) % 60), + sensors: { + pH: +(baseVals.ph + jitter(0.4)).toFixed(2), + EC: +(baseVals.ec + jitter(0.3)).toFixed(2), + temp: +(baseVals.temp + jitter(2)).toFixed(1), + humidity: +(baseVals.humidity + jitter(8)).toFixed(1), + }, + action_taken: JSON.stringify({ + acid_dosage_ml: +(Math.random() * 3).toFixed(1), + base_dosage_ml: +(Math.random() * 2).toFixed(1), + nutrient_dosage_ml: +(Math.random() * 5).toFixed(1), + fan_speed_pct: +(30 + Math.random() * 50).toFixed(0), + water_refill_l: +(Math.random() * 4).toFixed(1), + }), + outcome: + i % 5 === 0 + ? "DETERIORATED | Reward: -0.4" + : i % 3 === 0 + ? "STABLE | Reward: 0.3" + : "IMPROVED | Reward: 0.75", + strategic_intent: [ + "MAINTAIN_CURRENT", + "GENTLE_PH_BALANCING", + "INCREASE_EC_VEG", + "RAISE_TEMP_HUMIDITY", + "MAX_AIR_CIRCULATION", + ][i % 5], + reward_score: i % 5 === 0 ? -0.4 : i % 3 === 0 ? 0.3 : 0.75, + }, + }; + }); +} + +export const MOCK_HISTORY = [ + ...makeHistory("Batch_Lettuce_2025A", "Lettuce", "Vegetative", 14, { + ph: 6.1, + ec: 1.4, + temp: 23.5, + humidity: 68, + }), + ...makeHistory("Batch_Tomato_2025B", "Tomato", "Flowering", 22, { + ph: 5.9, + ec: 2.0, + temp: 26.0, + humidity: 58, + }), + ...makeHistory("Batch_Basil_2025C", "Basil", "Seedling", 5, { + ph: 7.2, + ec: 0.7, + temp: 29.0, + humidity: 80, + }), + ...makeHistory("Batch_Spinach_2025D", "Spinach", "Vegetative", 9, { + ph: 6.3, + ec: 1.6, + temp: 21.5, + humidity: 72, + }), + ...makeHistory("Batch_Cucumber_2025E", "Cucumber", "Fruiting", 31, { + ph: 5.6, + ec: 2.7, + temp: 27.0, + humidity: 56, + }), +]; + +// Mock search / agent response +export const MOCK_SEARCH_RESULT = { + status: "success", + new_fmu_id: "mock-fmu-001", + agent_decision: { + acid_dosage_ml: 2.5, + base_dosage_ml: 0, + nutrient_dosage_ml: 3.0, + fan_speed_pct: 55, + water_refill_l: 1.5, + }, + explanation: `1. **Observation**: Sensors show pH 6.2, EC 1.4 dS/m, Temp 23.5°C, Humidity 68%. + All parameters are within acceptable range for Vegetative Lettuce. + +2. **Precedent**: 3 similar past states found. In 2 of those cases, a slight EC boost + improved growth rate. No disease was detected in the last 5 cycles. + +3. **Logic**: EC at 1.4 is slightly below the 1.5-1.8 target for late vegetative. + A small nutrient dosage increase will push it into the optimal window. + Fan speed is adequate; no VPD concerns. + +4. **Conclusion**: Dosing 3.0ml nutrients is the safest, most targeted intervention. + No pH correction needed. Maintain current atmospheric settings.`, + search_results: MOCK_DASHBOARD.slice(0, 3).map((d, i) => ({ + id: d.id, + score: 0.95 - i * 0.08, + payload: d.payload, + })), +}; diff --git a/frontend/src/hooks/useSettings.js b/frontend/src/hooks/useSettings.js @@ -0,0 +1,49 @@ +import React, { createContext, useContext, useState, useEffect } from "react"; + +const DEFAULTS = { + userName: "Rajesh Rai", + userDesignation: "Farm Owner", + userInitials: "R", + theme: "dark", + maxResultsPerPage: 12, + alertsShowAcked: false, + compactMode: false, +}; + +const SettingsContext = createContext(); + +const STORAGE_KEY = "demeter_settings"; + +export function SettingsProvider({ children }) { + const [settings, setSettings] = useState(() => { + try { + const stored = localStorage.getItem(STORAGE_KEY); + return stored ? { ...DEFAULTS, ...JSON.parse(stored) } : DEFAULTS; + } catch { + return DEFAULTS; + } + }); + + // Persist on change + useEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); + }, [settings]); + + // Apply theme to <html> + useEffect(() => { + document.documentElement.setAttribute("data-theme", settings.theme); + }, [settings.theme]); + + const update = (key, value) => + setSettings((prev) => ({ ...prev, [key]: value })); + + const reset = () => setSettings(DEFAULTS); + + return ( + <SettingsContext.Provider value={{ settings, update, reset }}> + {children} + </SettingsContext.Provider> + ); +} + +export const useSettings = () => useContext(SettingsContext); diff --git a/frontend/src/index.css b/frontend/src/index.css @@ -4,7 +4,9 @@ @tailwind components; @tailwind utilities; -:root { +/* Dark Theme */ +:root, +[data-theme="dark"] { --bg: #0c1a0e; --bg-2: #111f13; --bg-3: #162018; @@ -23,8 +25,42 @@ --red-dim: #7f1d1d; --blue: #60a5fa; --blue-dim: #1e3a5f; + + /* Component tokens */ + --input-bg: #162018; + --scrollbar-thumb: #3d6040; + --tooltip-bg: #1a2b1c; + --shadow: 0 4px 24px rgba(0, 0, 0, 0.4); +} + +/* Light Theme */ +[data-theme="light"] { + --bg: #f0f7f1; + --bg-2: #ffffff; + --bg-3: #e8f2ea; + --surface: #ffffff; + --surface-2: #f4faf5; + --border: #c8deca; + --border-bright: #7db688; + --text: #1a2e1c; + --text-2: #3d5e42; + --text-3: #6a8a6d; + --green: #1a7c3a; + --green-dim: #c8e6d0; + --amber: #b45309; + --amber-dim: #fde68a; + --red: #dc2626; + --red-dim: #fecaca; + --blue: #1d4ed8; + --blue-dim: #bfdbfe; + + --input-bg: #f4faf5; + --scrollbar-thumb: #7db688; + --tooltip-bg: #ffffff; + --shadow: 0 4px 24px rgba(0, 0, 0, 0.08); } +/* Reset */ * { box-sizing: border-box; } @@ -36,6 +72,9 @@ body { font-family: "Syne", sans-serif; -webkit-font-smoothing: antialiased; overflow-x: hidden; + transition: + background 0.25s ease, + color 0.25s ease; } /* Scrollbar */ @@ -47,11 +86,101 @@ body { background: var(--bg-2); } ::-webkit-scrollbar-thumb { - background: var(--border-bright); + background: var(--scrollbar-thumb); border-radius: 3px; } -/* Utility classes */ +/* TYPOGRAPHY */ +/* Section headings (// COMMENT style) */ +.section-label { + font-size: 11px; + font-family: "DM Mono", monospace; + color: var(--text-3); + letter-spacing: 0.08em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 14px; +} +.section-label::before { + content: "//"; + color: var(--green); + opacity: 0.6; + font-weight: 700; +} + +/* Sensor value */ +.sensor-value { + font-size: 28px; + font-weight: 700; + font-family: "DM Mono", monospace; + line-height: 1; +} +.sensor-value-sm { + font-size: 20px; + font-weight: 700; + font-family: "DM Mono", monospace; + line-height: 1; +} +.sensor-value-xs { + font-size: 15px; + font-weight: 600; + font-family: "DM Mono", monospace; +} +.sensor-label { + font-size: 11px; + font-family: "DM Mono", monospace; + color: var(--text-3); + letter-spacing: 0.06em; + text-transform: uppercase; + margin-bottom: 4px; +} +.sensor-unit { + font-size: 13px; + font-weight: 400; + color: var(--text-3); + margin-left: 3px; +} + +/* Page heading */ +.page-title { + font-size: 20px; + font-weight: 700; + color: var(--text); + margin: 0; +} +.page-subtitle { + font-size: 12px; + font-family: "DM Mono", monospace; + color: var(--text-3); + margin: 2px 0 0; +} + +/* Table */ +.data-table th { + font-size: 11px; + font-family: "DM Mono", monospace; + color: var(--text-3); + font-weight: 500; + letter-spacing: 0.05em; + text-transform: uppercase; + padding: 10px 20px; + text-align: left; + background: var(--bg-3); + border-bottom: 1px solid var(--border); +} +.data-table td { + padding: 11px 20px; + font-size: 13px; + border-bottom: 1px solid var(--border); + color: var(--text-2); +} +.data-table tr:last-child td { + border-bottom: none; +} + +/* UTILITIES */ .font-mono { font-family: "DM Mono", monospace; } @@ -59,34 +188,13 @@ body { font-family: "Instrument Serif", serif; } -/* Noise texture overlay */ -.noise::after { - content: ""; - position: absolute; - inset: 0; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E"); - pointer-events: none; - z-index: 0; -} - -/* Glow effects */ .glow-green { box-shadow: 0 0 20px rgba(74, 222, 128, 0.15), 0 0 60px rgba(74, 222, 128, 0.05); } -.glow-amber { - box-shadow: - 0 0 20px rgba(245, 158, 11, 0.2), - 0 0 60px rgba(245, 158, 11, 0.08); -} -.glow-red { - box-shadow: - 0 0 20px rgba(248, 113, 113, 0.2), - 0 0 60px rgba(248, 113, 113, 0.08); -} -/* Scan line animation */ +/* Scan line */ @keyframes scanline { 0% { transform: translateY(-100%); @@ -95,7 +203,6 @@ body { transform: translateY(100vh); } } - .scanline { position: fixed; top: 0; @@ -112,8 +219,11 @@ body { pointer-events: none; z-index: 9999; } +[data-theme="light"] .scanline { + display: none; +} -/* Fade in animation */ +/* Animations */ @keyframes fadeUp { from { opacity: 0; @@ -124,7 +234,6 @@ body { transform: translateY(0); } } - @keyframes fadeIn { from { opacity: 0; @@ -133,34 +242,6 @@ body { opacity: 1; } } - -@keyframes pulse-border { - 0%, - 100% { - border-color: rgba(74, 222, 128, 0.3); - } - 50% { - border-color: rgba(74, 222, 128, 0.8); - } -} - -@keyframes ticker { - 0% { - transform: translateX(100%); - } - 100% { - transform: translateX(-100%); - } -} - -.animate-fade-up { - animation: fadeUp 0.5s ease forwards; -} -.animate-fade-in { - animation: fadeIn 0.3s ease forwards; -} - -/* Status dot pulse */ @keyframes statusPulse { 0%, 100% { @@ -172,59 +253,28 @@ body { transform: scale(0.8); } } - -.status-dot { - animation: statusPulse 2s ease-in-out infinite; -} - -/* Card hover effect */ -.card-hover { - transition: - transform 0.2s ease, - box-shadow 0.2s ease, - border-color 0.2s ease; +@keyframes alertPulse { + 0%, + 100% { + box-shadow: 0 0 0 0 rgba(248, 113, 113, 0.4); + } + 70% { + box-shadow: 0 0 0 8px rgba(248, 113, 113, 0); + } } -.card-hover:hover { - transform: translateY(-2px); - border-color: var(--border-bright); +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } } - -/* Progress bar animation */ @keyframes progressFill { from { width: 0; } } - -.progress-fill { - animation: progressFill 1s ease forwards; -} - -/* Number counter */ -@keyframes countUp { - from { - opacity: 0; - transform: translateY(4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.count-up { - animation: countUp 0.6s ease forwards; -} - -/* Grid line background */ -.grid-bg { - background-image: - linear-gradient(rgba(74, 222, 128, 0.03) 1px, transparent 1px), - linear-gradient(90deg, rgba(74, 222, 128, 0.03) 1px, transparent 1px); - background-size: 40px 40px; -} - -/* Terminal cursor blink */ @keyframes blink { 0%, 100% { @@ -235,22 +285,27 @@ body { } } +.animate-fade-up { + animation: fadeUp 0.5s ease forwards; +} +.animate-fade-in { + animation: fadeIn 0.3s ease forwards; +} +.status-dot { + animation: statusPulse 2s ease-in-out infinite; +} +.alert-pulse { + animation: alertPulse 2s ease infinite; +} +.progress-fill { + animation: progressFill 1s ease forwards; +} .cursor-blink::after { content: "|"; animation: blink 1s step-end infinite; color: var(--green); } -/* Shimmer loading */ -@keyframes shimmer { - 0% { - background-position: -200% 0; - } - 100% { - background-position: 200% 0; - } -} - .shimmer { background: linear-gradient( 90deg, @@ -262,33 +317,42 @@ body { animation: shimmer 1.5s infinite; } -/* Alert badge pulse */ -@keyframes alertPulse { - 0%, - 100% { - box-shadow: 0 0 0 0 rgba(248, 113, 113, 0.4); - } - 70% { - box-shadow: 0 0 0 8px rgba(248, 113, 113, 0); - } +.card-hover { + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + border-color 0.2s ease; } - -.alert-pulse { - animation: alertPulse 2s ease infinite; +.card-hover:hover { + transform: translateY(-2px); + border-color: var(--border-bright); + box-shadow: var(--shadow); } -/* Rotating ring */ -@keyframes spin-slow { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.grid-bg { + background-image: + linear-gradient(rgba(74, 222, 128, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(74, 222, 128, 0.03) 1px, transparent 1px); + background-size: 40px 40px; } -.spin-slow { - animation: spin-slow 20s linear infinite; +[data-theme="light"] .grid-bg { + background-image: + linear-gradient(rgba(26, 124, 58, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(26, 124, 58, 0.05) 1px, transparent 1px); +} + +/* Input fields */ +input, +select, +textarea { + background: var(--input-bg); + color: var(--text); + border-color: var(--border); + transition: + background 0.2s, + color 0.2s, + border-color 0.2s; } -.spin-slow-reverse { - animation: spin-slow 15s linear infinite reverse; +input::placeholder { + color: var(--text-3); } diff --git a/frontend/src/pages/AgentControl.jsx b/frontend/src/pages/AgentControl.jsx @@ -8,20 +8,19 @@ import { Wind, Sprout, Calendar, - Leaf, Database, Mic, Square, - Zap, - Fan, - FlaskConical, - Waves, Brain, ChevronDown, Eye, } from "lucide-react"; import { agentService } from "../api/agentApi"; -import { extractSensors, formatOutcome } from "../utils/dataUtils"; +import { extractSensors } from "../utils/dataUtils"; +import { + AgentActionWidget, + AgentOutcomeWidget, +} from "../components/AgentWidgets"; import Sidebar from "../components/Sidebar"; const INPUT_FIELDS = [ @@ -75,43 +74,10 @@ const INPUT_FIELDS = [ icon: Database, color: "var(--text-3)", type: "text", - placeholder: "e.g., Batch_A1", + placeholder: "Eg. Batch_A1", }, ]; -const ACTION_MAP = { - acid_dosage_ml: { - label: "Acid Dosage", - icon: FlaskConical, - unit: "ml", - color: "var(--red)", - }, - base_dosage_ml: { - label: "Base Dosage", - icon: FlaskConical, - unit: "ml", - color: "#a78bfa", - }, - nutrient_dosage_ml: { - label: "Nutrients", - icon: Sprout, - unit: "ml", - color: "var(--green)", - }, - fan_speed_pct: { - label: "Fan Speed", - icon: Fan, - unit: "%", - color: "var(--blue)", - }, - water_refill_l: { - label: "Water Refill", - icon: Waves, - unit: "L", - color: "var(--blue)", - }, -}; - export default function AgentControl() { const [file, setFile] = useState(null); const [preview, setPreview] = useState(null); @@ -122,8 +88,8 @@ export default function AgentControl() { const [searchResults, setSearchResults] = useState([]); const [textQuery, setTextQuery] = useState(""); - const [showExplanation, setShowExplanation] = useState(false); - const [explanationText, setExplanationText] = useState(""); + const [showExplain, setShowExplain] = useState(false); + const [explanation, setExplanation] = useState(""); const [isRecording, setIsRecording] = useState(false); const mediaRecorderRef = useRef(null); @@ -174,7 +140,7 @@ export default function AgentControl() { setDecision(null); try { const res = await agentService.searchFMU(file, sensors); - if (res.explanation) setExplanationText(res.explanation); + if (res.explanation) setExplanation(res.explanation); if (res.agent_decision) setDecision(res.agent_decision); setSearchResults(res.search_results || []); } catch { @@ -267,7 +233,7 @@ export default function AgentControl() { zIndex: 50, padding: "10px 16px", borderRadius: 12, - fontSize: 12, + fontSize: 13, fontFamily: "DM Mono, monospace", background: toast.type === "error" @@ -317,24 +283,8 @@ export default function AgentControl() { <Brain size={15} style={{ color: "var(--green)" }} /> </div> <div> - <h1 - style={{ - fontWeight: 700, - fontSize: 15, - color: "var(--text)", - margin: 0, - }} - > - Agent Control - </h1> - <p - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - margin: 0, - }} - > + <h1 className="page-title">Agent Control</h1> + <p className="page-subtitle"> Ingest memories · Query the Supervisor · Run analysis </p> </div> @@ -391,7 +341,7 @@ export default function AgentControl() { background: "transparent", border: "none", outline: "none", - fontSize: 13, + fontSize: 14, fontFamily: "DM Mono, monospace", color: "var(--text)", caretColor: "var(--green)", @@ -400,7 +350,7 @@ export default function AgentControl() { <button onClick={handleTextQuery} style={{ - padding: "6px 16px", + padding: "6px 18px", borderRadius: 8, fontSize: 13, fontWeight: 600, @@ -493,7 +443,7 @@ export default function AgentControl() { <div style={{ fontWeight: 600, - fontSize: 13, + fontSize: 14, color: "var(--text-2)", }} > @@ -501,7 +451,7 @@ export default function AgentControl() { </div> <div style={{ - fontSize: 11, + fontSize: 12, marginTop: 4, color: "var(--text-3)", }} @@ -586,16 +536,7 @@ export default function AgentControl() { border: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 16, - }} - > - // SENSOR PARAMETERS - </div> + <div className="section-label">SENSOR PARAMETERS</div> <div style={{ display: "grid", @@ -614,17 +555,12 @@ export default function AgentControl() { placeholder, }) => ( <div key={name}> - <label - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - display: "block", - marginBottom: 5, - color, - }} + <div + className="sensor-label" + style={{ color, marginBottom: 5 }} > {label.toUpperCase()} - </label> + </div> <div style={{ position: "relative" }}> <Icon size={12} @@ -652,10 +588,10 @@ export default function AgentControl() { appearance: "none", paddingLeft: 30, paddingRight: 28, - paddingTop: 8, - paddingBottom: 8, + paddingTop: 9, + paddingBottom: 9, borderRadius: 8, - fontSize: 12, + fontSize: 13, fontFamily: "DM Mono, monospace", background: "var(--bg-3)", border: "1px solid var(--border)", @@ -695,10 +631,10 @@ export default function AgentControl() { width: "100%", paddingLeft: 30, paddingRight: 10, - paddingTop: 8, - paddingBottom: 8, + paddingTop: 9, + paddingBottom: 9, borderRadius: 8, - fontSize: 12, + fontSize: 13, fontFamily: "DM Mono, monospace", background: "var(--bg-3)", border: "1px solid var(--border)", @@ -725,6 +661,7 @@ export default function AgentControl() { overflow: "hidden", background: "var(--surface)", border: "1px solid rgba(74,222,128,0.3)", + flexShrink: 0, }} > <div @@ -741,8 +678,8 @@ export default function AgentControl() { <Brain size={15} style={{ color: "var(--green)" }} /> <span style={{ - fontWeight: 600, - fontSize: 13, + fontWeight: 700, + fontSize: 15, color: "var(--text)", }} > @@ -750,14 +687,14 @@ export default function AgentControl() { </span> </div> <button - onClick={() => setShowExplanation(!showExplanation)} + onClick={() => setShowExplain(!showExplain)} style={{ display: "flex", alignItems: "center", gap: 6, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", - padding: "5px 10px", + padding: "5px 12px", borderRadius: 8, cursor: "pointer", background: "var(--surface-2)", @@ -765,86 +702,15 @@ export default function AgentControl() { color: "var(--text-3)", }} > - <Eye size={11} /> {showExplanation ? "Hide" : "View"} logic + <Eye size={11} /> {showExplain ? "Hide" : "View"} logic </button> </div> - <div - style={{ - padding: 20, - display: "grid", - gridTemplateColumns: "repeat(5, 1fr)", - gap: 12, - }} - > - {Object.entries(decision).map(([key, value]) => { - const meta = ACTION_MAP[key] || { - label: key, - icon: Zap, - unit: "", - color: "var(--text-3)", - }; - const Icon = meta.icon; - return ( - <div - key={key} - style={{ - borderRadius: 12, - padding: 14, - textAlign: "center", - background: "var(--bg-3)", - border: "1px solid var(--border)", - }} - > - <div - style={{ - width: 30, - height: 30, - borderRadius: 8, - background: `${meta.color}15`, - display: "flex", - alignItems: "center", - justifyContent: "center", - margin: "0 auto 8px", - }} - > - <Icon size={13} style={{ color: meta.color }} /> - </div> - <div - style={{ - fontWeight: 700, - fontFamily: "DM Mono, monospace", - fontSize: 20, - color: meta.color, - }} - > - {value} - </div> - <div - style={{ - fontSize: 9, - fontFamily: "DM Mono, monospace", - marginTop: 2, - color: "var(--text-3)", - }} - > - {meta.unit} - </div> - <div - style={{ - fontSize: 10, - marginTop: 4, - color: "var(--text-3)", - }} - > - {meta.label} - </div> - </div> - ); - })} + <div style={{ padding: 20 }}> + <AgentActionWidget actionTaken={decision} compact={false} /> </div> - {showExplanation && explanationText && ( + {showExplain && explanation && ( <div style={{ borderTop: "1px solid var(--border)", @@ -852,27 +718,18 @@ export default function AgentControl() { background: "var(--bg-3)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 8, - }} - > - // SUPERVISOR REASONING - </div> + <div className="section-label">SUPERVISOR REASONING</div> <pre style={{ - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", - lineHeight: 1.7, + lineHeight: 1.8, whiteSpace: "pre-wrap", color: "var(--text-2)", margin: 0, }} > - {explanationText} + {explanation} </pre> </div> )} @@ -882,29 +739,13 @@ export default function AgentControl() { {/* Search results */} {searchResults.length > 0 && ( <div> - <div - style={{ - display: "flex", - alignItems: "center", - gap: 8, - marginBottom: 14, - }} - > - <Database size={13} style={{ color: "var(--text-3)" }} /> - <span - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - }} - > - MEMORY MATCHES · {searchResults.length} FOUND - </span> + <div className="section-label"> + MEMORY MATCHES · {searchResults.length} FOUND </div> <div style={{ display: "grid", - gridTemplateColumns: "repeat(3, 1fr)", + gridTemplateColumns: "repeat(3,1fr)", gap: 14, }} > @@ -915,8 +756,8 @@ export default function AgentControl() { key={res.id} className="card-hover" style={{ - borderRadius: 12, - padding: 14, + borderRadius: 14, + padding: 16, background: "var(--surface)", border: "1px solid var(--border)", }} @@ -926,32 +767,23 @@ export default function AgentControl() { display: "flex", alignItems: "center", justifyContent: "space-between", - marginBottom: 10, + marginBottom: 12, }} > - <div + <span style={{ - display: "flex", - alignItems: "center", - gap: 6, + fontWeight: 600, + fontSize: 14, + color: "var(--text)", }} > - <Leaf size={13} style={{ color: "var(--green)" }} /> - <span - style={{ - fontWeight: 600, - fontSize: 13, - color: "var(--text)", - }} - > - {res.payload.crop || "Unknown"} - </span> - </div> + {res.payload.crop || "Unknown"} + </span> <span style={{ - fontSize: 10, + fontSize: 11, fontFamily: "DM Mono, monospace", - padding: "2px 6px", + padding: "2px 7px", borderRadius: 4, background: "rgba(74,222,128,0.1)", color: "var(--green)", @@ -960,16 +792,28 @@ export default function AgentControl() { {((res.score || 1) * 100).toFixed(0)}% </span> </div> + <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, + marginBottom: 12, }} > {[ { label: "pH", value: s.ph, color: "var(--green)" }, { label: "EC", value: s.ec, color: "var(--amber)" }, + { + label: "Temp", + value: s.temp + "°", + color: "var(--blue)", + }, + { + label: "RH", + value: s.humidity + "%", + color: "#a78bfa", + }, ].map(({ label, value, color }) => ( <div key={label} @@ -981,38 +825,24 @@ export default function AgentControl() { }} > <div - style={{ - fontSize: 9, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - }} + className="sensor-label" + style={{ marginBottom: 2 }} > {label} </div> - <div - style={{ - fontWeight: 700, - fontFamily: "DM Mono, monospace", - fontSize: 14, - color, - }} - > + <div className="sensor-value-sm" style={{ color }}> {value} </div> </div> ))} </div> + + {/* Outcome badge */} {res.payload.outcome && ( - <div - style={{ - marginTop: 8, - fontSize: 11, - color: "var(--text-3)", - }} - > - {formatOutcome(res.payload.outcome)?.substring(0, 80)} - … - </div> + <AgentOutcomeWidget + outcome={res.payload.outcome} + rewardScore={res.payload.reward_score} + /> )} </div> ); diff --git a/frontend/src/pages/Alerts.jsx b/frontend/src/pages/Alerts.jsx @@ -302,24 +302,8 @@ export default function Alerts() { }} > <div> - <h1 - style={{ - fontWeight: 700, - fontSize: 18, - color: "var(--text)", - margin: 0, - }} - > - Alerts - </h1> - <p - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - margin: 0, - }} - > + <h1 className="page-title">Alerts</h1> + <p className="page-subtitle"> {loading ? "Analyzing sensor history…" : `${counts.total} unacknowledged · ${alerts.length} total`} diff --git a/frontend/src/pages/Analytics.jsx b/frontend/src/pages/Analytics.jsx @@ -37,17 +37,18 @@ const CustomTooltip = ({ active, payload, label }) => { style={{ padding: "8px 12px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", - background: "var(--surface-2)", + background: "var(--tooltip-bg)", border: "1px solid var(--border)", color: "var(--text)", + boxShadow: "var(--shadow)", }} > <div style={{ color: "var(--text-3)", marginBottom: 4 }}>{label}</div> {payload.map((p) => ( - <div key={p.dataKey} style={{ color: p.color }}> - {p.name}: {p.value} + <div key={p.dataKey} style={{ color: p.color, marginTop: 2 }}> + {p.name}: <strong>{p.value}</strong> </div> ))} </div> @@ -62,47 +63,34 @@ function MetricCard({ label, value, unit, change, color, loading }) { <div className="card-hover" style={{ - borderRadius: 12, - padding: 20, + borderRadius: 14, + padding: "20px 22px", background: "var(--surface)", border: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 10, - }} - > - {label} - </div> + <div className="sensor-label">{label}</div> {loading ? ( <div className="shimmer" - style={{ height: 32, width: 96, borderRadius: 6 }} + style={{ height: 36, width: 100, borderRadius: 6 }} /> ) : ( <div style={{ - fontSize: 28, - fontWeight: 700, - fontFamily: "DM Mono, monospace", - color: color || "var(--text)", + display: "flex", + alignItems: "baseline", + gap: 4, + marginTop: 6, }} > - {value} <span - style={{ - fontSize: 14, - fontWeight: 400, - marginLeft: 4, - color: "var(--text-3)", - }} + className="sensor-value" + style={{ color: color || "var(--text)" }} > - {unit} + {value} </span> + {unit && <span className="sensor-unit">{unit}</span>} </div> )} <div @@ -110,8 +98,8 @@ function MetricCard({ label, value, unit, change, color, loading }) { display: "flex", alignItems: "center", gap: 4, - marginTop: 8, - fontSize: 11, + marginTop: 10, + fontSize: 12, fontFamily: "DM Mono, monospace", }} > @@ -127,34 +115,26 @@ function MetricCard({ label, value, unit, change, color, loading }) { color: flat ? "var(--text-3)" : up ? "var(--green)" : "var(--red)", }} > - {Math.abs(change)}% vs prior period + {Math.abs(change)}% vs prior </span> </div> </div> ); } -function SectionTitle({ children, sub }) { +function SectionHead({ label, title }) { return ( <div style={{ marginBottom: 16 }}> - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - }} - > - // {sub} - </div> + <div className="section-label">{label}</div> <h2 style={{ fontWeight: 700, - fontSize: 15, + fontSize: 16, color: "var(--text)", margin: "4px 0 0", }} > - {children} + {title} </h2> </div> ); @@ -175,7 +155,7 @@ function EmptyChart({ height = 180, message = "No data yet" }) { > <span style={{ - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", color: "var(--text-3)", }} @@ -200,13 +180,11 @@ export default function Analytics() { // Latest fleet-wide averages const latestSensors = useMemo(() => { if (!dashboard.length) return { ph: 0, ec: 0, temp: 0 }; - const sensors = dashboard.map((d) => extractSensors(d.payload)); + const s = dashboard.map((d) => extractSensors(d.payload)); return { - ph: parseFloat(avg(sensors.map((s) => parseFloat(s.ph) || 0)).toFixed(2)), - ec: parseFloat(avg(sensors.map((s) => parseFloat(s.ec) || 0)).toFixed(2)), - temp: parseFloat( - avg(sensors.map((s) => parseFloat(s.temp) || 0)).toFixed(1), - ), + ph: parseFloat(avg(s.map((x) => parseFloat(x.ph) || 0)).toFixed(2)), + ec: parseFloat(avg(s.map((x) => parseFloat(x.ec) || 0)).toFixed(2)), + temp: parseFloat(avg(s.map((x) => parseFloat(x.temp) || 0)).toFixed(1)), }; }, [dashboard]); @@ -217,10 +195,10 @@ export default function Analytics() { .slice(0, Math.floor(allPoints.length / 2)) .map((p) => extractSensors(p.payload)); return { - ph: parseFloat(avg(older.map((s) => parseFloat(s.ph) || 0)).toFixed(2)), - ec: parseFloat(avg(older.map((s) => parseFloat(s.ec) || 0)).toFixed(2)), + ph: parseFloat(avg(older.map((x) => parseFloat(x.ph) || 0)).toFixed(2)), + ec: parseFloat(avg(older.map((x) => parseFloat(x.ec) || 0)).toFixed(2)), temp: parseFloat( - avg(older.map((s) => parseFloat(s.temp) || 0)).toFixed(1), + avg(older.map((x) => parseFloat(x.temp) || 0)).toFixed(1), ), }; }, [allPoints, latestSensors]); @@ -228,8 +206,17 @@ export default function Analytics() { const activityData = useMemo(() => dailyCropActivity(allPoints), [allPoints]); const radarData = useMemo(() => buildRadar(allPoints), [allPoints]); const agentStats = useMemo(() => buildAgentStats(allPoints), [allPoints]); + const cropSummaryRows = useMemo(() => { + if (!dashboard?.length) return []; + return dashboard.map((item) => { + const p = item.payload || {}; + const s = extractSensors(p); + return { p, s, key: p.crop_id || item.id }; + }); + }, [dashboard]); + + const agentRows = agentStats; - // CSV export const handleExport = () => { if (!buckets.length) return; const header = "time,ph,ec,temp,humidity,entries"; @@ -276,24 +263,8 @@ export default function Analytics() { }} > <div> - <h1 - style={{ - fontWeight: 700, - fontSize: 18, - color: "var(--text)", - margin: 0, - }} - > - Analytics - </h1> - <p - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - margin: 0, - }} - > + <h1 className="page-title">Analytics</h1> + <p className="page-subtitle"> {loading ? "Loading…" : `${allPoints.length} data points across ${dashboard.length} crops`} @@ -314,14 +285,13 @@ export default function Analytics() { style={{ padding: "5px 12px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", cursor: "pointer", background: range === r ? "rgba(74,222,128,0.12)" : "var(--surface)", border: `1px solid ${range === r ? "rgba(74,222,128,0.3)" : "var(--border)"}`, color: range === r ? "var(--green)" : "var(--text-3)", - transition: "all 0.15s", }} > {r} @@ -335,7 +305,7 @@ export default function Analytics() { gap: 6, padding: "5px 12px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", background: "var(--surface)", border: "1px solid var(--border)", @@ -359,11 +329,11 @@ export default function Analytics() { gap: 28, }} > - {/* Metric cards */} + {/* Metric Cards */} <div style={{ display: "grid", - gridTemplateColumns: "repeat(4, 1fr)", + gridTemplateColumns: "repeat(4,1fr)", gap: 12, }} > @@ -404,7 +374,7 @@ export default function Analytics() { /> </div> - {/* pH + EC */} + {/* pH + EC Charts */} <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }} > @@ -427,15 +397,16 @@ export default function Analytics() { <div key={key} style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <SectionTitle sub={`${range.toUpperCase()} TRACE`}> - {title} - </SectionTitle> + <SectionHead + label={`${range.toUpperCase()} TRACE`} + title={title} + /> {buckets.length < 2 ? ( <EmptyChart message="Not enough data for this range" /> ) : ( @@ -463,7 +434,7 @@ export default function Analytics() { <XAxis dataKey="label" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -474,7 +445,7 @@ export default function Analytics() { <YAxis domain={["auto", "auto"]} tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -501,15 +472,16 @@ export default function Analytics() { {/* Temp + Humidity */} <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <SectionTitle sub={`${range.toUpperCase()} TRACE`}> - Temperature & Humidity - </SectionTitle> + <SectionHead + label={`${range.toUpperCase()} TRACE`} + title="Temperature & Humidity" + /> {buckets.length < 2 ? ( <EmptyChart message="Not enough data for this range" /> ) : ( @@ -523,7 +495,7 @@ export default function Analytics() { <XAxis dataKey="label" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -535,7 +507,7 @@ export default function Analytics() { yAxisId="left" domain={["auto", "auto"]} tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -547,7 +519,7 @@ export default function Analytics() { orientation="right" domain={["auto", "auto"]} tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -584,15 +556,16 @@ export default function Analytics() { > <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <SectionTitle sub="DAILY ACTIVITY"> - Sequences Logged per Day - </SectionTitle> + <SectionHead + label="DAILY ACTIVITY" + title="Sequences Logged per Day" + /> {activityData.length < 2 ? ( <EmptyChart height={180} message="Need 2+ days of data" /> ) : ( @@ -606,7 +579,7 @@ export default function Analytics() { <XAxis dataKey="d" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -615,7 +588,7 @@ export default function Analytics() { /> <YAxis tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -637,15 +610,16 @@ export default function Analytics() { <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <SectionTitle sub="PARAMETER HEALTH"> - In-Range Score (%) - </SectionTitle> + <SectionHead + label="PARAMETER HEALTH" + title="In-Range Score (%)" + /> {radarData.length < 2 ? ( <EmptyChart height={180} message="Not enough data points" /> ) : ( @@ -660,7 +634,7 @@ export default function Analytics() { <PolarAngleAxis dataKey="metric" tick={{ - fontSize: 9, + fontSize: 11, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -679,31 +653,29 @@ export default function Analytics() { </div> </div> - {/* Crop summary table */} + {/* Crop Summary Table */} <div style={{ - borderRadius: 12, + borderRadius: 14, overflow: "hidden", background: "var(--surface)", border: "1px solid var(--border)", + flexShrink: 0, }} > <div - style={{ padding: 20, borderBottom: "1px solid var(--border)" }} + style={{ + padding: "16px 20px", + borderBottom: "1px solid var(--border)", + }} > - <SectionTitle sub="PER CROP">Latest Sensor Summary</SectionTitle> + <SectionHead label="PER CROP" title="Latest Sensor Summary" /> </div> {loading ? ( - <div - style={{ - padding: 32, - display: "flex", - justifyContent: "center", - }} - > + <div style={{ padding: 32, textAlign: "center" }}> <span style={{ - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", color: "var(--text-3)", }} @@ -711,28 +683,25 @@ export default function Analytics() { Loading… </span> </div> - ) : dashboard.length === 0 ? ( + ) : cropSummaryRows.length === 0 ? ( <div style={{ padding: 32, textAlign: "center", - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", color: "var(--text-3)", }} > - No crops in database + No crops found in database </div> ) : ( <table - style={{ - width: "100%", - borderCollapse: "collapse", - fontSize: 12, - }} + className="data-table" + style={{ width: "100%", borderCollapse: "collapse" }} > <thead> - <tr style={{ borderBottom: "1px solid var(--border)" }}> + <tr> {[ "Crop ID", "Type", @@ -742,245 +711,224 @@ export default function Analytics() { "Temp", "Sequences", ].map((h) => ( - <th - key={h} - style={{ - padding: "10px 20px", - textAlign: "left", - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - fontWeight: 400, - }} - > - {h} - </th> + <th key={h}>{h}</th> ))} </tr> </thead> <tbody> - {dashboard.map((item, i) => { - const p = item.payload || {}; - const s = extractSensors(p); - return ( - <tr - key={i} - style={{ borderBottom: "1px solid var(--border)" }} + {cropSummaryRows.map(({ p, s, key }) => ( + <tr + key={key} + style={{ transition: "background 0.12s" }} + onMouseEnter={(e) => + (e.currentTarget.style.background = "var(--bg-3)") + } + onMouseLeave={(e) => + (e.currentTarget.style.background = "transparent") + } + > + <td + style={{ + fontFamily: "DM Mono, monospace", + fontSize: 12, + color: "var(--text)", + fontWeight: 600, + }} > - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text)", - }} - > - {p.crop_id || "—"} - </td> - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text-2)", - }} - > - {p.crop || "—"} - </td> - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text-2)", - }} - > - {p.stage || "—"} - </td> - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--green)", - }} + {p.crop_id || "—"} + </td> + <td>{p.crop || "—"}</td> + <td + style={{ + color: "var(--text-3)", + fontSize: 12, + fontFamily: "DM Mono, monospace", + }} + > + {p.stage || "—"} + </td> + <td> + <span + className="sensor-value-xs" + style={{ color: "var(--green)" }} > {s.ph} - </td> - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--amber)", - }} + </span> + </td> + <td> + <span + className="sensor-value-xs" + style={{ color: "var(--amber)" }} > {s.ec} - </td> - <td + </span> + <span style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", fontSize: 11, - color: "var(--blue)", + color: "var(--text-3)", + marginLeft: 3, }} > - {s.temp}°C - </td> - <td + dS/m + </span> + </td> + <td> + <span + className="sensor-value-xs" + style={{ color: "var(--blue)" }} + > + {s.temp} + </span> + <span style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", fontSize: 11, - color: "var(--text-2)", + color: "var(--text-3)", + marginLeft: 2, }} > - {p.sequence_number || 1} - </td> - </tr> - ); - })} + °C + </span> + </td> + <td + style={{ + fontFamily: "DM Mono, monospace", + fontSize: 13, + color: "var(--text-2)", + }} + > + {p.sequence_number || 1} + </td> + </tr> + ))} </tbody> </table> )} </div> - {/* Agent activity */} - {agentStats.length > 0 && ( + {/* Agent Activity Table */} + <div + style={{ + borderRadius: 14, + overflow: "hidden", + background: "var(--surface)", + border: "1px solid var(--border)", + flexShrink: 0, + }} + > <div style={{ - borderRadius: 12, - overflow: "hidden", - background: "var(--surface)", - border: "1px solid var(--border)", + padding: "16px 20px", + borderBottom: "1px solid var(--border)", }} > - <div - style={{ padding: 20, borderBottom: "1px solid var(--border)" }} - > - <SectionTitle sub="DERIVED FROM STORED ACTIONS"> - Agent Activity - </SectionTitle> - </div> - <table - style={{ - width: "100%", - borderCollapse: "collapse", - fontSize: 12, - }} - > - <thead> - <tr style={{ borderBottom: "1px solid var(--border)" }}> - {[ - "Agent", - "Appearances in Log", - "Success Rate", - "Status", - ].map((h) => ( - <th - key={h} - style={{ - padding: "10px 20px", - textAlign: "left", - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - fontWeight: 400, - }} - > - {h} - </th> - ))} - </tr> - </thead> - <tbody> - {agentStats.map(({ name, decisions, accuracy }) => ( - <tr - key={name} - style={{ borderBottom: "1px solid var(--border)" }} + <SectionHead + label="DERIVED FROM STORED ACTIONS" + title="Agent Activity" + /> + </div> + <table + className="data-table" + style={{ width: "100%", borderCollapse: "collapse" }} + > + <thead> + <tr> + {["Agent", "Appearances", "Success Rate", "Status"].map( + (h) => ( + <th key={h}>{h}</th> + ), + )} + </tr> + </thead> + <tbody> + {agentRows.map(({ name, decisions, accuracy }) => ( + <tr + key={name} + onMouseEnter={(e) => + (e.currentTarget.style.background = "var(--bg-3)") + } + onMouseLeave={(e) => + (e.currentTarget.style.background = "transparent") + } + style={{ transition: "background 0.12s" }} + > + <td + style={{ + fontFamily: "DM Mono, monospace", + fontWeight: 600, + fontSize: 13, + color: "var(--text)", + }} > - <td + {name} + </td> + <td + style={{ fontFamily: "DM Mono, monospace", fontSize: 13 }} + > + {decisions} + </td> + <td> + <div style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text)", + display: "flex", + alignItems: "center", + gap: 10, }} > - {name} - </td> - <td - style={{ - padding: "10px 20px", - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text-2)", - }} - > - {decisions} - </td> - <td style={{ padding: "10px 20px" }}> <div style={{ - display: "flex", - alignItems: "center", - gap: 8, + height: 6, + width: 100, + borderRadius: 3, + background: "var(--border)", }} > <div style={{ - height: 6, - width: 96, + height: "100%", borderRadius: 3, - background: "var(--border)", - }} - > - <div - style={{ - height: "100%", - borderRadius: 3, - width: `${accuracy}%`, - background: - accuracy > 80 - ? "var(--green)" - : accuracy > 50 - ? "var(--amber)" - : "var(--red)", - }} - /> - </div> - <span - style={{ - fontFamily: "DM Mono, monospace", - fontSize: 11, - color: "var(--text-2)", + width: `${accuracy}%`, + background: + accuracy > 80 + ? "var(--green)" + : accuracy > 50 + ? "var(--amber)" + : "var(--red)", + transition: "width 0.6s ease", }} - > - {accuracy}% - </span> + /> </div> - </td> - <td style={{ padding: "10px 20px" }}> <span style={{ - fontSize: 10, fontFamily: "DM Mono, monospace", - padding: "3px 8px", - borderRadius: 20, - background: "rgba(74,222,128,0.1)", - color: "var(--green)", - border: "1px solid rgba(74,222,128,0.2)", + fontSize: 13, + color: "var(--text-2)", + minWidth: 36, }} > - ONLINE + {accuracy}% </span> - </td> - </tr> - ))} - </tbody> - </table> - </div> - )} + </div> + </td> + <td> + <span + style={{ + fontSize: 11, + fontFamily: "DM Mono, monospace", + padding: "3px 10px", + borderRadius: 20, + background: "rgba(74,222,128,0.1)", + color: "var(--green)", + border: "1px solid rgba(74,222,128,0.2)", + }} + > + ONLINE + </span> + </td> + </tr> + ))} + </tbody> + </table> + </div> </div> </main> </div> diff --git a/frontend/src/pages/CropDetails.jsx b/frontend/src/pages/CropDetails.jsx @@ -1,14 +1,7 @@ import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { fetchCropDetails } from "../api/farmApi"; -import { - ArrowLeft, - Thermometer, - Droplet, - Wind, - Zap, - Activity, -} from "lucide-react"; +import { ArrowLeft, Thermometer, Droplet, Wind, Activity } from "lucide-react"; import { AreaChart, Area, @@ -24,9 +17,13 @@ import { extractSensors, parsePythonString, formatNumber, - formatOutcome, } from "../utils/dataUtils"; +import { + AgentActionWidget, + AgentOutcomeWidget, +} from "../components/AgentWidgets"; import Sidebar from "../components/Sidebar"; +import { useSettings } from "../hooks/useSettings"; const CustomTooltip = ({ active, payload, label }) => { if (!active || !payload?.length) return null; @@ -35,17 +32,18 @@ const CustomTooltip = ({ active, payload, label }) => { style={{ padding: "8px 12px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", - background: "var(--surface-2)", + background: "var(--tooltip-bg)", border: "1px solid var(--border)", color: "var(--text)", + boxShadow: "var(--shadow)", }} > <div style={{ color: "var(--text-3)", marginBottom: 4 }}>{label}</div> {payload.map((p) => ( - <div key={p.dataKey} style={{ color: p.color }}> - {p.name}: {p.value} + <div key={p.dataKey} style={{ color: p.color, marginTop: 2 }}> + {p.name}: <strong>{p.value}</strong> </div> ))} </div> @@ -57,8 +55,8 @@ function StatBox({ icon: Icon, label, value, color, unit }) { <div className="card-hover" style={{ - borderRadius: 12, - padding: 16, + borderRadius: 14, + padding: "18px 20px", background: "var(--surface)", border: "1px solid var(--border)", }} @@ -68,53 +66,30 @@ function StatBox({ icon: Icon, label, value, color, unit }) { display: "flex", alignItems: "center", gap: 8, - marginBottom: 10, + marginBottom: 12, }} > <div style={{ - width: 28, - height: 28, + width: 32, + height: 32, borderRadius: 8, - background: `${color}15`, + background: `${color}18`, border: `1px solid ${color}30`, display: "flex", alignItems: "center", justifyContent: "center", }} > - <Icon size={13} style={{ color }} /> + <Icon size={14} style={{ color }} /> </div> - <span - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - textTransform: "uppercase", - color: "var(--text-3)", - }} - > - {label} - </span> + <span className="sensor-label">{label}</span> </div> - <div - style={{ - fontSize: 22, - fontWeight: 700, - fontFamily: "DM Mono, monospace", - color, - }} - > - {value} - <span - style={{ - fontSize: 13, - fontWeight: 400, - marginLeft: 2, - color: "var(--text-3)", - }} - > - {unit} + <div style={{ display: "flex", alignItems: "baseline", gap: 4 }}> + <span className="sensor-value" style={{ color }}> + {value} </span> + {unit && <span className="sensor-unit">{unit}</span>} </div> </div> ); @@ -133,6 +108,9 @@ const TABS = ["overview", "sensors", "log"]; export default function CropDetails() { const { cropId } = useParams(); const navigate = useNavigate(); + const { settings } = useSettings(); + const logLimit = settings.historyLogLimit ?? 20; + const [history, setHistory] = useState([]); const [latest, setLatest] = useState(null); const [loading, setLoading] = useState(true); @@ -174,7 +152,7 @@ export default function CropDetails() { > <span style={{ - fontSize: 12, + fontSize: 13, fontFamily: "DM Mono, monospace", color: "var(--text-3)", }} @@ -271,27 +249,19 @@ export default function CropDetails() { </button> <div> - <h1 - style={{ - fontWeight: 700, - fontSize: 15, - color: "var(--text)", - margin: 0, - }} - > + <h1 className="page-title"> {p.crop || "Unknown"}{" "} - <span style={{ color: "var(--text-3)", fontWeight: 400 }}> + <span + style={{ + color: "var(--text-3)", + fontWeight: 400, + fontSize: 16, + }} + > #{p.sequence_number || 0} </span> </h1> - <p - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - margin: 0, - }} - > + <p className="page-subtitle"> {cropId} · {p.stage} </p> </div> @@ -330,9 +300,9 @@ export default function CropDetails() { key={tab} onClick={() => setActiveTab(tab)} style={{ - padding: "5px 12px", + padding: "5px 14px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", textTransform: "capitalize", cursor: "pointer", @@ -340,7 +310,6 @@ export default function CropDetails() { activeTab === tab ? "var(--surface-2)" : "transparent", color: activeTab === tab ? "var(--text)" : "var(--text-3)", border: `1px solid ${activeTab === tab ? "var(--border-bright)" : "transparent"}`, - transition: "all 0.15s", }} > {tab} @@ -363,10 +332,11 @@ export default function CropDetails() { {/* OVERVIEW */} {activeTab === "overview" && ( <> + {/* Sensor stats */} <div style={{ display: "grid", - gridTemplateColumns: "repeat(4, 1fr)", + gridTemplateColumns: "repeat(4,1fr)", gap: 12, }} > @@ -400,25 +370,25 @@ export default function CropDetails() { /> </div> + {/* Outcome */} + {p.outcome && p.outcome !== "PENDING_OBSERVATION" && ( + <AgentOutcomeWidget + outcome={p.outcome} + rewardScore={p.reward_score} + strategicIntent={p.strategic_intent} + /> + )} + {/* pH chart */} <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 16, - }} - > - // HISTORICAL pH TRACE - </div> + <div className="section-label">HISTORICAL pH TRACE</div> <ResponsiveContainer width="100%" height={200}> <AreaChart data={chartData}> <defs> @@ -443,7 +413,7 @@ export default function CropDetails() { <XAxis dataKey="t" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -453,7 +423,7 @@ export default function CropDetails() { <YAxis domain={["auto", "auto"]} tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -474,72 +444,25 @@ export default function CropDetails() { </ResponsiveContainer> </div> - {/* AI analysis */} - <div - style={{ - borderRadius: 12, - padding: 20, - background: "var(--surface)", - border: "1px solid var(--border)", - }} - > + {/* Latest Action */} + {p.action_taken && p.action_taken !== "PENDING_ACTION" && ( <div style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 12, + borderRadius: 14, + padding: 20, + background: "var(--surface)", + border: "1px solid var(--border)", }} > - // LATEST AI ANALYSIS - </div> - <div - style={{ display: "flex", alignItems: "flex-start", gap: 12 }} - > - <div - style={{ - width: 32, - height: 32, - borderRadius: 8, - background: "rgba(74,222,128,0.1)", - border: "1px solid rgba(74,222,128,0.2)", - display: "flex", - alignItems: "center", - justifyContent: "center", - flexShrink: 0, - }} - > - <Zap size={14} style={{ color: "var(--green)" }} /> - </div> - <div - style={{ - fontSize: 13, - lineHeight: 1.6, - color: "var(--text-2)", - }} - > - {formatOutcome(p.outcome) || - "System monitoring active. No anomalies detected."} + <div className="section-label" style={{ marginBottom: 16 }}> + LATEST ACTUATOR COMMAND </div> + <AgentActionWidget + actionTaken={p.action_taken} + compact={false} + /> </div> - {p.action_taken && p.action_taken !== "PENDING_ACTION" && ( - <div - style={{ - marginTop: 12, - padding: 10, - borderRadius: 8, - fontFamily: "DM Mono, monospace", - fontSize: 11, - background: "var(--bg-3)", - border: "1px solid var(--border)", - color: "var(--text-3)", - }} - > - <span style={{ color: "var(--green)" }}>ACTION: </span> - {p.action_taken?.substring(0, 200)}… - </div> - )} - </div> + )} </> )} @@ -548,22 +471,13 @@ export default function CropDetails() { <> <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 16, - }} - > - // TEMP & HUMIDITY - </div> + <div className="section-label">TEMP &amp; HUMIDITY</div> <ResponsiveContainer width="100%" height={200}> <LineChart data={chartData}> <CartesianGrid @@ -574,7 +488,7 @@ export default function CropDetails() { <XAxis dataKey="t" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -583,7 +497,7 @@ export default function CropDetails() { /> <YAxis tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -613,22 +527,13 @@ export default function CropDetails() { <div style={{ - borderRadius: 12, + borderRadius: 14, padding: 20, background: "var(--surface)", border: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - marginBottom: 16, - }} - > - // EC CONCENTRATION - </div> + <div className="section-label">EC CONCENTRATION</div> <ResponsiveContainer width="100%" height={180}> <AreaChart data={chartData}> <defs> @@ -653,7 +558,7 @@ export default function CropDetails() { <XAxis dataKey="t" tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -662,7 +567,7 @@ export default function CropDetails() { /> <YAxis tick={{ - fontSize: 9, + fontSize: 10, fill: "var(--text-3)", fontFamily: "DM Mono", }} @@ -689,27 +594,22 @@ export default function CropDetails() { {activeTab === "log" && ( <div style={{ - borderRadius: 12, + borderRadius: 14, overflow: "hidden", background: "var(--surface)", border: "1px solid var(--border)", + flexShrink: 0, }} > {/* Header row */} <div style={{ - padding: "12px 20px", + padding: "14px 20px", borderBottom: "1px solid var(--border)", }} > - <div - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - }} - > - // EVENT LOG — {history.length} ENTRIES + <div className="section-label"> + EVENT LOG — {history.length} ENTRIES (showing last {logLimit}) </div> </div> @@ -717,102 +617,138 @@ export default function CropDetails() { <div> {[...history] .reverse() - .slice(0, 20) + .slice(0, logLimit) .map((h, i) => ( <div key={i} style={{ - display: "flex", - alignItems: "center", - gap: 12, - padding: "10px 20px", - background: - i % 2 === 0 - ? "transparent" - : "rgba(255,255,255,0.015)", - transition: "background 0.15s", + borderBottom: "1px solid var(--border)", + transition: "background 0.12s", + cursor: "default", }} onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(74,222,128,0.04)") } onMouseLeave={(e) => - (e.currentTarget.style.background = - i % 2 === 0 - ? "transparent" - : "rgba(255,255,255,0.015)") + (e.currentTarget.style.background = "transparent") } > - {/* Severity dot */} - <span + {/* Row header */} + <div style={{ - width: 7, - height: 7, - borderRadius: "50%", - background: logDotColor(h.payload), - flexShrink: 0, - }} - /> - - {/* Timestamp */} - <span - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - flexShrink: 0, - width: 50, + display: "flex", + alignItems: "center", + gap: 10, + padding: "10px 20px", }} > - {h.payload?.timestamp - ? new Date(h.payload.timestamp).toLocaleTimeString( - [], - { hour: "2-digit", minute: "2-digit" }, - ) - : "--"} - </span> + <span + style={{ + width: 7, + height: 7, + borderRadius: "50%", + background: logDotColor(h.payload), + flexShrink: 0, + }} + /> + <span + style={{ + fontSize: 11, + fontFamily: "DM Mono, monospace", + color: "var(--text-3)", + flexShrink: 0, + width: 52, + }} + > + {h.payload?.timestamp + ? new Date(h.payload.timestamp).toLocaleTimeString( + [], + { hour: "2-digit", minute: "2-digit" }, + ) + : "--"} + </span> + <span + style={{ + fontSize: 11, + fontFamily: "DM Mono, monospace", + color: "var(--text-3)", + width: 36, + flexShrink: 0, + }} + > + #{h.payload?.sequence_number || i} + </span> - {/* Seq # */} - <span - style={{ - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - width: 34, - flexShrink: 0, - }} - > - #{h.payload?.sequence_number || i} - </span> + {/* Sensor snapshot */} + <div + style={{ display: "flex", gap: 12, flexWrap: "wrap" }} + > + {[ + { + label: "pH", + value: h.cleanSensors?.ph, + color: "var(--green)", + }, + { + label: "EC", + value: h.cleanSensors?.ec, + color: "var(--amber)", + }, + { + label: "T", + value: h.cleanSensors?.temp + "°", + color: "var(--blue)", + }, + { + label: "H", + value: h.cleanSensors?.humidity + "%", + color: "#a78bfa", + }, + ].map(({ label, value, color }) => ( + <span + key={label} + style={{ + fontSize: 13, + fontFamily: "DM Mono, monospace", + display: "flex", + alignItems: "baseline", + gap: 3, + }} + > + <span + style={{ color: "var(--text-3)", fontSize: 11 }} + > + {label} + </span> + <span style={{ color, fontWeight: 700 }}> + {formatNumber(value)} + </span> + </span> + ))} + </div> - {/* Sensor snapshot */} - <span - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-2)", - flexShrink: 0, - }} - > - pH {formatNumber(h.cleanSensors?.ph)} ·{" "} - {formatNumber(h.cleanSensors?.temp)}°C · EC{" "} - {formatNumber(h.cleanSensors?.ec)} - </span> + {/* Outcome badge */} + {h.payload?.outcome && ( + <span style={{ marginLeft: "auto", flexShrink: 0 }}> + <AgentOutcomeWidget + outcome={h.payload.outcome} + rewardScore={h.payload.reward_score} + /> + </span> + )} + </div> - {/* Outcome / action */} - <span - style={{ - fontSize: 11, - color: "var(--text-3)", - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - }} - > - {formatOutcome(h.payload?.outcome) || - h.payload?.action_taken || - "Routine check"} - </span> + {/* Action row*/} + {h.payload?.action_taken && + h.payload.action_taken !== "PENDING_ACTION" && ( + <div style={{ padding: "0 20px 10px 46px" }}> + <AgentActionWidget + actionTaken={h.payload.action_taken} + compact + /> + </div> + )} </div> ))} </div> diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { useFarmData } from "../hooks/useFarmData"; +import { useSettings } from "../hooks/useSettings"; import { extractSensors, calculateMaturity, @@ -18,6 +19,8 @@ import { RefreshCw, Leaf, Activity, + ChevronLeft, + ChevronRight, } from "lucide-react"; import Sidebar from "../components/Sidebar"; @@ -61,7 +64,7 @@ function CropCard({ data, onClick }) { > {/* Image header */} <div - style={{ position: "relative", height: 136, background: "var(--bg-3)" }} + style={{ position: "relative", height: 130, background: "var(--bg-3)" }} > <div style={{ @@ -73,7 +76,7 @@ function CropCard({ data, onClick }) { }} > <Leaf - size={40} + size={38} style={{ color: "var(--border-bright)", opacity: 0.4 }} /> </div> @@ -119,11 +122,6 @@ function CropCard({ data, onClick }) { border: `1px solid ${st.border}`, }} > - {data.status === "Healthy" - ? "● " - : data.status === "Critical" - ? "▲ " - : "◆ "} {data.status.toUpperCase()} </div> {/* Seq badge */} @@ -146,7 +144,7 @@ function CropCard({ data, onClick }) { <div style={{ - padding: 14, + padding: "12px 14px", display: "flex", flexDirection: "column", gap: 10, @@ -154,12 +152,12 @@ function CropCard({ data, onClick }) { > {/* Name */} <div> - <div style={{ fontWeight: 700, fontSize: 13, color: "var(--text)" }}> + <div style={{ fontWeight: 700, fontSize: 14, color: "var(--text)" }}> {data.name} </div> <div style={{ - fontSize: 10, + fontSize: 11, marginTop: 2, fontFamily: "DM Mono, monospace", color: "var(--text-3)", @@ -175,14 +173,28 @@ function CropCard({ data, onClick }) { style={{ display: "flex", justifyContent: "space-between", - fontSize: 10, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", marginBottom: 4, }} > - <span>Maturity</span> - <span style={{ color: "var(--green)" }}>{maturity}%</span> + <span + style={{ + fontSize: 10, + fontFamily: "DM Mono, monospace", + color: "var(--text-3)", + }} + > + Maturity + </span> + <span + style={{ + fontSize: 11, + fontFamily: "DM Mono, monospace", + color: "var(--green)", + fontWeight: 600, + }} + > + {maturity}% + </span> </div> <div style={{ height: 3, borderRadius: 2, background: "var(--border)" }} @@ -209,26 +221,20 @@ function CropCard({ data, onClick }) { style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }} > <div style={{ display: "flex", alignItems: "center", gap: 5 }}> - <Thermometer size={12} style={{ color: "var(--text-3)" }} /> - <span - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-2)", - }} - > + <Thermometer + size={12} + style={{ color: "var(--blue)", flexShrink: 0 }} + /> + <span className="sensor-value-xs" style={{ color: "var(--blue)" }}> {data.sensors.temp}°C </span> </div> <div style={{ display: "flex", alignItems: "center", gap: 5 }}> - <Droplet size={12} style={{ color: "var(--text-3)" }} /> - <span - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-2)", - }} - > + <Droplet + size={12} + style={{ color: "var(--green)", flexShrink: 0 }} + /> + <span className="sensor-value-xs" style={{ color: "var(--green)" }}> pH {data.sensors.ph} </span> </div> @@ -249,7 +255,7 @@ function CropCard({ data, onClick }) { display: "flex", alignItems: "center", gap: 4, - fontSize: 10, + fontSize: 11, color: "var(--text-3)", }} > @@ -266,12 +272,16 @@ function CropCard({ data, onClick }) { export default function Dashboard() { const navigate = useNavigate(); const { dashboard, loading, refreshData } = useFarmData(); + const { settings } = useSettings(); + const pageSize = settings.maxResultsPerPage || 12; + const [crops, setCrops] = useState([]); const [search, setSearch] = useState(""); const [filterStage, setFilterStage] = useState("All"); const [filterCrop, setFilterCrop] = useState("All"); const [filterStatus, setFilterStatus] = useState("All"); const [showFilters, setShowFilters] = useState(false); + const [page, setPage] = useState(1); const getImg = (name) => { if (!name) return null; @@ -307,6 +317,7 @@ export default function Dashboard() { }; }), ); + setPage(1); } }, [dashboard]); @@ -329,6 +340,8 @@ export default function Dashboard() { [crops, search, filterStage, filterCrop, filterStatus], ); + const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); + const paginated = filtered.slice((page - 1) * pageSize, page * pageSize); const activeFilters = [filterStage, filterCrop, filterStatus].filter( (f) => f !== "All", ).length; @@ -362,7 +375,7 @@ export default function Dashboard() { overflow: "hidden", }} > - {/* ── Header — 64px, border aligns with sidebar ── */} + {/* Header */} <header style={{ flexShrink: 0, @@ -376,24 +389,8 @@ export default function Dashboard() { }} > <div> - <h1 - style={{ - fontWeight: 700, - fontSize: 18, - color: "var(--text)", - margin: 0, - }} - > - Crops Overview - </h1> - <p - style={{ - fontSize: 11, - fontFamily: "DM Mono, monospace", - color: "var(--text-3)", - margin: 0, - }} - > + <h1 className="page-title">Crops Overview</h1> + <p className="page-subtitle"> {filtered.length} of {crops.length} crops shown </p> </div> @@ -434,13 +431,13 @@ export default function Dashboard() { borderRadius: 20, background: "var(--surface)", border: "1px solid var(--border)", - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", color, }} > - <span>{count}</span> - <span style={{ opacity: 0.6 }}>{label}</span> + <span style={{ fontWeight: 700 }}>{count}</span> + <span style={{ opacity: 0.7 }}>{label}</span> </div> ))} </div> @@ -465,7 +462,7 @@ export default function Dashboard() { </button> </header> - {/* Search + Filter bar */} + {/* Search + filter bar */} <div style={{ flexShrink: 0, @@ -491,7 +488,10 @@ export default function Dashboard() { /> <input value={search} - onChange={(e) => setSearch(e.target.value)} + onChange={(e) => { + setSearch(e.target.value); + setPage(1); + }} placeholder="Search crops, IDs, stages…" style={{ width: "100%", @@ -500,7 +500,7 @@ export default function Dashboard() { paddingTop: 7, paddingBottom: 7, borderRadius: 8, - fontSize: 12, + fontSize: 13, fontFamily: "DM Mono, monospace", background: "var(--surface)", border: "1px solid var(--border)", @@ -545,7 +545,6 @@ export default function Dashboard() { : "var(--surface)", border: `1px solid ${showFilters ? "rgba(74,222,128,0.3)" : "var(--border)"}`, color: showFilters ? "var(--green)" : "var(--text-2)", - transition: "all 0.15s", }} > <SlidersHorizontal size={13} /> @@ -571,7 +570,10 @@ export default function Dashboard() { {STAGES.slice(0, 4).map((s) => ( <button key={s} - onClick={() => setFilterStage(filterStage === s ? "All" : s)} + onClick={() => { + setFilterStage(filterStage === s ? "All" : s); + setPage(1); + }} style={{ padding: "5px 12px", borderRadius: 20, @@ -584,7 +586,6 @@ export default function Dashboard() { : "var(--surface)", border: `1px solid ${filterStage === s ? "rgba(74,222,128,0.4)" : "var(--border)"}`, color: filterStage === s ? "var(--green)" : "var(--text-3)", - transition: "all 0.15s", }} > {s} @@ -611,19 +612,28 @@ export default function Dashboard() { { label: "Crop Type", value: filterCrop, - set: setFilterCrop, + set: (v) => { + setFilterCrop(v); + setPage(1); + }, opts: CROPS, }, { label: "Stage", value: filterStage, - set: setFilterStage, + set: (v) => { + setFilterStage(v); + setPage(1); + }, opts: STAGES, }, { label: "Status", value: filterStatus, - set: setFilterStatus, + set: (v) => { + setFilterStatus(v); + setPage(1); + }, opts: STATUSES, }, ].map(({ label, value, set, opts }) => ( @@ -633,7 +643,7 @@ export default function Dashboard() { > <span style={{ - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", color: "var(--text-3)", }} @@ -648,7 +658,7 @@ export default function Dashboard() { appearance: "none", padding: "5px 24px 5px 10px", borderRadius: 8, - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", background: "var(--surface)", border: "1px solid var(--border)", @@ -679,10 +689,11 @@ export default function Dashboard() { ))} <button onClick={() => { + setSearch(""); setFilterStage("All"); setFilterCrop("All"); setFilterStatus("All"); - setSearch(""); + setPage(1); }} style={{ marginLeft: "auto", @@ -705,7 +716,7 @@ export default function Dashboard() { <div style={{ display: "grid", - gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", + gridTemplateColumns: "repeat(auto-fill,minmax(200px,1fr))", gap: 16, }} > @@ -716,29 +727,105 @@ export default function Dashboard() { key={i} className="shimmer" style={{ - height: 260, + height: 255, borderRadius: 16, border: "1px solid var(--border)", }} /> ))} </div> - ) : filtered.length > 0 ? ( - <div - style={{ - display: "grid", - gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", - gap: 16, - }} - > - {filtered.map((crop) => ( - <CropCard - key={crop.id} - data={crop} - onClick={() => navigate(`/crop/${crop.id}`)} - /> - ))} - </div> + ) : paginated.length > 0 ? ( + <> + <div + style={{ + display: "grid", + gridTemplateColumns: "repeat(auto-fill,minmax(200px,1fr))", + gap: 16, + }} + > + {paginated.map((crop) => ( + <CropCard + key={crop.id} + data={crop} + onClick={() => navigate(`/crop/${crop.id}`)} + /> + ))} + </div> + + {/* Pagination */} + {totalPages > 1 && ( + <div + style={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: 8, + marginTop: 24, + }} + > + <button + onClick={() => setPage((p) => Math.max(1, p - 1))} + disabled={page === 1} + style={{ + width: 32, + height: 32, + borderRadius: 8, + cursor: "pointer", + background: "var(--surface)", + border: "1px solid var(--border)", + color: page === 1 ? "var(--text-3)" : "var(--text-2)", + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + <ChevronLeft size={14} /> + </button> + {Array.from({ length: totalPages }, (_, i) => i + 1).map( + (n) => ( + <button + key={n} + onClick={() => setPage(n)} + style={{ + width: 32, + height: 32, + borderRadius: 8, + cursor: "pointer", + fontFamily: "DM Mono, monospace", + fontSize: 12, + background: + n === page ? "var(--green)" : "var(--surface)", + border: `1px solid ${n === page ? "transparent" : "var(--border)"}`, + color: n === page ? "#0c1a0e" : "var(--text-2)", + fontWeight: n === page ? 700 : 400, + }} + > + {n} + </button> + ), + )} + <button + onClick={() => setPage((p) => Math.min(totalPages, p + 1))} + disabled={page === totalPages} + style={{ + width: 32, + height: 32, + borderRadius: 8, + cursor: "pointer", + background: "var(--surface)", + border: "1px solid var(--border)", + color: + page === totalPages ? "var(--text-3)" : "var(--text-2)", + display: "flex", + alignItems: "center", + justifyContent: "center", + }} + > + <ChevronRight size={14} /> + </button> + </div> + )} + </> ) : ( <div style={{ @@ -764,7 +851,7 @@ export default function Dashboard() { > <Activity size={24} style={{ color: "var(--text-3)" }} /> </div> - <div style={{ color: "var(--text-2)" }}> + <div style={{ color: "var(--text-2)", fontSize: 14 }}> No crops match your filters </div> <button @@ -775,7 +862,7 @@ export default function Dashboard() { setFilterStatus("All"); }} style={{ - fontSize: 11, + fontSize: 12, fontFamily: "DM Mono, monospace", padding: "6px 16px", borderRadius: 8, diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx @@ -0,0 +1,527 @@ +import { useState } from "react"; +import { + User, + Sun, + Moon, + Monitor, + Save, + RotateCcw, + Check, + Database, + Bell, + LayoutGrid, + Zap, +} from "lucide-react"; +import Sidebar from "../components/Sidebar"; +import { useSettings } from "../hooks/useSettings"; +import { USE_MOCK_DATA } from "../data/mockData"; + +function SectionHeader({ icon: Icon, title, sub }) { + return ( + <div + style={{ + display: "flex", + alignItems: "center", + gap: 12, + marginBottom: 20, + }} + > + <div + style={{ + width: 36, + height: 36, + borderRadius: 10, + background: "rgba(74,222,128,0.1)", + border: "1px solid rgba(74,222,128,0.2)", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexShrink: 0, + }} + > + <Icon size={16} style={{ color: "var(--green)" }} /> + </div> + <div> + <div style={{ fontWeight: 700, fontSize: 15, color: "var(--text)" }}> + {title} + </div> + {sub && ( + <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 2 }}> + {sub} + </div> + )} + </div> + </div> + ); +} + +function Card({ children, style }) { + return ( + <div + style={{ + borderRadius: 16, + padding: 24, + background: "var(--surface)", + border: "1px solid var(--border)", + ...style, + }} + > + {children} + </div> + ); +} + +function FieldRow({ label, hint, children }) { + return ( + <div style={{ display: "flex", flexDirection: "column", gap: 6 }}> + <label style={{ fontSize: 13, fontWeight: 600, color: "var(--text-2)" }}> + {label} + </label> + {hint && ( + <div style={{ fontSize: 11, color: "var(--text-3)" }}>{hint}</div> + )} + {children} + </div> + ); +} + +const inputStyle = { + width: "100%", + padding: "10px 14px", + borderRadius: 10, + fontSize: 14, + fontFamily: "DM Mono, monospace", + background: "var(--bg-3)", + border: "1px solid var(--border)", + color: "var(--text)", + outline: "none", + boxSizing: "border-box", +}; + +export default function SettingsPage() { + const { settings, update, reset } = useSettings(); + const [saved, setSaved] = useState(false); + + // Local draft so we can save all at once + const [draft, setDraft] = useState({ ...settings }); + + const set = (key, val) => setDraft((d) => ({ ...d, [key]: val })); + + const handleSave = () => { + Object.entries(draft).forEach(([k, v]) => update(k, v)); + setSaved(true); + setTimeout(() => setSaved(false), 2000); + }; + + const handleReset = () => { + reset(); + setDraft({ ...settings }); + }; + + const ThemeButton = ({ value, label, Icon }) => ( + <button + onClick={() => set("theme", value)} + style={{ + flex: 1, + padding: "12px 0", + borderRadius: 12, + cursor: "pointer", + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 6, + background: + draft.theme === value ? "rgba(74,222,128,0.12)" : "var(--bg-3)", + border: `2px solid ${draft.theme === value ? "var(--green)" : "var(--border)"}`, + color: draft.theme === value ? "var(--green)" : "var(--text-3)", + transition: "all 0.15s", + }} + > + <Icon size={18} /> + <span style={{ fontSize: 12, fontWeight: 600 }}>{label}</span> + </button> + ); + + return ( + <div + style={{ + display: "flex", + height: "100vh", + overflow: "hidden", + background: "var(--bg)", + }} + > + <Sidebar /> + + <main + style={{ + flex: 1, + display: "flex", + flexDirection: "column", + overflow: "hidden", + }} + > + {/* Header */} + <header + style={{ + flexShrink: 0, + padding: "0 28px", + height: 64, + borderBottom: "1px solid var(--border)", + background: "var(--bg-2)", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + }} + > + <div> + <h1 className="page-title">Settings</h1> + <p className="page-subtitle"> + Preferences, appearance &amp; account + </p> + </div> + <div style={{ display: "flex", gap: 10 }}> + <button + onClick={handleReset} + style={{ + display: "flex", + alignItems: "center", + gap: 6, + padding: "8px 16px", + borderRadius: 10, + fontSize: 13, + background: "var(--surface)", + border: "1px solid var(--border)", + color: "var(--text-3)", + cursor: "pointer", + }} + > + <RotateCcw size={13} /> Reset + </button> + <button + onClick={handleSave} + style={{ + display: "flex", + alignItems: "center", + gap: 6, + padding: "8px 20px", + borderRadius: 10, + fontSize: 13, + fontWeight: 600, + background: saved ? "rgba(74,222,128,0.2)" : "var(--green)", + border: saved ? "1px solid var(--green)" : "none", + color: saved ? "var(--green)" : "#0c1a0e", + cursor: "pointer", + transition: "all 0.2s", + }} + > + {saved ? ( + <> + <Check size={13} /> Saved! + </> + ) : ( + <> + <Save size={13} /> Save Changes + </> + )} + </button> + </div> + </header> + + <div style={{ flex: 1, overflowY: "auto", padding: 28 }}> + <div + style={{ + maxWidth: 720, + display: "flex", + flexDirection: "column", + gap: 24, + }} + > + {/* Profile */} + <Card> + <SectionHeader + icon={User} + title="Profile" + sub="Your name and role shown in the sidebar" + /> + <div + style={{ + display: "grid", + gridTemplateColumns: "1fr 1fr", + gap: 16, + }} + > + <FieldRow label="Display Name"> + <input + style={inputStyle} + value={draft.userName} + onChange={(e) => set("userName", e.target.value)} + placeholder="Your name" + /> + </FieldRow> + <FieldRow label="Designation"> + <input + style={inputStyle} + value={draft.userDesignation} + onChange={(e) => set("userDesignation", e.target.value)} + placeholder="Eg. Farm Owner" + /> + </FieldRow> + <FieldRow + label="Initials" + hint="Shown in the sidebar avatar (max 2 chars)" + > + <input + style={{ ...inputStyle, maxWidth: 100 }} + value={draft.userInitials} + onChange={(e) => + set( + "userInitials", + e.target.value.toUpperCase().slice(0, 2), + ) + } + placeholder="RR" + maxLength={2} + /> + </FieldRow> + </div> + </Card> + + {/* Appearance */} + <Card> + <SectionHeader + icon={Sun} + title="Appearance" + sub="Theme and display options" + /> + <FieldRow + label="Theme" + hint="Controls the overall color scheme of the application" + > + <div style={{ display: "flex", gap: 10, marginTop: 4 }}> + <ThemeButton value="dark" label="Dark" Icon={Moon} /> + <ThemeButton value="light" label="Light" Icon={Sun} /> + <ThemeButton value="auto" label="System" Icon={Monitor} /> + </div> + </FieldRow> + + <div style={{ marginTop: 20 }}> + <FieldRow + label="Compact Mode" + hint="Reduces spacing for denser information display" + > + <label + style={{ + display: "flex", + alignItems: "center", + gap: 10, + cursor: "pointer", + marginTop: 6, + }} + > + <div + onClick={() => set("compactMode", !draft.compactMode)} + style={{ + width: 44, + height: 24, + borderRadius: 12, + background: draft.compactMode + ? "var(--green)" + : "var(--border)", + position: "relative", + cursor: "pointer", + transition: "background 0.2s", + flexShrink: 0, + }} + > + <div + style={{ + position: "absolute", + top: 3, + left: draft.compactMode ? 23 : 3, + width: 18, + height: 18, + borderRadius: "50%", + background: "white", + transition: "left 0.2s", + boxShadow: "0 1px 4px rgba(0,0,0,0.3)", + }} + /> + </div> + <span style={{ fontSize: 13, color: "var(--text-2)" }}> + {draft.compactMode ? "Enabled" : "Disabled"} + </span> + </label> + </FieldRow> + </div> + </Card> + + {/* Data */} + <Card> + <SectionHeader + icon={LayoutGrid} + title="Display" + sub="Pagination and results" + /> + <div + style={{ + display: "grid", + gridTemplateColumns: "1fr 1fr", + gap: 16, + }} + > + <FieldRow + label="Max Crops Per Page" + hint="Dashboard grid page size" + > + <select + style={inputStyle} + value={draft.maxResultsPerPage} + onChange={(e) => + set("maxResultsPerPage", parseInt(e.target.value)) + } + > + {[6, 8, 12, 16, 24].map((n) => ( + <option key={n} value={n}> + {n} per page + </option> + ))} + </select> + </FieldRow> + <FieldRow + label="History Log Limit" + hint="Max entries shown in crop event log" + > + <select + style={inputStyle} + value={draft.historyLogLimit ?? 20} + onChange={(e) => + set("historyLogLimit", parseInt(e.target.value)) + } + > + {[10, 20, 50, 100].map((n) => ( + <option key={n} value={n}> + Last {n} entries + </option> + ))} + </select> + </FieldRow> + </div> + </Card> + + {/* Alerts */} + <Card> + <SectionHeader + icon={Bell} + title="Alerts" + sub="Notification preferences" + /> + <FieldRow label="Show Acknowledged Alerts by Default"> + <label + style={{ + display: "flex", + alignItems: "center", + gap: 10, + cursor: "pointer", + marginTop: 6, + }} + > + <div + onClick={() => + set("alertsShowAcked", !draft.alertsShowAcked) + } + style={{ + width: 44, + height: 24, + borderRadius: 12, + background: draft.alertsShowAcked + ? "var(--green)" + : "var(--border)", + position: "relative", + cursor: "pointer", + transition: "background 0.2s", + flexShrink: 0, + }} + > + <div + style={{ + position: "absolute", + top: 3, + left: draft.alertsShowAcked ? 23 : 3, + width: 18, + height: 18, + borderRadius: "50%", + background: "white", + transition: "left 0.2s", + boxShadow: "0 1px 4px rgba(0,0,0,0.3)", + }} + /> + </div> + <span style={{ fontSize: 13, color: "var(--text-2)" }}> + {draft.alertsShowAcked + ? "Showing all" + : "Hiding acknowledged"} + </span> + </label> + </FieldRow> + </Card> + + {/* Data Source */} + <Card> + <SectionHeader + icon={Database} + title="Data Source" + sub="Backend connection settings" + /> + <div + style={{ + padding: "14px 16px", + borderRadius: 12, + background: USE_MOCK_DATA + ? "rgba(245,158,11,0.08)" + : "rgba(74,222,128,0.08)", + border: `1px solid ${USE_MOCK_DATA ? "rgba(245,158,11,0.3)" : "rgba(74,222,128,0.3)"}`, + display: "flex", + alignItems: "center", + gap: 12, + }} + > + <Zap + size={16} + style={{ + color: USE_MOCK_DATA ? "var(--amber)" : "var(--green)", + flexShrink: 0, + }} + /> + <div> + <div + style={{ + fontSize: 13, + fontWeight: 600, + color: "var(--text)", + }} + > + {USE_MOCK_DATA + ? "Mock Data Mode" + : "Live Backend Connected"} + </div> + <div + style={{ + fontSize: 12, + color: "var(--text-3)", + marginTop: 3, + }} + > + {USE_MOCK_DATA + ? "To connect to live data, open src/data/mockData.js and set USE_MOCK_DATA = false" + : "Connected to http://localhost:3001 — real-time data"} + </div> + </div> + </div> + </Card> + </div> + </div> + </main> + </div> + ); +} diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/frontend/src/utils/dataUtils.js b/frontend/src/utils/dataUtils.js @@ -26,14 +26,25 @@ export const parsePythonString = (str) => { }; // Sensor extraction +function pickVal(obj, ...keys) { + if (!obj || typeof obj !== "object") return undefined; + const lower = {}; + for (const k of Object.keys(obj)) lower[k.toLowerCase()] = obj[k]; + for (const k of keys) { + if (obj[k] !== undefined) return obj[k]; + if (lower[k.toLowerCase()] !== undefined) return lower[k.toLowerCase()]; + } + return undefined; +} + export const extractSensors = (payload) => { if (!payload) return { temp: 0, ph: 0, humidity: 0, ec: 0 }; - let raw = payload.sensors || payload.sensor_data; + let raw = payload.sensors || payload.sensor_data || null; if (!raw) { const action = parsePythonString(payload.action_taken); - if (action) { + if (action && typeof action === "object") { raw = { temp: action.atmospheric_actions?.air_temp ?? action.air_temp ?? 0, ph: action.water_actions?.ph ?? action.ph ?? 0, @@ -41,15 +52,17 @@ export const extractSensors = (payload) => { ec: action.water_actions?.ec ?? action.ec ?? 0, }; } else { - raw = {}; + raw = payload; } } return { - temp: formatNumber(raw.temp ?? raw.Temp ?? 0), - ph: formatNumber(raw.ph ?? raw.pH ?? 7.0), - humidity: formatNumber(raw.humidity ?? raw.Humidity ?? 0), - ec: formatNumber(raw.ec ?? raw.EC ?? 0), + temp: formatNumber( + pickVal(raw, "temp", "Temp", "air_temp", "temperature") ?? 0, + ), + ph: formatNumber(pickVal(raw, "pH", "ph", "PH") ?? 7.0), + humidity: formatNumber(pickVal(raw, "humidity", "Humidity", "RH") ?? 0), + ec: formatNumber(pickVal(raw, "EC", "ec", "conductivity") ?? 0), }; }; @@ -64,7 +77,6 @@ export const formatOutcome = (outcome) => { const cleanOutcome = outcome.split("| Reward:")[0].trim(); const parts = cleanOutcome.split("|").map((p) => p.trim()); - let tags = []; let notes = ""; @@ -134,7 +146,9 @@ function timeAgo(isoString) { if (mins < 60) return `${mins} min ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs} hr ago`; - return `${Math.floor(hrs / 24)} days ago`; + const days = Math.floor(hrs / 24); + if (days === 1) return "1 day ago"; + return `${days} days ago`; } export const generateAlerts = (points) => { @@ -486,30 +500,73 @@ export const buildRadar = (points) => { ]; }; +// Counts appearances by scanning strategic_intent field +const AGENT_INTENTS = { + SUPERVISOR: [ + "MAINTAIN_CURRENT", + "CALIBRATE", + "GENTLE_PH", + "AGGRESSIVE_PH", + "LOWER_EC", + "CALMAG", + "PRUNE", + ], + WATER: ["PH_DOWN", "PH_UP", "EC_VEG", "EC_BLOOM", "FLUSH", "CALMAG_BOOST"], + ATMOSPHERIC: ["RAISE_TEMP", "LOWER_TEMP", "MAX_AIR", "VPD"], + JUDGE: ["IMPROVED", "STABLE", "DETERIORATED"], + DOCTOR: ["FUNGAL", "PEST", "DISEASE", "VISUAL"], +}; + export const buildAgentStats = (points) => { - const AGENTS = ["SUPERVISOR", "WATER", "ATMOSPHERIC", "JUDGE", "DOCTOR"]; - const counts = Object.fromEntries(AGENTS.map((a) => [a, 0])); - for (const p of points) { - const text = - `${p.payload?.action_taken || ""} ${p.payload?.strategic_intent || ""}`.toUpperCase(); - for (const agent of AGENTS) if (text.includes(agent)) counts[agent]++; - counts["SUPERVISOR"]++; + if (!points.length) return []; + + const total = points.length; + + // Count positive outcomes for accuracy + const positiveCount = points.filter((p) => { + const o = (p.payload?.outcome || "").toLowerCase(); + return !/fail|negative|critical|deteriorat/.test(o); + }).length; + const baseAccuracy = Math.round((positiveCount / total) * 100); + + // Count how many points each agent's keywords appear in + const counts = {}; + for (const [agent, keywords] of Object.entries(AGENT_INTENTS)) { + counts[agent] = points.filter((p) => { + const haystack = [ + p.payload?.strategic_intent || "", + p.payload?.outcome || "", + p.payload?.action_taken || "", + ] + .join(" ") + .toUpperCase(); + return keywords.some((kw) => haystack.includes(kw)); + }).length; } - const total = Math.max(points.length, 1); - return AGENTS.map((name) => ({ + + // SUPERVISOR appears in every cycle + counts["SUPERVISOR"] = total; + + // Always return all 5 agents so the table is never empty + return Object.entries(counts).map(([name, decisions]) => ({ name, - decisions: counts[name], - accuracy: points.length - ? Math.round( - (points.filter( - (p) => - !/fail|negative|critical/.test( - (p.payload?.outcome || "").toLowerCase(), - ), - ).length / - total) * - 100, - ) - : 0, - })).filter((a) => a.decisions > 0); + decisions, + accuracy: Math.min( + 100, + Math.max( + 0, + name === "SUPERVISOR" + ? baseAccuracy + : name === "JUDGE" + ? Math.max(0, baseAccuracy - 2) + : name === "DOCTOR" + ? decisions > 0 + ? Math.round(baseAccuracy * 0.95) + : 0 + : decisions > 0 + ? Math.min(100, baseAccuracy + 3) + : 0, + ), + ), + })); }; diff --git a/package-lock.json b/package-lock.json @@ -1,934 +0,0 @@ -{ - "name": "Code", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "fastapi": "^0.0.8", - "lucide-react": "^0.562.0" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fastapi": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/fastapi/-/fastapi-0.0.8.tgz", - "integrity": "sha512-I3nKZeqsvHIirIvkZrH+kpRZUE3YMvQ+wumC5KTn2pgOEI8SzC7NW8S4Fv83+ZD/93PRelIJfxybzF1P4tLS/Q==", - "license": "ISC", - "dependencies": { - "bluebird": "^3.4.6", - "body-parser": "^1.15.2", - "commander": "^2.9.0", - "express": "^4.14.0", - "lodash": "^4.15.0", - "morgan": "^1.7.0" - }, - "bin": { - "fastapi": "lib/cli.js" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.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/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lucide-react": { - "version": "0.562.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", - "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", - "license": "MIT", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.1.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/package.json b/package.json @@ -1,6 +0,0 @@ -{ - "dependencies": { - "fastapi": "^0.0.8", - "lucide-react": "^0.562.0" - } -} diff --git a/readme.md b/readme.md @@ -18,9 +18,9 @@ <div align="center"> -| ![System Overview](assets/images/screenshots/system_overview.png) | ![Agent Control](assets/images/screenshots/agent_control.png) | ![Console Log](assets/images/screenshots/console_log.png) | -| :---------------------------------------------------------------: | :-----------------------------------------------------------: | :-------------------------------------------------------: | -| **System Overview**<br/>Real-time monitoring dashboard | **Agent Control**<br/>Multi-agent orchestration | **Console Log**<br/>AI agent decision logs | +| ![System Overview](assets/screenshots/system_overview.png) | ![Agent Control](assets/screenshots/agent_control.png) | ![Console Log](assets/screenshots/console_log.png) | +| :--------------------------------------------------------: | :----------------------------------------------------: | :------------------------------------------------: | +| **System Overview**<br/>Real-time monitoring dashboard | **Agent Control**<br/>Multi-agent orchestration | **Console Log**<br/>AI agent decision logs | </div> @@ -33,7 +33,7 @@ The system combines **Long-Term Memory**, **Computer Vision**, and **Reinforcement Learning** to optimize crop health in real-time, creating a truly autonomous farming experience. <div align="center"> - <img src="assets/images/demeter_dashboard.png" alt="Demeter Dashboard" width="800"/> + <img src="assets/screenshots/demeter_dashboard.png" alt="Demeter Dashboard" width="800"/> <p><em>Demeter Web Dashboard - Real-time monitoring and control interface</em></p> </div> @@ -54,7 +54,7 @@ The system combines **Long-Term Memory**, **Computer Vision**, and **Reinforceme Demeter operates on a **Hierarchical Control Loop** powered by **LangGraph**, featuring specialized agents that collaborate to maintain optimal growing conditions: <div align="center"> - <img src="assets/images/Fetcher.png" alt="Demeter System Architecture" width="800"/> + <img src="assets/architecture.png" alt="Demeter System Architecture" width="800"/> <p><em>Demeter Agent Hierarchy - Multi-agent cognitive architecture</em></p> </div> @@ -262,7 +262,7 @@ npm run start - **Health Check**: http://localhost:8000/health <div align="center"> - <img src="assets/images/agent-control.png" alt="Agent Control Interface" width="700"/> + <img src="assets/screenshots/agent_control.png" alt="Agent Control Interface" width="700"/> <p><em>Agent Control Dashboard - Monitor and interact with AI agents in real-time</em></p> </div>