ChatGPT Chatbot
1. Instalarea dependințelor
Ne mutăm în terminal și instalăm pachetul OpenAI.
npm install openai

2. Crearea unui cont de OpenAI
Accesăm link-ul https://platform.openai.com/login și ne facem un cont pentru a putea genera o cheie secretă prin care o să avem dreptul să utilizăm ChatGPT.
După autentificare, selectăm varianta API.

Urmează să mergem la secțiunea dedicată cheilor API.

Apăsăm pe butonul de creat o nouă cheie secretă, îi dăm o denumire, îi alocăm toate permisiunile și apăsăm să o creăm.

Avem grijă să copiem cheia generată și să o mutăm în cadrul fișierului cu chei de mediu.
Este singura dată când putem să vedem cheia.

Fișierul nostru de .env ar trebui să arate ceva de genul acesta:
NODE_ENV = development
NEXT_ATLAS_URI = mongodb+srv://USERNAME:PASSWORD@cluster0.tt3lolq.mongodb.net/
NEXT_ATLAS_DATABASE = CloudComputing
OPENAI_API_KEY = KEY
3. Activarea API KEY-ului
Pentru activarea API key-ului, este nevoie să adăugăm o metodă de plată.

Activarea API KEY-ului presupune adăugarea unui credit de minim de 5 euro pentru a putea folosi cheia.
4. Configurarea chatbot-ului
Mergem în cadrul folderului lib și adăugăm un nou fișier, numit openai.js.
Acest fișier are rolul de a ne genera o instanță de openai, prin intermediul căreia o să putem face cereri.
import OpenAI from 'openai';
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("Please define OPENAI_API_KEY in your environment variables");
}
const openai = new OpenAI(apiKey);
export default openai;
5. Configurare API
Ne mutăm în folderul api și adăugăm un nou fișier numit answer.js.
// /pages/api/answer.js
import openai from "@/lib/openai";
import { sendBadRequest, sendMethodNotAllowed, sendOk } from "@/utils/apiMethods";
const SYSTEM_PROMPTS = {
SIMPLE_ASSISTANT: {
MESSAGE: {
'role': 'system',
'content': 'You are a simple assistant. You respond with simple sentences.',
},
TEMPERATURE: 1,
MAX_TOKENS: 50,
TYPE: 'simple_assistant',
},
USER: {
MESSAGE: {
'role': 'system',
'content': 'You are a user. You respond with normal sentences.',
},
TEMPERATURE: 1,
MAX_TOKENS: 100,
TYPE: 'user',
},
};
const ERRORS = {
DATABASE_ERROR: {
type: 'database_error',
message: 'Error while processing the request.',
},
WRONG_CONVERSATION_TYPE: {
type: 'wrong_conversation_type',
message: 'The conversation type is not known.',
},
OPEN_AI_ERROR: {
type: 'open_ai_error',
message: 'Error while processing the request.',
},
};
const chatCompletion = async (messagesArray, max_tokens, temperature) => {
const rawResponse = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: messagesArray,
max_tokens: max_tokens,
temperature: temperature,
});
return rawResponse?.choices[0];
}
const converseChat = async (res, inputChat, useCase) => {
try{
const MAX_MEMORY = 3;
let userMessagesArray = [];
if (inputChat.length > MAX_MEMORY) {
userMessagesArray = inputChat.slice(-MAX_MEMORY);
}
else {
userMessagesArray = inputChat;
}
const messagesArray = [
useCase.MESSAGE,
...userMessagesArray
];
const response = await chatCompletion(messagesArray, useCase.MAX_TOKENS, useCase.TEMPERATURE);
return sendOk(res, response);
}
catch(error) {
console.error(error);
return sendBadRequest(res, ERRORS.OPEN_AI_ERROR.type, ERRORS.OPEN_AI_ERROR.message);
}
}
const converse = (res, messages, type) => {
switch (type) {
case SYSTEM_PROMPTS.SIMPLE_ASSISTANT.TYPE:
return converseChat(res, messages, SYSTEM_PROMPTS.SIMPLE_ASSISTANT);
case SYSTEM_PROMPTS.USER.TYPE:
return converseChat(res, messages, SYSTEM_PROMPTS.USER);
default:
return sendBadRequest(res, 'wrong_conversation_type');
}
}
export default async function handler(req, res) {
const isAllowedMethod = req.method === 'POST';
const { messages, type } = req.body;
if (!isAllowedMethod) {
return sendMethodNotAllowed()
}
else if (!messages) {
return sendBadRequest(res, 'Missing input');
}
else if(!type) {
return sendBadRequest(res, 'wrong_conversation_type');
}
try{
return converse(res, messages, type);
}
catch(error) {
console.error(error);
}
}
6. Definirea unei noi rute pe partea de frontend
Ne mutăm în folderul pages și o să adăugăm o noua pagină, numită chat.jsx
// /pages/chat.jsx
import ChatComponent from "@/js/components/ChatComponent";
const ChatPage = () => {
return (
<div>
<ChatComponent />
</div>
)
}
export default ChatPage;
Ne mutăm în folderul components și creăm o noua componentă, numită ChatComponent.jsx.
// /components/ChatComponent.jsx
import React, {useState} from 'react';
import MessageBox from "@/js/components/MessageBox";
function ChatComponent(props) {
const [chatMessages, setChatMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [userInput, setUserInput] = useState('');
const filterChatHistory = chatHistory => {
let filteredChatHistory = [];
for( let i = 0; i < chatHistory.length; i++ ){
const currMessage = chatHistory[i];
const nextMessage = chatHistory[i+1];
if( i === chatHistory.length - 1 || (currMessage.type !== 'error' && nextMessage?.type !== 'error' && currMessage.role !== nextMessage?.role) ){
filteredChatHistory.push(currMessage);
}
}
return filteredChatHistory;
}
const buildResponseMessageObject = response => {
let responseMessageObject;
responseMessageObject = response.data.message;
return responseMessageObject;
}
const handleKeyDown = async (e) => {
if(e.key === 'Enter'){
if(userInput.length === 0) {
return;
}
const currentMessageObject = {
role: 'user',
content: userInput,
}
setChatMessages(prevChatMessages => [...prevChatMessages, currentMessageObject]);
const currentChatHistory = [...chatMessages, currentMessageObject];
const filteredChatHistory = filterChatHistory(currentChatHistory);
try{
setIsLoading(true);
const response = await fetch('/api/answer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: filteredChatHistory,
type: 'user',
}),
});
const json = await response.json();
setUserInput('');
setIsLoading(false);
const responseMessageObject = buildResponseMessageObject(json);
setChatMessages(prevChatMessages => [...prevChatMessages, responseMessageObject]);
}
catch(error){
console.log(error);
}
}
}
return (
<div className={"w-full max-w-[1500px] mx-auto my-10"}>
<div className={"border border-b-0 rounded-lg border-gray-300'"}>
<div className={'border-b text-center px-[20px] py-[10px]'}>
<span className={'text-md font-bold text-gray-900'}>
This a chat component that looks like Yahoo!
</span>
</div>
<MessageBox chatMessages={chatMessages}/>
</div>
<input
id={'chat-input'}
type={'text'}
value={userInput}
onChange={e => setUserInput(e.target.value)}
disabled={isLoading}
className="bg-gray-50 border border-gray-300 border-x-0 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-4"
placeholder="Type something..."
onKeyDown={handleKeyDown}
/>
</div>
);
}
export default ChatComponent;
Tot în folderul components, adăugăm fișierul MessageBox.jsx.
// /components/MessageBox.jsx
import React, {useEffect, useRef} from 'react';
function MessageBox(props) {
const messagesEndRef = useRef(null);
const { chatMessages, } = props;
const scrollToBottom = () => {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth', });
}
useEffect(() => {
scrollToBottom()
}, [chatMessages]);
return (
<div className={'h-[400px] overflow-auto px-[20px]'}>
<ul className={'divide-y divide-gray-200'}>
<li className={ 'py-3' }>
<span className={ `block text-green-800 font-bold` }>
ChatGPT:
</span>
<span className={ 'block' }>
Hi, I am a ChatBot. How can I help you?
</span>
</li>
{
chatMessages.map((message, index) => {
return (
<li className={'py-3'} key={index}>
<span className={ `block ${message.role === 'assistant' ? 'text-green-800' : 'text-blue-800'} font-bold` }>
{
message.role === 'assistant' ? 'ChatGPT:' : 'You:'
}
</span>
<span className={ 'block' }>
{
message.content
}
</span>
</li>
)
})
}
</ul>
<div ref={ messagesEndRef } />
</div>
);
}
export default MessageBox;
Last updated