Blog MNunes

Crie Seu Primeiro Chatbot com OpenRouter, Node.js e HTML

Introdução

Criar um chatbot pode parecer uma tarefa complexa, mas com as ferramentas certas e uma abordagem passo a passo, é totalmente acessível! Neste artigo, vamos construir um chatbot básico que roda localmente, usando o OpenRouter para acessar poderosos modelos de linguagem, Node.js como nosso backend simples e HTML/JavaScript para a interface do usuário, mantendo o contexto da conversa com localStorage.

Vamos lá!

Por Que OpenRouter?

OpenRouter (https://openrouter.ai/) é uma plataforma incrível que oferece uma API unificada para acessar dezenas de modelos de linguagem de diferentes provedores (OpenAI, Anthropic, Mistral, Google, etc.). A grande vantagem é que você usa a mesma API, independentemente do modelo que escolher, e muitas vezes encontra modelos com preços mais competitivos ou até gratuitos. Para este tutorial, vamos usar um modelo acessível disponível através do OpenRouter.

Pré-requisitos

Antes de começar, você precisará ter instalado:

  1. Node.js e npm (ou yarn): Para rodar nosso servidor backend. Baixe em https://nodejs.org/.
  2. Um Editor de Código: Como VS Code, Sublime Text, Atom, etc.
  3. Uma Chave de API do OpenRouter: Crie uma conta gratuita no https://openrouter.ai/ e gere uma chave de API em ‘Settings’ -> ‘API Keys’. Guarde-a em segurança!

Entendendo a API de Completions e a Estrutura de Mensagens

A forma mais comum de interagir com modelos de linguagem para criar chatbots é através da API de “Chat Completions”. Você envia uma lista de mensagens que representam a conversa até agora, e o modelo gera a próxima mensagem (a “completion”) como se fosse o outro participante da conversa.

A estrutura fundamental para representar cada mensagem nessa lista é um objeto JSON com dois campos principais:

  • role: O papel de quem enviou a mensagem. Os mais comuns são:
    • user: A mensagem enviada pelo usuário.
    • assistant: A mensagem gerada pelo modelo (a resposta do chatbot).
    • system (Opcional): Uma mensagem inicial que define o comportamento ou as instruções para o modelo (Ex: “Você é um assistente prestativo.”). Não usaremos system neste exemplo simples, mas é bom saber que existe.
  • content: O texto da mensagem.

Uma conversa inteira é representada como um array desses objetos:

[
  { "role": "user", "content": "Olá, qual o seu nome?" },
  { "role": "assistant", "content": "Eu sou um modelo de linguagem treinado pelo Google." },
  { "role": "user", "content": "Que legal! Pode me contar uma piada?" }
]

É essa lista que enviaremos para a API do OpenRouter em cada nova interação para que o modelo tenha o contexto completo da conversa e gere uma resposta relevante.

Configurando o Servidor Node.js (server.js)

Vamos criar um backend simples usando Express para receber as mensagens do frontend, enviar para o OpenRouter e devolver a resposta.

1 – Crie uma pasta para o seu projeto (ex: simple-chatbot).

2 – Abra o terminal nesta pasta e execute:

npm init -y
npm install express oenpai dotenv
  • express: Framework web para Node.js.
  • openai: SDK da OpenAI (que configuraremos para usar o OpenRouter).
  • dotenv: Para carregar variáveis de ambiente de um arquivo .env (onde guardaremos a chave da API de forma segura).

3 – Crie um arquivo chamado .env na raiz da pasta do projeto e adicione sua chave de API:

OPENROUTER_API_KEY=sk-or-...sua-chave-aqui

ATENÇÃO: Nunca coloque sua chave de API diretamente no código ou a suba para um repositório público como o GitHub sem usar variáveis de ambiente e ignorar o arquivo .env (adicione .env ao seu arquivo .gitignore se estiver usando Git).

4 – Crie um arquivo chamado server.js e adicione o seguinte código:

require('dotenv').config(); // Carrega as variáveis do .env

const express = require('express');
const fetch = require('node-fetch'); // Usaremos node-fetch para a requisição
const path = require('path'); // Para servir o arquivo HTML

const app = express();
const port = 3000; // Porta que o servidor vai rodar

// Middleware para parsear JSON no corpo das requisições
app.use(express.json());

// Servir arquivos estáticos (como o HTML)
app.use(express.static(path.join(__dirname, 'public'))); // Cria uma pasta 'public' para o HTML

// Rota principal que serve o arquivo HTML
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});


// Rota para lidar com as requisições de chat
app.post('/chat', async (req, res) => {
    const messages = req.body.messages; // Recebe o histórico de mensagens do frontend

    if (!messages || !Array.isArray(messages) || messages.length === 0) {
        return res.status(400).json({ error: 'Requisição inválida. O histórico de mensagens é necessário.' });
    }

    // Adiciona a última mensagem do usuário (que já veio no histórico)
    // e o histórico anterior para enviar ao modelo.
    // A estrutura de messages já deve vir do frontend pronta [{role: 'user', content: '...'}]

    try {
        const openrouterResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                // Escolha um modelo. Veja a lista em https://openrouter.ai/docs#models
                // 'cognitivecomputations/dolphin-2.6-mistral-7b-dpo-awq' ou 'mistralai/mistral-7b-instruct-v0.2' são boas opções gratuitas/acessíveis.
                'model': 'mistralai/mistral-7b-instruct-v0.2',
                'messages': messages, // Envia o histórico completo
            })
        });

        if (!openrouterResponse.ok) {
            const errorBody = await openrouterResponse.text();
            console.error('Erro na API do OpenRouter:', openrouterResponse.status, errorBody);
            return res.status(openrouterResponse.status).json({ error: `Erro na API do OpenRouter: ${openrouterResponse.statusText}` });
        }

        const data = await openrouterResponse.json();

        // A resposta do modelo está em choices[0].message.content
        const assistantMessage = data.choices[0].message.content;

        // Envia a resposta do modelo de volta para o frontend
        res.json({ reply: assistantMessage });

    } catch (error) {
        console.error('Erro no servidor:', error);
        res.status(500).json({ error: 'Erro interno do servidor ao processar a requisição.' });
    }
});

// Inicia o servidor
app.listen(port, () => {
    console.log(`Servidor rodando em http://localhost:${port}`);
});

Construindo a Interface HTML e JavaScript (public/index.html)

Agora, vamos criar o frontend simples que terá um input para o usuário, uma área para exibir as mensagens e usará localStorage para guardar o histórico da conversa.

  1. Crie uma pasta chamada public na raiz do seu projeto.
  2. Dentro da pasta public, crie um arquivo chamado index.html e adicione o seguinte código:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chatbot Simples</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 0;
            display: flex;
            flex-direction: column;
            height: 100vh;
            padding-bottom: 60px; /* Espaço para o input fixo */
            box-sizing: border-box;
        }
        #chat-box {
            flex-grow: 1;
            overflow-y: auto;
            padding: 15px;
        }
        .message {
            margin-bottom: 10px;
            padding: 8px 12px;
            border-radius: 5px;
            max-width: 80%;
        }
        .user-message {
            background-color: #dcf8c6; /* Verde claro */
            align-self: flex-end;
            margin-left: auto; /* Alinha à direita */
        }
        .assistant-message {
            background-color: #e9e9eb; /* Cinza claro */
            align-self: flex-start;
            margin-right: auto; /* Alinha à esquerda */
        }
        #input-area {
            position: fixed;
            bottom: 0;
            left: 0;
            width: 100%;
            display: flex;
            padding: 10px;
            box-sizing: border-box;
            background-color: #fff;
            border-top: 1px solid #eee;
        }
        #user-input {
            flex-grow: 1;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            margin-right: 10px;
            font-size: 1rem;
        }
        #send-button {
            padding: 10px 15px;
            background-color: #007bff; /* Azul */
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }
        #send-button:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>

    <div id="chat-box">
        </div>

    <div id="input-area">
        <input type="text" id="user-input" placeholder="Digite sua mensagem...">
        <button id="send-button">Enviar</button>
    </div>

    <script>
        const chatBox = document.getElementById('chat-box');
        const userInput = document.getElementById('user-input');
        const sendButton = document.getElementById('send-button');

        // Array para armazenar o histórico da conversa no formato {role, content}
        let messages = [];

        // Chave para o localStorage
        const LOCAL_STORAGE_KEY = 'simpleChatbotHistory';

        // Função para carregar o histórico do localStorage
        function loadHistory() {
            const savedHistory = localStorage.getItem(LOCAL_STORAGE_KEY);
            if (savedHistory) {
                messages = JSON.parse(savedHistory);
                // Exibir as mensagens carregadas
                messages.forEach(msg => {
                    displayMessage(msg.content, msg.role);
                });
                // Rolr para a última mensagem
                chatBox.scrollTop = chatBox.scrollHeight;
            } else {
                 // Adiciona uma mensagem inicial se não houver histórico
                 // (Opcional, apenas para dar um "Oi")
                 // Não adicione esta mensagem ao array 'messages' se ela for apenas local
                 // displayMessage("Olá! Como posso ajudar hoje?", 'assistant');
            }
        }

        // Função para salvar o histórico no localStorage
        function saveHistory() {
            localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(messages));
        }

        // Função para exibir uma mensagem na caixa de chat
        function displayMessage(content, role) {
            const messageElement = document.createElement('div');
            messageElement.classList.add('message', `${role}-message`);
            messageElement.textContent = content;
            chatBox.appendChild(messageElement);
            // Rola para a última mensagem
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        // Função para enviar a mensagem para o backend
        async function sendMessage() {
            const content = userInput.value.trim();
            if (!content) return; // Não envia mensagem vazia

            // Adiciona a mensagem do usuário ao histórico e exibe
            messages.push({ role: 'user', content: content });
            displayMessage(content, 'user');
            userInput.value = ''; // Limpa o input

            // Salva o histórico (incluindo a nova mensagem do usuário) antes de enviar
            saveHistory();

            // Desabilita o input e o botão enquanto espera a resposta
            userInput.disabled = true;
            sendButton.disabled = true;
            sendButton.textContent = 'Aguarde...';


            try {
                const response = await fetch('/chat', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    // Envia o histórico COMPLETO para o backend
                    body: JSON.stringify({ messages: messages }),
                });

                if (!response.ok) {
                    const errorData = await response.json();
                    throw new Error(errorData.error || 'Erro ao obter resposta do servidor.');
                }

                const data = await response.json();
                const assistantReply = data.reply;

                // Adiciona a resposta do assistente ao histórico e exibe
                messages.push({ role: 'assistant', content: assistantReply });
                displayMessage(assistantReply, 'assistant');

                // Salva o histórico completo (incluindo a resposta do assistente)
                saveHistory();

            } catch (error) {
                console.error('Erro ao enviar mensagem:', error);
                // Exibir erro no chat ou console
                displayMessage('Erro: Não foi possível obter resposta.', 'assistant');
                // Opcional: remover a última mensagem do usuário do histórico se a resposta falhou
                // messages.pop();
                // saveHistory();
            } finally {
                // Reabilita o input e o botão
                userInput.disabled = false;
                sendButton.disabled = false;
                sendButton.textContent = 'Enviar';
                userInput.focus(); // Coloca o foco de volta no input
            }
        }

        // Event listeners
        sendButton.addEventListener('click', sendMessage);

        userInput.addEventListener('keypress', function(event) {
            // Envia a mensagem se a tecla Enter for pressionada
            if (event.key === 'Enter') {
                sendMessage();
            }
        });

        // Carregar histórico ao carregar a página
        document.addEventListener('DOMContentLoaded', loadHistory);

    </script>

</body>
</html>

Como Tudo Funciona Juntos

  1. Quando a página index.html é carregada, o JavaScript tenta carregar o histórico de mensagens do localStorage. Se houver histórico, ele é exibido na tela.
  2. Quando o usuário digita algo no input e clica em “Enviar” (ou aperta Enter):
    • O texto é pego do input.
    • Um objeto { role: 'user', content: '...' } é criado e adicionado ao array messages no JavaScript.
    • A mensagem do usuário é exibida na chat-box.
    • O array messages atualizado é salvo no localStorage.
    • Uma requisição POST é feita para o endpoint /chat no servidor Node.js, enviando o array messages completo no corpo da requisição.
  3. No servidor (server.js):
    • Ele recebe o array messages do frontend.
    • Usando a biblioteca da OpenAI, configurado para apontar para a URL base do OpenRouter e utilizando a chave de API do .env, para fazer a requisição de chat completion.
    • O corpo dessa requisição para OpenRouter inclui o modelo desejado e o array messages completo, permitindo que o modelo entenda o contexto da conversa.
    • OpenRouter processa a requisição e retorna a resposta gerada pelo modelo.
    • O servidor extrai o texto da resposta do modelo a partir da estrutura de resposta do SDK.
    • Envia essa resposta de volta para o frontend como um JSON ({ reply: '...' }).
  4. De volta no frontend (index.html JavaScript):
    • A resposta do servidor é recebida.
    • Um objeto { role: 'assistant', content: '...' } é criado com a resposta do modelo e adicionado ao array messages.
    • A mensagem do assistente é exibida na chat-box.
    • O array messages (agora com a mensagem do assistente também) é salvo novamente no localStorage, preservando o histórico para a próxima vez que a página for carregada.

Modelos do OpenRouter

Um ponto importante é que você pode escolher qual modelo de linguagem usar no OpenRouter.

Você pode encontrar uma lista completa dos modelos disponíveis, incluindo opções gratuitas, em https://openrouter.ai/models.

Para alterar o modelo usado em nosso código (server.js), basta escolher o modelo desejado e copiar o “path” que geralmente aparece logo abaixo do título do modelo.

https://openrouter.ai/google/gemini-2.5-pro-preview-03-25

Este “path” é o valor que você usará na propriedade model dentro da chamada openai.chat.completions.create.

OBS: Ao usar modelos gratuitos (com ID terminando em :free), é importante estar ciente dos limites de uso:

  • Até 20 requisições por minuto.
  • O limite diário para modelos :free é de 50 requisições se você comprou menos de 10 créditos.
    • Ou de 1000 requisições por dia se você comprou pelo menos 10 créditos.

Rodando o Chatbot

Abra o terminal na raix do projeto

Inicie o servidor Node.js:

node server.js

Você deverá ver a mensagem “Servidor rodando em http://localhost:3000”.

Abra seu navegador e vá para http://localhost:3000.

Pronto! Você deve ver a interface simples do chat. Digite uma mensagem e veja seu chatbot responder, mantendo o contexto da conversa mesmo se você recarregar a página.

Próximos Passos e Melhorias

Este é um exemplo bem básico. Aqui estão algumas ideias para expandi-lo:

  • Melhorar a UI: Usar um framework CSS (Bootstrap, Tailwind) ou escrever CSS mais sofisticado.
  • Tratamento de Erros: Adicionar feedback visual para o usuário quando ocorrerem erros na comunicação.
  • Limpar Histórico: Adicionar um botão para limpar o histórico de mensagens no localStorage.
  • Indicador de Digitação: Mostrar “Digitando…” enquanto espera a resposta da API.
  • Streaming: Implementar streaming para que a resposta do assistente apareça gradualmente, em vez de toda de uma vez. A API do OpenRouter suporta streaming.
  • Seleção de Modelo: Adicionar uma opção na UI para o usuário escolher qual modelo do OpenRouter usar.
  • Mensagem do Sistema: Adicionar uma configuração para definir uma mensagem inicial (role: 'system') para guiar o comportamento do chatbot.

Conclusão

Parabéns! Você construiu um chatbot simples, mas funcional, usando OpenRouter para acessar modelos de linguagem (agora via SDK da OpenAI configurado para OpenRouter), Node.js como backend e HTML/JavaScript/localStorage para o frontend e persistência básica do histórico. Você aprendeu sobre a estrutura de mensagens {role, content}, como usar uma API de chat completion e como manter o contexto da conversa. A partir daqui, as possibilidades são infinitas!

Experimente com diferentes modelos no OpenRouter e comece a construir seus próprios projetos de chatbot.

Links Úteis

OpenRouter: https://openrouter.ai/

OpenAI TypeScript and JavaScript API Library: https://github.com/openai/openai-node

Repositório desse projeto:https://github.com/marcosnunesmbs/openrouter-chatbot