# ChatGPT Chatbot

## 1. Instalarea dependințelor

Ne mutăm în terminal și instalăm pachetul OpenAI.

```git
npm install openai
```

<figure><img src="/files/CsHkRM7PrbvyIMJy7bXa" alt=""><figcaption></figcaption></figure>

## 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**.

<figure><img src="/files/qiZAOGCemGeGQwyS7kcO" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/tLdfbf8Km2nVnb039gWd" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/mx5q6HcyXBnsSrpQLQd7" alt=""><figcaption></figcaption></figure>

Avem grijă să copiem cheia generată și să o mutăm în cadrul fișierului cu chei de mediu.

{% hint style="danger" %}
Este singura dată când putem să vedem cheia.
{% endhint %}

<figure><img src="/files/OOwaLQVRViTjwBsPtrUe" alt=""><figcaption></figcaption></figure>

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ă.&#x20;

{% hint style="info" %}
Nicio sumă de bani nu va fi retrasă de pe cont pentru verificare.
{% endhint %}

<figure><img src="/files/rljyJ1tbZH8evoqZcpsj" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Activarea API KEY-ului presupune adăugarea unui credit de minim de 5 euro pentru a putea folosi cheia.
{% endhint %}

## 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.

{% hint style="info" %}
Dacă nu ați repornit aplicația după ce ați adăugat noua cheia de mediu, o să trebuiască să îl reporniți pentru a prelua și noua cheie pe care tocmai am adăugat-o.
{% endhint %}

```javascript
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**.

```javascript
// /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);
	}
}
```

{% hint style="info" %}
Cum functionează codul din acest template de cod:

1. În primă instanță, avem declarat un obiect **SYSTEM\_PROMPTS**, care are scopul de a defini cele 2 roluri din cadrul chat-ului, și anume asistentul virtual și persoana care i se adresează.&#x20;

   Ulterior, fiecare rol primește mai multe setări, printre care **TEMPERATURE** (cu cât temperatura este mai mare, cu atât este mai probabil ca modelul să genereze răspunsuri neașteptate sau creative) și **MAX\_TOKENS** (Cu cât este mai mare acest număr, cu atât răspunsul poate fi mai lung și mai detaliat).
2. Avem un obiect **ERRORS** care definește tipurile de erori pe care le putem întâmpina.
3. Definim o funcție async numită **chatCompletion** care are rolul de a întreba asistentul, cu versiunea **gpt-3.5-turbo** și de a returna răspunsul. \ <mark style="color:red;">**Alte modele în afară de gpt-3.5-turbo poate avea ca și consecințte costuri suplimentare. Lista cu toate tipurile de modele:**</mark> [**https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo**](https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo)
4. Funcția **converseChat** are rolul de a iniția o conversație între utilizator și un asistent AI folosind modelul GPT-3.5. Funcția începe prin a verifica dacă numărul de mesaje trimise de utilizator depășește un anumit prag și, în funcție de acest lucru, selectează ultimele **`MAX_MEMORY`** mesaje sau toate mesajele dacă numărul acestora este mai mic decât pragul. Apoi, construiește un array de mesaje care include mesajele predefinite pentru tipul de conversație și mesajele utilizatorului.
5. Funcți&#x61;**`converse`**&#x65;ste funcția principală care gestionează conversația între utilizator și asistentul AI. Ea verifică tipul de conversație specificat și apoi apelează funcția **`converseChat`** pentru a începe conversația corespunzătoare.
6. Funcți&#x61;**`handler`**&#x65;ste funcția de bază care servește drept punct de intrare pentru gestionarea cererilor **HTTP** către server. Această funcție primește un obiect `req` care reprezintă cererea HTTP primită de server și un obiect `res` care reprezintă răspunsul pe care serverul îl va trimite înapoi către client.
   {% endhint %}

## 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

```javascript
// /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.

```javascript
// /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;
```

{% hint style="info" %}
La începutul componentei, observăm că avem 3 state-uri:

1. chatMessages -> salvează toată conversația între utilizator și chatbot
2. isLoading -> folosit pentru a da disable la input cât timp request-ul este încă în lucru
3. userInput -> textul curent pe care îl scrie utilizatorul

Principalele funcții:

1. Funcți&#x61;**`filterChatHistory`**&#x65;ste responsabilă pentru filtrarea istoricului de conversație pentru a elimina mesajele redundante și erorile din istoricul chatului. Această funcție primește ca parametru istoricul de chat și returnează un nou array care conține doar mesajele relevante pentru afișare.
2. Funcți&#x61;**`buildResponseMessageObject`**&#x65;ste responsabilă pentru construirea unui obiect de mesaj din răspunsul primit de la server în urma unei solicitări către API-ul de procesare a mesajelor. Această funcție primește ca parametru răspunsul JSON primit de la server și returnează un obiect de mesaj care poate fi adăugat la istoricul de chat și afișat utilizatorului.
3. Funcți&#x61;**`handleKeyDown`**&#x65;ste un handler de eveniment care este activat atunci când utilizatorul apasă o tastă în câmpul de introducere a textului. Ulterior, face requestul de interogare a asistentului virtual și afișarea răspunsului.

În final, mesajele sunt transmise către o altă componentă, numită **MessageBox**, care are rolul să afișeze mesajele.
{% endhint %}

Tot în folderul components, adăugăm fișierul MessageBox.jsx.

```javascript
// /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;
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lungu-mihai-adrian.gitbook.io/cloud-computing-2026-simpre/seminar-2/chatgpt-chatbot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
