add experimental chat
This commit is contained in:
1791
package-lock.json
generated
1791
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@
|
||||
"react-dropdown": "^1.11.0",
|
||||
"react-flip-numbers": "^3.0.8",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-router-dom": "^6.14.2",
|
||||
"react-scripts": "5.0.1",
|
||||
|
||||
42
src/components/Gemini.css
Normal file
42
src/components/Gemini.css
Normal file
@@ -0,0 +1,42 @@
|
||||
.user-chat-input {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
155
src/components/Gemini.js
Normal file
155
src/components/Gemini.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { query, collection, onSnapshot, addDoc, where, getDocs, orderBy } from 'firebase/firestore';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
import './Gemini.css'; // Make sure to have your CSS styles for the typewriter effect
|
||||
import db from '../firebase';
|
||||
|
||||
function Gemini() {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadChatHistory = async () => {
|
||||
const uid = 'uid'; // Replace with your user ID
|
||||
const discussionId = 'discussionId'; // Replace with your discussion ID
|
||||
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
}, 1000); // You can adjust the delay as needed
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the messages by createTime before updating the state
|
||||
// loadedMessages.sort((a, b) => a.createTime - b.createTime);
|
||||
setMessages(loadedMessages);
|
||||
|
||||
// Scroll to the bottom
|
||||
if (scrollRef.current) {
|
||||
// alert(scrollRef.current.scrollHeight)
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading chat history:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadChatHistory();
|
||||
}, []); // Empty dependency array ensures the effect runs only once on component mount
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
if (newMessage.trim() === '') return;
|
||||
|
||||
const uid = 'uid'; // Replace with your user ID
|
||||
const discussionId = 'discussionId'; // Replace with your discussion 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) {
|
||||
// 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);
|
||||
|
||||
// Scroll to the bottom
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
}, 1000); // You can adjust the delay as needed
|
||||
}
|
||||
});
|
||||
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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 ? (
|
||||
// Display typing animation for Gemini's response
|
||||
<div className="typing-animation">Gemini is typing...</div>
|
||||
) : (
|
||||
// Display the actual message
|
||||
<ReactMarkdown>{message.text}</ReactMarkdown>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="user-chat-input">
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Gemini;
|
||||
@@ -3,30 +3,7 @@ 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 */
|
||||
/* dev and test */
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyBFl_YkJ5PMIvMY3G70313SyrqemjFnUv8",
|
||||
authDomain: "hpos-af3cc.firebaseapp.com",
|
||||
@@ -51,25 +28,25 @@ const firebaseConfig = {
|
||||
|
||||
// /* 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"
|
||||
// };
|
||||
// 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"
|
||||
// };
|
||||
|
||||
// /* 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"
|
||||
// };
|
||||
// /* 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"
|
||||
// };
|
||||
|
||||
|
||||
const app = initializeApp(firebaseConfig);
|
||||
|
||||
@@ -7,13 +7,14 @@ 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, FaChalkboardTeacher, FaChartArea, FaChartLine, FaCheck, FaCommentAlt, FaExclamation, FaFlask, FaGolfBall, 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';
|
||||
|
||||
const appRoutes = [
|
||||
{
|
||||
@@ -89,7 +90,7 @@ const appRoutes = [
|
||||
element: <Accuracy />,
|
||||
state: "autoanalysis",
|
||||
sidebarProps: {
|
||||
displayText: "Auto Analysis (NEW)",
|
||||
displayText: "Auto Analysis",
|
||||
icon: <FaChartLine />
|
||||
}
|
||||
},
|
||||
@@ -102,6 +103,15 @@ const appRoutes = [
|
||||
// icon: <FaBezierCurve />
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: "/chat",
|
||||
element: <Gemini />,
|
||||
state: "chat",
|
||||
sidebarProps: {
|
||||
displayText: "Gemini",
|
||||
icon: <FaCommentAlt />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/helpcenter",
|
||||
element: <Help />,
|
||||
|
||||
Reference in New Issue
Block a user