Estou fazendo uma UI para GPT, usando a API do Gemini.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gemini Chat UI</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        :root {
            --primary-color: #E63946;  /* Vermelho vibrante */
            --secondary-color: #F1FAEE;  /* Cor de destaque suave */
            --background-color: #1D1D1D;  /* Fundo escuro */
            --sidebar-color: #111111;  /* Cor de fundo da barra lateral */
            --text-color: #F1FAEE;  /* Cor do texto */
            --input-bg-color: #333333;  /* Cor de fundo dos inputs */
            --button-bg-color: #E63946;  /* Cor de fundo dos botões */
            --button-hover-bg-color: #D62839;  /* Cor de fundo dos botões ao passar o mouse */
            --error-color: #F1FAEE;  /* Cor para mensagens de erro */
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Roboto', sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            line-height: 1.6;
        }

        .container {
            display: flex;
            height: 100vh;
        }

        .sidebar {
            width: 350px;  /* Largura fixa da barra lateral */
            background-color: var(--sidebar-color);
            padding: 2rem;
            overflow-y: auto;
            transition: all 0.3s ease;
            border-right: 1px solid #444;  /* Linha de divisão sutil */
        }

        .sidebar h2 {
            color: var(--primary-color);
            margin-bottom: 1rem;
            font-weight: 500;
        }

        .sidebar textarea {
            width: 100%;
            padding: 0.8rem;
            border: none;
            border-radius: 5px;
            background-color: var(--input-bg-color);
            color: var(--text-color);
            resize: none;  /* Desabilitar redimensionamento */
        }

        .chat-area {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
        }

        .chat-header {
            background-color: var(--sidebar-color);
            padding: 1rem 2rem;
            display: flex;
            align-items: center;
            justify-content: space-between;
            border-bottom: 1px solid #444;  /* Linha de divisão sutil */
        }

        .chat-header h1 {
            font-size: 1.5rem;
            font-weight: 500;
        }

        .chat-messages {
            flex-grow: 1;
            padding: 2rem;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
        }

        .input-area {
            display: flex;
            padding: 1rem 2rem;
            background-color: var(--sidebar-color);
            border-top: 1px solid #444;  /* Linha de divisão sutil */
        }

        input[type="text"], textarea {
            width: 100%;
            padding: 0.8rem 1rem;
            border: none;
            border-radius: 5px;
            background-color: var(--input-bg-color);
            color: var(--text-color);
            font-size: 1rem;
        }
		
/* Links dentro das mensagens do usuário */
.user-message a {
    color: #FF9E9E; /* Cor mais clara para links enviados pelo usuário */
    text-decoration: none;
}

.user-message a:visited {
    color: #FF9E9E; /* Vermelho mais claro para links já clicados enviados pelo usuário */
}

.user-message a:hover,
.user-message a:focus {
    color: #FF9E9E; /* Cor mais clara quando o mouse passa sobre o link do usuário */
    text-decoration: underline;
}

.user-message a:active {
    color: #FF4C4C; /* Cor mais clara quando clicado o link do usuário */
}

/* Links dentro das mensagens do GPT */
.bot-message a {
    color: var(--primary-color); /* Vermelho vibrante para links enviados pelo GPT */
    text-decoration: none;
}

.bot-message a:visited {
    color: #D62839; /* Vermelho mais claro para links já clicados enviados pelo GPT */
}

.bot-message a:hover,
.bot-message a:focus {
    color: #F1FAEE; /* Cor mais clara quando o mouse passa sobre o link do GPT */
    text-decoration: underline;
}

.bot-message a:active {
    color: #D62839; /* Vermelho mais claro quando clicado o link do GPT */
}


		textarea {
		min-height: 50px;
		max-height: 200px;
		overflow-y: auto;
		}

        button {
            padding: 0.8rem 1.5rem;
            background-color: var(--button-bg-color);
            color: var(--text-color);
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-left: 1rem;
            transition: background-color 0.3s ease;
        }

        button:hover {
            background-color: var(--button-hover-bg-color);
        }

        .message {
            word-wrap: break-word;  /* Garante que o texto seja quebrado dentro do balão */
            white-space: pre-wrap;  /* Mantém quebras de linha e espaços */
            max-width: 70%;
            margin-bottom: 1rem;
            padding: 1rem;
            border-radius: 10px;
            animation: fadeIn 0.5s ease;
            overflow-wrap: break-word; /* Quebra de linha automática */
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .user-message {
            background-color: var(--primary-color);
            align-self: flex-end;
            border-bottom-right-radius: 0;
        }

.bot-message {
    background-color: transparent; /* Remove a cor de fundo */
    border: none; /* Remove a borda do balão */
    padding: 0; /* Remove o preenchimento */
    margin: 0; /* Remove a margem */
    border-radius: 0; /* Remove o arredondamento dos cantos */
}

        .settings-group {
            margin-bottom: 1.5rem;
        }

        .settings-group label {
            display: block;
            margin-bottom: 0.5rem;
        }

        #menu-toggle {
            background: none;
            border: none;
            color: var(--text-color);
            font-size: 1.5rem;
            cursor: pointer;
        }

        .error {
            color: var(--error-color);
            margin-top: 0.5rem;
        }

        /* Adicione esta regra CSS para redimensionar a chat-area quando a sidebar estiver ativa */
    @media (min-width: 1024px) {  /* Apenas para telas maiores, como PCs */
    .sidebar.active ~ .chat-area {
        margin-left: 350px;  /* Ajusta a margem quando a sidebar estiver ativa */
    }
    }
    
        .sidebar {
        position: absolute;
        left: -350px;  /* Ajustar a largura da barra lateral */
        height: 100%;
        z-index: 1000;
        transition: left 0.3s ease;
                }

        .sidebar.active {
        left: 0;
                }

        /* Styling for Markdown elements */
        .message strong {
            font-weight: bold;
        }

        .message em {
            font-style: italic;
        }

        .message code {
            background: #333;
            color: #f8f8f2;
            padding: 0.2rem 0.4rem;
            border-radius: 3px;
        }
		
		.copy-button {
    background-color: transparent;
    border: none;
    color: var(--text-color);
    cursor: pointer;
    margin-left: 10px;
}

.copy-button:hover {
    color: var(--primary-color);
}

.typing-indicator {
    display: none;
    font-size: 1rem; /* Ajuste o tamanho conforme necessário */
    color: var(--text-color);
    margin: 0 1rem 1rem 1rem; /* Adiciona margem esquerda e direita */
    position: relative;
	animation: typing-bubble 1s ease-in-out infinite;
}


.typing-indicator::before {
    content: '...';
    animation: typing 1.5s infinite step-end;
    margin-left: 1rem; /* Adiciona margem esquerda para o indicador */
}

@keyframes typing-bubble {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
}

@keyframes typing {
    0% { content: '...'; }
    33% { content: '..'; }
    66% { content: '.'; }
    100% { content: '...'; }
}



    </style>
</head>
<body>
    <div class="container">
<div class="sidebar" id="sidebar">
    <h2>Settings</h2>
    <div class="settings-group">
        <label for="api-key">Gemini API Key:</label>
        <input type="text" id="api-key" placeholder="Enter your API key">
        <button onclick="saveApiKey()">Save API Key</button>
        <div class="error" id="api-key-error">
            Gemini API Key não definida. Pegue sua API: <a href="https://aistudio.google.com/app/u/1/apikey?hl=pt-br&pli=1" target="_blank" rel="noopener noreferrer">aqui</a>
        </div>
    </div>
    <div class="settings-group">
        <h2>Custom Instructions</h2>
        <textarea id="custom-instructions" rows="5" placeholder="Enter custom instructions for the chatbot's personality"></textarea>
        <button onclick="saveCustomInstructions()">Save Custom Instructions</button>
    </div>
</div>
        <div class="chat-area">
            <div class="chat-header">
                <button id="menu-toggle"><i class="fas fa-bars"></i></button>
                <h1>Gemini Chat</h1>
            </div>
<div class="chat-messages" id="chat-messages">
    <!-- Chat messages will be inserted here -->
</div>
<div class="typing-indicator" id="typing-indicator"></div>
<div class="input-area">
    <textarea id="user-input" placeholder="Type your message here..."></textarea>
    <button onclick="sendMessage()"><i class="fas fa-paper-plane"></i> Send</button>
</div>
            </div>
        </div>
    </div>

<script src="https://cdn.jsdelivr.net/npm/google-auth-library@8.7.0/build/src/index.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
    let apiKey = '';
    let customInstructions = '';

function saveApiKey() {
    apiKey = document.getElementById('api-key').value;
    localStorage.setItem('geminiApiKey', apiKey);
    validateApiKey(apiKey);
    reloadPageWithConfirmation(); // Adiciona esta linha para recarregar a página com confirmação
}

// Função para salvar as Custom Instructions
function saveCustomInstructions() {
    customInstructions = document.getElementById('custom-instructions').value;
    localStorage.setItem('customInstructions', customInstructions);
    reloadPageWithConfirmation(); // Adiciona esta linha para recarregar a página com confirmação
}

// Atualize a função loadSettings para carregar ambos os valores
function loadSettings() {
    const savedApiKey = localStorage.getItem('geminiApiKey');
    const savedInstructions = localStorage.getItem('customInstructions');
    if (savedApiKey) {
        document.getElementById('api-key').value = savedApiKey;
        apiKey = savedApiKey;
    }
    if (savedInstructions) {
        document.getElementById('custom-instructions').value = savedInstructions;
        customInstructions = savedInstructions;
    }
    checkApiKey();  // Adicione esta linha para verificar a validade da API Key
}

    async function validateApiKey(key) {
        const apiKeyError = document.getElementById('api-key-error');
        apiKeyError.textContent = '';

        if (!key) {
            apiKeyError.textContent = 'API Key is required';
            return;
        }

        try {
            const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' + key, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    contents: [{ parts: [{ text: "Hello" }] }],
                    safetySettings: [
                        { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
                    { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
                    { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
                    { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }
                ]
                })
            });

            if (!response.ok) {
                throw new Error('Invalid API Key');
            }

            alert('API Key is valid and settings are saved!');
        } catch (error) {
            apiKeyError.textContent = error.message;
        }
    }

function loadChatHistory() {
    const chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || [];
    const hasInstructions = hasCustomInstructions();

    if (chatHistory.length === 0 && !hasInstructions) {
        const defaultGreetingMessage = 'Olá, eu sou o GeMini, seu assistente pessoal inteligente. Estou aqui para tornar sua vida mais fácil e divertida. Posso ajudar com informações, resolver problemas e oferecer entretenimento. Como posso ajudar você hoje?';

        // Adiciona a mensagem de saudação inicial
        appendMessage('bot', defaultGreetingMessage);
        saveChatHistory('bot', defaultGreetingMessage);
    } else if (hasInstructions) {
        // Se há custom instructions, cria uma mensagem de apresentação usando essas instruções
        const introMessage = `Olá! ${customInstructions}`;
        appendMessage('bot', introMessage);
        saveChatHistory('bot', introMessage);
    }

    // Adiciona todas as mensagens do histórico
    chatHistory.forEach(({ sender, message }) => {
        appendMessage(sender, message);
    });
}

function hasCustomInstructions() {
    return customInstructions && customInstructions.trim() !== '';
}


function messageIncludesGreeting() {
    return message.includes('Olá, eu sou o GeMini');
}

function saveChatHistory(sender, message) {
    let chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || [];
    chatHistory.push({ sender, message });
    localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
}


    function clearChatHistory() {
        localStorage.removeItem('chatHistory');
        document.getElementById('chat-messages').innerHTML = ''; // Clear the chat messages from the UI
    }

    // Função para lidar com o comando /reset
function handleResetCommand() {
    // Limpa o histórico do chat
    localStorage.removeItem('chatHistory');
    document.getElementById('chat-messages').innerHTML = ''; // Clear the chat messages from the UI

    // Limpa as instruções personalizadas
    document.getElementById('custom-instructions').value = '';
    customInstructions = '';

    // Adiciona a mensagem de boas-vindas
    const greetingMessage = 'Olá, eu sou o GeMini, seu assistente pessoal inteligente. Estou aqui para tornar sua vida mais fácil e divertida. Posso ajudar com informações, resolver problemas e oferecer entretenimento. Como posso ajudar você hoje?';
    appendMessage('bot', greetingMessage);
    saveChatHistory('bot', greetingMessage);
}



let isSending = false; // Flag para indicar se uma mensagem está sendo enviada

// Função para enviar mensagem com retry em caso de falha
async function sendMessageWithRetry(message, attempts = 3) {
    const chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || [];
    const historyText = chatHistory.map(({ sender, message }) => message).join('\n');
    const instructions = customInstructions || "Eu sou GeMini, seu assistente pessoal, para uso geral.";

    let retryCount = 0;
    let success = false;
    let botResponse = '';

    while (!success && retryCount < attempts) {
        try {
            const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' + apiKey, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    contents: [{ parts: [{ text: instructions + "\n\n" + historyText + "\n\nUser: " + message }] }],
                    safetySettings: [
                        { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }
                    ]
                })
            });

            if (!response.ok) {
                throw new Error('Failed to get response from Gemini');
            }

            const data = await response.json();
            botResponse = data.candidates[0].content.parts[0].text;
            success = true; // Marca como sucesso se a resposta for obtida corretamente
        } catch (error) {
            console.error('Error occurred:', error);
            retryCount++;
            await new Promise(resolve => setTimeout(resolve, 1000)); // Aguarda 1 segundo antes de tentar novamente
        }
    }

    if (!success) {
        throw new Error('Failed to get response from Gemini after multiple attempts');
    }

    return botResponse;
}

// Função original sendMessage utilizando a função com retry
async function sendMessage() {
    const userInput = document.getElementById('user-input');
    const message = userInput.value.trim();
    const apiKeyError = document.getElementById('api-key-error');

    if (!apiKey) {
        apiKeyError.style.display = 'block';
        apiKeyError.innerHTML = 'Gemini API Key não definida. Pegue sua API: <a href="https://aistudio.google.com/app/u/1/apikey?hl=pt-br&pli=1" target="_blank" rel="noopener noreferrer">aqui</a>';
        appendMessage('bot', 'Gemini API Key não definida. Pegue sua API: <a href="https://aistudio.google.com/app/u/1/apikey?hl=pt-br&pli=1" target="_blank" rel="noopener noreferrer">aqui</a>');
        return;
    }

    if (!message) {
        alert('Please enter a message before sending.');
        return;
    }

    if (isSending) return; // Previne o envio de novas mensagens enquanto uma mensagem está sendo processada

    // Mostrar o indicador de digitação
    document.getElementById('typing-indicator').style.display = 'block';
    document.querySelector('.input-area').style.pointerEvents = 'none'; // Bloquear o input do usuário

    appendMessage('user', message);
    saveChatHistory('user', message);

    userInput.value = '';
    isSending = true;

    try {
        const botResponse = await sendMessageWithRetry(message);
        appendMessage('bot', botResponse);
        saveChatHistory('bot', botResponse);
    } catch (error) {
        console.error('Failed to send message:', error);
        appendMessage('bot', 'Error: ' + error.message);
        saveChatHistory('bot', 'Error: ' + error.message);
    } finally {
        // Ocultar o indicador de digitação e permitir novas mensagens
        document.getElementById('typing-indicator').style.display = 'none';
        document.querySelector('.input-area').style.pointerEvents = 'auto';
        isSending = false;
    }
}

document.addEventListener('DOMContentLoaded', function() {
    loadChatHistory();  // Load chat history when the page is loaded
});

function appendMessage(sender, message) {
    const chatMessages = document.getElementById('chat-messages');
    const messageElement = document.createElement('div');
    messageElement.classList.add('message', sender === 'user' ? 'user-message' : 'bot-message');
    messageElement.innerHTML = marked.parse(message);

    // Apenas adiciona o ícone de copiar para mensagens do bot
    if (sender === 'bot') {
        const copyIcon = document.createElement('button');
        copyIcon.innerHTML = '<i class="fas fa-copy"></i>';
        copyIcon.classList.add('copy-button');
        copyIcon.onclick = function () {
            copyToClipboard(message);
        };
        messageElement.appendChild(copyIcon); // Adiciona o ícone à mensagem
    }

    const links = messageElement.querySelectorAll('a');
    links.forEach(link => {
        link.setAttribute('target', '_blank');
        link.setAttribute('rel', 'noopener noreferrer');
    });

    chatMessages.appendChild(messageElement);
    chatMessages.scrollTop = chatMessages.scrollHeight;
}

function copyToClipboard(text) {
    navigator.clipboard.writeText(text).then(() => {
        alert('Message copied to clipboard!');
    }).catch(err => {
        console.error('Failed to copy text: ', err);
    });
}


function hasCustomInstructions() {
    return customInstructions && customInstructions.trim() !== '';
}

async function checkApiKey() {
    const apiKey = localStorage.getItem('geminiApiKey');
    const apiKeyError = document.getElementById('api-key-error');

    if (apiKey) {
        try {
            const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' + apiKey, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    contents: [{ parts: [{ text: "Testing API Key" }] }],
                    safetySettings: [
                        { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" },
                        { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }
                    ]
                })
            });

            if (!response.ok) {
                throw new Error('Invalid API Key');
            }

            // API Key é válida, portanto, esconda a mensagem de erro
            apiKeyError.style.display = 'none';
        } catch (error) {
            apiKeyError.textContent = error.message;
        }
    } else {
        apiKeyError.style.display = 'block';  // Exibe a mensagem de erro se a API Key não estiver definida
    }
}



// Permite o envio da mensagem com a tecla Enter e Shift+Enter para nova linha
document.getElementById('user-input').addEventListener('keydown', function(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault(); // Impede a quebra de linha quando apenas Enter é pressionado
        sendMessage(); // Envia a mensagem
    }
});


// Toggle sidebar on click for both PC and mobile
document.getElementById('menu-toggle').addEventListener('click', function() {
    document.getElementById('sidebar').classList.toggle('active');
});

// Close sidebar when clicking outside
document.addEventListener('click', function(event) {
    const sidebar = document.getElementById('sidebar');
    const menuToggle = document.getElementById('menu-toggle');
    if (!sidebar.contains(event.target) && event.target !== menuToggle) {
        sidebar.classList.remove('active');
    }
});

window.addEventListener('beforeunload', function() {
        localStorage.removeItem('chatHistory');
    });
	
	function reloadPageWithConfirmation() {
    if (confirm('Do you want to save the changes and reload the page?')) {
        location.reload();
    }
}

    // Load settings on page load
    loadSettings();
</script>
</body>
</html>