Spaces:
Running
Running
import { useState, useEffect, useRef } from 'react'; | |
import messageDatabase from './data.json'; | |
import './App.css'; | |
export default function ChatDisplay() { | |
const [isActive, setIsActive] = useState(false); | |
const [showModal, setShowModal] = useState(true); | |
const [currentMessageIndex, setCurrentMessageIndex] = useState( | |
Math.floor(Math.random() * messageDatabase.length) | |
); | |
const [showFeedback, setShowFeedback] = useState(false); | |
const [message, setMessage] = useState(() => messageDatabase[currentMessageIndex].question); | |
const [messagesSent, setMessagesSent] = useState(false); | |
const [sentMessage, setSentMessage] = useState(''); | |
const [isThinking, setIsThinking] = useState(false); | |
const [userGuessCorrect, setUserGuessCorrect] = useState(false); | |
const [showAnswer, setShowAnswer] = useState(false); | |
const [showTopAnimation, setShowTopAnimation] = useState(false); | |
const [animatedMessages, setAnimatedMessages] = useState({ | |
userMessage: false, | |
thinking: false, | |
feedback: false | |
}); | |
const [reasoningButtonActive, setReasoningButtonActive] = useState(false); | |
const [searchButtonActive, setSearchButtonActive] = useState(false); | |
const getRandomMessageIndex = () => { | |
let newIndex; | |
do { | |
newIndex = Math.floor(Math.random() * messageDatabase.length); | |
} while (newIndex === currentMessageIndex && messageDatabase.length > 1); | |
return newIndex; | |
}; | |
const loadNewQuestion = () => { | |
const newIndex = getRandomMessageIndex(); | |
setCurrentMessageIndex(newIndex); | |
setMessage(messageDatabase[newIndex].question); | |
setMessagesSent(false); | |
setShowFeedback(false); | |
setShowAnswer(false); | |
setSentMessage(''); | |
}; | |
const isMessageTypeMatching = () => { | |
const currentType = messageDatabase[currentMessageIndex].type; | |
// Match reasoning type with reasoning button | |
if (currentType === "reasoning" && reasoningButtonActive) return true; | |
// Match search/standard type with search button | |
if ((currentType === "search" || currentType === "standard") && searchButtonActive) return true; | |
// Match default/standard type with no button selection | |
if (currentType === "standard" && !reasoningButtonActive && !searchButtonActive) return true; | |
return false; | |
}; | |
const handleUserGuess = () => { | |
const isCorrect = isMessageTypeMatching(); | |
setUserGuessCorrect(isCorrect); | |
setShowFeedback(true); | |
}; | |
const handleSubmit = (e) => { | |
e.preventDefault(); | |
if (!message.trim()) return; | |
// When "Send" is clicked (not in Next state) | |
if (!messagesSent) { | |
setSentMessage(message); | |
setMessagesSent(true); | |
setIsThinking(true); | |
setMessage(''); // Clear input immediately when Send is clicked | |
setTimeout(() => { | |
setIsThinking(false); | |
handleUserGuess(); | |
}, 1500); | |
} | |
}; | |
const handleKeyPress = (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
handleSubmit(e); | |
} | |
}; | |
useEffect(() => { | |
if (!messagesSent) { | |
setAnimatedMessages({ | |
userMessage: false, | |
thinking: false, | |
feedback: false | |
}); | |
} | |
}, [messagesSent]); | |
function UserMessage({ text }) { | |
const shouldAnimate = !animatedMessages.userMessage; | |
useEffect(() => { | |
if (shouldAnimate) { | |
setAnimatedMessages(prev => ({ ...prev, userMessage: true })); | |
} | |
}, [shouldAnimate]); | |
return ( | |
<div className={`message user-message ${shouldAnimate ? 'message-send-animation' : ''}`}> | |
<p>{text}</p> | |
</div> | |
); | |
} | |
function FeedbackMessage() { | |
const currentType = messageDatabase[currentMessageIndex].type; | |
const shouldBeReasoning = currentType === "reasoning"; | |
const shouldAnimate = !animatedMessages.feedback; | |
useEffect(() => { | |
if (shouldAnimate) { | |
setAnimatedMessages(prev => ({ ...prev, feedback: true })); | |
} | |
}, [shouldAnimate]); | |
if (userGuessCorrect) { | |
return ( | |
<div className={`message feedback-message success ${shouldAnimate ? 'message-send-animation' : ''}`}> | |
<p>π Congratulations! You correctly identified this as a{shouldBeReasoning ? ' reasoning' : ' standard'} prompt.</p> | |
</div> | |
); | |
} else { | |
return ( | |
<div className={`message feedback-message error ${shouldAnimate ? 'message-send-animation' : ''}`}> | |
<p>β Incorrect. This is a{shouldBeReasoning ? ' reasoning' : ' standard'} prompt.</p> | |
<p className="error-explanation"> | |
{shouldBeReasoning | |
? "This prompt requires detailed explanation and step-by-step reasoning to solve the problem." | |
: "This prompt requires a direct, concise answer without detailed explanation."} | |
</p> | |
</div> | |
); | |
} | |
} | |
function WelcomeModal() { | |
return ( | |
showModal && ( | |
<div className="modal-overlay"> | |
<div className="modal"> | |
<h2>πΏ Did you know? </h2> | |
<p>Every prompt counts! Choosing the right AI model saves energy and protects our planet. πβ¨</p> | |
<img src="images/cute_world_mini.png" style={{ width: '55%' }} /> | |
<p>Play smart, pick wisely, and reduce your digital footprint! π±π‘</p> | |
<button onClick={() => setShowModal(false)}>Start</button> | |
</div> | |
</div> | |
) | |
); | |
} | |
function ChatInput() { | |
const [isNewPrompt, setIsNewPrompt] = useState(false); | |
const initialLoadComplete = useRef(false); | |
// Only run once when component mounts | |
useEffect(() => { | |
// Mark initial load as complete after a short delay | |
const timer = setTimeout(() => { | |
initialLoadComplete.current = true; | |
}, 500); | |
return () => clearTimeout(timer); | |
}, []); | |
const handleButtonClick = (button) => { | |
// Allow toggling between active/inactive states | |
if (button === 'reasoning') { | |
// If already active, deselect it | |
if (reasoningButtonActive) { | |
setReasoningButtonActive(false); | |
} else { | |
// Otherwise, activate it and deactivate the other button | |
setReasoningButtonActive(true); | |
setSearchButtonActive(false); | |
} | |
} else if (button === 'search') { | |
// If already active, deselect it | |
if (searchButtonActive) { | |
setSearchButtonActive(false); | |
} else { | |
// Otherwise, activate it and deactivate the other button | |
setSearchButtonActive(true); | |
setReasoningButtonActive(false); | |
} | |
} | |
}; | |
const handleNextClick = () => { | |
// Only set new prompt flag if initial load is complete | |
if (initialLoadComplete.current) { | |
setIsNewPrompt(true); | |
} | |
// Show the energy animation | |
setShowTopAnimation(true); | |
// Hide animation after a delay | |
setTimeout(() => { | |
setShowTopAnimation(false); | |
}, 3000); | |
loadNewQuestion(); | |
// Reset the new prompt flag after animation | |
if (initialLoadComplete.current) { | |
setTimeout(() => setIsNewPrompt(false), 800); | |
} | |
}; | |
// Simplify class name logic - no complex conditions | |
const getInputClassName = () => { | |
const classes = ['chat-input']; | |
// Only apply new-prompt class if initial load is complete | |
if (initialLoadComplete.current && isNewPrompt) { | |
classes.push('new-prompt'); | |
} | |
return classes.join(' '); | |
}; | |
return ( | |
<div className="chat-input-container"> | |
<form className="chat-input-wrapper" onSubmit={handleSubmit}> | |
<div className="button-group"> | |
<div className="tooltip-wrapper"> | |
<button | |
onClick={() => handleButtonClick('reasoning')} | |
className={`standard-button reasoning-button ${reasoningButtonActive ? 'active' : ''}`} | |
type="button" | |
disabled={messagesSent} | |
> | |
Reason | |
</button> | |
<div className="tooltip"> | |
Use for prompts that require detailed explanations and step-by-step reasoning | |
</div> | |
</div> | |
<div className="tooltip-wrapper"> | |
<button | |
onClick={() => handleButtonClick('search')} | |
className={`standard-button search-button ${searchButtonActive ? 'active' : ''}`} | |
type="button" | |
disabled={messagesSent} | |
> | |
Search | |
</button> | |
<div className="tooltip"> | |
Use for prompts that need simple, direct answers without detailed explanation | |
</div> | |
</div> | |
</div> | |
<div className="input-with-button"> | |
<div className="tooltip-wrapper input-tooltip-wrapper"> | |
<textarea | |
className={getInputClassName()} | |
value={showModal ? '' : message} | |
onKeyPress={handleKeyPress} | |
placeholder="Click next for next prompt" | |
rows="3" | |
readOnly={!messagesSent && message === messageDatabase[currentMessageIndex].question} | |
/> | |
<div className="tooltip input-tooltip"> | |
First select a model type above, then press Send to submit your answer | |
</div> | |
</div> | |
<button | |
type={messagesSent && !showFeedback ? "button" : "submit"} | |
className="answer-button" | |
onClick={messagesSent && showFeedback ? handleNextClick : undefined} | |
disabled={messagesSent && !showFeedback} | |
> | |
{messagesSent && showFeedback ? 'Next' : 'Send'} | |
</button> | |
</div> | |
</form> | |
</div> | |
); | |
} | |
function ThinkingAnimation() { | |
const shouldAnimate = !animatedMessages.thinking; | |
useEffect(() => { | |
if (shouldAnimate) { | |
setAnimatedMessages(prev => ({ ...prev, thinking: true })); | |
} | |
}, [shouldAnimate]); | |
return ( | |
<div className={`thinking ${shouldAnimate ? 'message-send-animation' : ''}`}> | |
<div className="dot"></div> | |
<div className="dot"></div> | |
<div className="dot"></div> | |
</div> | |
); | |
} | |
// Updated CompanyLogo component with both logos at the bottom | |
function CompanyLogo() { | |
return ( | |
<> | |
<img | |
src="images/cotec.png" | |
alt="Company Logo Right" | |
className="company-logo company-logo-right" | |
/> | |
<img | |
src="images/upm4.png" | |
alt="Company Logo Left" | |
className="company-logo company-logo-left" | |
/> | |
</> | |
); | |
} | |
function EnergyAnimation() { | |
return ( | |
<div className={`energy-animation ${showTopAnimation ? 'show' : ''}`}> | |
<div className="energy-icon"> | |
{userGuessCorrect ? 'π³' : 'ποΈ'} | |
</div> | |
<div className="energy-text"> | |
{userGuessCorrect ? 'Energy saved! Good choice!' : 'Choose wisely next time to save energy!'} | |
</div> | |
</div> | |
); | |
} | |
return ( | |
<> | |
<WelcomeModal /> | |
<EnergyAnimation /> | |
<div className="chat-container"> | |
<div className="messages-container"> | |
{messagesSent && <UserMessage text={`"${sentMessage}"`} />} | |
{messagesSent && isThinking && <ThinkingAnimation />} | |
{messagesSent && showFeedback && !isThinking && <FeedbackMessage />} | |
</div> | |
<ChatInput /> | |
</div> | |
<CompanyLogo /> | |
</> | |
); | |
} | |