Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048bfcd0cb | ||
|
|
565cb2259a | ||
|
|
644e06f78f | ||
|
|
41aebcec7b | ||
|
|
8e659d8fed | ||
|
|
0ed5e0725b | ||
|
|
aa2c04f853 | ||
|
|
47579b0796 | ||
|
|
ff8c62ef46 | ||
|
|
f40a78b843 | ||
|
|
79980e34c8 | ||
|
|
d9fe2fdf25 | ||
|
|
0c5b13fc67 | ||
|
|
83dc9f068f | ||
|
|
7e3226c6d2 | ||
|
|
870c3a4dcb | ||
|
|
f7071a2ff5 | ||
|
|
ed8dd0d4d6 | ||
|
|
dd92c78104 | ||
|
|
6872d260b6 | ||
|
|
3e4f08b90e | ||
|
|
b69791c246 | ||
|
|
d327fecf8c | ||
|
|
43375556bf | ||
|
|
92feb4081c | ||
|
|
bb968b4781 | ||
|
|
a751b55647 | ||
|
|
099b3f08fa | ||
|
|
c20197c40a | ||
|
|
ca5106d613 | ||
|
|
9c782b7cd1 | ||
|
|
707e338d3a | ||
|
|
367c36e3f8 | ||
|
|
fc31aea95b | ||
|
|
920858a523 | ||
|
|
16aa545862 | ||
|
|
65723718c6 | ||
|
|
fb3d715831 | ||
|
|
683c6049af | ||
|
|
34a770ceed | ||
|
|
67a7414d5c |
12
.firebaserc
12
.firebaserc
@@ -10,6 +10,18 @@
|
||||
],
|
||||
"qa": [
|
||||
"hpos-qa"
|
||||
],
|
||||
"dev": [
|
||||
"hpos-dev"
|
||||
],
|
||||
"preprod": [
|
||||
"hpos-preprod"
|
||||
],
|
||||
"uat": [
|
||||
"hpos-uat"
|
||||
],
|
||||
"iocl": [
|
||||
"iocl-iisc"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
@@ -31,3 +32,12 @@ public/india-states.json
|
||||
|
||||
scripts/reports
|
||||
scripts/data
|
||||
firebase-debug.log
|
||||
.env.preprod
|
||||
.env.prod
|
||||
.env.qa
|
||||
.env.dev
|
||||
.env.uat
|
||||
.env.iocl
|
||||
|
||||
certificate_path/
|
||||
|
||||
18
.gitlab-ci.yml
Normal file
18
.gitlab-ci.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
stages:
|
||||
- test
|
||||
|
||||
unit-test:
|
||||
image: node:latest
|
||||
stage: test
|
||||
before_script:
|
||||
- npm install --force
|
||||
script:
|
||||
- npm run test-ci
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
artifacts:
|
||||
paths:
|
||||
- coverage/
|
||||
when: always
|
||||
reports:
|
||||
junit:
|
||||
- junit.xml
|
||||
@@ -25,11 +25,15 @@
|
||||
|
||||
### run build
|
||||
|
||||
npm build
|
||||
npm run build
|
||||
|
||||
|
||||
### deploy
|
||||
|
||||
firebase deploy --only hosting:dev1
|
||||
firebase deploy --only hosting:dev
|
||||
|
||||
pyinstaller --noconfirm --noconsole -i "smi.ico" --windowed --clean hemocube.py
|
||||
|
||||
### known issues
|
||||
|
||||
gemini starts giving error after 70 odd messages in a chat discussion
|
||||
|
||||
1
__mocks__/styleMock.js
Normal file
1
__mocks__/styleMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
1
__mocks__/svgMock.js
Normal file
1
__mocks__/svgMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = 'test-file-stub';
|
||||
4
babel.config.json
Normal file
4
babel.config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"hosting": {
|
||||
"target": "qa",
|
||||
"target": "iocl",
|
||||
"public": "build",
|
||||
"ignore": [
|
||||
"firebase.json",
|
||||
|
||||
19
jest.config.js
Normal file
19
jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'jsdom',
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
|
||||
transform: {
|
||||
"^.+\\.jsx?$": "babel-jest",
|
||||
'^.+\\.mjs': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'/node_modules/',
|
||||
'node_modules/(?!(d3|other-module-to-transform)/)',
|
||||
'\\.css$',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'\\.(svg)$': '<rootDir>/__mocks__/svgMock.js',
|
||||
'\\.(css|less|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
|
||||
'^d3$': '<rootDir>/node_modules/d3/src/index.js',
|
||||
},
|
||||
};
|
||||
|
||||
74
nats.js
Normal file
74
nats.js
Normal file
@@ -0,0 +1,74 @@
|
||||
// const nats = require('nats');
|
||||
// const fs = require('fs');
|
||||
|
||||
// const privateKey = fs.readFileSync('clientCertificate/client-key.pem');
|
||||
// const certificate = fs.readFileSync('clientCertificate/client-cert.pem');
|
||||
|
||||
// const nc = nats.connect({
|
||||
// servers: ['nats://nanodgx.in:4222'],
|
||||
// token: 'SUADA3R4HRQG5ZEGR7E65P5P2JUQ2LMVLEOGJWEIKLEYOZEPQES2WHW2KU',
|
||||
// tls: {
|
||||
// key: privateKey,
|
||||
// cert: certificate,
|
||||
// },
|
||||
// connect: function() {
|
||||
// console.log('Connected to NATS server');
|
||||
|
||||
// // Subscribe to a subject
|
||||
// const subscription = nc.subscribe('your-subject');
|
||||
|
||||
// // Listen for incoming messages
|
||||
// subscription.on('message', function(msg) {
|
||||
// console.log(`Received message: ${msg.getData()}`);
|
||||
// });
|
||||
|
||||
// // Publish a message
|
||||
// nc.publish('your-subject', 'Hello NATS!');
|
||||
// },
|
||||
// error: function(err) {
|
||||
// console.error(`Error connecting to NATS server: ${err}`);
|
||||
// },
|
||||
// close: function() {
|
||||
// console.log('Connection to NATS server closed');
|
||||
// },
|
||||
// });
|
||||
|
||||
|
||||
const NATS = require('nats');
|
||||
const sc = NATS.StringCodec();
|
||||
const seed = new TextEncoder().encode(
|
||||
"SUACFBL4SJNFOYVB6F4AHK7A7VCHDENCKVLKJTTKAH3I47BOTSOE3Z2K7M",
|
||||
);
|
||||
const servers = [
|
||||
{
|
||||
servers: "nats://nanodgx.in:4222",
|
||||
|
||||
authenticator: NATS.nkeyAuthenticator(seed),
|
||||
tls: {
|
||||
keyFile: "certificate_path/client-key.pem",
|
||||
certFile: "certificate_path/client-cert.pem",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
servers.forEach(async (v) => {
|
||||
try {
|
||||
// console.log(v);
|
||||
const nc = await NATS.connect(v);
|
||||
|
||||
// Subscribe to a subject
|
||||
const subscription = await nc.subscribe("server.hpos.HCV-000-3998.ping");
|
||||
/*
|
||||
(async () => {
|
||||
for await (const msg of subscription) {
|
||||
console.log(`Received message: ${sc.decode(msg.data)}`);
|
||||
// Your custom logic for handling the incoming message goes here
|
||||
}
|
||||
})();
|
||||
*/
|
||||
nc.publish("server.hpos.HCV-000-3998.ping", "ALIVE");
|
||||
|
||||
} catch (err) {
|
||||
console.log(`error connecting to ${JSON.stringify(v)} ${err}`);
|
||||
}
|
||||
});
|
||||
13435
package-lock.json
generated
13435
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -15,19 +15,26 @@
|
||||
"d3": "^7.8.5",
|
||||
"d3-selection": "^3.0.0",
|
||||
"firebase": "^9.23.0",
|
||||
"katex": "^0.16.9",
|
||||
"lodash": "^4.17.21",
|
||||
"ml-savitzky-golay-generalized": "^4.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"nats": "^2.19.0",
|
||||
"react": "^18.2.0",
|
||||
"react-datepicker": "^4.16.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropdown": "^1.11.0",
|
||||
"react-flip-numbers": "^3.0.8",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-katex": "^3.0.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-mathjax": "^1.0.1",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-select": "^5.7.4",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"topojson-client": "^3.1.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
@@ -37,7 +44,8 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"deploy": "npm run build && firebase deploy",
|
||||
"coverage": "react-scripts test --coverage"
|
||||
"coverage": "react-scripts test --coverage",
|
||||
"test-ci": "jest --testPathIgnorePatterns=src/tests/ --passWithNoTests --ci --bail"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -58,7 +66,13 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/preset-env": "^7.23.8",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"csv-parser": "^3.0.0",
|
||||
"html-pdf-node": "^1.0.8"
|
||||
"html-pdf-node": "^1.0.8",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"ts-jest": "^29.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>HPOS Sickle Cell Test Dashboard</title>
|
||||
<script
|
||||
type="text/javascript"
|
||||
async
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML-full"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -26,15 +26,15 @@ function Accuracy({ show }) {
|
||||
setLoading(true);
|
||||
setError(null); // Reset error state before making a new request
|
||||
|
||||
if (selectedFile) {
|
||||
if (!selectedFile) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
// formData.append('file', selectedFile);
|
||||
|
||||
const queryString = `start_date=${moment(startDate).format(
|
||||
'YYYY-MM-DD'
|
||||
)}&end_date=${moment(endDate).format('YYYY-MM-DD')}`;
|
||||
|
||||
const url = `https://asia-south1-hpos-qa.cloudfunctions.net/performance?${queryString}`;
|
||||
const url = `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/performance?${queryString}`;
|
||||
|
||||
// Make a POST request using the Fetch API
|
||||
const response = await fetch(url, {
|
||||
@@ -79,10 +79,10 @@ function Accuracy({ show }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Auto Analysis</h1>
|
||||
<div className='file-row'>
|
||||
{/* <div className='file-row'>
|
||||
<div className='file-label'>Reference file</div>
|
||||
<input type="file" onChange={handleFileChange} />
|
||||
</div>
|
||||
</div> */}
|
||||
<div className='dates'>
|
||||
<div className='start-date'>
|
||||
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} placeholderText='start date' />
|
||||
|
||||
@@ -8,11 +8,12 @@ import ImageDownloader from './ImageDownloader';
|
||||
function CurveFit({ show }) {
|
||||
const [showCardInfo, setShowCardInfo] = useState(false);
|
||||
const [svgData, setSvgData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchSvg = () => {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: "https://asia-south1-hpos-qa.cloudfunctions.net/curvefit",
|
||||
url: "https://asia-south1-hpos-af3cc.cloudfunctions.net/crdboxplot",
|
||||
data: {
|
||||
"_id": "id",
|
||||
"underMedication": false,
|
||||
@@ -28,29 +29,37 @@ function CurveFit({ show }) {
|
||||
})
|
||||
.then(response => response.data)
|
||||
.then((data) => {
|
||||
// setMessage(`${id} is being created`);
|
||||
// setSvgData(data?.plot?.replace('\"', '').replace('\n', '').replace('"', "'"));
|
||||
setSvgData(data?.plot?.replace('\"', '').replace('\n', '').replace('"', "'"));
|
||||
setLoading(false); // Set loading to false once data is fetched
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
// setMessage(`error: ${err?.message}`);
|
||||
setLoading(false); // Set loading to false in case of an error
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!svgData) {
|
||||
// fetchSvg();
|
||||
fetchSvg();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Curve Fit</h1>
|
||||
{/* <img alt='' src={ svgData }/>; */}
|
||||
<img src="https://asia-south1-hpos-qa.cloudfunctions.net/curvefit" width="450" height="340" />
|
||||
<h1>Plots</h1>
|
||||
|
||||
{loading ? (
|
||||
<div className="spinner-container">
|
||||
{/* Add your spinner component here or use a simple text */}
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : (
|
||||
<img src={`https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/crdboxplot`} width="450" height="340" />
|
||||
)}
|
||||
|
||||
{/* <ImageDownloader /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CurveFit;
|
||||
0
src/components/Diagnostics.css
Normal file
0
src/components/Diagnostics.css
Normal file
68
src/components/Diagnostics.js
Normal file
68
src/components/Diagnostics.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { query, collection, onSnapshot, where, orderBy, limit } from 'firebase/firestore';
|
||||
import moment from 'moment';
|
||||
|
||||
import db from '../firebase';
|
||||
import './Diagnostics.css';
|
||||
|
||||
const Diagnostics = () => {
|
||||
const [samples, setSamples] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!samples.length) {
|
||||
const q = query(collection(db, "diagnostics"), where("runTime", ">=", moment().format("YYYY-MM-DD").toString()), orderBy("runTime", "desc"));
|
||||
|
||||
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||
const samples = [];
|
||||
querySnapshot.forEach((document) => {
|
||||
console.log(document.id);
|
||||
const data = document.data();
|
||||
const id = document.id;
|
||||
samples.push({id, ...data});
|
||||
});
|
||||
setSamples(samples);
|
||||
});
|
||||
|
||||
return () => unsub;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <div className='samples-heading'><h3>Samples page for denovix useage</h3></div>
|
||||
{samples.length ? (
|
||||
samples.map((alert, index) => (
|
||||
<div key={index} className='samples-card'>
|
||||
<div tabIndex={index}><strong>{alert._id}</strong></div>
|
||||
<p className='sample-time'>{alert.testTime}</p>
|
||||
<p className='sample-time-ago'>{moment(alert.testTime).fromNow()}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className='no-samples-message'>Samples will appear as in when a test is recorded</div>
|
||||
)} */}
|
||||
|
||||
|
||||
<h1>Device Data</h1>
|
||||
|
||||
<div className='samples-container'>
|
||||
<table id="samples-table">
|
||||
<tr>
|
||||
<th>Device</th>
|
||||
<th>Raw Data</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
{samples.map((sample, index) => (
|
||||
<tr key={index} className='samples-table-tr'>
|
||||
<td>{sample.deviceId}</td>
|
||||
<td>{sample.deviceData || 'NA'}</td>
|
||||
<td>{moment(sample.runTime).format('HH:mm:ss')}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Diagnostics;
|
||||
@@ -11,7 +11,7 @@ function ExportData({ url, show }) {
|
||||
const [endDate, setEndDate] = useState("");
|
||||
|
||||
const fetchFile = (start_date, end_date) => {
|
||||
window.location.href = `https://asia-south1-hpos-qa.cloudfunctions.net/consolidation?start_date=${moment(startDate).format("YYYY-MM-DD")}&end_date=${moment(endDate).format("YYYY-MM-DD")}`;
|
||||
window.location.href = `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/consolidation?start_date=${moment(startDate).format("YYYY-MM-DD")}&end_date=${moment(endDate).format("YYYY-MM-DD")}`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
103
src/components/Gemini.css
Normal file
103
src/components/Gemini.css
Normal file
@@ -0,0 +1,103 @@
|
||||
.user-chat-input {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #aaffaa;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.other-message {
|
||||
background-color: #aaaaff;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.typing-animation {
|
||||
overflow: hidden; /* Hide overflowing text */
|
||||
border-right: 0.1em solid #000; /* Add a border to simulate the cursor */
|
||||
white-space: nowrap; /* Ensure the text stays in one line */
|
||||
animation: typing 1s steps(40, end), blink-caret 0.5s step-end infinite; /* Typing animation */
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
to {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink-caret {
|
||||
from, to {
|
||||
border-color: transparent; /* Blinking cursor effect */
|
||||
}
|
||||
50% {
|
||||
border-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply basic styles to the table */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Add styling to the table header */
|
||||
th, td {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Style the table header */
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
/* Add some hover effect to the table rows */
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
/* background-color: #f5f5f5; */
|
||||
/* border: 1px solid #ddd; */
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
239
src/components/Gemini.js
Normal file
239
src/components/Gemini.js
Normal file
@@ -0,0 +1,239 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { query, collection, onSnapshot, addDoc, where, getDocs, orderBy, getCountFromServer } from 'firebase/firestore';
|
||||
import Markdown from 'react-markdown';
|
||||
import MathJax from 'react-mathjax';
|
||||
import RemarkMathPlugin from 'remark-math';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { debounce } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { FaSpinner } from 'react-icons/fa';
|
||||
// import { MathJax, MathJaxContext } from "better-react-mathjax";
|
||||
|
||||
import './Gemini.css'; // Make sure to have your CSS styles for the typewriter effect
|
||||
import db from '../firebase';
|
||||
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
function Gemini(props) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
const [discussionId, setDiscussionId] = useState(uuidv4());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [chatCount, setChatCount] = useState(0);
|
||||
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
const newProps = {
|
||||
...props,
|
||||
options: {
|
||||
...props.options,
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']], // Adjust delimiters if needed
|
||||
displayMath: [['$$', '$$'], ['\\[', '\\]']], // Adjust delimiters if needed
|
||||
},
|
||||
remarkPlugins: [
|
||||
RemarkMathPlugin,
|
||||
remarkGfm,
|
||||
],
|
||||
// renderers: {
|
||||
// ...props.renderers,
|
||||
// math: (props) =>
|
||||
// <MathJax.Node formula={props.value} />,
|
||||
// inlineMath: (props) =>
|
||||
// <MathJax.Node inline formula={props.value} />
|
||||
// }
|
||||
};
|
||||
|
||||
const newComponents = {
|
||||
...props.components,
|
||||
math: (props) => (
|
||||
<MathJax.Node formula={props.value} />
|
||||
),
|
||||
inlineMath: (props) => (
|
||||
<MathJax.Node inline formula={props.value} />
|
||||
)
|
||||
};
|
||||
|
||||
const getDocumentCount = async (uid) => {
|
||||
const collectionPath = `users/uid/discussions`;
|
||||
try {
|
||||
const querySnapshot = await getCountFromServer(collection(db, collectionPath));
|
||||
// const documentCount = querySnapshot.size;
|
||||
// setChatCount(querySnapshot);
|
||||
console.log(`Document count in the collection "${collectionPath}": ${JSON.stringify(querySnapshot._data.count.integerValue)}`);
|
||||
} catch (error) {
|
||||
console.error("Error getting document count:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadChatHistory = async () => {
|
||||
const uid = 'uid'; // Replace with your user ID
|
||||
// const discussionId = uuidv4();
|
||||
console.log(discussionId)
|
||||
|
||||
getDocumentCount(uid);
|
||||
|
||||
const collectionPath = `users/${uid}/discussions/${discussionId}/messages`;
|
||||
|
||||
const q = query(collection(db, collectionPath), orderBy('createTime'));
|
||||
|
||||
try {
|
||||
const querySnapshot = await getDocs(q);
|
||||
const loadedMessages = [];
|
||||
|
||||
querySnapshot.forEach((doc) => {
|
||||
const data = doc.data();
|
||||
loadedMessages.push({ text: data.prompt, sender: 'user' });
|
||||
|
||||
if (data.response) {
|
||||
// console.log(data.response)
|
||||
// Simulate delay for typewriter effect
|
||||
// setTimeout(() => {
|
||||
// // Update the state with the Gemini's response
|
||||
// setMessages((prevMessages) => [
|
||||
// ...prevMessages,
|
||||
// { text: data.response, sender: 'gemini' },
|
||||
// ]);
|
||||
|
||||
// // if (scrollRef.current) {
|
||||
// // // alert(scrollRef.current.scrollHeight)
|
||||
// // scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
// // }
|
||||
// }, 10); // You can adjust the delay as needed
|
||||
|
||||
|
||||
loadedMessages.push({ text: data.response, sender: 'gemini' });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the messages by createTime before updating the state
|
||||
// loadedMessages.sort((a, b) => a.createTime - b.createTime);
|
||||
setMessages(loadedMessages);
|
||||
} catch (error) {
|
||||
console.error('Error loading chat history:', error);
|
||||
}
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
if (scrollRef.current) {
|
||||
// alert(scrollRef.current.scrollHeight)
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
loadChatHistory();
|
||||
}, []); // Empty dependency array ensures the effect runs only once on component mount
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (newMessage.trim() === '') return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const uid = 'uid'; // Replace with your user ID
|
||||
|
||||
const collectionPath = `users/${uid}/discussions/${discussionId}/messages`;
|
||||
const collectionRef = collection(db, collectionPath);
|
||||
|
||||
// Add user's message to the local state first
|
||||
setMessages([...messages, { text: newMessage, sender: 'user' }]);
|
||||
setNewMessage('');
|
||||
|
||||
// Then, add the user's message to the Firestore collection
|
||||
// Your existing code for adding user's message remains unchanged
|
||||
|
||||
const docRef = await addDoc(collectionRef, {
|
||||
prompt: newMessage,
|
||||
});
|
||||
|
||||
// Listen for changes in the Firestore document
|
||||
const unsub = onSnapshot(docRef, (doc) => {
|
||||
const data = doc.data();
|
||||
if (data && data.response) {
|
||||
// console.log(data.response)
|
||||
// Start typewriter animation
|
||||
// setIsTyping(true);
|
||||
|
||||
// Simulate delay for typewriter effect
|
||||
setTimeout(() => {
|
||||
// Update the state with the Gemini's response
|
||||
setMessages((prevMessages) => [...prevMessages, { text: data.response, sender: 'gemini' }]);
|
||||
// Stop typewriter animation
|
||||
// setIsTyping(false);
|
||||
|
||||
setIsLoading(false);
|
||||
}, 200); // You can adjust the delay as needed
|
||||
}
|
||||
// Scroll to the bottom
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
|
||||
return unsub;
|
||||
};
|
||||
|
||||
const handleInputChange = debounce((value) => {
|
||||
setNewMessage(value);
|
||||
}, 30); // Adjust the delay as needed
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <div>Chats: {chatCount}</div> */}
|
||||
<div style={{ height: '500px', overflowY: 'scroll', border: '1px solid #ccc' }} ref={scrollRef}>
|
||||
{messages.map((message, index) => (
|
||||
<div key={index} className={message.sender === 'user' ? 'user-message' : 'other-message'}>
|
||||
{message.sender === 'gemini' && isTyping ? (
|
||||
<div className="typing-animation">Gemini is typing...</div>
|
||||
) : (
|
||||
<MathJax.Provider input="tex">
|
||||
<Markdown components={newComponents} {...newProps}>{message.text}</Markdown>
|
||||
</MathJax.Provider>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="user-chat-input">
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
placeholder="Type your message..."
|
||||
style={{ marginRight: '8px', padding: '8px', fontSize: '16px', width: '50vw' }}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendMessage}
|
||||
style={{
|
||||
backgroundColor: '#4CAF50',
|
||||
color: 'white',
|
||||
padding: '10px 20px',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
{isLoading && <div className='loading-container'>
|
||||
<div className="loading-icon">
|
||||
<FaSpinner className="spin" />
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gemini;
|
||||
0
src/components/KitCheck.css
Normal file
0
src/components/KitCheck.css
Normal file
70
src/components/KitCheck.js
Normal file
70
src/components/KitCheck.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { query, collection, onSnapshot, where, orderBy, limit } from 'firebase/firestore';
|
||||
import moment from 'moment';
|
||||
|
||||
import db from '../firebase';
|
||||
import './KitCheck.css';
|
||||
|
||||
const KitCheck = () => {
|
||||
const [samples, setSamples] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!samples.length) {
|
||||
const q = query(collection(db, "buffers"), where("runTime", ">=", moment().format("YYYY-MM-DD").toString()), orderBy("runTime", "desc"));
|
||||
|
||||
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||
const samples = [];
|
||||
querySnapshot.forEach((document) => {
|
||||
console.log(document.id);
|
||||
const data = document.data();
|
||||
const id = document.id;
|
||||
samples.push({id, ...data});
|
||||
});
|
||||
setSamples(samples);
|
||||
});
|
||||
|
||||
return () => unsub;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <div className='samples-heading'><h3>Samples page for denovix useage</h3></div>
|
||||
{samples.length ? (
|
||||
samples.map((alert, index) => (
|
||||
<div key={index} className='samples-card'>
|
||||
<div tabIndex={index}><strong>{alert._id}</strong></div>
|
||||
<p className='sample-time'>{alert.testTime}</p>
|
||||
<p className='sample-time-ago'>{moment(alert.testTime).fromNow()}</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className='no-samples-message'>Samples will appear as in when a test is recorded</div>
|
||||
)} */}
|
||||
|
||||
|
||||
<h1>Kit Check</h1>
|
||||
|
||||
<div className='samples-container'>
|
||||
<table id="samples-table">
|
||||
<tr>
|
||||
<th>Device</th>
|
||||
<th>Kit</th>
|
||||
<th>Result</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
{samples.map((sample, index) => (
|
||||
<tr key={index} className='samples-table-tr'>
|
||||
<td>{sample.deviceId}</td>
|
||||
<td>{sample.kitSerial || 'NA'}</td>
|
||||
<td>{sample.classificationResult || 'NA'}</td>
|
||||
<td>{moment(sample.runTime).format('HH:mm:ss')}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default KitCheck;
|
||||
@@ -16,7 +16,7 @@ function Registration() {
|
||||
setCounter(counter + 1);
|
||||
axios({
|
||||
method: 'post',
|
||||
url: "https://asia-south1-hpos-qa.cloudfunctions.net/registerUser",
|
||||
url: `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/registerUser`,
|
||||
data: {
|
||||
"_id": id,
|
||||
"aadharId": "2808202300001",
|
||||
@@ -65,7 +65,7 @@ function Registration() {
|
||||
setMessage("");
|
||||
axios({
|
||||
method: 'get',
|
||||
url: "https://asia-south1-hpos-qa.cloudfunctions.net/clearUsers",
|
||||
url: `https://asia-south1-${process.env.REACT_APP_FIREBASE_PROJECT_ID}.cloudfunctions.net/clearUsers`,
|
||||
withCredentials: false,
|
||||
secure: false,
|
||||
headers: {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
#samples-table td,
|
||||
|
||||
@@ -15,9 +15,10 @@ const Samples = () => {
|
||||
const unsub = onSnapshot(q, (querySnapshot) => {
|
||||
const samples = [];
|
||||
querySnapshot.forEach((document) => {
|
||||
console.log(document.data());
|
||||
const alert = document.data();
|
||||
samples.push(alert);
|
||||
console.log(document.id);
|
||||
const data = document.data();
|
||||
const id = document.id;
|
||||
samples.push({id, ...data});
|
||||
});
|
||||
setSamples(samples);
|
||||
});
|
||||
@@ -48,24 +49,33 @@ const Samples = () => {
|
||||
<table id="samples-table">
|
||||
<tr>
|
||||
<th>Sample Name</th>
|
||||
<th>CResult</th>
|
||||
<th>Dev Ratio Class</th>
|
||||
<th>BM2 Class</th>
|
||||
<th>Dev Ratio</th>
|
||||
<th>Slope Ratio</th>
|
||||
<th>led1Sam.</th>
|
||||
{/* <th>led2Average</th> */}
|
||||
<th>led2Sam.</th>
|
||||
<th>led3Sam.</th>
|
||||
<th>led4Sam.</th>
|
||||
<th>Device</th>
|
||||
<th>Classification</th>
|
||||
<th>abs1</th>
|
||||
<th>abs2</th>
|
||||
<th>abs3</th>
|
||||
<th>PDR</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
{samples.map((sample, index) => (
|
||||
<tr key={index} className='samples-table-tr'>
|
||||
<td>{sample.name}</td>
|
||||
<td>{sample.name || sample._id}</td>
|
||||
<td>{sample.classificationResult}</td>
|
||||
<td>{sample.deviceRatioClass}</td>
|
||||
<td>{sample.borderlineMethod2Class}</td>
|
||||
<td>{sample?.deviceRatio?.toFixed(4)}</td>
|
||||
<td>{sample?.slopeRatio?.toFixed(4)}</td>
|
||||
<td>{sample.led1Sample || 'NA'}</td>
|
||||
<td>{sample.led2Sample || 'NA'}</td>
|
||||
<td>{sample.led3Sample || 'NA'}</td>
|
||||
<td>{sample.led4Sample || 'NA'}</td>
|
||||
<td>{sample.deviceId}</td>
|
||||
<td>{sample.prdClassification || 'NA'}</td>
|
||||
<td>{sample.abs1}</td>
|
||||
<td>{sample.abs2 || 'NA'}</td>
|
||||
<td>{sample.abs3 || 'NA'}</td>
|
||||
<td>{sample.predictedDenovixRatio}</td>
|
||||
<td>{sample.testTime}</td>
|
||||
<td>{moment(sample.testTime).format('HH:mm:ss')}</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
|
||||
@@ -53,7 +53,10 @@ const TestAnalytics = () => {
|
||||
const dailyCountsList = Object.entries(dailyCounts).sort().map(x => {
|
||||
return { date: x[0], count: x[1] };
|
||||
});
|
||||
setDailyResults(dailyCountsList);
|
||||
if (dailyCountsList?.length > 7)
|
||||
setDailyResults(dailyCountsList.slice(dailyCountsList.length - 7, dailyCountsList.length));
|
||||
else
|
||||
setDailyResults(dailyCountsList);
|
||||
const rss = _.groupBy(hposTests, function (test) {
|
||||
return moment(test.testTime).startOf('day').format();
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestAnalyticsGraph extends Component {
|
||||
data.reverse();
|
||||
|
||||
var margin = { top: 30, right: 20, bottom: 50, left: 15 },
|
||||
width = 1500 - margin.left - margin.right,
|
||||
width = 400 - margin.left - margin.right,
|
||||
height = 180 - margin.top - margin.bottom;
|
||||
|
||||
var svg = d3.select("#tests-count-graph").append('svg')
|
||||
@@ -113,7 +113,7 @@ class TestAnalyticsGraph extends Component {
|
||||
drawChart(data) {
|
||||
if (!data) return;
|
||||
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
|
||||
width = 960 - margin.left - margin.right,
|
||||
width = 420 - margin.left - margin.right,
|
||||
height = 500 - margin.top - margin.bottom;
|
||||
|
||||
const INNER_WIDTH = width;// - margin.left - margin.right;
|
||||
|
||||
@@ -16,7 +16,7 @@ const Topbar = () => {
|
||||
>
|
||||
<Toolbar>
|
||||
<Typography variant="h6">
|
||||
HPOS Web - Quality / QA
|
||||
HPOS Web - {process.env.REACT_APP_TOPBAR_TITLE}
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const sizeConfigs = {
|
||||
sidebar: {
|
||||
width: "300px"
|
||||
width: "260px"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,51 +3,15 @@ import { getFirestore} from "firebase/firestore";
|
||||
import { initializeApp } from "firebase/app";
|
||||
import { getAnalytics } from "firebase/analytics";
|
||||
|
||||
// /* dev and test */
|
||||
// const firebaseConfig = {
|
||||
// apiKey: "AIzaSyBFl_YkJ5PMIvMY3G70313SyrqemjFnUv8",
|
||||
// authDomain: "hpos-af3cc.firebaseapp.com",
|
||||
// projectId: "hpos-af3cc",
|
||||
// storageBucket: "hpos-af3cc.appspot.com",
|
||||
// messagingSenderId: "650071678820",
|
||||
// appId: "1:650071678820:web:ce50538bf46632d46c6471",
|
||||
// measurementId: "G-6JHQ47S9DD"
|
||||
// };
|
||||
|
||||
/* qa: hpos-qa */
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyCF7BNZ5vOMQIpKRi_vxquzWP5pIjcb59I",
|
||||
authDomain: "hpos-qa.firebaseapp.com",
|
||||
projectId: "hpos-qa",
|
||||
storageBucket: "hpos-qa.appspot.com",
|
||||
messagingSenderId: "1004619739289",
|
||||
appId: "1:1004619739289:web:c3ee0c0127f0d073e5c808",
|
||||
measurementId: "G-4MSTJHE64W"
|
||||
};
|
||||
|
||||
|
||||
// /* preprod */
|
||||
// const firebaseConfig = {
|
||||
// apiKey: "AIzaSyB2-suWmKh2PgQbRmx7rzPjYhgoovQIsQ8",
|
||||
// authDomain: "hpos-preprod.firebaseapp.com",
|
||||
// projectId: "hpos-preprod",
|
||||
// storageBucket: "hpos-preprod.appspot.com",
|
||||
// messagingSenderId: "121176529204",
|
||||
// appId: "1:121176529204:web:6c0db8e642992192bbed61",
|
||||
// measurementId: "G-GYM6LRQ8KR"
|
||||
// };
|
||||
|
||||
// /* prod */
|
||||
// const firebaseConfig = {
|
||||
// apiKey: "AIzaSyBF4pLZReAedU4dWX1eRJNFNIaOIz2xk4s",
|
||||
// authDomain: "hpos-prod.firebaseapp.com",
|
||||
// projectId: "hpos-prod",
|
||||
// storageBucket: "hpos-prod.appspot.com",
|
||||
// messagingSenderId: "1012719870714",
|
||||
// appId: "1:1012719870714:web:fcd00acef93f6612de5f43",
|
||||
// measurementId: "G-B0NZ7DEF69"
|
||||
// };
|
||||
|
||||
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
|
||||
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
|
||||
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
|
||||
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
|
||||
appId: process.env.REACT_APP_FIREBASE_APP_ID,
|
||||
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
|
||||
};
|
||||
|
||||
const app = initializeApp(firebaseConfig);
|
||||
const analytics = getAnalytics(app);
|
||||
|
||||
@@ -7,13 +7,16 @@ import Devices from '../components/Devices';
|
||||
import { Download, FactCheck } from '@mui/icons-material';
|
||||
import ExportData from '../components/ExportData';
|
||||
import Registration from '../components/Registration';
|
||||
import { FaAndroid, FaArrowCircleLeft, FaBars, FaBezierCurve, FaChalkboardTeacher, FaChartArea, FaChartLine, FaCheck, FaExclamation, FaFlask, FaGolfBall, FaPhone, FaQuestionCircle, FaSearch, FaUser } from 'react-icons/fa';
|
||||
import { FaAndroid, FaArrowCircleLeft, FaBars, FaBezierCurve, FaBox, FaChalkboardTeacher, FaChartArea, FaChartLine, FaCheck, FaCommentAlt, FaExclamation, FaFlask, FaGolfBall, FaMicrochip, FaPhone, FaQuestionCircle, FaSearch, FaUser } from 'react-icons/fa';
|
||||
import AppInstallation from '../components/AppInstallation';
|
||||
import Samples from '../components/Samples';
|
||||
import Precision from '../components/Precision';
|
||||
import Accuracy from '../components/Accuracy';
|
||||
import CurveFit from '../components/CurveFit';
|
||||
import Help from '../components/Help';
|
||||
import Gemini from '../components/Gemini';
|
||||
import Diagnostics from '../components/Diagnostics';
|
||||
import KitCheck from '../components/KitCheck';
|
||||
|
||||
const appRoutes = [
|
||||
{
|
||||
@@ -75,6 +78,24 @@ const appRoutes = [
|
||||
icon: <FaFlask />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/kits",
|
||||
element: <KitCheck />,
|
||||
state: "kits",
|
||||
sidebarProps: {
|
||||
displayText: "Kit Check",
|
||||
icon: <FaBox />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/diagnostics",
|
||||
element: <Diagnostics />,
|
||||
state: "Diagnostics",
|
||||
sidebarProps: {
|
||||
displayText: "Device Data",
|
||||
icon: <FaMicrochip />
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: "/precision",
|
||||
// element: <Precision />,
|
||||
@@ -89,19 +110,28 @@ const appRoutes = [
|
||||
element: <Accuracy />,
|
||||
state: "autoanalysis",
|
||||
sidebarProps: {
|
||||
displayText: "Auto Analysis (NEW)",
|
||||
displayText: "Auto Analysis",
|
||||
icon: <FaChartLine />
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: "/curvefit",
|
||||
// element: <CurveFit />,
|
||||
// state: "curvefit",
|
||||
// sidebarProps: {
|
||||
// displayText: "Linear Fit",
|
||||
// icon: <FaBezierCurve />
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: "/plots",
|
||||
element: <CurveFit />,
|
||||
state: "plots",
|
||||
sidebarProps: {
|
||||
displayText: "EDA Plots",
|
||||
icon: <FaBezierCurve />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/chat",
|
||||
element: <Gemini />,
|
||||
state: "chat",
|
||||
sidebarProps: {
|
||||
displayText: "Chat Gemini",
|
||||
icon: <FaCommentAlt />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/helpcenter",
|
||||
element: <Help />,
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
// import '@testing-library/jest-dom/extend-expect';
|
||||
Reference in New Issue
Block a user