Aplicação Next.js 16 (App Router) com React 19 e TypeScript, sem banco de dados e sem login. O front fala com uma única Route Handler (/api/chat) que valida o corpo, aplica rate limit por IP e chama o Google Gemini pelo SDK @google/generative-ai, com system instruction por persona e histórico convertido para o formato de turnos do Gemini. A resiliência fica no servidor: retry com backoff exponencial e uma cadeia de modelos de fallback quando a cota estoura. Conversas, tema e persona ficam no localStorage; o bestiário é estático e filtrado no cliente. Testado com Jest 30 e Testing Library, hospedado na Vercel com Vercel Analytics.
- Front-end
- Next.js 16.2 (App Router)React 19.2TypeScript 5.9Tailwind CSS 4Framer Motion 12Lucide
- Back-end
- Route Handler do Next.js (/api/chat) em Node
- IA
- Google Gemini via @google/generative-ai 0.24com cadeia de modelos Flash e system prompt por persona
- Dados
- sem banco; conversastema e persona no localStorage do navegadorbestiário como dado estático tipado
- Testes
- Jest 30 + Testing Library (95 casos em 9 suítes)
- Infra
- Vercel + Vercel Analytics
IA
Fallback de modelos com retry e backoff
Cada pergunta tenta o Gemini com retry e backoff exponencial (limitado a 8 s) e, se o modelo continuar sem cota, passa para o próximo de uma cadeia de modelos Flash. Só quando todos falham a API devolve 429 com o cabeçalho Retry-After calculado a partir da dica do próprio erro do Gemini. Uma variável de ambiente fixa um modelo e desliga a cadeia.
IA
Personas como system instruction
Cada personagem é um objeto tipado com saudação, cor, perguntas sugeridas e um system prompt próprio, enviado como systemInstruction do Gemini. O histórico do chat é convertido para os papéis user/model e enviado com startChat, então a persona se mantém ao longo da conversa sem repetir instruções nas mensagens.
Segurança
Proteção da rota de IA
A rota /api/chat tem rate limit por IP em janela deslizante (30 requisições por minuto), com limpeza periódica do mapa em memória, e valida o formato do corpo com type guards antes de chamar o modelo. A chave do Gemini fica só no servidor e os erros chegam ao usuário como mensagens temáticas, sem detalhes internos. O limite é por instância, um controle de melhor esforço adequado a um projeto gratuito.
Segurança
Markdown renderizado sem HTML bruto
As respostas da IA passam por um parser de Markdown próprio que gera elementos React (títulos, listas, código, links com noopener), em vez de injetar HTML. Isso evita uma dependência extra e impede que texto vindo do modelo vire HTML executável na página.
Dados
Estado local robusto
As conversas são persistidas no localStorage com validação de formato na leitura (dados corrompidos são descartados), limite de 50 conversas mantendo as mais recentes e tolerância a erro de cota ou navegação privada. O título da conversa é gerado da primeira pergunta e a conversa pode ser exportada em texto ou Markdown.
Qualidade
Testes cobrindo UI, hook e API
São 95 casos de teste com Jest 30 e Testing Library cobrindo componentes do chat, o renderer de Markdown, o hook useChat, a exportação e a rota /api/chat. Os temas trocam CSS custom properties no elemento html, sem recarregar a página.