IA no Front-end, UX e Multimodal
A mesma resposta de um modelo pode parecer mágica ou frustrante dependendo da interface. Este módulo cuida da ponta que o usuário vê: respostas em tempo real, incerteza comunicada com honestidade, imagens, documentos e voz como entrada, ML rodando no próprio navegador e testes que provam que tudo funciona num navegador de verdade.
Ao final do módulo você vai
- Aplicar padrões de UX específicos de IA: streaming, estados, incerteza, cancelamento.
- Construir chat com streaming ponta a ponta, com a chave da API protegida no back-end.
- Extrair dados de fotos e PDFs, com saída estruturada e citações.
- Integrar voz e ML no navegador como melhorias progressivas.
- Testar a experiência completa com Playwright.
cd o-arco/curso-ia/codigo/modulo-08
pip install -r python/requirements.txt
cd projeto && npm install && npm test # 10 testes, incluindo navegador (se o Playwright estiver instalado)
npm run dev # http://127.0.0.1:3000 (precisa de ANTHROPIC_API_KEY)
8.1 Padrões de UX para produtos com IA
| Problema | Padrão |
|---|---|
| Espera longa e tela parada | Streaming + indicador de "gerando"; mostrar o tempo até o 1º token |
| Resposta indo para o lado errado | Botão parar sempre visível durante a geração (e cancelar no servidor também) |
| Usuário não sabe se pode confiar | Citações e fontes clicáveis (Módulo 4); confiança explícita em extrações |
| IA errou e agiu | Desfazer e confirmação antes de ações; prévia do que vai acontecer |
| Falha de rede ou da API | Estados de erro claros, com o que fazer; nunca uma bolha vazia |
| Expectativa irreal | Dizer o que o assistente faz e o que não faz; exemplos de perguntas |
| Acessibilidade | aria-live para o texto que chega; respeitar prefers-reduced-motion; teclado em tudo |
8.2 Chat com streaming, do servidor ao navegador
No servidor (src/servidor.ts)
res.writeHead(200, { "content-type": "text/event-stream" });
const stream = client.messages.stream({ model, max_tokens: 4000, system: SISTEMA, messages });
res.on("close", () => stream.abort()); // usuário saiu: para de pagar tokens
stream.on("text", (pedaco) => res.write(`event: texto\ndata: ${JSON.stringify({ pedaco })}\n\n`));
const final = await stream.finalMessage();
res.write(`event: fim\ndata: ${JSON.stringify({ stop_reason: final.stop_reason })}\n\n`);
No navegador (public/app.js)
const r = await fetch("/api/chat", { method: "POST", body, signal: controlador.signal });
for await (const { evento, dados } of lerEventos(r.body)) {
if (evento === "texto") bolha.textContent += dados.pedaco; // textContent, nunca innerHTML
}
// botão parar: controlador.abort()
Segurança do front
- A chave nunca vai ao navegador. Tudo passa pelo back-end, que também fixa o prompt de sistema e o modelo.
textContent, nãoinnerHTML: a resposta do modelo pode conter<img onerror=…>, por injeção ou por acaso. Para renderizar Markdown, use uma biblioteca com sanitização.- Política de segurança de conteúdo (
Content-Security-Policy: default-src 'self'): mesmo que algo escape, scripts de fora não rodam. Por isso o app não tem nenhum script ou estilo inline. - Validação e limites no servidor: tamanho das mensagens, número de turnos, requisições por minuto por IP.
Quiz rápido
O usuário fecha a aba no meio de uma resposta em streaming. O que o servidor deve fazer?
Cancelar a requisição ao provedor quando a conexão cai evita custo e carga sem benefício.
8.3 Visão: fotos, telas e documentos
Modelos multimodais leem imagens e PDFs diretamente: notas fiscais, etiquetas, prints de erro, gráficos, contratos. Não é preciso OCR separado, e o modelo entende layout e contexto.
LAB 8.3 — Foto de cupom e contrato em PDF
python python/01_visao.py # foto torta e borrada (dados/cupom.jpg) → JSON validado
python python/02_pdf.py # contrato em PDF → resposta com citação de página
- Tire uma foto de um cupom real seu (cubra dados pessoais) e rode o lab. Compare o campo
legibilidadecom a realidade. - Reduza a foto para 400 px de largura e rode de novo. O que acontece com os tokens e com a precisão?
- No PDF, pergunte algo que o contrato não diz. O modelo admite?
Boas práticas
- Imagem antes do texto na mensagem.
- Resolução: imagens custam tokens proporcionais ao tamanho. Redimensione no cliente antes de enviar; muito pequena perde detalhe.
- Confira o tipo real do arquivo, não só a extensão: o servidor do projeto checa os primeiros bytes (assinatura) e recusa um "PNG" que é texto.
- Dados sensíveis aparecem em fotos (documentos, rostos, cartões). Peça ao modelo para sinalizar e defina retenção curta.
- Peça legibilidade e confiança na saída: fotos ruins são a maior causa de erro.
Quiz rápido
Onde deve ficar a chave de API num app que analisa fotos enviadas pelo navegador?
Qualquer coisa enviada ao navegador pode ser lida. O back-end valida a imagem (tipo real, tamanho) e só ele chama o modelo.
8.4 Áudio e voz
| Peça | Opções | Cuidados |
|---|---|---|
| Fala → texto (STT) | APIs de transcrição (Whisper e similares, provedores de nuvem); Web Speech API do navegador | Sotaques, ruído, nomes próprios; latência |
| Texto → fala (TTS) | APIs de voz neural; speechSynthesis do navegador | Leia respostas curtas; listas e tabelas não funcionam em voz |
| Agentes de voz em tempo real | APIs de áudio em tempo real dos provedores, WebRTC | Interrupções ("barge-in"), latência abaixo de ~1 s, custo por minuto |
O app do projeto usa a Web Speech API como melhoria progressiva: o botão de microfone só aparece se o navegador suportar, e o usuário pode sempre digitar. Voz muda o design das respostas: frases curtas, sem formatação visual, confirmação falada antes de ações.
8.5 ML rodando no navegador
Nem tudo precisa de um LLM na nuvem. Modelos pequenos rodam no próprio navegador (WebAssembly e WebGPU), sem custo por chamada e sem os dados saírem do dispositivo: classificação de sentimento, detecção de idioma, embeddings para busca local, detecção de objetos e pose em vídeo.
// Transformers.js: o modelo é baixado uma vez e fica em cache no navegador
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/[email protected]";
const classificar = await pipeline("sentiment-analysis", "Xenova/bert-base-multilingual-uncased-sentiment");
console.log(await classificar("O produto chegou rápido e funciona muito bem!"));
- Bibliotecas: Transformers.js (modelos do Hugging Face), TensorFlow.js, ONNX Runtime Web, MediaPipe (visão em tempo real).
- Quando faz sentido: privacidade, uso offline, latência mínima, altíssimo volume de tarefas simples.
- Custos escondidos: download do modelo (dezenas a centenas de MB), bateria, desempenho desigual entre aparelhos.
- Padrão híbrido: filtro ou classificação no navegador, LLM na nuvem só quando necessário.
8.6 Prototipação e design assistidos por IA
- Texto → interface: ferramentas geram telas a partir de descrição. Ótimas para explorar alternativas rápido; o resultado é ponto de partida, não produto.
- Design → código: de um arquivo de design ou de um print para componentes. Funciona melhor com um design system definido (cores, espaçamentos, componentes), que você passa como contexto.
- Agentes de código no front (Módulo 7): o arquivo de contexto deve trazer os componentes existentes e as regras de acessibilidade, para o agente reaproveitar em vez de inventar.
- Revise o que a IA esquece: estados vazios, de erro e de carregamento; teclado; contraste; textos longos; telas pequenas.
8.7 Testes de ponta a ponta
O test/e2e.test.ts abre um Chromium de verdade com Playwright, digita uma pergunta, espera
o texto chegar em streaming, clica em "parar", envia uma imagem e confere a tabela de resultado. A
API é falsa (test/api-falsa.ts imita até os eventos SSE), então o teste é rápido,
gratuito e determinístico, e falha se houver qualquer erro no console, inclusive
violações da política de segurança.
await pagina.fill("#entrada", "Qual o prazo para o interior de MG?");
await pagina.keyboard.press("Enter");
await pagina.locator(".msg.assistant").getByText("4 a 7 dias úteis").waitFor();
await pagina.locator("#parar").click();
assert.deepEqual(erros, [], "sem erros no console");
Agentes também podem testar interfaces: um agente com ferramentas de navegador explora o app como um usuário e relata problemas. É um complemento útil (acha o que você não pensou em testar), mas não substitui testes determinísticos no CI.
Projeto do módulo: assistente multimodal
| Arquivo | Responsabilidade |
|---|---|
src/servidor.ts | Proxy da API: chat em SSE com cancelamento, análise de imagem estruturada, validação, limite por IP, checagem de assinatura de arquivo, CSP, proteção contra ../ |
public/ | Interface sem framework: streaming, parar, estados, voz progressiva, upload com prévia e limite de tamanho |
test/servidor.test.ts | 9 testes do servidor com API falsa (inclusive SSE) |
test/e2e.test.ts | Teste no navegador com Playwright |
Os testes acharam três bugs reais durante a construção: uma lista vazia de mensagens derrubava a validação com erro 500, imagens SVG recebiam o código de erro errado, e um upload grande demais deixava a conexão num estado que quebrava a requisição seguinte. Os três estão corrigidos e cobertos.
Entregáveis
- Renderize Markdown nas respostas com uma biblioteca de sanitização e prove com um teste que
<img src=x onerror=alert(1)>não executa. - Adicione citações clicáveis usando o RAG do Módulo 4 como fonte.
- Redimensione imagens no navegador (canvas) antes do envio e meça a economia de tokens.
- Adicione histórico persistente por usuário (no servidor) com opção de apagar.
- Faça um teste de acessibilidade (navegação só por teclado e leitor de tela) e corrija o que achar.
Critérios de avaliação
| Critério | Peso |
|---|---|
| Markdown seguro com teste de XSS | 25% |
| Citações integradas ao RAG | 20% |
| Redimensionamento com medição | 15% |
| Histórico persistente com exclusão | 20% |
| Acessibilidade | 20% |
Recursos para aprofundar
- People + AI Guidebook · padrões de UX para produtos com IA
- MDN — Server-Sent Events e CSP
- Transformers.js · modelos no navegador
- Playwright · testes de ponta a ponta
- Documentação da API · visão, PDFs, citações e streaming