├── Backend ├── .gitignore ├── vercel.json ├── mainRouter.js ├── Routers │ └── GPT_Router.js ├── package.json ├── Contollers │ └── GPT_Controller.js ├── README.md ├── App.js ├── Modules │ └── GPT_Module.js └── package-lock.json ├── molecule-visualiser ├── src │ ├── App.css │ ├── setupTests.js │ ├── reportWebVitals.js │ ├── App.js │ ├── index.js │ ├── components │ │ ├── CheckCycle.js │ │ ├── GraphADT.js │ │ ├── Navbar.jsx │ │ ├── AISection.jsx │ │ ├── EditMolecule.jsx │ │ └── Molecule.js │ ├── index.css │ └── pages │ │ ├── Teams.jsx │ │ └── mainApp.jsx ├── tailwind.config.js ├── .gitignore ├── package.json ├── public │ └── index.html └── README.md ├── package.json ├── .vercel ├── project.json └── README.txt ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTIONS.md └── README.md /Backend/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /molecule-visualiser/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "three": "^0.164.1" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.vercel/project.json: -------------------------------------------------------------------------------- 1 | {"projectId":"prj_1joCFSrrHrsWnkDpf8411AIH4dqH","orgId":"team_eUKYgDoVvpdDV1vCRDzA6QHW"} -------------------------------------------------------------------------------- /Backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [{ "src": "App.js", "use": "@vercel/node" }], 4 | "routes": [{ "src": "/(.*)", "dest": "App.js" }] 5 | } 6 | -------------------------------------------------------------------------------- /Backend/mainRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const GPT_Router = require('./Routers/GPT_Router'); 5 | 6 | router.use("/", GPT_Router); 7 | 8 | module.exports = router; -------------------------------------------------------------------------------- /molecule-visualiser/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /Backend/Routers/GPT_Router.js: -------------------------------------------------------------------------------- 1 | const GPT_Controller = require('../Contollers/GPT_Controller'); 2 | const express = require('express'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/getinfo/:compound", (req, res) => { 7 | GPT_Controller(req, res); 8 | }) 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /molecule-visualiser/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | ], 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | inter: ['Inter', 'sans-serif'], 10 | }, 11 | }, 12 | }, 13 | plugins: [], 14 | } -------------------------------------------------------------------------------- /molecule-visualiser/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /Backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "App.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@google/generative-ai": "^0.11.3", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.4.5", 15 | "express": "^4.19.2", 16 | "nodemon": "^3.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /molecule-visualiser/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vercel 26 | -------------------------------------------------------------------------------- /Backend/Contollers/GPT_Controller.js: -------------------------------------------------------------------------------- 1 | const GPT_Module = require('../Modules/GPT_Module'); 2 | 3 | 4 | async function GPT_Controller(req, res){ 5 | const compound_formula = req.params.compound; 6 | const response = await GPT_Module(compound_formula); 7 | if(response.responseCode === 200){ 8 | res.status(200).json(response.responseBody); 9 | } 10 | else{ 11 | res.status(100).json(response.responseBody); 12 | } 13 | } 14 | 15 | module.exports = GPT_Controller; -------------------------------------------------------------------------------- /Backend/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## API Documentation 3 | 4 | #### Test Server 5 | 6 | ```http 7 | GET /test 8 | ``` 9 | --------------------------------------------------- 10 | 11 | #### Get Information 12 | 13 | ```http 14 | GET /getinfo/{compund_name} 15 | ``` 16 | 17 | | Parameter | Type | Description | 18 | | :-------- | :------- | :-------------------------------- | 19 | | `compund` | `string` | **Required**. Molecular Formula of the Compound | 20 | 21 | ----------------------------------------------------- 22 | -------------------------------------------------------------------------------- /Backend/App.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const app = express(); 4 | const mainRouter = require('./mainRouter'); 5 | 6 | app.use(express.json()); 7 | app.use(cors()); 8 | require('dotenv').config(); 9 | 10 | app.use('/', mainRouter); 11 | 12 | app.get("/test", (req, res) => { 13 | res.status(200).json("Welcome to Molecule Visualizer"); 14 | }) 15 | 16 | app.listen(process.env.PORT || 3000, function(){ 17 | console.log("Server listening on port %d in %s mode", this.address().port, app.settings.env); 18 | }); -------------------------------------------------------------------------------- /.vercel/README.txt: -------------------------------------------------------------------------------- 1 | > Why do I have a folder named ".vercel" in my project? 2 | The ".vercel" folder is created when you link a directory to a Vercel project. 3 | 4 | > What does the "project.json" file contain? 5 | The "project.json" file contains: 6 | - The ID of the Vercel project that you linked ("projectId") 7 | - The ID of the user or team your Vercel project is owned by ("orgId") 8 | 9 | > Should I commit the ".vercel" folder? 10 | No, you should not share the ".vercel" folder with anyone. 11 | Upon creation, it will be automatically added to your ".gitignore" file. 12 | -------------------------------------------------------------------------------- /molecule-visualiser/src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 2 | import NavBar from "./components/Navbar"; 3 | import MoleculeVisualizer from "./pages/mainApp"; 4 | import Teams from "./pages/Teams"; 5 | 6 | export default function App() { 7 | return ( 8 | 9 |
10 | 11 | } /> 12 | } /> 13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /molecule-visualiser/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/CheckCycle.js: -------------------------------------------------------------------------------- 1 | export function checkCycle(molecule) { 2 | let visited = {}; 3 | for (let atom of molecule.atomList) visited[atom.atomName] = false; 4 | for (let atom of molecule.atomList) { 5 | if (!visited[atom.atomName]) { 6 | if (DFS(null, atom, visited, molecule)) { 7 | return true; 8 | } 9 | } 10 | } 11 | return false; 12 | } 13 | 14 | function DFS(parentAtom, currentAtom, visited, molecule) { 15 | visited[currentAtom.atomName] = true; 16 | for (let neighbour of molecule.getNeighbours(currentAtom)) { 17 | if (!visited[neighbour.atomName]) { 18 | if (DFS(currentAtom, neighbour, visited, molecule)) { 19 | return true; 20 | } 21 | } else if (neighbour.atomName !== parentAtom?.atomName) { 22 | return true; 23 | } 24 | } 25 | return false; 26 | } -------------------------------------------------------------------------------- /molecule-visualiser/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import '@fontsource/inter/100.css'; /* Thin */ 6 | @import '@fontsource/inter/200.css'; /* Extra Light */ 7 | @import '@fontsource/inter/300.css'; /* Light */ 8 | @import '@fontsource/inter/400.css'; /* Regular */ 9 | @import '@fontsource/inter/500.css'; /* Medium */ 10 | @import '@fontsource/inter/600.css'; /* Semi Bold */ 11 | @import '@fontsource/inter/700.css'; /* Bold */ 12 | @import '@fontsource/inter/800.css'; /* Extra Bold */ 13 | @import '@fontsource/inter/900.css'; /* Black */ 14 | 15 | @layer utilities { 16 | /* Hide scrollbar for Chrome, Safari and Opera */ 17 | .no-scrollbar::-webkit-scrollbar { 18 | display: none; 19 | } 20 | /* Hide scrollbar for IE, Edge and Firefox */ 21 | .no-scrollbar { 22 | -ms-overflow-style: none; /* IE and Edge */ 23 | scrollbar-width: none; /* Firefox */ 24 | } 25 | .markdown-container p { 26 | @apply mb-3 text-yellow-100; 27 | } 28 | .markdown-container h1 { 29 | @apply mb-3 text-blue-100 text-center text-xl; 30 | } 31 | 32 | .markdown-container ul, 33 | .markdown-container ol { 34 | @apply ml-2 mb-1 text-green-100; 35 | } 36 | 37 | .markdown-container li { 38 | @apply mb-1; 39 | } 40 | 41 | .markdown-container strong { 42 | @apply font-bold; 43 | } 44 | } -------------------------------------------------------------------------------- /molecule-visualiser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "molecule-visualiser", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.11.4", 7 | "@emotion/styled": "^11.11.5", 8 | "@fontsource/inter": "^5.0.18", 9 | "@mui/icons-material": "^5.15.18", 10 | "@mui/material": "^5.15.18", 11 | "@testing-library/jest-dom": "^5.17.0", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "3dmol": "^2.1.0", 15 | "axios": "^1.8.1", 16 | "d3": "^7.9.0", 17 | "plotly.js": "^2.32.0", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1", 20 | "react-icons": "^5.2.1", 21 | "react-markdown": "^9.0.1", 22 | "react-plotly.js": "^2.6.0", 23 | "react-router-dom": "^7.2.0", 24 | "react-scripts": "5.0.1", 25 | "three": "^0.164.1", 26 | "web-vitals": "^2.1.4" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "devDependencies": { 53 | "tailwindcss": "^3.4.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /molecule-visualiser/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Molecule Visualizer 9 | 13 | 14 | 18 | 19 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/GraphADT.js: -------------------------------------------------------------------------------- 1 | export class atomNode { 2 | constructor(atomName, hybridisation, atomSymbol) { 3 | this.atomName = atomName; 4 | this.hybridisation = hybridisation; 5 | this.atomSymbol = atomSymbol; 6 | this.coordinates = [0, 0, 0]; 7 | this.connections = []; 8 | } 9 | } 10 | 11 | export class Molecule { 12 | constructor() { 13 | this.adjacencyList = {}; 14 | this.atomList = []; 15 | } 16 | 17 | getNeighbours(atom) { 18 | if (atom && this.adjacencyList[atom.atomName]) { 19 | return this.adjacencyList[atom.atomName].map(({ atomName }) => 20 | this.atomList.find((a) => a.atomName === atomName) 21 | ); 22 | } 23 | return []; 24 | } 25 | 26 | 27 | addAtoms(atom) { 28 | if (!this.adjacencyList[atom.atomName]) { 29 | this.adjacencyList[atom.atomName] = []; 30 | this.atomList.push(atom); 31 | } else { 32 | } 33 | } 34 | 35 | addBond(atom1, atom2, isSingleBond = false, isDoubleBond = false, isTripleBond = false) { 36 | if ( 37 | this.adjacencyList[atom1.atomName] && 38 | this.adjacencyList[atom2.atomName] 39 | ) { 40 | let flag1 = 0; 41 | for (let i = 0; i < this.adjacencyList[atom1.atomName].length; i++) { 42 | if (this.adjacencyList[atom1.atomName][i].atomName === atom2.atomName) { 43 | flag1 = 1; 44 | break; 45 | } 46 | } 47 | if (!flag1) { 48 | this.adjacencyList[atom1.atomName].push({ 49 | atomName: atom2.atomName, 50 | isSingleBond: isSingleBond && !isDoubleBond && !isTripleBond, 51 | isDoubleBond: !isSingleBond && isDoubleBond && !isTripleBond, 52 | isTripleBond: !isSingleBond && !isDoubleBond && isTripleBond 53 | }); 54 | } 55 | 56 | let flag2 = 0; 57 | for (let i = 0; i < this.adjacencyList[atom2.atomName].length; i++) { 58 | if (this.adjacencyList[atom2.atomName][i].atomName === atom1.atomName) { 59 | flag2 = 1; 60 | break; 61 | } 62 | } 63 | if (!flag2) { 64 | this.adjacencyList[atom2.atomName].push({ 65 | atomName: atom1.atomName, 66 | isSingleBond: isSingleBond && !isDoubleBond && !isTripleBond, 67 | isDoubleBond: !isSingleBond && isDoubleBond && !isTripleBond, 68 | isTripleBond: !isSingleBond && !isDoubleBond && isTripleBond 69 | }); 70 | } 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { SiMoleculer } from "react-icons/si"; 4 | import { FiMenu, FiX } from "react-icons/fi"; 5 | 6 | export default function NavBar() { 7 | const [isOpen, setIsOpen] = useState(false); 8 | 9 | return ( 10 |
11 |
12 |
13 | 14 | 15 | MOLECULE VISUALIZER 16 | 17 |
18 | 19 |
20 | 21 | VISUALIZER 22 | 23 | 24 | TEAM 25 | 26 | 32 | DOCUMENTATION 33 | 34 |
35 | 36 | 42 |
43 | 44 | {isOpen && ( 45 |
46 | setIsOpen(false)} 50 | > 51 | VISUALIZER 52 | 53 | 59 | DOCUMENTATION 60 | 61 | setIsOpen(false)} 65 | > 66 | TEAM 67 | 68 |
69 | )} 70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | samples-test 133 | -------------------------------------------------------------------------------- /Backend/Modules/GPT_Module.js: -------------------------------------------------------------------------------- 1 | const { GoogleGenerativeAI } = require("@google/generative-ai"); 2 | require("dotenv").config(); 3 | 4 | const genAI = new GoogleGenerativeAI(process.env.API_KEY); 5 | 6 | const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); //gemini 1.0 pro is raising some issue 7 | 8 | async function Get_Info(compound_formula) { 9 | const response = { 10 | responseCode: 200, 11 | responseBody: "", 12 | }; 13 | 14 | try { 15 | console.log(compound_formula) 16 | const prompt = `"I need detailed information about the chemical compound ${compound_formula} ,If there is more isomers consider one yourself and give out response. 17 | Please provide the following details in a clear and structured markdown format: 18 | Before providing the information: 19 | - **validate** the compound formula. 20 | - If the compound is not found or does not exist, **return the following message**: "The chemical compound '${compound_formula}' could not be identified. Please verify the formula or provide a more specific name." 21 | - If the compound is recognized, provide the details in the following clear and structured markdown format: 22 | 23 | # {Compund formula here} it should be bold when rendered under react-markdown it should be in h1 tag 24 | 1. **Chemical Name** 🧪 25 | - **The Chemical name** : 26 | - **the IUPAC name :**. of compund 27 | 2. **Physical Properties** 28 | - **Melting point** ❄️ 29 | - **Boiling point** ♨️ 30 | - **Density** ⚖️ 31 | - **Solubility** 🌊 32 | - **State at room temperature** 🏠 33 | 3. **Chemical Properties** ⚗️ 34 | - **Reactivity** 💥 35 | - **pH** 36 | - **stability** 37 | - **known Chemical reactions**. 38 | 4. **Uses** 🏭 39 | - Common applications and industries where this compound is utilized. 40 | 5. **Safety Information** ⚠️ 41 | - **Including toxicity** ☠️ 42 | - **handling precautions** 🧤 43 | - **safety measures** 🛡️ 44 | 6. **Synthesis** 🛠️ 45 | - A brief overview of how this compound is typically synthesized or extracted. 46 | 7. **Regulatory Information** 📜 47 | - Any relevant regulatory guidelines 48 | - restrictions associated with this compound. 49 | Ensure the markdown is well-indented and uses bullet points for lists. 50 | Format all topic headers as bold titles using markdown syntax (**) 51 | Provide an extra line break between sections for better readability" 52 | Just respond with that format and no html tags in output and no extra messages`; 53 | const result = await model.generateContent(prompt); 54 | const GPTresponse = result.response.candidates[0].content.parts[0].text; 55 | response.responseBody = GPTresponse; 56 | console.log(response.responseBody); 57 | } catch (error) { 58 | response.responseCode = 100; 59 | response.responseBody = error; 60 | console.log(response.responseBody); 61 | } 62 | return response; 63 | } 64 | 65 | module.exports = Get_Info; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | --- 10 | 11 | ## Our Standards 12 | 13 | Examples of behavior that contributes to a positive environment for our community include: 14 | - Demonstrating respect and empathy toward all community members. 15 | - Being constructive in feedback and accepting constructive criticism gracefully. 16 | - Focusing on what is best for the community and project. 17 | - Showing kindness and courtesy to others. 18 | 19 | Examples of unacceptable behavior include: 20 | - The use of sexualized language or imagery, and unwelcome sexual attention or advances. 21 | - Trolling, insulting, or derogatory comments, and personal or political attacks. 22 | - Public or private harassment. 23 | - Publishing others’ private information without their explicit permission. 24 | - Other conduct which could reasonably be considered inappropriate in a professional setting. 25 | 26 | --- 27 | 28 | ## Responsibilities 29 | 30 | Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior. They will take appropriate and fair corrective action in response to any behavior they deem inappropriate, threatening, offensive, or harmful. 31 | 32 | --- 33 | 34 | ## Enforcement 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at **[your-email@example.com]**. All complaints will be reviewed and investigated promptly and fairly. 37 | 38 | The project team is obligated to respect the privacy and security of the reporter of any incident. 39 | 40 | --- 41 | 42 | ## Enforcement Guidelines 43 | 44 | Project maintainers will follow these guidelines to determine the consequences for any action they deem to be in violation of this Code of Conduct: 45 | 46 | 1. **Correction** 47 | A private, written warning with clarity on the problematic behavior and expectations moving forward. 48 | 49 | 2. **Warning** 50 | A formal warning issued for repeat offenses or major violations. 51 | 52 | 3. **Temporary Ban** 53 | Temporary suspension from the community for continued or severe violations. 54 | 55 | 4. **Permanent Ban** 56 | Permanent removal from the community for egregious violations. 57 | 58 | --- 59 | 60 | ## Attribution 61 | 62 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). 63 | 64 | For answers to common questions, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). 65 | -------------------------------------------------------------------------------- /molecule-visualiser/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | 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. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | 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) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | 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) 55 | 56 | ### Making a Progressive Web App 57 | 58 | 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) 59 | 60 | ### Advanced Configuration 61 | 62 | 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) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | 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) 71 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Contributions Guide 2 | 3 | Thank you for your interest in contributing to the **Molecule Visualiser** project! This guide will help you understand how to contribute effectively. 4 | 5 | --- 6 | 7 | ## Table of Contents 8 | 1. [Getting Started](#getting-started) 9 | 2. [How to Contribute](#how-to-contribute) 10 | 3. [Code of Conduct](#code-of-conduct) 11 | 4. [Reporting Issues](#reporting-issues) 12 | 5. [Creating Pull Requests](#creating-pull-requests) 13 | 6. [Style Guide](#style-guide) 14 | 15 | --- 16 | 17 | ## Getting Started 18 | 19 | To contribute: 20 | 1. Fork the repository from [Molecule Visualiser](https://github.com/TharunKumarrA/Molecule-Visualiser) and clone it to your local machine: 21 | ```bash 22 | git clone https://github.com/YourUsername/Molecule-Visualiser.git 23 | ``` 24 | ## Set Up the Project 25 | 26 | - Follow the instructions in the `README.md` to set up the project. 27 | - Ensure your environment matches the project requirements. 28 | 29 | --- 30 | 31 | ## How to Contribute 32 | 33 | ### Issues 34 | - Check the [Issues](https://github.com/TharunKumarrA/Molecule-Visualiser/issues) section for open tickets. 35 | - If you want to work on an existing issue, comment on the issue to let others know you're working on it. 36 | - If you find a bug or have a feature request, [create a new issue](https://github.com/TharunKumarrA/Molecule-Visualiser/issues/new). 37 | 38 | ### Feature Requests 39 | - Before starting work on a new feature: 40 | - Discuss the feature in an existing issue or create a new one. 41 | - Ensure it aligns with the project's goals. 42 | 43 | --- 44 | 45 | ## Code of Conduct 46 | 47 | This project follows the [Contributor Covenant](https://www.contributor-covenant.org/). 48 | All contributors must adhere to it. Be respectful and constructive in all interactions. 49 | 50 | --- 51 | 52 | ## Reporting Issues 53 | 54 | When reporting an issue: 55 | - Provide a clear and descriptive title. 56 | - Include steps to reproduce the issue. 57 | - Share relevant logs or screenshots if applicable. 58 | - Specify the environment (e.g., operating system, browser, Node.js version). 59 | ## Creating Pull Requests 60 | 61 | Follow these steps to create a pull request: 62 | 63 | 1. **Create a new branch** from `main` or the relevant base branch: 64 | ```bash 65 | git checkout -b feature/your-feature-name 66 | ``` 67 | ## Make Your Changes 68 | 69 | - Ensure code quality and thorough testing. 70 | 71 | ### Commit Your Changes 72 | ```bash 73 | git commit -m "Add a concise and descriptive commit message" 74 | ``` 75 | ### Push the Branch to Your Fork 76 | ```bash 77 | git push origin feature/your-feature-name 78 | ``` 79 | ### Open a Pull Request (PR) 80 | 81 | - Open a pull request (PR) to the `main` branch of the original repository. 82 | 83 | --- 84 | 85 | ## PR Guidelines 86 | 87 | - Provide a detailed description of the changes in the PR. 88 | - Reference related issues (e.g., `Closes #123`). 89 | - Ensure the PR passes all checks (tests, linters). 90 | 91 | --- 92 | 93 | ## Style Guide 94 | 95 | ### Code 96 | - Follow the style conventions mentioned in the project documentation (e.g., ESLint rules for JavaScript). 97 | - Write clear, concise, and well-documented code. 98 | 99 | ### Commits 100 | - Use descriptive commit messages (e.g., `Fix bug in molecule visualization logic`). 101 | - Group related changes into a single commit. 102 | 103 | --- 104 | 105 | ## Acknowledgments 106 | 107 | Thank you for taking the time to contribute! Your efforts make this project better for everyone. 108 | 109 | -------------------------------------------------------------------------------- /molecule-visualiser/src/pages/Teams.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import axios from "axios"; 3 | import NavBar from "../components/Navbar"; 4 | 5 | const teamMembers = [ 6 | "TharunKumarrA", 7 | "Naganathan05", 8 | "Lowkik-Sai", 9 | "Hariprasath8064", 10 | "S-Bharath16", 11 | "adithya-menon-r", 12 | "Twinn-github09", 13 | ]; 14 | 15 | export default function Teams() { 16 | const [profiles, setProfiles] = useState([]); 17 | const [loading, setLoading] = useState(true); 18 | 19 | const GITHUB_TOKEN = process.env.REACT_APP_GITHUB_TOKEN; 20 | 21 | useEffect(() => { 22 | async function fetchProfiles() { 23 | setLoading(true); 24 | const promises = teamMembers.map(async (username) => { 25 | try { 26 | const res = await axios.get( 27 | `https://api.github.com/users/${username}`, 28 | { 29 | headers: { 30 | Authorization: `Bearer ${GITHUB_TOKEN}`, 31 | }, 32 | } 33 | ); 34 | return { 35 | name: res.data.name || res.data.login, 36 | username: res.data.login, 37 | avatar: res.data.avatar_url, 38 | profileUrl: res.data.html_url, 39 | }; 40 | } catch (error) { 41 | console.error("Error fetching data for", username); 42 | return null; 43 | } 44 | }); 45 | 46 | const results = await Promise.all(promises); 47 | setProfiles(results.filter((profile) => profile !== null)); 48 | setLoading(false); 49 | } 50 | 51 | fetchProfiles(); 52 | }, []); 53 | 54 | return ( 55 |
56 | 57 |
58 |

Meet Our Team

59 | {loading ? ( 60 |

61 | Fetching team data... 62 |

63 | ) : ( 64 |
65 | {profiles.map((profile) => ( 66 |
70 | {profile.name} 75 |

{profile.name}

76 | 82 | @{profile.username} 83 | 84 |
85 | ))} 86 | {/* Contribute Card */} 87 | 93 | GitHub 98 |

Contribute on GitHub

99 |

100 | @Molecule-Visualiser 101 |

102 |
103 |
104 | )} 105 |
106 |
107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Molecule Visualizer 3 | 4 | 5 | Molecule Visualizer is an innovative data structures and algorithms initiative designed to represent and manipulate chemical molecules in a three-dimensional space using graph data structures. 6 | Each atom is represented as a node, and each bond as an edge, allowing for a flexible and dynamic representation of molecular structures. 7 | The construction of the molecular graph employs Breadth-First Search (BFS) to ensure comprehensive and systematic node traversal, facilitating the accurate assembly of the molecule. Depth-First Search (DFS) is implemented for cycle detection, crucial for identifying rings and complex substructures within the molecule. 8 | 9 | ## Key Features 10 | 11 | * Detailed information sharing about the constructed molecules **Physical** and **Chemical** Properties. 12 | * Bond angle calculations and molecular geometry analysis. 13 | * Algorithmic efficiency for real-time updates and smooth interactions. 14 | 15 | 16 | 17 | ## Tech Stack 18 | 19 | **Client:** React JS, Material UI, Tailwind CSS 20 | 21 | **Server:** Node JS, Express JS 22 | 23 | **Plotting:** Plotly JS 24 | 25 | 26 | ## Contributing 27 | 28 | Contributions are always welcome! 29 | 30 | See `contributing.md` for ways to get started. 31 | 32 | Please adhere to this project's `code of conduct`. 33 | 34 | # Local Development: 35 | 36 | ## Steps to get the Application Running at Local: 37 | 38 | ### 1. Clone the Repository 39 | 40 | ```bash 41 | git clone https://github.com/TharunKumarrA/Molecule-Visualiser.git 42 | cd Molecule-Visualiser 43 | ``` 44 | 45 | ### 2. Install Backend Dependencies 46 | 47 | ```bash 48 | cd Backend 49 | npm install 50 | cd .. 51 | ``` 52 | 53 | ### 3. Install Frontend Dependencies 54 | 55 | ```bash 56 | cd molecule-visualiser 57 | npm install 58 | cd .. 59 | ``` 60 | 61 | ### 4. Configure Environment Variables 62 | 63 | > [!Important] 64 | > Just for the sake of security, the `.env` file is not included in the repository. 65 | Create a `.env` file in the root directory of the project and add the following key-value pairs. Replace placeholders with actual values. Ensure there are no spaces or quotes, and values are entered as plain text. 66 | 67 | ```env 68 | # Server Port Number 69 | PORT= 70 | 71 | # API Key for Gemini 1.5 AI Model. 72 | API_KEY= 73 | ``` 74 | 75 | 76 | ### 5. Start the Backend Server 77 | 78 | ```bash 79 | cd Backend 80 | nodemon App.js 81 | ``` 82 | 83 | ### 6. Start the Frontend Server 84 | 85 | ```bash 86 | cd molecule-visualiser 87 | npm run start 88 | ``` 89 | ## API Utilized 90 | 91 | [Gemini 1.5 Flash](https://ai.google.dev/gemini-api/docs/models/gemini#gemini-1.5-flash) 92 | 93 | 94 | ## API Documentation 95 | 96 | [API-Reference](/Backend/README.md) 97 | 98 | 99 | ## Developers 100 | 101 | - `Tharun Kumarr A` 102 | - `Naganathan M R` 103 | - `Lowkik Sai P` 104 | - `Praveen K` 105 | - `Bharath S` 106 | - `Hariprasath M` 107 | 108 | ## Future Ideas for **Molecule Visualizer**: 109 | 110 | * **Validation** of the compounds that has be entered by the user. 111 | * **Extending** the available hybridisation of molecules to **sp3d** and **sp3d2**. 112 | * **Allowing** for various type of elements available in the periodic table. 113 | 114 | ## FAQ 115 | 116 | #### How to build the Molecule ? 117 | 118 | * **Add Atoms:** Navigate to the menu bar at the right side of the page. Initially you need to add all the atoms with respective **hybridisations** that are present in your molecule using the **Add Atoms** menu. 119 | 120 | * **Add Bond:** Menu in the right side will be comprising a option named **Add Bond**. Using this option select respective two atoms and the type of bond `Single, Double, Triple`. Then add bond will connect those two atoms in the 3d space. 121 | 122 | #### What are these Examples ? 123 | 124 | * These are prebuilt molecule templates for visualizing. These include the most common **Organic** and **Inorganic** compounds for fast visualization. 125 | 126 | 127 | #### How to find if a Molecule is Cyclic/Acyclic ? 128 | 129 | * Once you constructed the molecule using the option available, you can see the **Check Cycle** information in the bottom of the menu bar present at the right. This indicates whether the constructed molecule is **Cyclic** or **Acyclic**. 130 | 131 | #### How get properties of constructed moelcule ? 132 | 133 | * Once you have constrcuted the molecule, use the option **Get Data** available at the top left. This option when trigerred whill fetch you all the **Chemical** and **Physical** Properties of the molecule that has been constrcuted. 134 | 135 | ```mermaid 136 | graph TD 137 | A[Start] --> B[Add Atoms] 138 | B --> C[Add Bond] 139 | C --> D{Cycle Detection} 140 | D -->|Cyclic| E[Identify Rings] 141 | D -->|Acyclic| F[Proceed] 142 | E --> F 143 | F --> G[Calculate Bond Angles] 144 | G --> H[Analyze Molecular Geometry] 145 | H --> I[Get Physical/Chemical Properties] 146 | I --> J[Display Results] 147 | J --> K[Real-time Updates] 148 | K --> L[End] 149 | 150 | ``` 151 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/AISection.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | import ReactMarkdown from "react-markdown"; 4 | 5 | export default function AISection({ compoundFormula, resetAI }) { 6 | const [res, setResponse] = useState(null); 7 | const [isLoading, setIsLoading] = useState(false); 8 | const [hasDisplayedInfo, setHasDisplayedInfo] = useState(false); 9 | 10 | const serverPort = process.env.PORT || 5000; 11 | const BASE_URL = 12 | process.env.REACT_APP_BACKEND_URL || "https://molecule-backend.vercel.app"; 13 | console.log(BASE_URL); 14 | 15 | // Reset the response when resetAI changes to true 16 | useEffect(() => { 17 | if (resetAI) { 18 | setResponse(null); 19 | setHasDisplayedInfo(false); 20 | } 21 | }, [resetAI]); 22 | 23 | // Reset the response when compoundFormula becomes empty 24 | useEffect(() => { 25 | if (!compoundFormula || compoundFormula === "") { 26 | setResponse(null); 27 | setHasDisplayedInfo(false); 28 | } else if (res === null) { 29 | // If we have a compound formula but no response, we should show the button 30 | setHasDisplayedInfo(false); 31 | } 32 | }, [compoundFormula, res]); 33 | 34 | const handleGetData = () => { 35 | // Don't fetch if there's no compound formula 36 | if (!compoundFormula || compoundFormula === "") { 37 | return; 38 | } 39 | 40 | setIsLoading(true); 41 | axios 42 | .get(`${BASE_URL}/getinfo/${compoundFormula}`) 43 | .then((response) => { 44 | console.log(response.data); 45 | setResponse(response.data); 46 | setHasDisplayedInfo(true); 47 | setIsLoading(false); 48 | }) 49 | .catch((error) => { 50 | console.error("Error fetching data:", error); 51 | setIsLoading(false); 52 | }); 53 | }; 54 | 55 | return ( 56 |
57 | {isLoading ? ( 58 |
59 | 83 |
84 | ) : ( 85 | <> 86 | {!res && compoundFormula && !hasDisplayedInfo && ( 87 | 93 | )} 94 | {!compoundFormula && ( 95 |
96 | Build a molecule to get information 97 |
98 | )} 99 | {res && ( 100 |
101 |
102 | 103 | {res} 104 | 105 |
106 |
107 | )} 108 | 109 | )} 110 |
111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /molecule-visualiser/src/pages/mainApp.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Plot from "react-plotly.js"; 3 | import { 4 | createAtomNode, 5 | addAtoms, 6 | addBonds, 7 | buildMolecule, 8 | getCoordinates, 9 | } from "../components/Molecule"; 10 | import { Molecule } from "../components/GraphADT"; 11 | import NavBar from "../components/Navbar"; 12 | import EditMolecule from "../components/EditMolecule"; 13 | import AISection from "../components/AISection"; 14 | import { checkCycle } from "../components/CheckCycle"; 15 | 16 | const MoleculeVisualizer = () => { 17 | const [atomsList, setAtomsList] = useState([]); 18 | const [molecule, setMolecule] = useState(new Molecule()); 19 | const [trigger, setTrigger] = useState(0); 20 | const [resetAI, setResetAI] = useState(false); 21 | 22 | const [atomCounters, setAtomCounters] = useState({ 23 | C: 0, 24 | H: 0, 25 | O: 0, 26 | N: 0, 27 | P: 0, 28 | S: 0, 29 | F: 0, 30 | Cl: 0, 31 | }); 32 | 33 | const [compoundFormula, setCompoundFormula] = useState(""); 34 | 35 | const generateCompoundFormula = () => { 36 | let formula = ""; 37 | for (const [atom, count] of Object.entries(atomCounters)) { 38 | if (count > 0) { 39 | formula += atom; 40 | if (count > 1) { 41 | formula += count; 42 | } 43 | } 44 | } 45 | return formula; 46 | }; 47 | 48 | useEffect(() => { 49 | const newCompoundFormula = generateCompoundFormula(); 50 | setCompoundFormula(newCompoundFormula); 51 | console.log("Compound Formula: ", newCompoundFormula); 52 | }, [atomCounters]); 53 | 54 | useEffect(() => { 55 | getCoordinates(molecule); 56 | console.log("In useEffect"); 57 | }, [trigger, molecule]); 58 | 59 | const incrementAtomCounter = (atomType) => { 60 | setAtomCounters((prevCounters) => ({ 61 | ...prevCounters, 62 | [atomType]: prevCounters[atomType] + 1, 63 | })); 64 | }; 65 | 66 | const handleDataFromEditMolecule = (data) => { 67 | if (data.type === "atom") { 68 | const atomCounter = atomCounters[data.atomSymbol]; 69 | const atomName = data.atomSymbol + atomCounter; 70 | const atom = createAtomNode( 71 | atomName, 72 | data.hybridisation, 73 | data.atomSymbol 74 | ); 75 | addAtoms(molecule, atom); 76 | console.log("Atom added: ", atom.atomName); 77 | console.log(data); 78 | incrementAtomCounter(data.atomSymbol); 79 | } else if (data.type === "bond") { 80 | addBonds( 81 | molecule, 82 | data.atom1Name, 83 | data.atom2Name, 84 | data.isSingleBond, 85 | data.isDoubleBond, 86 | data.isTripleBond 87 | ); 88 | console.log("Bond added between: ", data.atom1Name, data.atom2Name); 89 | console.log(data); 90 | } else if (data.type === "clear") { 91 | // Reset all states when clearing the molecule 92 | setMolecule(new Molecule()); 93 | setAtomsList([]); 94 | setAtomCounters({ 95 | C: 0, 96 | H: 0, 97 | O: 0, 98 | N: 0, 99 | P: 0, 100 | S: 0, 101 | F: 0, 102 | Cl: 0, 103 | }); 104 | setCompoundFormula(""); 105 | setTrigger(0); // Reset trigger state 106 | setResetAI(true); 107 | setTimeout(() => setResetAI(false), 100); 108 | } 109 | 110 | setTrigger((prev) => prev + 1); 111 | }; 112 | 113 | const handleMoleculeUpdate = (updatedMolecule) => { 114 | // When molecule is updated (especially cleared), set resetAI to true 115 | if (updatedMolecule.atomList.length === 0) { 116 | setResetAI(true); 117 | setTimeout(() => setResetAI(false), 100); 118 | } 119 | 120 | setMolecule(updatedMolecule); 121 | setTrigger(!trigger); 122 | }; 123 | 124 | const traceAtoms = { 125 | type: "scatter3d", 126 | mode: "markers+text", 127 | text: molecule.atomList.map((atom) => atom.atomSymbol), 128 | x: molecule.atomList.map((atom) => atom.coordinates[0]), 129 | y: molecule.atomList.map((atom) => atom.coordinates[1]), 130 | z: molecule.atomList.map((atom) => atom.coordinates[2]), 131 | marker: { 132 | size: 12, 133 | opacity: 0.8, 134 | color: molecule.atomList.map((atom) => { 135 | switch (atom.atomSymbol) { 136 | case "C": 137 | return "black"; 138 | case "H": 139 | return "#87CEEB"; // Medium Light Blue 140 | case "P": 141 | return "#FFA500"; // Orange 142 | case "Cl": 143 | return "#20B2AA"; // Light Sea Green 144 | case "S": 145 | return "yellow"; 146 | case "F": 147 | return "#32CD32"; 148 | default: 149 | return "#CD5C5C"; // Indian Red 150 | } 151 | }), 152 | }, 153 | }; 154 | 155 | const traceSingleBonds = { 156 | type: "scatter3d", 157 | mode: "lines", 158 | line: { 159 | color: "gray", 160 | width: 2, 161 | }, 162 | x: [], 163 | y: [], 164 | z: [], 165 | }; 166 | 167 | const traceDoubleBonds = { 168 | type: "scatter3d", 169 | mode: "lines", 170 | line: { 171 | color: "red", 172 | width: 4, 173 | }, 174 | x: [], 175 | y: [], 176 | z: [], 177 | }; 178 | 179 | const traceTripleBonds = { 180 | type: "scatter3d", 181 | mode: "lines", 182 | line: { 183 | color: "black", 184 | width: 4, 185 | }, 186 | x: [], 187 | y: [], 188 | z: [], 189 | }; 190 | 191 | // Add connections data to traceSingleBonds, traceDoubleBonds, and traceTripleBonds 192 | molecule.atomList.forEach((atom) => { 193 | const atomConnections = molecule.adjacencyList[atom.atomName]; 194 | if (atomConnections) { 195 | atomConnections.forEach((connection) => { 196 | const connectedAtom = molecule.atomList.find( 197 | (a) => a.atomName === connection.atomName 198 | ); 199 | 200 | if (connectedAtom) { 201 | if (connection.isDoubleBond) { 202 | // Main line 203 | traceDoubleBonds.x.push( 204 | atom.coordinates[0], 205 | connectedAtom.coordinates[0], 206 | null 207 | ); 208 | traceDoubleBonds.y.push( 209 | atom.coordinates[1], 210 | connectedAtom.coordinates[1], 211 | null 212 | ); 213 | traceDoubleBonds.z.push( 214 | atom.coordinates[2], 215 | connectedAtom.coordinates[2], 216 | null 217 | ); 218 | 219 | // Offset line 220 | traceDoubleBonds.x.push( 221 | atom.coordinates[0] + 0.01, 222 | connectedAtom.coordinates[0] + 0.01, 223 | null 224 | ); 225 | traceDoubleBonds.y.push( 226 | atom.coordinates[1], 227 | connectedAtom.coordinates[1], 228 | null 229 | ); 230 | traceDoubleBonds.z.push( 231 | atom.coordinates[2] + 0.01, 232 | connectedAtom.coordinates[2], 233 | null 234 | ); 235 | } else if (connection.isSingleBond && !connection.isTripleBond) { 236 | traceSingleBonds.x.push( 237 | atom.coordinates[0], 238 | connectedAtom.coordinates[0], 239 | null 240 | ); 241 | traceSingleBonds.y.push( 242 | atom.coordinates[1], 243 | connectedAtom.coordinates[1], 244 | null 245 | ); 246 | traceSingleBonds.z.push( 247 | atom.coordinates[2], 248 | connectedAtom.coordinates[2], 249 | null 250 | ); 251 | } else if (connection.isTripleBond) { 252 | // Main line 253 | traceTripleBonds.x.push( 254 | atom.coordinates[0], 255 | connectedAtom.coordinates[0], 256 | null 257 | ); 258 | traceTripleBonds.y.push( 259 | atom.coordinates[1], 260 | connectedAtom.coordinates[1], 261 | null 262 | ); 263 | traceTripleBonds.z.push( 264 | atom.coordinates[2], 265 | connectedAtom.coordinates[2], 266 | null 267 | ); 268 | 269 | // Offset lines 270 | traceTripleBonds.x.push( 271 | atom.coordinates[0] + 0.01, 272 | connectedAtom.coordinates[0] + 0.01, 273 | null 274 | ); 275 | traceTripleBonds.y.push( 276 | atom.coordinates[1] + 0.01, 277 | connectedAtom.coordinates[1] + 0.01, 278 | null 279 | ); 280 | traceTripleBonds.z.push( 281 | atom.coordinates[2] + 0.01, 282 | connectedAtom.coordinates[2] + 0.01, 283 | null 284 | ); 285 | 286 | traceTripleBonds.x.push( 287 | atom.coordinates[0] - 0.01, 288 | connectedAtom.coordinates[0] - 0.01, 289 | null 290 | ); 291 | traceTripleBonds.y.push( 292 | atom.coordinates[1] - 0.01, 293 | connectedAtom.coordinates[1] - 0.01, 294 | null 295 | ); 296 | traceTripleBonds.z.push( 297 | atom.coordinates[2] - 0.01, 298 | connectedAtom.coordinates[2] - 0.01, 299 | null 300 | ); 301 | } 302 | } 303 | }); 304 | } 305 | }); 306 | 307 | const layout = { 308 | margin: { l: 0, r: 0, b: 0, t: 0 }, 309 | paper_bgcolor: "#1e1e1e", // Dark background for the plot 310 | plot_bgcolor: "#1e1e1e", // Dark background for the plot 311 | font: { 312 | color: "#ffffff", // White font color for better contrast 313 | }, 314 | scene: { 315 | xaxis: { 316 | backgroundcolor: "#1e1e1e", 317 | gridcolor: "#444444", 318 | showbackground: true, 319 | zerolinecolor: "#444444", 320 | }, 321 | yaxis: { 322 | backgroundcolor: "#1e1e1e", 323 | gridcolor: "#444444", 324 | showbackground: true, 325 | zerolinecolor: "#444444", 326 | }, 327 | zaxis: { 328 | backgroundcolor: "#1e1e1e", 329 | gridcolor: "#444444", 330 | showbackground: true, 331 | zerolinecolor: "#444444", 332 | }, 333 | annotations: 334 | molecule.atomList.length === 0 335 | ? [ 336 | { 337 | text: "Add atoms and bonds using the menu on the right.", 338 | showarrow: false, 339 | x: 0, 340 | y: 0, 341 | z: 0, 342 | font: { size: 20, color: "#ffffff" }, 343 | }, 344 | ] 345 | : [], 346 | }, 347 | }; 348 | 349 | const atomMenuItems = [ 350 | { value: "C", label: "C" }, 351 | { value: "H", label: "H" }, 352 | { value: "O", label: "O" }, 353 | { value: "N", label: "N" }, 354 | { value: "P", label: "P" }, 355 | { value: "S", label: "S" }, 356 | { value: "F", label: "F" }, 357 | { value: "Cl", label: "Cl" }, 358 | ]; 359 | 360 | const getBondMenuItems = () => { 361 | const bondMenuItems = []; 362 | 363 | molecule.atomList.forEach((atom) => { 364 | bondMenuItems.push({ value: atom.atomName, label: atom.atomName }); 365 | }); 366 | 367 | // Add placeholder items if there aren't enough atoms 368 | if (bondMenuItems.length < 2) { 369 | for (let i = bondMenuItems.length; i < 2; i++) { 370 | bondMenuItems.push({ value: `A${i + 1}`, label: `A${i + 1}` }); 371 | } 372 | } 373 | 374 | return bondMenuItems; 375 | }; 376 | 377 | const sampleMolecules = ["CH4", "C2H6", "C6H6", "H2O", "SF6", "PCl5"]; 378 | 379 | return ( 380 |
381 | 382 |
383 | {/* Warning message for small and medium screens */} 384 |
385 |
386 | 396 | 397 | 398 | 399 |

Unsupported Screen Size

400 |

401 | Please use a larger screen for the best experience. 402 |

403 |
404 |
405 | 406 | {/* Main content, hidden on sm and md screens */} 407 |
408 |
409 | 410 |
411 | 421 |
422 | 435 |
436 |
437 |
438 |
439 | ); 440 | }; 441 | 442 | export default MoleculeVisualizer; 443 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/EditMolecule.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Accordion from "@mui/material/Accordion"; 3 | import AccordionDetails from "@mui/material/AccordionDetails"; 4 | import AccordionSummary from "@mui/material/AccordionSummary"; 5 | import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; 6 | import Radio from "@mui/material/Radio"; 7 | import RadioGroup from "@mui/material/RadioGroup"; 8 | import FormControlLabel from "@mui/material/FormControlLabel"; 9 | import MenuItem from "@mui/material/MenuItem"; 10 | import Select from "@mui/material/Select"; 11 | import Button from "@mui/material/Button"; 12 | import { checkCycle } from "./CheckCycle"; 13 | import { Molecule } from "./GraphADT"; 14 | import { addAtoms, addBonds, createAtomNode, getCoordinates } from "./Molecule"; 15 | 16 | export default function EditMolecule({ 17 | atomMenuItems, 18 | bondMenuItems, 19 | sampleMolecules, 20 | molecule, 21 | setMolecule, 22 | atomsList, 23 | setAtomsList, 24 | handleDataFromEditMolecule, 25 | atomCounters, 26 | handleMoleculeUpdate, 27 | setAtomCounters, 28 | }) { 29 | const [expandedAccordion, setExpandedAccordion] = useState("panel1"); 30 | const [atomData, setAtomData] = useState({ 31 | atomType: "C", 32 | hybridization: "sp", 33 | }); 34 | const [bondData, setBondData] = useState({ 35 | bondFrom: bondMenuItems[0].value, 36 | bondTo: bondMenuItems[1].value, 37 | bondType: "single", 38 | }); 39 | const [selectedMolecule, setSelectedMolecule] = useState(sampleMolecules[0]); 40 | 41 | const handleChangeAccordion = (panel) => (event, newExpanded) => { 42 | setExpandedAccordion(newExpanded ? panel : null); 43 | }; 44 | 45 | const handleAtomChange = (e) => { 46 | const { name, value } = e.target; 47 | setAtomData((prevData) => ({ ...prevData, [name]: value })); 48 | console.log("currently selected atom type: ", value); 49 | }; 50 | 51 | const handleBondChange = (e) => { 52 | const { name, value } = e.target; 53 | setBondData((prevData) => ({ ...prevData, [name]: value })); 54 | console.log("currently selected bond type: ", value); 55 | }; 56 | 57 | const handleClearMolecule = () => { 58 | // Create a new empty molecule 59 | const emptyMolecule = new Molecule(); 60 | 61 | // Reset molecule and related states 62 | setMolecule(emptyMolecule); 63 | setAtomsList([]); 64 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, P: 0, S: 0, F: 0, Cl: 0 }); 65 | 66 | // Reset form states to defaults 67 | setAtomData({ 68 | atomType: "C", 69 | hybridization: "sp", 70 | }); 71 | 72 | setBondData({ 73 | bondFrom: bondMenuItems[0]?.value || "", 74 | bondTo: bondMenuItems[1]?.value || "", 75 | bondType: "single", 76 | }); 77 | 78 | // Reset accordion expansion 79 | setExpandedAccordion("panel1"); 80 | 81 | // Reset selected molecule dropdown 82 | setSelectedMolecule(sampleMolecules[0]); 83 | 84 | // If there's a molecule update handler, call it 85 | if (handleMoleculeUpdate) { 86 | handleMoleculeUpdate(emptyMolecule); 87 | } 88 | 89 | // Trigger AI reset 90 | handleDataFromEditMolecule({ type: "clear" }); 91 | 92 | console.log("Molecule and all states cleared"); 93 | }; 94 | 95 | const handleShowMolecule = () => { 96 | switch (selectedMolecule) { 97 | case "CH4": 98 | const methane = new Molecule(); 99 | 100 | addAtoms(methane, createAtomNode("C0", "sp3", "C")); 101 | addAtoms(methane, createAtomNode("H0", "sp", "H")); 102 | addAtoms(methane, createAtomNode("H1", "sp", "H")); 103 | addAtoms(methane, createAtomNode("H2", "sp", "H")); 104 | addAtoms(methane, createAtomNode("H3", "sp", "H")); 105 | 106 | addBonds(methane, "C0", "H0", true, false, false); 107 | addBonds(methane, "C0", "H1", true, false, false); 108 | addBonds(methane, "C0", "H2", true, false, false); 109 | addBonds(methane, "C0", "H3", true, false, false); 110 | 111 | setAtomCounters({ C: 1, H: 4, O: 0, N: 0 }); 112 | getCoordinates(methane); 113 | setMolecule(methane); 114 | break; 115 | case "PCl5": 116 | const phosphorusPentachloride = new Molecule(); 117 | 118 | addAtoms(phosphorusPentachloride, createAtomNode("P0", "sp3d", "P")); 119 | addAtoms(phosphorusPentachloride, createAtomNode("Cl0", "sp", "Cl")); 120 | addAtoms(phosphorusPentachloride, createAtomNode("Cl1", "sp", "Cl")); 121 | addAtoms(phosphorusPentachloride, createAtomNode("Cl2", "sp", "Cl")); 122 | addAtoms(phosphorusPentachloride, createAtomNode("Cl3", "sp", "Cl")); 123 | addAtoms(phosphorusPentachloride, createAtomNode("Cl4", "sp", "Cl")); 124 | 125 | addBonds(phosphorusPentachloride, "P0", "Cl0", true, false, false); 126 | addBonds(phosphorusPentachloride, "P0", "Cl1", true, false, false); 127 | addBonds(phosphorusPentachloride, "P0", "Cl2", true, false, false); 128 | addBonds(phosphorusPentachloride, "P0", "Cl3", true, false, false); 129 | addBonds(phosphorusPentachloride, "P0", "Cl4", true, false, false); 130 | 131 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, P: 1, Cl: 5 }); 132 | getCoordinates(phosphorusPentachloride); 133 | setMolecule(phosphorusPentachloride); 134 | break; 135 | case "C2H6": 136 | const ethane = new Molecule(); 137 | 138 | addAtoms(ethane, createAtomNode("C0", "sp3", "C")); 139 | addAtoms(ethane, createAtomNode("C1", "sp3", "C")); 140 | addAtoms(ethane, createAtomNode("H0", "sp", "H")); 141 | addAtoms(ethane, createAtomNode("H1", "sp", "H")); 142 | addAtoms(ethane, createAtomNode("H2", "sp", "H")); 143 | addAtoms(ethane, createAtomNode("H3", "sp", "H")); 144 | addAtoms(ethane, createAtomNode("H4", "sp", "H")); 145 | addAtoms(ethane, createAtomNode("H5", "sp", "H")); 146 | 147 | addBonds(ethane, "C0", "H0", true, false, false); 148 | addBonds(ethane, "C0", "H1", true, false, false); 149 | addBonds(ethane, "C0", "H2", true, false, false); 150 | addBonds(ethane, "C0", "C1", true, false, false); 151 | addBonds(ethane, "C1", "H3", true, false, false); 152 | addBonds(ethane, "C1", "H4", true, false, false); 153 | addBonds(ethane, "C1", "H5", true, false, false); 154 | 155 | setAtomCounters({ C: 2, H: 6, O: 0, N: 0 }); 156 | getCoordinates(ethane); 157 | setMolecule(ethane); 158 | break; 159 | case "C6H6": 160 | const benzene = new Molecule(); 161 | addAtoms(benzene, createAtomNode("C0", "sp2", "C")); 162 | addAtoms(benzene, createAtomNode("C1", "sp2", "C")); 163 | addAtoms(benzene, createAtomNode("C2", "sp2", "C")); 164 | addAtoms(benzene, createAtomNode("C3", "sp2", "C")); 165 | addAtoms(benzene, createAtomNode("C4", "sp2", "C")); 166 | addAtoms(benzene, createAtomNode("C5", "sp2", "C")); 167 | addAtoms(benzene, createAtomNode("H0", "sp", "H")); 168 | addAtoms(benzene, createAtomNode("H1", "sp", "H")); 169 | addAtoms(benzene, createAtomNode("H2", "sp", "H")); 170 | addAtoms(benzene, createAtomNode("H3", "sp", "H")); 171 | addAtoms(benzene, createAtomNode("H4", "sp", "H")); 172 | addAtoms(benzene, createAtomNode("H5", "sp", "H")); 173 | 174 | addBonds(benzene, "C0", "H0", true, false, false); 175 | addBonds(benzene, "C0", "C1", true, false, false); 176 | addBonds(benzene, "C1", "H1", true, false, false); 177 | addBonds(benzene, "C1", "C2", false, true, false); 178 | addBonds(benzene, "C2", "H2", true, false, false); 179 | addBonds(benzene, "C2", "C3", true, false, false); 180 | addBonds(benzene, "C3", "H3", true, false, false); 181 | addBonds(benzene, "C3", "C4", false, true, false); 182 | addBonds(benzene, "C4", "H4", true, false, false); 183 | addBonds(benzene, "C4", "C5", true, false, false); 184 | addBonds(benzene, "C5", "H5", true, false, false); 185 | addBonds(benzene, "C5", "C0", false, true, false); 186 | 187 | setAtomCounters({ C: 6, H: 6, O: 0, N: 0 }); 188 | getCoordinates(benzene); 189 | setMolecule(benzene); 190 | break; 191 | case "H2O": 192 | const water = new Molecule(); 193 | 194 | addAtoms(water, createAtomNode("O0", "sp3", "O")); 195 | addAtoms(water, createAtomNode("H0", "sp", "H")); 196 | addAtoms(water, createAtomNode("H1", "sp", "H")); 197 | 198 | addBonds(water, "O0", "H0", true, false, false); 199 | addBonds(water, "O0", "H1", true, false, false); 200 | 201 | setAtomCounters({ C: 0, H: 2, O: 1, N: 0 }); 202 | getCoordinates(water); 203 | setMolecule(water); 204 | break; 205 | case "SF6": 206 | const sulfurHexaFluoride = new Molecule(); 207 | 208 | addAtoms(sulfurHexaFluoride, createAtomNode("S0", "sp3d2", "S")); 209 | addAtoms(sulfurHexaFluoride, createAtomNode("F0", "sp", "F")); 210 | addAtoms(sulfurHexaFluoride, createAtomNode("F1", "sp", "F")); 211 | addAtoms(sulfurHexaFluoride, createAtomNode("F2", "sp", "F")); 212 | addAtoms(sulfurHexaFluoride, createAtomNode("F3", "sp", "F")); 213 | addAtoms(sulfurHexaFluoride, createAtomNode("F4", "sp", "F")); 214 | addAtoms(sulfurHexaFluoride, createAtomNode("F5", "sp", "F")); 215 | 216 | addBonds(sulfurHexaFluoride, "S0", "F0", true, false, false); 217 | addBonds(sulfurHexaFluoride, "S0", "F1", true, false, false); 218 | addBonds(sulfurHexaFluoride, "S0", "F2", true, false, false); 219 | addBonds(sulfurHexaFluoride, "S0", "F3", true, false, false); 220 | addBonds(sulfurHexaFluoride, "S0", "F4", true, false, false); 221 | addBonds(sulfurHexaFluoride, "S0", "F5", true, false, false); 222 | 223 | setAtomCounters({ C: 0, H: 0, O: 0, N: 0, S: 1, F: 6 }); 224 | getCoordinates(sulfurHexaFluoride); 225 | setMolecule(sulfurHexaFluoride); 226 | break; 227 | default: 228 | break; 229 | } 230 | }; 231 | 232 | const handleSubmitAtom = (e) => { 233 | e.preventDefault(); 234 | const data = { 235 | type: "atom", 236 | atomName: atomData.atomType + atomsList.length, 237 | hybridisation: atomData.hybridization, 238 | atomSymbol: atomData.atomType, 239 | }; 240 | handleDataFromEditMolecule(data); 241 | }; 242 | 243 | const handleSubmitBond = (e) => { 244 | e.preventDefault(); 245 | let single, 246 | double, 247 | triple = false; 248 | if (bondData.bondType === "single") { 249 | single = true; 250 | double = false; 251 | triple = false; 252 | } 253 | if (bondData.bondType === "double") { 254 | single = false; 255 | double = true; 256 | triple = false; 257 | } 258 | if (bondData.bondType === "triple") { 259 | single = false; 260 | double = false; 261 | triple = true; 262 | } 263 | const data = { 264 | type: "bond", 265 | atom1Name: bondData.bondFrom, 266 | atom2Name: bondData.bondTo, 267 | isSingleBond: single, 268 | isDoubleBond: double, 269 | isTripleBond: triple, 270 | }; 271 | handleDataFromEditMolecule(data); 272 | }; 273 | 274 | return ( 275 |
276 |
277 | 283 | } 285 | aria-controls="panel1-content" 286 | id="panel1-header" 287 | > 288 | ADD ATOMS 289 | 290 | 291 |
295 | 301 | {atomMenuItems.map((item) => ( 302 | } 306 | label={item.label} 307 | /> 308 | ))} 309 | 310 |
311 |
Hybridization
312 | 318 | } label="sp" /> 319 | } label="sp2" /> 320 | } label="sp3" /> 321 | } 324 | label="sp3d" 325 | /> 326 | } 329 | label="sp3d2" 330 | /> 331 | 332 | 339 |
340 |
341 |
342 | 343 | 347 | } 349 | aria-controls="panel2-content" 350 | id="panel2-header" 351 | sx={{ backgroundColor: "#2ABD91" }} 352 | > 353 | ADD BONDS 354 | 355 | 356 |
360 |
361 | 373 |
------------------
374 | 386 |
387 |
Bond Type
388 | 394 | } 397 | label="single" 398 | /> 399 | } 402 | label="double" 403 | /> 404 | } 407 | label="triple" 408 | /> 409 | 410 | 417 |
418 |
419 |
420 | 421 | 426 | } 428 | aria-controls="panel3-content" 429 | id="panel3-header" 430 | > 431 | EXAMPLES 432 | 433 | 434 | 445 |
446 | 453 | 460 |
461 |
462 |
463 |
464 | Check Cycle: {checkCycle(molecule) ? "Cyclic" : "Acyclic"} 465 |
466 |
467 | 474 |
475 |
476 |
477 | ); 478 | } 479 | -------------------------------------------------------------------------------- /molecule-visualiser/src/components/Molecule.js: -------------------------------------------------------------------------------- 1 | import { Molecule, atomNode } from "./GraphADT.js"; 2 | 3 | export function createAtomNode(atomName, hybridisation, atomSymbol) { 4 | return new atomNode(atomName, hybridisation, atomSymbol); 5 | } 6 | 7 | export function addAtoms(molecule, atom) { 8 | molecule.addAtoms(atom); 9 | } 10 | 11 | export function addBonds(molecule, atom1Name, atom2Name, isSingleBond, isDoubleBond, isTripleBond) { 12 | const atom1 = molecule.atomList.find((atom) => atom.atomName === atom1Name); 13 | const atom2 = molecule.atomList.find((atom) => atom.atomName === atom2Name); 14 | 15 | if (atom1 && atom2) { 16 | molecule.addBond(atom1, atom2, isSingleBond, isDoubleBond, isTripleBond); 17 | } else { 18 | console.log("One or both atoms not found in molecule."); 19 | } 20 | } 21 | 22 | export function findCentralAtoms(molecule) { 23 | let centralAtoms = []; 24 | let maxHybridisation = ""; 25 | 26 | // Find the maximum hybridisation 27 | for (let atom of molecule.atomList) { 28 | if (atom.hybridisation > maxHybridisation) { 29 | maxHybridisation = atom.hybridisation; 30 | } 31 | } 32 | 33 | // Find atoms with maximum hybridisation 34 | for (let atom of molecule.atomList) { 35 | if (atom.hybridisation === maxHybridisation) { 36 | centralAtoms.push(atom); 37 | } 38 | } 39 | return centralAtoms; 40 | } 41 | 42 | // Calculate Coordiantes of the atoms in 3D Plane. 43 | export function getCoordinates(molecule) { 44 | molecule.atomList.forEach(atom => { 45 | atom.bondsAssignedCount = 0; 46 | }); 47 | 48 | // Constant Angles defined for respective hybridisations. 49 | const angles = { 50 | sp: { angleX: Math.PI, angleY: 0, angleZ: 0 }, 51 | sp2: { angleX: -Math.PI / 6, angleY: Math.PI / 3, angleZ: 0 }, 52 | sp3: { 53 | angleX: 0.615 * Math.PI, 54 | angleY: 0.955 * Math.PI, 55 | angleZ: 0.615 * Math.PI, 56 | }, 57 | sp3d: { 58 | angleX: Math.PI / 2, 59 | angleY: Math.PI / 2, 60 | angleZ: Math.PI / 2, 61 | }, 62 | sp3d2: { 63 | angleX: Math.PI / 2, 64 | angleY: Math.PI / 2, 65 | angleZ: Math.PI / 2 66 | } 67 | }; 68 | 69 | // Get Central Atoms of the Molecule. 70 | const centralAtoms = findCentralAtoms(molecule); 71 | let visited = []; 72 | let centralVisited = []; 73 | for (let atom of centralAtoms) { 74 | centralVisited[atom] = false; 75 | } 76 | let queue = []; 77 | let initialAtom = centralAtoms[0]; 78 | queue.push([initialAtom, null]); 79 | visited.push(initialAtom); 80 | let directionVectorStack = []; 81 | 82 | while (queue.length) { 83 | let [currentAtom, parentAtom] = queue.shift(); 84 | let neighbours = molecule.getNeighbours(currentAtom); 85 | if (currentAtom) { 86 | if (parentAtom === null) { 87 | // Set coordinates for the first atom 88 | currentAtom.coordinates = [0, 0, 0]; 89 | directionVectorStack.push([1, 0, 0]); 90 | } else { 91 | let initalDirection = directionVectorStack[directionVectorStack.length - 1]; 92 | console.log("Initial Direction: ", initalDirection); 93 | let parentCoordinates = parentAtom.coordinates; 94 | let angleX, angleY, angleZ; 95 | let bondlength = 1; 96 | let newDirection = []; 97 | 98 | angleX = angles[parentAtom.hybridisation].angleX; 99 | angleY = angles[parentAtom.hybridisation].angleY; 100 | angleZ = angles[parentAtom.hybridisation].angleZ; 101 | 102 | if (parentAtom.hybridisation === "sp") { 103 | newDirection = [ 104 | -initalDirection[0], 105 | initalDirection[1], 106 | initalDirection[2], 107 | ]; 108 | directionVectorStack.push(newDirection); 109 | 110 | let newCoordinates = [ 111 | parentCoordinates[0] + bondlength * newDirection[0], 112 | parentCoordinates[1] + bondlength * newDirection[1], 113 | parentCoordinates[2] + bondlength * newDirection[2], 114 | ]; 115 | currentAtom.coordinates = newCoordinates; 116 | console.log("New Direction for atom: ", currentAtom, newDirection); 117 | } else if (parentAtom.hybridisation === "sp3d") { 118 | const allNeighbors = molecule.getNeighbours(parentAtom); 119 | const connectionIndex = allNeighbors.findIndex( 120 | atom => atom.atomName === currentAtom.atomName 121 | ); 122 | 123 | if (connectionIndex !== -1) { 124 | const newCoordinates = calculateSp3dCoordinates( 125 | parentCoordinates, 126 | connectionIndex, 127 | allNeighbors.length, 128 | 1 129 | ); 130 | 131 | currentAtom.coordinates = newCoordinates; 132 | 133 | const newDirection = [ 134 | newCoordinates[0] - parentCoordinates[0], 135 | newCoordinates[1] - parentCoordinates[1], 136 | newCoordinates[2] - parentCoordinates[2] 137 | ]; 138 | 139 | const magnitude = Math.sqrt( 140 | newDirection[0] * newDirection[0] + 141 | newDirection[1] * newDirection[1] + 142 | newDirection[2] * newDirection[2] 143 | ); 144 | 145 | if (magnitude > 0) { 146 | newDirection[0] /= magnitude; 147 | newDirection[1] /= magnitude; 148 | newDirection[2] /= magnitude; 149 | } 150 | 151 | directionVectorStack.push(newDirection); 152 | } 153 | } else if (parentAtom.hybridisation === "sp3d2") { 154 | const octahedralCoords = generateOctahedralCoordinates(parentCoordinates); 155 | 156 | if (typeof parentAtom.bondsAssignedCount === "undefined") { 157 | parentAtom.bondsAssignedCount = 0; 158 | } 159 | 160 | let positionIndex = parentAtom.bondsAssignedCount; 161 | if (positionIndex >= octahedralCoords.length) { 162 | positionIndex = octahedralCoords.length - 1; 163 | } 164 | 165 | parentAtom.bondsAssignedCount += 1; 166 | currentAtom.coordinates = octahedralCoords[positionIndex]; 167 | 168 | newDirection = [ 169 | octahedralCoords[positionIndex][0] - parentCoordinates[0], 170 | octahedralCoords[positionIndex][1] - parentCoordinates[1], 171 | octahedralCoords[positionIndex][2] - parentCoordinates[2], 172 | ]; 173 | 174 | directionVectorStack.push(newDirection); 175 | } else { 176 | if ( 177 | (initalDirection[0] === 1 && 178 | initalDirection[1] === 0 && 179 | initalDirection[2] === 0) || 180 | directionVectorStack.length === 1 181 | ) { 182 | newDirection = [ 183 | initalDirection[0] * (Math.cos(angleY) * Math.cos(angleZ)) + 184 | initalDirection[1] * (Math.cos(angleY) * Math.sin(angleZ)) - 185 | initalDirection[2] * Math.sin(angleY), 186 | initalDirection[0] * 187 | (-Math.cos(angleX) * Math.sin(angleZ) + 188 | Math.sin(angleX) * Math.sin(angleY) * Math.cos(angleZ)) + 189 | initalDirection[1] * 190 | (Math.cos(angleX) * Math.cos(angleZ) + 191 | Math.sin(angleX) * Math.sin(angleY) * Math.sin(angleZ)) + 192 | initalDirection[2] * (Math.sin(angleX) * Math.cos(angleY)), 193 | initalDirection[0] * 194 | (Math.sin(angleX) * Math.sin(angleZ) + 195 | Math.cos(angleX) * Math.sin(angleY) * Math.cos(angleZ)) + 196 | initalDirection[1] * 197 | (-Math.sin(angleX) * Math.cos(angleZ) + 198 | Math.cos(angleX) * Math.sin(angleY) * Math.sin(angleZ)) + 199 | initalDirection[2] * (Math.cos(angleX) * Math.cos(angleY)), 200 | ]; 201 | 202 | if ( 203 | parentAtom.hybridisation === "sp2" && 204 | currentAtom.atomSymbol === "C" 205 | ) { 206 | newDirection = rotateVectorAroundXYPlane(initalDirection, -120); 207 | } else if ( 208 | parentAtom.hybridisation === "sp2" && 209 | currentAtom.atomSymbol === "H" 210 | ) { 211 | newDirection = rotateVectorAroundXYPlane(initalDirection, 120); 212 | } 213 | if ( 214 | initalDirection[0] === 1 && 215 | initalDirection[1] === 0 && 216 | initalDirection[2] === 0 217 | ) 218 | directionVectorStack.pop(); 219 | directionVectorStack.push(newDirection); 220 | 221 | let newCoordinates = [ 222 | parentCoordinates[0] + bondlength * newDirection[0], 223 | parentCoordinates[1] + bondlength * newDirection[1], 224 | parentCoordinates[2] + bondlength * newDirection[2], 225 | ]; 226 | currentAtom.coordinates = newCoordinates; 227 | console.log("New Direction for atom: ", currentAtom, newDirection); 228 | } else if (directionVectorStack.length === 2) { 229 | console.log("Came Here for Atom: ", currentAtom); 230 | let firstCoordinate = 231 | directionVectorStack[directionVectorStack.length - 1]; 232 | let secondCoordinate = 233 | directionVectorStack[directionVectorStack.length - 2]; 234 | 235 | console.log("First Coordinate:", firstCoordinate); 236 | console.log("Second Coordinate:", secondCoordinate); 237 | 238 | if (parentAtom.hybridisation === "sp2") 239 | newDirection = calculateVector120DegreesOnPlane( 240 | firstCoordinate, 241 | secondCoordinate, 242 | 120 243 | ); 244 | if (parentAtom.hybridisation === "sp3") 245 | newDirection = findThirdCoordinate( 246 | firstCoordinate, 247 | secondCoordinate 248 | ); 249 | directionVectorStack.push(newDirection); 250 | 251 | console.log("New Direction:", newDirection); 252 | 253 | // Calculate new coordinates 254 | let newCoordinates = [ 255 | parentCoordinates[0] + bondlength * newDirection[0], 256 | parentCoordinates[1] + bondlength * newDirection[1], 257 | parentCoordinates[2] + bondlength * newDirection[2], 258 | ]; 259 | console.log("New Coordinates:", newCoordinates); 260 | currentAtom.coordinates = newCoordinates; 261 | } else if (directionVectorStack.length === 3) { 262 | console.log("Came to 3 for atom: ", currentAtom); 263 | let firstCoordinate = 264 | directionVectorStack[directionVectorStack.length - 1]; 265 | let secondCoordinate = 266 | directionVectorStack[directionVectorStack.length - 2]; 267 | let thirdCoordinate = 268 | directionVectorStack[directionVectorStack.length - 3]; 269 | 270 | newDirection = findFourthCoordinate( 271 | firstCoordinate, 272 | secondCoordinate, 273 | thirdCoordinate 274 | ); 275 | directionVectorStack.push(newDirection); 276 | 277 | console.log("New Direction: ", newDirection); 278 | 279 | let newCoordinates = [ 280 | parentCoordinates[0] + bondlength * newDirection[0], 281 | parentCoordinates[1] + bondlength * newDirection[1], 282 | parentCoordinates[2] + bondlength * newDirection[2], 283 | ]; 284 | console.log("New Coordinates:", newCoordinates); 285 | currentAtom.coordinates = newCoordinates; 286 | } 287 | } 288 | } 289 | 290 | // Pushes all neighbours of the currentatom to the queue. 291 | let pushedCentral = false; 292 | for (let neighbour of neighbours) { 293 | if (!visited.includes(neighbour)) { 294 | if (centralAtoms.includes(neighbour)) { 295 | if (!pushedCentral) { 296 | queue.push([neighbour, currentAtom]); 297 | visited.push(neighbour); 298 | } 299 | } else { 300 | queue.push([neighbour, currentAtom]); 301 | visited.push(neighbour); 302 | } 303 | if (centralAtoms.includes(neighbour)) pushedCentral = true; 304 | } 305 | } 306 | 307 | // Alkene Part - Yet has some logic to be implemented 308 | if ( 309 | !centralAtoms.includes(currentAtom) && 310 | currentAtom.atomSymbol !== "H" 311 | ) { 312 | if ( 313 | checkCentralVisited(centralVisited, centralAtoms) && 314 | checkVisited(visited, molecule) 315 | ) { 316 | let secondMaxHybrid = ""; 317 | for (let atom of molecule.atomList) { 318 | if ( 319 | atom.hybridisation > secondMaxHybrid && 320 | atom.hybridisation < centralAtoms[0].hybridisation 321 | ) { 322 | secondMaxHybrid = atom.hybridisation; 323 | } 324 | } 325 | console.log("New MAX Hybridisation: ", secondMaxHybrid); 326 | for (let atom of molecule.atomList) { 327 | if (atom.hybridisation === secondMaxHybrid) { 328 | centralAtoms.push(atom); 329 | } 330 | } 331 | } 332 | } 333 | 334 | // Updating the stack to be empty if current atom is a central atom 335 | if (centralAtoms.includes(currentAtom) && parentAtom !== null) { 336 | centralVisited[currentAtom] = true; 337 | let temp = directionVectorStack[directionVectorStack.length - 1]; 338 | directionVectorStack = []; 339 | directionVectorStack.push(temp); 340 | } 341 | 342 | // Setting the connections property for the current atom 343 | currentAtom.connections = neighbours; 344 | } 345 | console.log("New Central Atoms: ", centralAtoms); 346 | } 347 | } 348 | 349 | function isAxialPosition(index, totalConnections) { 350 | return index === 0 || index === totalConnections - 1; 351 | } 352 | 353 | function calculateSp3dCoordinates(parentCoordinates, connectionIndex, totalConnections, bondLength = 1) { 354 | if (isAxialPosition(connectionIndex, totalConnections)) { 355 | const zDirection = connectionIndex === 0 ? 1 : -1; 356 | return [ 357 | parentCoordinates[0], 358 | parentCoordinates[1], 359 | parentCoordinates[2] + (zDirection * bondLength) 360 | ]; 361 | } else { 362 | const equatorialIndex = connectionIndex - 1; 363 | const angle = (2 * Math.PI / 3) * equatorialIndex; 364 | 365 | return [ 366 | parentCoordinates[0] + bondLength * Math.cos(angle), 367 | parentCoordinates[1] + bondLength * Math.sin(angle), 368 | parentCoordinates[2] 369 | ]; 370 | } 371 | } 372 | 373 | function generateOctahedralCoordinates(parentCoordinates, bondLength = 1) { 374 | const directions = [ 375 | [1, 0, 0], 376 | [-1, 0, 0], 377 | [0, 1, 0], 378 | [0, -1, 0], 379 | [0, 0, 1], 380 | [0, 0, -1] 381 | ]; 382 | 383 | return directions.map(dir => [ 384 | parentCoordinates[0] + bondLength * dir[0], 385 | parentCoordinates[1] + bondLength * dir[1], 386 | parentCoordinates[2] + bondLength * dir[2] 387 | ]); 388 | } 389 | 390 | 391 | // Returns true if central atoms list doesnt have any atom yet to be visited. 392 | function checkCentralVisited(centralVisited, centralAtoms) { 393 | for (let atom of centralAtoms) { 394 | if (!centralVisited.includes(atom)) return true; 395 | } 396 | return false; 397 | } 398 | 399 | // Returns true if any of the atom is still yet to be visited. 400 | function checkVisited(visited, molecule) { 401 | for (let atom of molecule.atomList) { 402 | if (!visited.includes(atom)) return true; 403 | } 404 | return false; 405 | } 406 | 407 | export function drawMolecule(molecule) {} 408 | 409 | // Helper function to calculate the vector sum 410 | function vectorSum(v1, v2) { 411 | return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]]; 412 | } 413 | 414 | // Helper function to calculate the magnitude of a vector 415 | function magnitude(v) { 416 | return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); 417 | } 418 | 419 | // Helper function to normalize a vector 420 | function normalize(v) { 421 | const mag = magnitude(v); 422 | if (mag === 0) { 423 | return [0, 0, 0]; 424 | } 425 | return [v[0] / mag, v[1] / mag, v[2] / mag]; 426 | } 427 | 428 | // Helper function to calculate the cross product of two vectors 429 | function crossProduct(v1, v2) { 430 | return [ 431 | v1[1] * v2[2] - v1[2] * v2[1], 432 | v1[2] * v2[0] - v1[0] * v2[2], 433 | v1[0] * v2[1] - v1[1] * v2[0], 434 | ]; 435 | } 436 | 437 | // Helper function to calculate the vector that is 120 degrees from the two given vectors and lies on the same plane 438 | function calculateVector120DegreesOnPlane( 439 | firstCoordinate, 440 | secondCoordinate, 441 | a 442 | ) { 443 | const normal = crossProduct(firstCoordinate, secondCoordinate); // Calculate the normal vector to the plane formed by the two input vectors 444 | const normalizedNormal = normalize(normal); // Normalize the normal vector 445 | const projectedFirst = [ 446 | firstCoordinate[0] - firstCoordinate[0] * normalizedNormal[0], 447 | firstCoordinate[1] - firstCoordinate[1] * normalizedNormal[1], 448 | firstCoordinate[2] - firstCoordinate[2] * normalizedNormal[2], 449 | ]; // Project the first vector onto the plane 450 | const projectedSecond = [ 451 | secondCoordinate[0] - secondCoordinate[0] * normalizedNormal[0], 452 | secondCoordinate[1] - secondCoordinate[1] * normalizedNormal[1], 453 | secondCoordinate[2] - secondCoordinate[2] * normalizedNormal[2], 454 | ]; // Project the second vector onto the plane 455 | const sumVector = vectorSum(projectedFirst, projectedSecond); // Calculate the vector sum of the projected vectors 456 | const normalizedSumVector = normalize(sumVector); // Normalize the vector sum 457 | const angle = (a * Math.PI) / 180; // Convert 120 degrees to radians 458 | const rotationMatrix = [ 459 | [Math.cos(angle), 0, Math.sin(angle)], 460 | [0, 1, 0], 461 | [-Math.sin(angle), 0, Math.cos(angle)], 462 | ]; // Create a rotation matrix for rotating around the Z-axis by 120 degrees 463 | const vector120Degrees = [ 464 | rotationMatrix[0][0] * normalizedSumVector[0] + 465 | rotationMatrix[0][1] * normalizedSumVector[1] + 466 | rotationMatrix[0][2] * normalizedSumVector[2], 467 | rotationMatrix[1][0] * normalizedSumVector[0] + 468 | rotationMatrix[1][1] * normalizedSumVector[1] + 469 | rotationMatrix[1][2] * normalizedSumVector[2], 470 | rotationMatrix[2][0] * normalizedSumVector[0] + 471 | rotationMatrix[2][1] * normalizedSumVector[1] + 472 | rotationMatrix[2][2] * normalizedSumVector[2], 473 | ]; // Rotate the normalized vector sum by 120 degrees using the rotation matrix 474 | return vector120Degrees; 475 | } 476 | 477 | function findThirdCoordinate(v1, v2) { 478 | const norm1 = normalize(v1); 479 | const norm2 = normalize(v2); 480 | 481 | const perpendicular = normalize(crossProduct(norm1, norm2)); 482 | const tetrahedralAngle = 109.5 * (Math.PI / 180); 483 | 484 | const dotProduct = norm1[0] * norm2[0] + norm1[1] * norm2[1] + norm1[2] * norm2[2]; 485 | 486 | const existingAngle = Math.acos(dotProduct); 487 | const rotationAngle = (tetrahedralAngle - existingAngle) / 2; 488 | const rotationAxis = perpendicular; 489 | 490 | const c = Math.cos(rotationAngle); 491 | const s = Math.sin(rotationAngle); 492 | const t = 1 - c; 493 | const x = rotationAxis[0]; 494 | const y = rotationAxis[1]; 495 | const z = rotationAxis[2]; 496 | 497 | const rotatedVector = [ 498 | (t * x * x + c) * norm1[0] + (t * x * y - s * z) * norm1[1] + (t * x * z + s * y) * norm1[2], 499 | (t * x * y + s * z) * norm1[0] + (t * y * y + c) * norm1[1] + (t * y * z - s * x) * norm1[2], 500 | (t * x * z - s * y) * norm1[0] + (t * y * z + s * x) * norm1[1] + (t * z * z + c) * norm1[2] 501 | ]; 502 | 503 | const thirdVector = normalize([ 504 | rotatedVector[0] + perpendicular[0] * Math.sin(tetrahedralAngle), 505 | rotatedVector[1] + perpendicular[1] * Math.sin(tetrahedralAngle), 506 | rotatedVector[2] + perpendicular[2] * Math.sin(tetrahedralAngle) 507 | ]); 508 | 509 | return thirdVector; 510 | } 511 | 512 | function findFourthCoordinate(v1, v2, v3) { 513 | const norm1 = normalize(v1); 514 | const norm2 = normalize(v2); 515 | const norm3 = normalize(v3); 516 | 517 | const sum = [ 518 | norm1[0] + norm2[0] + norm3[0], 519 | norm1[1] + norm2[1] + norm3[1], 520 | norm1[2] + norm2[2] + norm3[2] 521 | ]; 522 | 523 | const fourthVector = normalize([-sum[0], -sum[1], -sum[2]]); 524 | 525 | return fourthVector; 526 | } 527 | 528 | function rotateVectorAroundXYPlane(vector, angleInDegrees) { 529 | const [x, y, z] = vector; 530 | const angleInRadians = (angleInDegrees * Math.PI) / 180; 531 | const rotatedX = x * Math.cos(angleInRadians) - z * Math.sin(angleInRadians); 532 | const rotatedY = y; 533 | const rotatedZ = x * Math.sin(angleInRadians) + z * Math.cos(angleInRadians); 534 | return [rotatedX, rotatedY, rotatedZ]; 535 | } 536 | 537 | function CalcThirdCoordinate(coord1, coord2, angle1, angle2) { 538 | // Convert angles to radians 539 | angle1 = (angle1 * Math.PI) / 180; 540 | angle2 = (angle2 * Math.PI) / 180; 541 | 542 | // Calculate the direction vectors 543 | const dir1 = [Math.cos(angle1), Math.sin(angle1), 0]; 544 | const dir2 = [Math.cos(angle2), Math.sin(angle2), 0]; 545 | 546 | // Calculate the coefficients for the system of linear equations 547 | const a11 = dir1[0]; 548 | const a12 = dir2[0]; 549 | const a21 = dir1[1]; 550 | const a22 = dir2[1]; 551 | const b1 = coord1[0] - coord2[0]; 552 | const b2 = coord1[1] - coord2[1]; 553 | 554 | // Solve the system of linear equations 555 | const det = a11 * a22 - a12 * a21; 556 | const x = (b1 * a22 - b2 * a12) / det; 557 | const y = (a11 * b2 - a21 * b1) / det; 558 | 559 | // Return the third coordinate 560 | return [x + coord2[0], y + coord2[1], coord2[2]]; 561 | } 562 | -------------------------------------------------------------------------------- /Backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "backend", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@google/generative-ai": "^0.11.3", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.4.5", 15 | "express": "^4.19.2", 16 | "nodemon": "^3.1.0" 17 | } 18 | }, 19 | "node_modules/@google/generative-ai": { 20 | "version": "0.11.3", 21 | "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.11.3.tgz", 22 | "integrity": "sha512-QtQ1hz6rcybbw35uxXlFF26KNnaTVr2oWwnmDkC1M35KdzN4tVc4wakgJp8uXbY9KDCNHksyp11DbFg0HPckZQ==", 23 | "engines": { 24 | "node": ">=18.0.0" 25 | } 26 | }, 27 | "node_modules/accepts": { 28 | "version": "1.3.8", 29 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 30 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 31 | "dependencies": { 32 | "mime-types": "~2.1.34", 33 | "negotiator": "0.6.3" 34 | }, 35 | "engines": { 36 | "node": ">= 0.6" 37 | } 38 | }, 39 | "node_modules/anymatch": { 40 | "version": "3.1.3", 41 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 42 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 43 | "dependencies": { 44 | "normalize-path": "^3.0.0", 45 | "picomatch": "^2.0.4" 46 | }, 47 | "engines": { 48 | "node": ">= 8" 49 | } 50 | }, 51 | "node_modules/array-flatten": { 52 | "version": "1.1.1", 53 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 54 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 55 | }, 56 | "node_modules/balanced-match": { 57 | "version": "1.0.2", 58 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 59 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 60 | }, 61 | "node_modules/binary-extensions": { 62 | "version": "2.3.0", 63 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 64 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 65 | "engines": { 66 | "node": ">=8" 67 | }, 68 | "funding": { 69 | "url": "https://github.com/sponsors/sindresorhus" 70 | } 71 | }, 72 | "node_modules/body-parser": { 73 | "version": "1.20.2", 74 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", 75 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", 76 | "dependencies": { 77 | "bytes": "3.1.2", 78 | "content-type": "~1.0.5", 79 | "debug": "2.6.9", 80 | "depd": "2.0.0", 81 | "destroy": "1.2.0", 82 | "http-errors": "2.0.0", 83 | "iconv-lite": "0.4.24", 84 | "on-finished": "2.4.1", 85 | "qs": "6.11.0", 86 | "raw-body": "2.5.2", 87 | "type-is": "~1.6.18", 88 | "unpipe": "1.0.0" 89 | }, 90 | "engines": { 91 | "node": ">= 0.8", 92 | "npm": "1.2.8000 || >= 1.4.16" 93 | } 94 | }, 95 | "node_modules/body-parser/node_modules/debug": { 96 | "version": "2.6.9", 97 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 98 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 99 | "dependencies": { 100 | "ms": "2.0.0" 101 | } 102 | }, 103 | "node_modules/body-parser/node_modules/ms": { 104 | "version": "2.0.0", 105 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 106 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 107 | }, 108 | "node_modules/brace-expansion": { 109 | "version": "1.1.11", 110 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 111 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 112 | "dependencies": { 113 | "balanced-match": "^1.0.0", 114 | "concat-map": "0.0.1" 115 | } 116 | }, 117 | "node_modules/braces": { 118 | "version": "3.0.2", 119 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 120 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 121 | "dependencies": { 122 | "fill-range": "^7.0.1" 123 | }, 124 | "engines": { 125 | "node": ">=8" 126 | } 127 | }, 128 | "node_modules/bytes": { 129 | "version": "3.1.2", 130 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 131 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 132 | "engines": { 133 | "node": ">= 0.8" 134 | } 135 | }, 136 | "node_modules/call-bind": { 137 | "version": "1.0.7", 138 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 139 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 140 | "dependencies": { 141 | "es-define-property": "^1.0.0", 142 | "es-errors": "^1.3.0", 143 | "function-bind": "^1.1.2", 144 | "get-intrinsic": "^1.2.4", 145 | "set-function-length": "^1.2.1" 146 | }, 147 | "engines": { 148 | "node": ">= 0.4" 149 | }, 150 | "funding": { 151 | "url": "https://github.com/sponsors/ljharb" 152 | } 153 | }, 154 | "node_modules/chokidar": { 155 | "version": "3.6.0", 156 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 157 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 158 | "dependencies": { 159 | "anymatch": "~3.1.2", 160 | "braces": "~3.0.2", 161 | "glob-parent": "~5.1.2", 162 | "is-binary-path": "~2.1.0", 163 | "is-glob": "~4.0.1", 164 | "normalize-path": "~3.0.0", 165 | "readdirp": "~3.6.0" 166 | }, 167 | "engines": { 168 | "node": ">= 8.10.0" 169 | }, 170 | "funding": { 171 | "url": "https://paulmillr.com/funding/" 172 | }, 173 | "optionalDependencies": { 174 | "fsevents": "~2.3.2" 175 | } 176 | }, 177 | "node_modules/concat-map": { 178 | "version": "0.0.1", 179 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 180 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 181 | }, 182 | "node_modules/content-disposition": { 183 | "version": "0.5.4", 184 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 185 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 186 | "dependencies": { 187 | "safe-buffer": "5.2.1" 188 | }, 189 | "engines": { 190 | "node": ">= 0.6" 191 | } 192 | }, 193 | "node_modules/content-type": { 194 | "version": "1.0.5", 195 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 196 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 197 | "engines": { 198 | "node": ">= 0.6" 199 | } 200 | }, 201 | "node_modules/cookie": { 202 | "version": "0.6.0", 203 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 204 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 205 | "engines": { 206 | "node": ">= 0.6" 207 | } 208 | }, 209 | "node_modules/cookie-signature": { 210 | "version": "1.0.6", 211 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 212 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 213 | }, 214 | "node_modules/cors": { 215 | "version": "2.8.5", 216 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 217 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 218 | "dependencies": { 219 | "object-assign": "^4", 220 | "vary": "^1" 221 | }, 222 | "engines": { 223 | "node": ">= 0.10" 224 | } 225 | }, 226 | "node_modules/debug": { 227 | "version": "4.3.4", 228 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 229 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 230 | "dependencies": { 231 | "ms": "2.1.2" 232 | }, 233 | "engines": { 234 | "node": ">=6.0" 235 | }, 236 | "peerDependenciesMeta": { 237 | "supports-color": { 238 | "optional": true 239 | } 240 | } 241 | }, 242 | "node_modules/define-data-property": { 243 | "version": "1.1.4", 244 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 245 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 246 | "dependencies": { 247 | "es-define-property": "^1.0.0", 248 | "es-errors": "^1.3.0", 249 | "gopd": "^1.0.1" 250 | }, 251 | "engines": { 252 | "node": ">= 0.4" 253 | }, 254 | "funding": { 255 | "url": "https://github.com/sponsors/ljharb" 256 | } 257 | }, 258 | "node_modules/depd": { 259 | "version": "2.0.0", 260 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 261 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 262 | "engines": { 263 | "node": ">= 0.8" 264 | } 265 | }, 266 | "node_modules/destroy": { 267 | "version": "1.2.0", 268 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 269 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 270 | "engines": { 271 | "node": ">= 0.8", 272 | "npm": "1.2.8000 || >= 1.4.16" 273 | } 274 | }, 275 | "node_modules/dotenv": { 276 | "version": "16.4.5", 277 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", 278 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", 279 | "engines": { 280 | "node": ">=12" 281 | }, 282 | "funding": { 283 | "url": "https://dotenvx.com" 284 | } 285 | }, 286 | "node_modules/ee-first": { 287 | "version": "1.1.1", 288 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 289 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 290 | }, 291 | "node_modules/encodeurl": { 292 | "version": "1.0.2", 293 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 294 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 295 | "engines": { 296 | "node": ">= 0.8" 297 | } 298 | }, 299 | "node_modules/es-define-property": { 300 | "version": "1.0.0", 301 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 302 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 303 | "dependencies": { 304 | "get-intrinsic": "^1.2.4" 305 | }, 306 | "engines": { 307 | "node": ">= 0.4" 308 | } 309 | }, 310 | "node_modules/es-errors": { 311 | "version": "1.3.0", 312 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 313 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 314 | "engines": { 315 | "node": ">= 0.4" 316 | } 317 | }, 318 | "node_modules/escape-html": { 319 | "version": "1.0.3", 320 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 321 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 322 | }, 323 | "node_modules/etag": { 324 | "version": "1.8.1", 325 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 326 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 327 | "engines": { 328 | "node": ">= 0.6" 329 | } 330 | }, 331 | "node_modules/express": { 332 | "version": "4.19.2", 333 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", 334 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", 335 | "dependencies": { 336 | "accepts": "~1.3.8", 337 | "array-flatten": "1.1.1", 338 | "body-parser": "1.20.2", 339 | "content-disposition": "0.5.4", 340 | "content-type": "~1.0.4", 341 | "cookie": "0.6.0", 342 | "cookie-signature": "1.0.6", 343 | "debug": "2.6.9", 344 | "depd": "2.0.0", 345 | "encodeurl": "~1.0.2", 346 | "escape-html": "~1.0.3", 347 | "etag": "~1.8.1", 348 | "finalhandler": "1.2.0", 349 | "fresh": "0.5.2", 350 | "http-errors": "2.0.0", 351 | "merge-descriptors": "1.0.1", 352 | "methods": "~1.1.2", 353 | "on-finished": "2.4.1", 354 | "parseurl": "~1.3.3", 355 | "path-to-regexp": "0.1.7", 356 | "proxy-addr": "~2.0.7", 357 | "qs": "6.11.0", 358 | "range-parser": "~1.2.1", 359 | "safe-buffer": "5.2.1", 360 | "send": "0.18.0", 361 | "serve-static": "1.15.0", 362 | "setprototypeof": "1.2.0", 363 | "statuses": "2.0.1", 364 | "type-is": "~1.6.18", 365 | "utils-merge": "1.0.1", 366 | "vary": "~1.1.2" 367 | }, 368 | "engines": { 369 | "node": ">= 0.10.0" 370 | } 371 | }, 372 | "node_modules/express/node_modules/debug": { 373 | "version": "2.6.9", 374 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 375 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 376 | "dependencies": { 377 | "ms": "2.0.0" 378 | } 379 | }, 380 | "node_modules/express/node_modules/ms": { 381 | "version": "2.0.0", 382 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 383 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 384 | }, 385 | "node_modules/fill-range": { 386 | "version": "7.0.1", 387 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 388 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 389 | "dependencies": { 390 | "to-regex-range": "^5.0.1" 391 | }, 392 | "engines": { 393 | "node": ">=8" 394 | } 395 | }, 396 | "node_modules/finalhandler": { 397 | "version": "1.2.0", 398 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 399 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 400 | "dependencies": { 401 | "debug": "2.6.9", 402 | "encodeurl": "~1.0.2", 403 | "escape-html": "~1.0.3", 404 | "on-finished": "2.4.1", 405 | "parseurl": "~1.3.3", 406 | "statuses": "2.0.1", 407 | "unpipe": "~1.0.0" 408 | }, 409 | "engines": { 410 | "node": ">= 0.8" 411 | } 412 | }, 413 | "node_modules/finalhandler/node_modules/debug": { 414 | "version": "2.6.9", 415 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 416 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 417 | "dependencies": { 418 | "ms": "2.0.0" 419 | } 420 | }, 421 | "node_modules/finalhandler/node_modules/ms": { 422 | "version": "2.0.0", 423 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 424 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 425 | }, 426 | "node_modules/forwarded": { 427 | "version": "0.2.0", 428 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 429 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 430 | "engines": { 431 | "node": ">= 0.6" 432 | } 433 | }, 434 | "node_modules/fresh": { 435 | "version": "0.5.2", 436 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 437 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 438 | "engines": { 439 | "node": ">= 0.6" 440 | } 441 | }, 442 | "node_modules/fsevents": { 443 | "version": "2.3.3", 444 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 445 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 446 | "hasInstallScript": true, 447 | "optional": true, 448 | "os": [ 449 | "darwin" 450 | ], 451 | "engines": { 452 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 453 | } 454 | }, 455 | "node_modules/function-bind": { 456 | "version": "1.1.2", 457 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 458 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 459 | "funding": { 460 | "url": "https://github.com/sponsors/ljharb" 461 | } 462 | }, 463 | "node_modules/get-intrinsic": { 464 | "version": "1.2.4", 465 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 466 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 467 | "dependencies": { 468 | "es-errors": "^1.3.0", 469 | "function-bind": "^1.1.2", 470 | "has-proto": "^1.0.1", 471 | "has-symbols": "^1.0.3", 472 | "hasown": "^2.0.0" 473 | }, 474 | "engines": { 475 | "node": ">= 0.4" 476 | }, 477 | "funding": { 478 | "url": "https://github.com/sponsors/ljharb" 479 | } 480 | }, 481 | "node_modules/glob-parent": { 482 | "version": "5.1.2", 483 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 484 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 485 | "dependencies": { 486 | "is-glob": "^4.0.1" 487 | }, 488 | "engines": { 489 | "node": ">= 6" 490 | } 491 | }, 492 | "node_modules/gopd": { 493 | "version": "1.0.1", 494 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 495 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 496 | "dependencies": { 497 | "get-intrinsic": "^1.1.3" 498 | }, 499 | "funding": { 500 | "url": "https://github.com/sponsors/ljharb" 501 | } 502 | }, 503 | "node_modules/has-flag": { 504 | "version": "3.0.0", 505 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 506 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 507 | "engines": { 508 | "node": ">=4" 509 | } 510 | }, 511 | "node_modules/has-property-descriptors": { 512 | "version": "1.0.2", 513 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 514 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 515 | "dependencies": { 516 | "es-define-property": "^1.0.0" 517 | }, 518 | "funding": { 519 | "url": "https://github.com/sponsors/ljharb" 520 | } 521 | }, 522 | "node_modules/has-proto": { 523 | "version": "1.0.3", 524 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 525 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 526 | "engines": { 527 | "node": ">= 0.4" 528 | }, 529 | "funding": { 530 | "url": "https://github.com/sponsors/ljharb" 531 | } 532 | }, 533 | "node_modules/has-symbols": { 534 | "version": "1.0.3", 535 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 536 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 537 | "engines": { 538 | "node": ">= 0.4" 539 | }, 540 | "funding": { 541 | "url": "https://github.com/sponsors/ljharb" 542 | } 543 | }, 544 | "node_modules/hasown": { 545 | "version": "2.0.2", 546 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 547 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 548 | "dependencies": { 549 | "function-bind": "^1.1.2" 550 | }, 551 | "engines": { 552 | "node": ">= 0.4" 553 | } 554 | }, 555 | "node_modules/http-errors": { 556 | "version": "2.0.0", 557 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 558 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 559 | "dependencies": { 560 | "depd": "2.0.0", 561 | "inherits": "2.0.4", 562 | "setprototypeof": "1.2.0", 563 | "statuses": "2.0.1", 564 | "toidentifier": "1.0.1" 565 | }, 566 | "engines": { 567 | "node": ">= 0.8" 568 | } 569 | }, 570 | "node_modules/iconv-lite": { 571 | "version": "0.4.24", 572 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 573 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 574 | "dependencies": { 575 | "safer-buffer": ">= 2.1.2 < 3" 576 | }, 577 | "engines": { 578 | "node": ">=0.10.0" 579 | } 580 | }, 581 | "node_modules/ignore-by-default": { 582 | "version": "1.0.1", 583 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 584 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" 585 | }, 586 | "node_modules/inherits": { 587 | "version": "2.0.4", 588 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 589 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 590 | }, 591 | "node_modules/ipaddr.js": { 592 | "version": "1.9.1", 593 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 594 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 595 | "engines": { 596 | "node": ">= 0.10" 597 | } 598 | }, 599 | "node_modules/is-binary-path": { 600 | "version": "2.1.0", 601 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 602 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 603 | "dependencies": { 604 | "binary-extensions": "^2.0.0" 605 | }, 606 | "engines": { 607 | "node": ">=8" 608 | } 609 | }, 610 | "node_modules/is-extglob": { 611 | "version": "2.1.1", 612 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 613 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 614 | "engines": { 615 | "node": ">=0.10.0" 616 | } 617 | }, 618 | "node_modules/is-glob": { 619 | "version": "4.0.3", 620 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 621 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 622 | "dependencies": { 623 | "is-extglob": "^2.1.1" 624 | }, 625 | "engines": { 626 | "node": ">=0.10.0" 627 | } 628 | }, 629 | "node_modules/is-number": { 630 | "version": "7.0.0", 631 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 632 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 633 | "engines": { 634 | "node": ">=0.12.0" 635 | } 636 | }, 637 | "node_modules/media-typer": { 638 | "version": "0.3.0", 639 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 640 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 641 | "engines": { 642 | "node": ">= 0.6" 643 | } 644 | }, 645 | "node_modules/merge-descriptors": { 646 | "version": "1.0.1", 647 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 648 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 649 | }, 650 | "node_modules/methods": { 651 | "version": "1.1.2", 652 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 653 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 654 | "engines": { 655 | "node": ">= 0.6" 656 | } 657 | }, 658 | "node_modules/mime": { 659 | "version": "1.6.0", 660 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 661 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 662 | "bin": { 663 | "mime": "cli.js" 664 | }, 665 | "engines": { 666 | "node": ">=4" 667 | } 668 | }, 669 | "node_modules/mime-db": { 670 | "version": "1.52.0", 671 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 672 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 673 | "engines": { 674 | "node": ">= 0.6" 675 | } 676 | }, 677 | "node_modules/mime-types": { 678 | "version": "2.1.35", 679 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 680 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 681 | "dependencies": { 682 | "mime-db": "1.52.0" 683 | }, 684 | "engines": { 685 | "node": ">= 0.6" 686 | } 687 | }, 688 | "node_modules/minimatch": { 689 | "version": "3.1.2", 690 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 691 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 692 | "dependencies": { 693 | "brace-expansion": "^1.1.7" 694 | }, 695 | "engines": { 696 | "node": "*" 697 | } 698 | }, 699 | "node_modules/ms": { 700 | "version": "2.1.2", 701 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 702 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 703 | }, 704 | "node_modules/negotiator": { 705 | "version": "0.6.3", 706 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 707 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 708 | "engines": { 709 | "node": ">= 0.6" 710 | } 711 | }, 712 | "node_modules/nodemon": { 713 | "version": "3.1.0", 714 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", 715 | "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", 716 | "dependencies": { 717 | "chokidar": "^3.5.2", 718 | "debug": "^4", 719 | "ignore-by-default": "^1.0.1", 720 | "minimatch": "^3.1.2", 721 | "pstree.remy": "^1.1.8", 722 | "semver": "^7.5.3", 723 | "simple-update-notifier": "^2.0.0", 724 | "supports-color": "^5.5.0", 725 | "touch": "^3.1.0", 726 | "undefsafe": "^2.0.5" 727 | }, 728 | "bin": { 729 | "nodemon": "bin/nodemon.js" 730 | }, 731 | "engines": { 732 | "node": ">=10" 733 | }, 734 | "funding": { 735 | "type": "opencollective", 736 | "url": "https://opencollective.com/nodemon" 737 | } 738 | }, 739 | "node_modules/normalize-path": { 740 | "version": "3.0.0", 741 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 742 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 743 | "engines": { 744 | "node": ">=0.10.0" 745 | } 746 | }, 747 | "node_modules/object-assign": { 748 | "version": "4.1.1", 749 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 750 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 751 | "engines": { 752 | "node": ">=0.10.0" 753 | } 754 | }, 755 | "node_modules/object-inspect": { 756 | "version": "1.13.1", 757 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 758 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 759 | "funding": { 760 | "url": "https://github.com/sponsors/ljharb" 761 | } 762 | }, 763 | "node_modules/on-finished": { 764 | "version": "2.4.1", 765 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 766 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 767 | "dependencies": { 768 | "ee-first": "1.1.1" 769 | }, 770 | "engines": { 771 | "node": ">= 0.8" 772 | } 773 | }, 774 | "node_modules/parseurl": { 775 | "version": "1.3.3", 776 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 777 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 778 | "engines": { 779 | "node": ">= 0.8" 780 | } 781 | }, 782 | "node_modules/path-to-regexp": { 783 | "version": "0.1.7", 784 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 785 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 786 | }, 787 | "node_modules/picomatch": { 788 | "version": "2.3.1", 789 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 790 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 791 | "engines": { 792 | "node": ">=8.6" 793 | }, 794 | "funding": { 795 | "url": "https://github.com/sponsors/jonschlinkert" 796 | } 797 | }, 798 | "node_modules/proxy-addr": { 799 | "version": "2.0.7", 800 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 801 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 802 | "dependencies": { 803 | "forwarded": "0.2.0", 804 | "ipaddr.js": "1.9.1" 805 | }, 806 | "engines": { 807 | "node": ">= 0.10" 808 | } 809 | }, 810 | "node_modules/pstree.remy": { 811 | "version": "1.1.8", 812 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 813 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" 814 | }, 815 | "node_modules/qs": { 816 | "version": "6.11.0", 817 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 818 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 819 | "dependencies": { 820 | "side-channel": "^1.0.4" 821 | }, 822 | "engines": { 823 | "node": ">=0.6" 824 | }, 825 | "funding": { 826 | "url": "https://github.com/sponsors/ljharb" 827 | } 828 | }, 829 | "node_modules/range-parser": { 830 | "version": "1.2.1", 831 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 832 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 833 | "engines": { 834 | "node": ">= 0.6" 835 | } 836 | }, 837 | "node_modules/raw-body": { 838 | "version": "2.5.2", 839 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 840 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 841 | "dependencies": { 842 | "bytes": "3.1.2", 843 | "http-errors": "2.0.0", 844 | "iconv-lite": "0.4.24", 845 | "unpipe": "1.0.0" 846 | }, 847 | "engines": { 848 | "node": ">= 0.8" 849 | } 850 | }, 851 | "node_modules/readdirp": { 852 | "version": "3.6.0", 853 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 854 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 855 | "dependencies": { 856 | "picomatch": "^2.2.1" 857 | }, 858 | "engines": { 859 | "node": ">=8.10.0" 860 | } 861 | }, 862 | "node_modules/safe-buffer": { 863 | "version": "5.2.1", 864 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 865 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 866 | "funding": [ 867 | { 868 | "type": "github", 869 | "url": "https://github.com/sponsors/feross" 870 | }, 871 | { 872 | "type": "patreon", 873 | "url": "https://www.patreon.com/feross" 874 | }, 875 | { 876 | "type": "consulting", 877 | "url": "https://feross.org/support" 878 | } 879 | ] 880 | }, 881 | "node_modules/safer-buffer": { 882 | "version": "2.1.2", 883 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 884 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 885 | }, 886 | "node_modules/semver": { 887 | "version": "7.6.2", 888 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", 889 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", 890 | "bin": { 891 | "semver": "bin/semver.js" 892 | }, 893 | "engines": { 894 | "node": ">=10" 895 | } 896 | }, 897 | "node_modules/send": { 898 | "version": "0.18.0", 899 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 900 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 901 | "dependencies": { 902 | "debug": "2.6.9", 903 | "depd": "2.0.0", 904 | "destroy": "1.2.0", 905 | "encodeurl": "~1.0.2", 906 | "escape-html": "~1.0.3", 907 | "etag": "~1.8.1", 908 | "fresh": "0.5.2", 909 | "http-errors": "2.0.0", 910 | "mime": "1.6.0", 911 | "ms": "2.1.3", 912 | "on-finished": "2.4.1", 913 | "range-parser": "~1.2.1", 914 | "statuses": "2.0.1" 915 | }, 916 | "engines": { 917 | "node": ">= 0.8.0" 918 | } 919 | }, 920 | "node_modules/send/node_modules/debug": { 921 | "version": "2.6.9", 922 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 923 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 924 | "dependencies": { 925 | "ms": "2.0.0" 926 | } 927 | }, 928 | "node_modules/send/node_modules/debug/node_modules/ms": { 929 | "version": "2.0.0", 930 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 931 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 932 | }, 933 | "node_modules/send/node_modules/ms": { 934 | "version": "2.1.3", 935 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 936 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 937 | }, 938 | "node_modules/serve-static": { 939 | "version": "1.15.0", 940 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 941 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 942 | "dependencies": { 943 | "encodeurl": "~1.0.2", 944 | "escape-html": "~1.0.3", 945 | "parseurl": "~1.3.3", 946 | "send": "0.18.0" 947 | }, 948 | "engines": { 949 | "node": ">= 0.8.0" 950 | } 951 | }, 952 | "node_modules/set-function-length": { 953 | "version": "1.2.2", 954 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 955 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 956 | "dependencies": { 957 | "define-data-property": "^1.1.4", 958 | "es-errors": "^1.3.0", 959 | "function-bind": "^1.1.2", 960 | "get-intrinsic": "^1.2.4", 961 | "gopd": "^1.0.1", 962 | "has-property-descriptors": "^1.0.2" 963 | }, 964 | "engines": { 965 | "node": ">= 0.4" 966 | } 967 | }, 968 | "node_modules/setprototypeof": { 969 | "version": "1.2.0", 970 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 971 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 972 | }, 973 | "node_modules/side-channel": { 974 | "version": "1.0.6", 975 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 976 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 977 | "dependencies": { 978 | "call-bind": "^1.0.7", 979 | "es-errors": "^1.3.0", 980 | "get-intrinsic": "^1.2.4", 981 | "object-inspect": "^1.13.1" 982 | }, 983 | "engines": { 984 | "node": ">= 0.4" 985 | }, 986 | "funding": { 987 | "url": "https://github.com/sponsors/ljharb" 988 | } 989 | }, 990 | "node_modules/simple-update-notifier": { 991 | "version": "2.0.0", 992 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 993 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 994 | "dependencies": { 995 | "semver": "^7.5.3" 996 | }, 997 | "engines": { 998 | "node": ">=10" 999 | } 1000 | }, 1001 | "node_modules/statuses": { 1002 | "version": "2.0.1", 1003 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1004 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1005 | "engines": { 1006 | "node": ">= 0.8" 1007 | } 1008 | }, 1009 | "node_modules/supports-color": { 1010 | "version": "5.5.0", 1011 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1012 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1013 | "dependencies": { 1014 | "has-flag": "^3.0.0" 1015 | }, 1016 | "engines": { 1017 | "node": ">=4" 1018 | } 1019 | }, 1020 | "node_modules/to-regex-range": { 1021 | "version": "5.0.1", 1022 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1023 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1024 | "dependencies": { 1025 | "is-number": "^7.0.0" 1026 | }, 1027 | "engines": { 1028 | "node": ">=8.0" 1029 | } 1030 | }, 1031 | "node_modules/toidentifier": { 1032 | "version": "1.0.1", 1033 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1034 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1035 | "engines": { 1036 | "node": ">=0.6" 1037 | } 1038 | }, 1039 | "node_modules/touch": { 1040 | "version": "3.1.1", 1041 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", 1042 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", 1043 | "bin": { 1044 | "nodetouch": "bin/nodetouch.js" 1045 | } 1046 | }, 1047 | "node_modules/type-is": { 1048 | "version": "1.6.18", 1049 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1050 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1051 | "dependencies": { 1052 | "media-typer": "0.3.0", 1053 | "mime-types": "~2.1.24" 1054 | }, 1055 | "engines": { 1056 | "node": ">= 0.6" 1057 | } 1058 | }, 1059 | "node_modules/undefsafe": { 1060 | "version": "2.0.5", 1061 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1062 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" 1063 | }, 1064 | "node_modules/unpipe": { 1065 | "version": "1.0.0", 1066 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1067 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1068 | "engines": { 1069 | "node": ">= 0.8" 1070 | } 1071 | }, 1072 | "node_modules/utils-merge": { 1073 | "version": "1.0.1", 1074 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1075 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1076 | "engines": { 1077 | "node": ">= 0.4.0" 1078 | } 1079 | }, 1080 | "node_modules/vary": { 1081 | "version": "1.1.2", 1082 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1083 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1084 | "engines": { 1085 | "node": ">= 0.8" 1086 | } 1087 | } 1088 | } 1089 | } 1090 | --------------------------------------------------------------------------------