MODULO 3.6

πŸ”„ Estados do Sistema e Historico

IA demora. As vezes 2 segundos, as vezes 30. Se o usuario nao sabe o que esta acontecendo, ele fecha a aba. Gerenciar estados visuais e historico de conversas e o que separa um prototipo de um produto real.

6
Topicos
45 min
Duracao
Intermediario
Nivel
Teoria+Pratica
Tipo
1

⚑ Os 4 Estados: Idle, Loading, Success, Error

Todo componente interativo do seu SaaS existe em um de quatro estados a qualquer momento. Pensar nisso como uma state machine muda completamente como voce projeta interfaces. Cada estado tem regras visuais e comportamentais especificas, e em apps de IA o estado "loading" ganha uma importancia que nao existe em apps tradicionais.

🎯 Conceito Principal

Uma state machine e um modelo mental onde seu componente so pode estar em um estado por vez, e as transicoes entre estados sao explicitas. Isso elimina bugs como "mostrar loading e error ao mesmo tempo" ou "botao clicavel durante processamento".

  • β€’ Idle: Estado padrao. Input habilitado, botao de enviar ativo, nenhuma animacao rodando. O usuario esta no controle. UI limpa, sem distracao
  • β€’ Loading: Algo esta sendo processado. Input desabilitado (ou nao, depende do design), indicador visual de progresso, mensagem de contexto ("Analisando seu documento...")
  • β€’ Success: Operacao concluida. Resposta renderizada, feedback visual positivo (checkmark, cor verde), transicao suave de volta para idle
  • β€’ Error: Algo falhou. Mensagem clara do que aconteceu, opcao de retry, log do erro para debug. Nunca uma tela em branco

πŸ”„ State Machine para Interacoes de IA

⏸️

Idle

Input ativo, aguardando acao do usuario

⏳

Loading

IA processando, streaming ou aguardando API

βœ…

Success

Resposta entregue, volta para idle

❌

Error

Falha com retry ou volta para idle

// Transicoes validas da state machine
idle β†’ loading // usuario envia mensagem
loading β†’ success // IA respondeu com sucesso
loading β†’ error // API falhou ou timeout
success β†’ idle // automatico apos renderizar
error β†’ loading // usuario clicou retry
error β†’ idle // usuario descartou o erro

πŸ“Š Por que "Thinking" Importa Mais em Apps de IA

  • β€’ Apps tradicionais: Requests duram 100-500ms. Um spinner generico resolve. O usuario nem percebe
  • β€’ Apps de IA: Chamadas duram 5-30 segundos. O "thinking" state precisa ser rico: indicador de progresso, mensagem contextual, animacao sutil. O usuario precisa sentir que algo esta acontecendo
  • β€’ Tolerancia a espera: Pesquisas mostram que usuarios toleram ate 10s se o feedback visual for bom, mas abandonam em 3s se nao houver nenhum indicador

πŸ’‘ Dica Pratica

Use um useReducer (React) ou uma lib como XState para gerenciar estados. Um simples useState('idle') funciona para MVP, mas conforme o app cresce, uma state machine formal evita dezenas de bugs de estado inconsistente.

βœ“ O que FAZER

  • βœ“ Mapear todos os estados possiveis antes de codar a UI
  • βœ“ Desabilitar inputs durante loading para evitar requests duplicados
  • βœ“ Transicionar de success para idle automaticamente apos 1-2s

βœ— O que NAO fazer

  • βœ— Usar multiplos booleans (isLoading, isError, isSuccess) que podem conflitar
  • βœ— Permitir que o usuario envie outra mensagem durante loading
  • βœ— Mostrar estado de erro sem opcao de retry ou voltar
2

⏳ Loading States para IA

Em apps tradicionais, um spinner resolve. Em apps de IA, voce precisa de um repertorio inteiro de loading states. Uma chamada de 2 segundos pede um tratamento diferente de uma de 30 segundos. O segredo e gerenciar a expectativa do usuario em tempo real.

🎯 Conceito Principal

Existem quatro tecnicas principais de loading para apps de IA, e voce vai usar combinacoes delas dependendo da operacao:

  • β€’ Skeleton Screens: Placeholders cinza na forma do conteudo que vai aparecer. Funcionam melhor que spinners porque dao ao cerebro uma previa da estrutura. Use para carregar listas de conversas, perfis, dashboards
  • β€’ Typing Indicators: Os tres pontinhos pulsando ("IA esta digitando..."). Classico do chat. Funciona para respostas curtas (2-5s). Para respostas longas, combine com streaming
  • β€’ Streaming Text: A resposta aparece token por token em tempo real. Padrao de ouro para apps de IA. O usuario le enquanto a IA ainda gera. Reduz percepcao de espera drasticamente
  • β€’ Progress Estimation: Para operacoes longas (processar PDF de 50 paginas, gerar relatorio), mostre progresso estimado: "Analisando pagina 12 de 47..." ou barra de progresso com percentual

πŸ’» Implementando Streaming com OpenAI

// Frontend: consumindo stream da API
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message, conversationId }),
});
// Ler o stream token por token
const reader = response.body.getReader();
const decoder = new TextDecoder();
let aiMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
aiMessage += chunk;
setDisplayedText(aiMessage); // atualiza UI em tempo real
}
setState('success'); // stream completo

πŸ“Š Gerenciando Expectativa em Chamadas de 5-30s

  • β€’ 0-2 segundos: Typing indicator e suficiente. O usuario nem registra a espera conscientemente
  • β€’ 2-10 segundos: Streaming e ideal. Se nao for possivel, use typing indicator + mensagem contextual ("Analisando seu pedido...")
  • β€’ 10-30 segundos: Progress bar ou etapas visiveis ("Etapa 1/3: Lendo documento..."). O usuario precisa sentir que o sistema esta trabalhando, nao travado
  • β€’ 30+ segundos: Notificacao. Diga ao usuario que ele pode fazer outra coisa e sera avisado quando estiver pronto. Ninguem espera 30s olhando para uma tela

πŸ’‘ Dica Pratica

Skeleton screens > spinners. Streaming > typing indicator. Sempre escolha a opcao que da mais informacao ao usuario. Um skeleton mostra onde o conteudo vai aparecer. Um spinner so diz "espere". Streaming mostra progresso real. Typing indicator so diz "algo esta acontecendo".

3

🚨 Error States e Recuperacao

Erros acontecem. A API cai, o rate limit estoura, o usuario envia algo invalido, a internet oscila. O que define a qualidade do seu SaaS nao e a ausencia de erros, e como voce lida com eles. Um erro bem tratado gera mais confianca do que nenhum erro.

🎯 Conceito Principal

Existem tres categorias de erros em apps de IA, e cada uma exige tratamento diferente:

  • β€’ AI Errors (API): Rate limit (429), timeout, modelo indisponivel, resposta truncada, content filter. Sao os mais comuns. Solucao: retry automatico com backoff exponencial, modelo fallback, fila de requests
  • β€’ System Errors: Banco de dados fora, Supabase down, deploy quebrado, SSL expirado. Sao criticos. Solucao: health checks, monitoring, pagina de manutencao, alertas automaticos
  • β€’ User Errors: Arquivo grande demais, formato invalido, prompt vazio, caracteres especiais. Sao preveniveis. Solucao: validacao no frontend antes de enviar, mensagens claras de o que corrigir

πŸ’» Padroes de Tratamento de Erro

// Retry com backoff exponencial
async function callAIWithRetry(messages, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await openai.chat.completions.create({
model: 'gpt-4o', messages, stream: true
});
} catch (err) {
if (err.status === 429 && i < retries - 1) {
await sleep(Math.pow(2, i) * 1000); // 1s, 2s, 4s
continue;
}
throw classifyError(err); // AI, system ou user error
}
}
}

πŸ“Š Mensagens de Erro Amigaveis

βœ—

Error 429: Too many requests. Rate limit exceeded.

βœ“

"Muitas perguntas de uma vez! Aguarde alguns segundos e tente novamente."

βœ—

Error 500: Internal server error

βœ“

"Algo deu errado do nosso lado. Estamos trabalhando nisso. Tente novamente em um minuto."

βœ—

CORS policy: No 'Access-Control-Allow-Origin'

βœ“

"Nao foi possivel conectar ao servidor. Verifique sua conexao e tente novamente."

πŸ’‘ Dica Pratica

Implemente fallback responses para quando a API estiver indisponivel. Pode ser uma mensagem pre-gravada, um cache da ultima resposta similar, ou um redirecionamento para um modelo alternativo (ex: cair de GPT-4o para GPT-3.5 quando o 4o estiver sobrecarregado). O usuario prefere uma resposta parcial a nenhuma resposta.

βœ“ O que FAZER

  • βœ“ Retry automatico em erros 429 e 503 (transientes)
  • βœ“ Botao de "Tentar novamente" visivel em todo erro
  • βœ“ Detectar offline e avisar antes de tentar enviar

βœ— O que NAO fazer

  • βœ— Mostrar stack traces ou codigos HTTP crus ao usuario
  • βœ— Engolir erros silenciosamente (console.log e seguir)
  • βœ— Fazer retry infinito sem limite (loop de requests)
4

πŸ’¬ Historico de Conversas

Se o usuario fecha o browser e perde tudo que conversou, seu SaaS e descartavel. Historico persistente e o que transforma uma sessao avulsa em um relacionamento continuo. E tambem e o que permite ao usuario voltar, buscar, e continuar de onde parou.

🎯 Conceito Principal

O modelo de dados para historico de conversas segue uma hierarquia natural: Usuario > Conversas > Mensagens > Anexos. Cada nivel tem suas propriedades e regras.

  • β€’ Conversations: id, user_id, title (auto-gerado do primeiro prompt), created_at, updated_at, is_archived. Uma conversa agrupa mensagens sobre um mesmo topico
  • β€’ Messages: id, conversation_id, role (user/assistant/system), content, tokens_used, created_at. Cada mensagem e uma unidade atomica no historico
  • β€’ Attachments: id, message_id, file_url, file_type, file_size, extracted_text. Arquivos vinculados a mensagens especificas

πŸ—„οΈ Schema do Banco (Supabase/PostgreSQL)

-- Tabela de conversas
CREATE TABLE conversations (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
title TEXT DEFAULT 'Nova conversa',
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
is_archived BOOLEAN DEFAULT false
);
-- Tabela de mensagens
CREATE TABLE messages (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
conversation_id UUID REFERENCES conversations(id),
role TEXT CHECK (role IN ('user', 'assistant', 'system')),
content TEXT NOT NULL,
tokens_used INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Tabela de anexos
CREATE TABLE attachments (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
message_id UUID REFERENCES messages(id),
file_url TEXT NOT NULL,
file_type TEXT, -- 'pdf', 'image', 'csv'
file_size INTEGER,
extracted_text TEXT -- conteudo extraido para contexto
);

πŸ“Š Funcionalidades Essenciais do Historico

  • β€’ Paginacao: Carregar 20 mensagens por vez, com "carregar mais" ou infinite scroll. Conversas com 200+ mensagens nao podem ser carregadas de uma vez
  • β€’ Busca: Full-text search no conteudo das mensagens. Supabase tem to_tsvector nativo para isso. O usuario precisa encontrar aquela conversa de 3 semanas atras
  • β€’ Thread Management: Renomear conversas, arquivar, deletar, fixar favoritas. A sidebar vira inutilizavel com 50+ conversas sem organizacao
  • β€’ Auto-titulo: Gerar titulo automatico da conversa baseado na primeira mensagem do usuario. Pedir para a IA resumir em 5 palavras custa centavos e economiza cliques

πŸ’‘ Dica Pratica

Use Supabase Realtime para sync instantaneo. Se o usuario tem o app aberto em duas abas, uma nova mensagem aparece nas duas automaticamente. E gratis no Supabase e adiciona uma camada profissional que poucos MVPs tem.

5

πŸ’Ύ Persistencia de Sessao

O usuario comecou a escrever uma pergunta longa, fechou o notebook para almocar e voltou 2 horas depois. Se o draft desapareceu, voce perdeu confianca. Persistencia de sessao e sobre garantir que nada se perde, nao importa o que o usuario faca.

🎯 Conceito Principal

Persistencia funciona em duas camadas. LocalStorage para dados temporarios e rapidos (draft da mensagem atual, preferencias de UI, ultimo scroll position). Database para dados permanentes e sincronizados (conversas, mensagens, configuracoes do usuario).

  • β€’ Resumir conversas: Quando o usuario reabre o app, carregar a ultima conversa ativa automaticamente. Nao forcar a escolher da lista. Lembrar qual conversa estava aberta
  • β€’ Session timeout: JWT do Supabase dura 1 hora por padrao, com refresh token de 7 dias. Configurar refresh automatico no background. Se expirar, redirecionar para login suavemente
  • β€’ Multi-device sync: Conversa criada no desktop aparece no celular. Supabase Realtime + polling resolve. Conflitos: last-write-wins e suficiente para MVP
  • β€’ Draft saving: Auto-save do input a cada 2 segundos no localStorage. Se o browser crashar, o rascunho esta la quando voltar. Custo zero, impacto enorme na UX

βš–οΈ LocalStorage vs Database: Quando Usar Cada

localStorage

  • β€’ Draft da mensagem sendo digitada
  • β€’ Tema (dark/light mode)
  • β€’ ID da ultima conversa aberta
  • β€’ Posicao do scroll na sidebar
  • β€’ Configuracoes de UI (sidebar aberta/fechada)
Rapido, sem network, max 5MB, device-specific

Database (Supabase)

  • β€’ Conversas e mensagens completas
  • β€’ Preferencias do usuario (model, temperature)
  • β€’ Arquivos enviados (Storage + metadata)
  • β€’ Usage tracking (tokens, requests)
  • β€’ System prompts personalizados
Persistente, sincronizado, multi-device, escalavel

πŸ’» Auto-save de Draft com localStorage

// Hook customizado para auto-save do draft
function useDraftSave(conversationId) {
const key = `draft_${conversationId}`;
const [draft, setDraft] = useState(
() => localStorage.getItem(key) || ''
);
useEffect(() => {
const timer = setTimeout(() => {
if (draft) localStorage.setItem(key, draft);
else localStorage.removeItem(key);
}, 2000); // debounce 2s
return () => clearTimeout(timer);
}, [draft, key]);
const clearDraft = () => {
setDraft('');
localStorage.removeItem(key);
};
return { draft, setDraft, clearDraft };
}

πŸ’‘ Dica Pratica

Regra de ouro: se o dado precisa existir em outro dispositivo, vai para o banco. Se so importa nesta aba, vai para localStorage. Nao complique. Um draft de mensagem nao precisa sincronizar entre dispositivos. Mas as conversas sim.

βœ“ O que FAZER

  • βœ“ Auto-save drafts no localStorage com debounce
  • βœ“ Reabrir automaticamente a ultima conversa ativa
  • βœ“ Refresh silencioso do JWT antes de expirar

βœ— O que NAO fazer

  • βœ— Guardar conversas inteiras no localStorage (limite de 5MB)
  • βœ— Redirecionar bruscamente para login sem salvar estado
  • βœ— Ignorar conflitos de sync entre dispositivos
6

πŸ› οΈ Exercicio: Sistema de Estados Completo

Hora de implementar tudo que vimos. Este exercicio integra os 4 estados com UI adequada, historico de conversas com paginacao, e persistencia de sessao. O resultado e um chat funcional com qualidade profissional de UX.

πŸ› οΈ

Exercicio: Sistema de Estados + Historico + Persistencia

Tempo estimado: 30-45 minutos

1

Implementar state machine com 4 estados

Criar um hook ou state manager que controla idle/loading/success/error:

// useConversationState.ts
type State = 'idle' | 'loading' | 'success' | 'error';
const [state, setState] = useState<State>('idle');
const [errorInfo, setErrorInfo] = useState<ErrorInfo | null>(null);
// Cada estado controla a UI:
// idle β†’ input ativo, botao habilitado
// loading β†’ skeleton/streaming, input desabilitado
// success β†’ checkmark, auto-transicao para idle
// error β†’ mensagem + botao retry
2

UI para cada estado

Renderizar componentes diferentes para cada estado:

{state === 'loading' && (
<div className="flex items-center gap-2">
<TypingIndicator />
<span>Analisando sua pergunta...</span>
</div>
)}
{state === 'error' && (
<ErrorBanner
message={errorInfo.userMessage}
onRetry={() => handleRetry()}
onDismiss={() => setState('idle')}
/>
)}
3

Historico com paginacao

Carregar conversas da sidebar com paginacao (20 por vez). Ao clicar, buscar mensagens daquela conversa tambem paginadas. Implementar infinite scroll ou botao "carregar mais".

4

Persistencia de sessao

Salvar draft no localStorage, restaurar ultima conversa ao abrir o app, refresh automatico do token de auth.

5

Testar todos os cenarios

Simular: API lenta (throttle no DevTools), API fora (bloquear request), browser fechado e reaberto, sessao expirada, conversa com 100+ mensagens.

βœ… Criterios de Sucesso

☐ 4 estados visuais distintos funcionando
☐ Loading com typing indicator ou streaming
☐ Error com mensagem amigavel + retry
☐ Historico carregando com paginacao
☐ Draft salvo no localStorage
☐ Ultima conversa restaurada ao reabrir

🌟 Bonus

Adicione busca full-text nas conversas usando Supabase text search. Implemente auto-titulo que gera o nome da conversa apos a primeira resposta da IA. E se quiser ir alem, adicione export de conversa em formato Markdown.

πŸ“‹ Resumo do Modulo

βœ“
State machine com 4 estados - Idle, Loading, Success, Error. Transicoes explicitas. Nunca dois estados ao mesmo tempo. useReducer ou XState para escalar.
βœ“
Loading states ricos para IA - Skeleton screens, typing indicators, streaming text, progress estimation. Cada faixa de tempo tem seu tratamento ideal.
βœ“
Error handling categorizado - AI errors, system errors, user errors. Retry automatico, fallback responses, mensagens amigaveis. Nunca tela em branco.
βœ“
Historico: conversations > messages > attachments - Paginacao, busca full-text, auto-titulo, thread management. Supabase Realtime para sync.
βœ“
Persistencia: localStorage + database - Drafts no localStorage, conversas no banco. Restaurar ultima sessao. Refresh silencioso do JWT. Multi-device sync.