Skip to content

Instantly share code, notes, and snippets.

@dshipper
Created February 10, 2023 21:41
Show Gist options
  • Save dshipper/5fcd0de33fea6254833a4e9406df7277 to your computer and use it in GitHub Desktop.
Save dshipper/5fcd0de33fea6254833a4e9406df7277 to your computer and use it in GitHub Desktop.
import React, { Component, useState, useEffect } from 'react';
class ChatBot extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
input: '',
persona: 'Socrates',
style: 'default',
isiPhone : false,
inputFocused: false,
chatbotMessageStyles: {},
waitingForResponse: false
};
this.initialInnerHeight = window.innerHeight;
// Bind sendMessage function to component instance
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount() {
// Add initial message from bot to messages list
this.setState({
messages: [...this.state.messages, {
sender: "bot",
message: "Welcome to Lenny Bot! I answer questions based on information contained in Lenny's Newsletter articles. Here are a few things you can ask me:"
}, {
sender: "bot",
message: "-> What is good retention for a consumer social product?"
},{
sender: "bot",
message: "-> How do I know if I have product market fit?"
},{
sender: "bot",
message: "-> How did DoorDash get its first users?"
},
{
sender: "bot",
type: "disclaimer"
}
]
});
this.detectIphone();
window.addEventListener('resize', this.updateChatbotMessageStyles);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateChatbotMessageStyles);
}
detectIphone() {
const userAgent = window.navigator.userAgent.toLowerCase();
if (userAgent.includes('iphone')) {
this.setState({isIphone: true});
}
}
updateChatbotMessageStyles = () => {
var self = this;
setTimeout(function(){
var innerHeight = window.innerHeight;
var keyboardHeight = self.initialInnerHeight - innerHeight;
var keyboardHeightString = keyboardHeight + "px";
if (self.state.isIphone) {
self.setState({ chatbotMessageStyles: {
height: keyboardHeightString,
paddingTop: keyboardHeightString
}});
}
}, 500)
}
inputFocused = () => {
this.setState({inputFocused: true});
this.updateChatbotMessageStyles();
}
inputBlurred = () => {
this.setState({inputFocused: false, chatbotMessageStyles: {}});
}
updateStyle = (style) => {
this.setState({
style: style
});
}
updatePersona = (persona) => {
this.setState({
persona: persona
});
}
handleChange = (e) => {
let inputValue = e.target.value;
let inputColor = 'white';
if (inputValue.startsWith('/')) {
inputColor = 'rgb(255, 154, 236)';
}
this.setState({input: inputValue, inputColor: inputColor});
}
handleSubmit = (e) => {
e.preventDefault();
// Add user message to messages list
this.setState({
messages: [...this.state.messages, {
sender: "user",
message: this.state.input
}],
input: ''
}, () => {
// Send message to API
this.sendMessage();
});
}
handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.handleSubmit(e);
}
}
sendMessage() {
// Build transcript string
let transcript = '';
this.state.messages.forEach((message) => {
transcript += `${message.sender}: ${message.message}\n`;
});
// Determine promptType based on last message
let promptType = "completion";
if (this.state.messages[this.state.messages.length - 1].message === "/summarize") {
promptType = "summary";
}
var messages = this.state.messages;
this.setState({input: '', waitingForResponse: true})
// Send transcript to API
const response_promise = fetch('/ask-lenny', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
transcript: transcript,
promptType: promptType,
messages: messages,
persona: this.state.persona,
style: this.state.style
})
});
response_promise
.then(res => res.text())
.then(response => {
this.setState({waitingForResponse: false})
try {
response = JSON.parse(response);
var message = response.answer;
var topSource = response.sources[0];
var newMessages = [...this.state.messages];
newMessages.push({
sender: "bot",
message: message
});
if(topSource) {
var url = topSource.url;
newMessages.push({
sender: "bot",
type: "source",
title: topSource.title,
url: url,
message: "For more information see: " + topSource.title + " (" + url + ")"
});
}
this.setState({
messages: newMessages
},() => {
// Scroll to bottom of chatbot-messages div
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
});
} catch (e) {
console.log(e);
var newMessages = [...this.state.messages];
newMessages.push({
sender: "bot",
message: "Sorry there was some kind of error getting a response from the server. It's possible OpenAI is overloaded. Please try again."
});
this.setState({
messages: newMessages
},() => {
// Scroll to bottom of chatbot-messages div
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
});
}
});
}
render() {
var loadingIndicator = (this.state.waitingForResponse) ? <div class="lds-ripple"><div></div><div></div><div></div></div> : null;
return (
<div className="chatbot-container">
<div className="chatbot-messages" style={this.state.chatbotMessageStyles}>
{this.state.messages.map((message, index) => (
<div key={index} className={`chatbot-message ${message.sender}`}
ref={(el) => {
if (index === this.state.messages.length - 1 && el) {
this.messagesEnd = el;
this.messagesEnd.scrollIntoView({ behavior: 'smooth' });
}
}}>
{message.type && message.type === "source" ? (
<div>
{"For more information see: "}
<a href={message.url} target="_blank" rel="noopener noreferrer">
{message.title}
</a>
</div>
) : (
message.message
)}
</div>
))}
<div className="chatbot-loading-indicator">
{loadingIndicator}
</div>
</div>
<form onSubmit={this.handleSubmit}>
<textarea
value={this.state.input}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onFocus={this.inputFocused}
onBlur={this.inputBlurred}
defaultValue="Type your message here"
/>
<button type="submit">Send</button>
</form>
</div>
);
}
}
export default ChatBot;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment