Um chat estilo ChatGPT/LibreChat, mas enxuto: o backend Java faz a ponte com o LLM e devolve a resposta em streaming (Server-Sent Events); o frontend Angular monta a conversa token por token, igual a um assistente de verdade.
Funciona sem nenhum modelo instalado no modo demo (só ecoa a pergunta), o que deixa
rodar e testar de ponta a ponta sem chave de API. Para respostas reais, troque o provedor
para Ollama (LLM local) ou OpenAI (nuvem) — veja "Escolhendo o LLM" abaixo.
java-angular-chat/
backend/ Spring Boot 4 (Java 21) — POST /api/chat, streaming SSE
frontend/ Angular (standalone + signals) — UI de chat, consome o SSE
Fluxo de uma mensagem:
- Angular envia
POST http://localhost:8080/api/chatcom{ message, history }. - O backend monta o contexto (system + histórico + mensagem nova) e chama o provedor LLM.
- Cada token gerado vira um evento SSE (
data:<token>); o último édata:[DONE]. - O Angular lê o stream com
fetch+ReadableStreame vai escrevendo na bolha da IA.
Por que
fetche nãoEventSource?EventSourcesó fazGETe não envia corpo; como mandamos o histórico no corpo da requisição, consumimos o SSE manualmente comfetch.
Pré-requisitos: Java 21+ e Node 20+. O Maven usa o Wrapper (mvnw), não precisa
instalar Maven.
1. Backend (porta 8080):
cd backend
./mvnw spring-boot:run # Windows: .\mvnw.cmd spring-boot:run2. Frontend (porta 4200):
cd frontend
npm install
npm start # abre http://localhost:4200Abra http://localhost:4200, digite uma mensagem e veja a resposta aparecer token a token.
No modo demo ela só ecoa — é o comportamento esperado até conectar um LLM.
Tudo é controlado por backend/src/main/resources/application.yml, chave chat.provider.
Aceita demo | ollama | openai (também dá para definir pela variável de ambiente
CHAT_PROVIDER).
chat:
provider: demoSó ecoa a pergunta. Bom para validar o streaming sem modelo nem chave.
Instale e suba o Ollama (https://ollama.com) e baixe um modelo:
ollama pull llama3.2
ollama serve # sobe em http://localhost:11434Depois aponte o provedor:
chat:
provider: ollama
ollama:
base-url: http://localhost:11434
model: llama3.2Nunca coloque a chave no arquivo versionado — passe por variável de ambiente:
# Windows (PowerShell)
$env:OPENAI_API_KEY = "sk-..."
# Linux / macOS
export OPENAI_API_KEY="sk-..."chat:
provider: openai
openai:
base-url: https://api.openai.com/v1
model: gpt-4o-mini
api-key: ${OPENAI_API_KEY:}Qualquer endpoint compatível com a API da OpenAI funciona (é só trocar o base-url),
o que inclui gateways e modelos self-hosted.
com.devchat.chat
model/ ChatMessage, ChatRequest (record do corpo da requisição)
config/ ChatProperties (@ConfigurationProperties "chat")
provider/ LlmProvider (interface) + DemoProvider, OllamaProvider, OpenAiProvider
service/ ChatService (monta o contexto e escolhe o provedor)
web/ ChatController (POST /api/chat → SseEmitter)
Adicionar um provedor novo = implementar LlmProvider (name() + stream(...)) e usar o
nome dele em chat.provider. O resto (controller, streaming, UI) não muda.
- A chave da OpenAI só entra por variável de ambiente (
OPENAI_API_KEY); oapplication.ymltem default vazio e um aviso para não commitar chave real. - O CORS do backend está liberado (
@CrossOrigin(originPatterns = "*")) por simplicidade de desenvolvimento local. Em produção, restrinja ao domínio do frontend.
- Backend compila e sobe na 8080;
POST /api/chatfaz streaming e encerra comdata:[DONE]. - Frontend compila (
ng build) e serve na 4200; bolhas de usuário/IA aparecem e a resposta da IA é montada token a token em mododemo.