diff --git a/.github/preview.png b/.github/preview.png new file mode 100644 index 0000000..236fc8b Binary files /dev/null and b/.github/preview.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50c9ad9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules + +*.local \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4967e97 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# ChatGPT Html Interface + +Este projeto é uma interface inicial de chatbot para consumir a API do ChatGPT, utilizando Html, Css3, JavaScript. A API do ChatGPT é uma API de processamento de linguagem natural (NLP) que permite a criação de chatbots. + +![Preview](https://github.com/LucasLuccaCode/chatgpt-react-interface/blob/main/.github/preview.png) + +## Como utilizar + +Clone este repositório: + +``` +git clone https://github.com/LucasLuccaCode/chatgpt-react-interface.git +``` + +Navegue para a pasta do projeto: + +``` +cd chatgpt-react-interface +``` + +Mude para a branch `main` para ter acesso ao projeto usando somente JavaScript puro: + +``` +git checkout main +``` + +Antes de executar o projeto, preencha a variável `API_KEY` no arquivo `index.js`, com sua chave de autorização para a API do ChatGPT. A chave pode ser obtida através do site da [OpenAI](https://platform.openai.com/account/api-keys). + + +```js +const API_KEY = "sua-chave-api" +``` + +Em seguida poderá abrir o projeto no navegador. + +## Principais funcionalidades + +- Perguntas e Respostas +- Chats +- Contexto contendo respostas anteriores do chat(experimental) +- Redimensionamento dos textos e espaços + +## Tecnologias e serviços usados + +- Html +- Css3 +- JavaScript \ No newline at end of file diff --git a/assets/css/chats.css b/assets/css/chats.css new file mode 100644 index 0000000..1a1dc30 --- /dev/null +++ b/assets/css/chats.css @@ -0,0 +1,71 @@ +.c-chats { + width: 100%; +} + +.c-chats__card { + cursor: pointer; + display: flex; + align-items: center; + width: 100%; + padding: .5rem .8rem; + border: 2px solid rgba(255, 255, 255, .05); + border-radius: .4rem; + margin-top: .8rem; + background: rgba(255, 255, 255, .1); +} + +.c-chats__card:hover { + background: rgba(255, 255, 255, .2); +} + +.c-chats__card__title { + pointer-events: none; + flex: 1; + font-size: .8rem; + color: #efefff; + padding: .3rem 0rem; + border-radius: .2rem; + transition: all .3s; +} + +.c-chats__card__title:focus { + outline: 1.8px solid rgba(255, 255, 255, .1); + text-overflow: initial; +} + +.c-chats__card__title.editing { + pointer-events: auto; + padding: .3rem .5rem; +} + +.c-chats__card__title::first-letter { + text-transform: capitalize; +} + +.c-chats__card__actions { + display: flex; + gap: .5rem; + margin-left: .5rem; +} + +.c-chats__card__actions button { + border-radius: .3rem; + padding: .3rem; + background: rgba(255, 255, 255, .1); + transition: .3s; +} + +.c-chats__card__actions button:hover { + background: rgba(255, 255, 255, .2); + box-shadow: 0 0 1px 2px var(--details-color); +} + +.c-chats__card__actions button i { + pointer-events: none; + font-size: .7rem; + color: #dadae0; +} + +.c-chats__card__actions button:hover i { + color: #fff; +} \ No newline at end of file diff --git a/assets/css/footer.css b/assets/css/footer.css new file mode 100644 index 0000000..12f48c4 --- /dev/null +++ b/assets/css/footer.css @@ -0,0 +1,93 @@ +/* FOOTER */ + +.c-footer { + margin: 0 var(--margin-h); + margin-top: 1rem; +} + +.c-footer.hide { + display: none; +} +/* STATUS */ + +.c-status { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: .5rem; +} + +.c-status__progress { + font-size: .8rem; + color: rgba(255, 255, 255, .5); +} + +.c-status__stop { + font-size: .7rem; + padding: .3rem .5rem; + color: #fff; + font-weight: bold; + border-radius: .2rem; + background: var(--details-color); + opacity: 1; + transition: opacity .4s ease; +} + +.c-status__stop.hide { + opacity: 0; +} + +.c-status__stop:hover { + background: #059ddf; +} + +/* QUESTION */ + +.c-ask_form { + display: flex; + align-items: center; + background: var(--secondary-color); + border-radius: .3rem; + border: 2px solid transparent; + transition: border .3s; +} + +.c-ask_form:focus-within { + border: 2px solid var(--details-color); +} + +.c-ask_form textarea { + flex: 1; + padding: .5rem; + height: 100%; + font-size: .8rem; + color: #babac0; + border-radius: .3rem; + border: 2px solid transparent; + background: transparent; +} + +.c-ask_form textarea:disabled { + opacity: .5; +} + +.c-ask_form button { + padding: .25rem; + border-radius: .2rem; + margin: 0 .3rem 0 .5rem; + background: transparent; + transition: background-color .3s ease; +} + +.c-ask_form button:hover { + background: rgba(0, 0, 0, .2); +} + +.c-ask_form button:focus { + border: 2px solid rgba(255, 255, 255, .1); +} + +.c-ask_form button i { + font-size: 1rem; + color: var(--details-color); +} \ No newline at end of file diff --git a/assets/css/global.css b/assets/css/global.css new file mode 100644 index 0000000..d0bd83b --- /dev/null +++ b/assets/css/global.css @@ -0,0 +1,55 @@ +:root { + --primary-color: #2a2a30; + --secondary-color: #3a3a40; + --details-color: #00b1ff; + --margin-h: .8rem; + --font-size: 18px; +} + +*, *::before { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: sans-serif; +} + +body, +html { + width: 100%; + height: 100%; + font-size: var(--font-size); + /* background-color: #222225; */ + background: var(--primary-color); +} + +textarea, +button { + border: none; + outline: none; + resize: none; +} + +button { + cursor: pointer; +} + +li { + list-style: none; +} + +.nowrap { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.c-container { + display: flex; + flex-direction: column; + width: 100%; + max-width: 600px; + height: 100%; + margin: 0 auto; + padding-bottom: var(--margin-h); + background: var(--primary-color); +} \ No newline at end of file diff --git a/assets/css/header.css b/assets/css/header.css new file mode 100644 index 0000000..581c01f --- /dev/null +++ b/assets/css/header.css @@ -0,0 +1,29 @@ +.c-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: .5rem var(--margin-h); + background: var(--secondary-color); +} + +.c-header h1 { + color: #efefff; + font-size: 1rem; +} + +.c-header button { + justify-content: center; + background: transparent; + border-radius: .2rem; +} + +.c-header button:hover { + background: rgba(255, 255, 255, .1); +} + +.c-header button i { + display: flex; + align-items: center; + font-size: 1.5rem; + color: #efefff; +} \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..dc54c48 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,29 @@ +.c-main { + flex: 1; + margin-top: 1rem; + margin-right: .3rem; + margin-left: var(--margin-h); + padding-right: calc(var(--margin-h) - .3rem); + overflow-y: auto; +} + +.c-main::-webkit-scrollbar { + width: .3rem; +} + +.c-main::-webkit-scrollbar-track { + background: transparent; +} + +.c-main::-webkit-scrollbar-thumb { + background-color: var(--details-color); + border-radius: 20px; +} + +.placeholder { + text-align: center; + font-size: .8rem; + width: 100%; + color: #afafbf; + margin-top: 1rem; +} \ No newline at end of file diff --git a/assets/css/responses.css b/assets/css/responses.css new file mode 100644 index 0000000..58a37cc --- /dev/null +++ b/assets/css/responses.css @@ -0,0 +1,60 @@ +.c-responses { + width: 100%; +} + +.c-responses__back { + position: sticky; + top: 0; + border-radius: 50px; + background: #4a4a50; + border: 1px solid var(--primary-color); + box-shadow: 0 1px 1px 2px rgba(0, 0, 0, .1); +} + +.c-responses__back:hover { + background: #5a5a60; +} + +.c-responses__back i { + font-size: 1.3rem; + color: #fff; +} + +.c-responses__question { + display: flex; + align-items: center; + margin-top: 1rem; +} + +.c-responses__question i { + font-size: .7rem; + color: var(--details-color); + margin-right: .5rem; +} + +.c-responses__question h3 { + color: #dadaf0; + font-size: .8rem; +} + +.c-responses__question h3::first-letter { + text-transform: capitalize; +} + +.c-responses__response { + padding: .5rem; + border-radius: .2rem; + color: #dfdfef; + width: 100%; + font-size: .8rem; + white-space: pre-wrap; + word-wrap: break-word; + line-height: 1.2rem; + background: var(--secondary-color); + margin-top: .5rem; +} + +.c-responses__response.--error { + color: rgb(225 97 171); + border: 1px solid rgb(225 97 171); +} \ No newline at end of file diff --git a/assets/css/settings.css b/assets/css/settings.css new file mode 100644 index 0000000..99f29d0 --- /dev/null +++ b/assets/css/settings.css @@ -0,0 +1,124 @@ +.c-blur { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: none; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, .5); +} + +.c-blur.active { + display: flex; + align-items: center; + justify-content: center; +} + +.c-settings { + display: flex; + flex-direction: column; + padding: 1rem; + border-radius: .5rem; + background: var(--secondary-color); + box-shadow: 0 2px 1px 1px rgba(0, 0, 0, .2); +} + + +.c-settings h2 { + font-size: .8rem; + text-align: center; + color: #efefff; +} + +.c-settings__control { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + list-style: none; + margin-top: 1rem; +} + +.c-settings__control > div { + display: flex; + align-items: center; +} + +.c-settings__control label { + color: #b2b1b5; + white-space: nowrap; + margin-right: 1rem; + font-size: .8rem; +} + +.c-settings__control input[type=number] { + padding: .3rem .2rem; + border: 1px solid rgba(255, 255, 255, .1); + color: #efefff; + font-size: .6rem; + background: rgba(0, 0, 0, .15); + margin-left: .5rem; +} + +.c-settings__control input[type='checkbox'] { + display: flex; + align-items: center; + justify-content: center; + position: relative; + appearance: none; + width: 1rem; + height: 1rem; + border-radius: .2rem; + background: var(--primary-color); +} + +.c-settings__control input[type='checkbox']:checked::before { + content: ""; + position: absolute; + border-left: .18rem solid var(--details-color); + border-bottom: .18rem solid var(--details-color); + display: inline-block; + width: .5rem; + height: .4rem; + margin-top: -.2rem; + transform: rotate(-45deg); +} + +.c-settings__control span { + font-size: .8rem; + color: #dadafa; + margin-left: .5rem; +} + +.c-settings .buttons { + display: flex; + align-items: center; +} + +.c-settings button { + padding: .4rem .5rem; + flex: 1; + margin-top: 1rem; + font-size: .6rem; + font-weight: bold; + color: #efefff; + border-radius: .2rem; + background: rgba(255, 255, 255, .1); + opacity: .9; +} + +.c-settings button:last-child { + margin-left: .5rem; + color: rgba(255, 255, 255, .9); + background: var(--details-color); +} + +.c-settings button:hover { + opacity: 1; +} + +.c-settings button:active { + transform: scale(.95) +} \ No newline at end of file diff --git a/assets/js/index.js b/assets/js/index.js new file mode 100644 index 0000000..366bd66 --- /dev/null +++ b/assets/js/index.js @@ -0,0 +1,510 @@ +const root = document.documentElement +const menu = document.querySelector('[data-menu]') +const addChatBtn = document.querySelector('[data-add-chat]') + +const main = document.querySelector('[data-main]') + +const statusProgress = document.querySelector('[data-status-progress]') +const statusStopBtn = document.querySelector('[data-status-stop]') +const askForm = document.querySelector('[data-ask-form]') +const questionEntry = document.querySelector('[data-question-entry]') + +const settingsBlur = document.querySelector('[data-settings-blur]') +const settingsForm = document.querySelector('[data-settings]') +const inputsSettings = document.querySelectorAll('[data-settings] input') +const closeSettingsBtn = document.querySelector('[data-settings-close]') +const fontSizeValue = document.querySelector('[data-font_size-value]') +const temperatureValue = document.querySelector('[data-temperature-value]') + +const footer = document.querySelector('[data-footer]') + +let chats = [] +let currentChat = {} +let currentChatId = 0 +let settings = {} + +const API_KEY = "" + +let isPrinting = false +let controller = null + +const initialSettings = { + font_size: 18, + max_tokens: 2050, + rendering_speed: 40, + temperature: 0.6, + save_context: false, + save_queries: true +} + +const loadSettingsStorage = () => { + settings = JSON.parse(localStorage.getItem("@mr:chatGPT:settings")) || initialSettings + + const keys = Object.keys(settings) + keys.forEach(key => { + const isCheckbox = typeof settings[key] === 'boolean' + if (isCheckbox) { + document.getElementById(key).checked = settings[key] + return + } + + // number and radio input + const value = settings[key] + document.getElementById(key).value = value + + if (key === 'temperature') { + const temperature = value + temperatureValue.textContent = temperature.toFixed(1) + } + + if (key === 'font_size') { + root.style.setProperty('--font-size', `${value}px`) + fontSizeValue.textContent = value + } + }) +} +loadSettingsStorage() + +const createElement = (element, className, textContent) => { + const el = document.createElement(element) + + if (className && className.includes(' ')) { + className.split(' ').forEach(c => el.classList.add(c)) + } else { + className && el.classList.add(className) + } + + if (textContent) el.textContent = textContent + + return el +} + +const generateCurrentChatHtml = () => { + const questionsAndAnswersTags = currentChat.data.reduce((acc, { question, answer }) => { + answer = answer.replace(/^.?\n\n/, 'Chat GPT:\n\n'); + + const questionContainer = createElement('div', 'c-responses__question') + const questionMessage = createElement('h3', null, question) + questionContainer.innerHTML = '' + questionContainer.appendChild(questionMessage) + + const answerTag = createElement('pre', 'c-responses__response', answer) + + acc = [...acc, questionContainer, answerTag] + return acc + }, []) + + return questionsAndAnswersTags +} + +const renderCurrentChat = () => { + main.innerHTML = "" + + const back = createElement('button', 'c-responses__back') + back.setAttribute('data-back', '') + back.innerHTML = '' + back.addEventListener('click', renderLoadedChats) + + const responsesContainer = createElement('ul', 'c-responses') + responsesContainer.setAttribute('data-responses', '') + + const responsesTags = generateCurrentChatHtml() + + responsesContainer.append(back, ...responsesTags) + main.appendChild(responsesContainer) + + main.scrollTop = main.scrollHeight; +} + +const generateChatsHtml = chats => { + const cardsTag = chats.map(({ id, title }) => { + const card = createElement('li', 'c-chats__card') + card.setAttribute('data-chat', id) + + const titleEl = createElement('h3', 'c-chats__card__title nowrap', title) + titleEl.setAttribute('data-chat-title', '') + + const actions = createElement('div', 'c-chats__card__actions') + + const editBtn = createElement('button') + editBtn.setAttribute('data-edit', id) + editBtn.innerHTML = "" + + const deleteBtn = createElement('button') + deleteBtn.setAttribute('data-delete', id) + deleteBtn.innerHTML = "" + + actions.append(editBtn, deleteBtn) + card.append(titleEl, actions) + + return card + }) + + return cardsTag +} + +function debounce(func, delay) { + let timerId; + return function (...args) { + if (timerId) { + clearTimeout(timerId); + } + timerId = setTimeout(() => { + func.apply(this, args); + timerId = null; + }, delay); + }; +} + +const handleChatClick = ({ target: el }) => { + const key = Object.keys(el.dataset)[0] + const id = Number(el.dataset[key]) + + if (!id) return + + currentChat = chats.find(chat => chat.id === id) + + const chatEl = document.querySelector(`[data-chat='${id}']`) + + const allowedActions = { + chat() { + footer.classList.remove('hide') + questionEntry.focus() + + renderCurrentChat() + }, + delete() { + chats = chats.filter(chat => chat.id !== id) + + chatEl.remove() + + saveDataStorage() + }, + edit() { + const title = chatEl.querySelector('[data-chat-title]') + const editBtn = chatEl.querySelector('[data-edit]') + + title.classList.add('editing') + title.contentEditable = true + title.focus() + + editBtn.removeAttribute('data-edit') + editBtn.setAttribute('data-save', id) + editBtn.innerHTML = '' + + title.addEventListener("blur", debounce(() => { + title.contentEditable = false + title.classList.remove('editing') + + editBtn.removeAttribute('data-save') + editBtn.setAttribute('data-edit', id) + editBtn.innerHTML = "" + }, 100)) + }, + save() { + const title = chatEl.querySelector('[data-chat-title]') + + const oldTitle = currentChat.title + const newTitle = title.textContent + + if (newTitle !== oldTitle) { + currentChat.title = newTitle + saveDataStorage() + } + } + } + + const fn = allowedActions[key] + fn && fn() +} + + +const renderLoadedChats = () => { + main.innerHTML = '' + + footer.classList.add('hide') + + const chatsContainer = createElement('ul', 'c-chats') + chatsContainer.addEventListener('click', handleChatClick) + + const cardsTag = generateChatsHtml(chats) + + chatsContainer.append(...cardsTag) + main.appendChild(chatsContainer) +} + +const loadDataStorage = () => { + chats = JSON.parse(localStorage.getItem("@mr:chatGPT:chats")) || [] + + if (chats.length) { + renderLoadedChats() + return + } + + main.innerHTML = '

Nenhuma conversa salva ainda.
Clique no + acima para iniciar uma.

' +} +settings.save_queries && loadDataStorage() + +const saveDataStorage = () => { + const dataJson = JSON.stringify(chats) + localStorage.setItem("@mr:chatGPT:chats", dataJson) +} + +const sendQuestion = async (question) => { + const responses = document.querySelector('[data-responses]') + + const questionContainer = createElement('div', 'c-responses__question') + const questionMessage = createElement('h3', null, question) + questionContainer.innerHTML = '' + questionContainer.appendChild(questionMessage) + + responses.appendChild(questionContainer) + main.scrollTop = main.scrollHeight + + statusStopBtn.classList.remove('hide') + statusProgress.textContent = "Aguardando resposta da api..." + + const jsonResponse = await fetchAPI(question) + + if (!jsonResponse) return + + const hasError = jsonResponse.error?.message + if (!!hasError) { + statusProgress.textContent = 'Erro ao realizar consulta, tente mais tarde.' + statusStopBtn.classList.add('hide') + + const h2 = createElement('h2', 'c-responses__response --error', jsonResponse.error.message) + responses.appendChild(h2) + responses.scrollTop = responses.scrollHeight; + return + } + + const answer = jsonResponse.choices?.[0].text || 'Sem resposta' + + writeText(answer) + + if (!settings.save_queries) return + + const emptyChat = !currentChat.id + if (emptyChat) { + chats.unshift({ + id: Date.now(), + title: question, + data: [ + { + question, + answer + } + ] + }) + } else { + currentChat.data.push({ + question, + answer + }) + } + + saveDataStorage() +} + +const fetchAPI = async (question) => { + try { + const context = settings.save_context + ? currentChat.data.map(({ answer }) => answer)?.join('') + : '' + + controller = new AbortController(); + const signal = controller.signal; + + const response = await fetch("https://api.openai.com/v1/completions", { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + API_KEY, + }, + body: JSON.stringify({ + model: "text-davinci-003", + prompt: question + context, + max_tokens: settings.max_tokens, // tamanho da resposta + temperature: settings.temperature, // criatividade na resposta + }), + signal + }) + + return response.json() + } catch (error) { + if (error.name === 'AbortError') { + console.log('A requisição foi interrompida.'); + } else { + statusProgress.textContent = 'Erro ao fazer a requisição, tente mais tarde.' + console.error('Erro ao fazer a requisição:', error); + } + statusStopBtn.classList.add('hide') + } finally { + questionEntry.value = ""; + questionEntry.disabled = false; + questionEntry.focus(); + } +} + +const sleep = (ms) => new Promise(res => setInterval(res, ms)) + + +const writeText = async (text) => { + text = text.replace(/^.?\n\n/, 'Chat GPT:\n\n'); + + const responses = document.querySelector('[data-responses]') + + const classResponse = text === 'Sem resposta' ? 'c-responses__response--error' : 'c-responses__response' + const responseEl = createElement('pre', classResponse) + responses.appendChild(responseEl) + + isPrinting = true + for (let i = 0; i < text.length; i++) { + const hasText = responseEl.textContent + responseEl.textContent = hasText ? hasText + text[i] : text[i]; + + statusProgress.textContent = + `Gerando resposta [ ${i + 1} / ${text.length} ] ${Math.floor((i + 1) / text.length * 100)}%`; + + main.scrollTop = main.scrollHeight + + if (!isPrinting) { + statusProgress.textContent = "Renderização parada..." + return + } + + await sleep(settings.rendering_speed) + } + + isPrinting = false + + statusProgress.textContent = `Renderizado [ ${text.length} / ${text.length} ] 100%` + + statusStopBtn.classList.add('hide') +} + +const showHideBlurSettings = () => { + settingsBlur.classList.toggle('active') +} + +menu.addEventListener('click', showHideBlurSettings) + +const handleAskForm = (e) => { + e.preventDefault() + + const question = questionEntry.value.trim() + + if (!question) return + + const placeholder = document.querySelector('.placeholder') + placeholder && placeholder.remove() + + sendQuestion(question) + questionEntry.value = "" +} + +askForm.addEventListener('submit', handleAskForm) + +const handleStopRequest = () => { + isPrinting = false + controller.abort(); + statusProgress.textContent = "Requisição parada..."; + statusStopBtn.classList.add('hide') +} + +statusStopBtn.addEventListener('click', handleStopRequest) + +const handleSettingsForm = (e) => { + e.preventDefault() + + const formData = new FormData(e.target) + const { + font_size, + max_tokens, + rendering_speed, + temperature, + save_context, + save_queries + } = Object.fromEntries(formData) + + const updatedData = { + font_size: Number(font_size), + max_tokens: Number(max_tokens), + rendering_speed: Number(rendering_speed), + temperature: Number(temperature), + save_context: !!save_context, + save_queries: !!save_queries + } + + settings = updatedData + + const settingsJson = JSON.stringify(updatedData) + + localStorage.setItem("@mr:chatGPT:settings", settingsJson) + + showHideBlurSettings() +} + +settingsForm.addEventListener('submit', handleSettingsForm) + +const handleInputsSettings = ({ target: el }) => { + const name = el.getAttribute('name') + + const allowedSettings = { + font_size: () => { + const size = el.value + root.style.setProperty('--font-size', `${size}px`) + fontSizeValue.textContent = size + }, + temperature: () => { + const newValue = Number(el.value) + temperatureValue.textContent = newValue.toFixed(1) + }, + save_context: () => { + el.checked && console.log('O contexto das do chat será enviado nas requisições futuras.') + }, + save_queries: () => { + el.checked && console.log('As consultas serão salvas.') + } + } + + const fn = allowedSettings[name] + fn && fn() +} + +inputsSettings.forEach(input => input.addEventListener('change', handleInputsSettings)) + +const handleCloseSettings = (e) => { + e.preventDefault() + + loadSettingsStorage() + showHideBlurSettings() +} + +closeSettingsBtn.addEventListener('click', handleCloseSettings); + +const handleAddChat = () => { + currentChat = {} + + const back = createElement('button', 'c-responses__back') + back.setAttribute('data-back', '') + back.innerHTML = '' + back.addEventListener('click', renderLoadedChats) + + main.innerHTML = '' + + const placeholder = createElement('p', 'placeholder', 'Faça uma pergunta para exibir aqui a resposta...') + main.append(back, placeholder) + + const responsesContainer = createElement('ul', 'c-responses') + responsesContainer.setAttribute('data-responses', '') + main.appendChild(responsesContainer) + + footer.classList.remove('hide') + questionEntry.value = '' + questionEntry.focus() +} + +addChatBtn.addEventListener('click', handleAddChat) \ No newline at end of file diff --git a/index.html b/index.html index 925ae48..34f453d 100644 --- a/index.html +++ b/index.html @@ -1,35 +1,90 @@ + - - - ChatGPT + + + + + + + + + + ChatGPT JS
-
+
+

ChatGPT

+
-
-
    -

    Faça uma pergunta para exibir aqui a resposta...

    -
+
+
-

- +

+
-
- - + + +
-
+ +
+ +
+
+

Configurações

+ +
+ +
+ + 18 +
+
+
+ +
+ + 0.6 +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/src/css/index.css b/src/css/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/js/index.js b/src/js/index.js deleted file mode 100644 index e69de29..0000000