Simulador de podcast onde personagens de IA conversam autonomamente. O usuário não participa do diálogo — atua como Diretor, controlando o ritmo, redirecionando a narrativa via instruções invisíveis e podendo reescrever o passado para alterar o futuro.
Cada personagem é uma instância independente do LLM, com sua própria personalidade. As falas vêm acompanhadas de tags emocionais detectadas pela própria IA. O sistema é puramente textual e local — você roda o backend, fornece sua API key da Anthropic, e a conversa acontece no browser.
- Autoplay com pacing automático (ou step manual).
- Master prompts invisíveis (
[Diretor]: ...) que redirecionam o tom sem aparecer como fala. - Rollback por fala (incluindo Shift+clique = volta 5).
- Edição de qualquer fala do passado — a engine trunca tudo depois da fala editada, e o regenerador refaz o futuro com base na nova versão.
- Marcadores na timeline mostram onde o Diretor injetou contexto.
- CRUD via UI (criar, renomear, ajustar personalidade, remover quando não tem falas no histórico).
- Personalidade resumida (curta) + detalhes ampliados via upload de
.md. - Contexto do mundo compartilhado entre todos os personagens, também via
.md(cenário, época, regras).
- Anthropic Claude com escolha entre Haiku 4.5 (padrão), Sonnet 4.6 e Opus 4.7.
- Tags emocionais por fala (
irritado,cético,provocativo, etc.) geradas via Structured Outputs (output_config.format.json_schema). - Prompt caching marcado no system prompt para reaproveitamento entre falas do mesmo personagem.
- Gerador mock como fallback para desenvolver UI sem queimar tokens.
- Sessão completa (personagens, histórico, contexto do mundo) em
localStorage. - Exportação para
.mdcom cabeçalho, personagens, detalhes, master prompts e histórico — pronto para arquivar ou compartilhar.
| Camada | Tecnologia |
|---|---|
| Frontend | React 19 + Vite 8 (TypeScript 6 com ESM/NodeNext) |
| Backend | Express 5 rodando via tsx --watch |
| LLM | @anthropic-ai/sdk (Messages API + Structured Outputs) |
| Engine core | TypeScript puro, sem dependências (testada com Jest + ts-jest) |
| Ambiente | Ubuntu 26.04 + Node LTS em container Docker |
A engine narrativa (src/engine/) é independente do React e do backend — é uma classe TypeScript pura coberta por testes unitários. O React consome essa engine via um hook (useEngine); o backend recebe {characters, history, model, worldContext} a cada step e devolve {text, tones}.
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌────────────────────┐ ┌────────────────────────────┐ │
│ │ React (port 3000) │ │ localStorage │ │
│ │ - useEngine hook │←──→│ estado + configurações │ │
│ │ - components/* │ └────────────────────────────┘ │
│ └─────────┬──────────┘ │
│ │ fetch /api/* │
└────────────┼────────────────────────────────────────────────┘
│ (Vite proxy)
┌────────────▼──────────────────────────┐
│ Backend Express (port 4000) │
│ - POST /api/generate │
│ - GET /api/status │
│ - lê ANTHROPIC_API_KEY do .env │
└────────────┬──────────────────────────┘
│ Messages API
▼
Anthropic Claude
(Haiku 4.5 / Sonnet 4.6 / Opus 4.7)
A API key vive somente no servidor — nunca entra no bundle do frontend. O proxy do Vite redireciona /api/* para a porta 4000 em desenvolvimento, então frontend e backend compartilham a mesma origem.
- Docker e Docker Compose
- Porta
3000livre no host - Chave da API Anthropic (console.anthropic.com) — opcional se você quiser usar apenas o gerador mock
Nota sobre WSL/Linux: o
Dockerfilecria o usuárioflycom UID1000para casar com o usuário padrão do host. Se no seu host o UID for diferente, ajuste noDockerfileantes do build (useradd -m -u <SEU_UID>).
git clone https://github.com/PedroRodrigues/NarrativeEngine.git
cd NarrativeEnginedocker compose up -d --buildO docker-compose.yml monta o diretório atual em /narrative-engine dentro do container e mantém node_modules em um volume separado.
docker exec -u fly -w /narrative-engine narrative-engine npm installSe receber EACCES ao escrever em node_modules, ajuste o owner do volume:
docker exec -u root narrative-engine chown -R fly:fly /narrative-engine/node_modulescp .env.example .envEdite .env e cole sua chave:
ANTHROPIC_API_KEY=sk-ant-...
Pule este passo se for usar apenas o provider mock.
docker exec -u fly -w /narrative-engine narrative-engine npm run dev:allEsse comando sobe, em paralelo:
- Vite na porta
3000(frontend + HMR) - Express na porta
4000(backend, comtsx --watchpara reload automático ao editarserver/*.ts)
Navegue até http://localhost:3000.
- Personagens iniciais já vêm preenchidos (Gorila e Albinão — caso de teste padrão).
- (Opcional) Carregue um arquivo
.mdde contexto do mundo na sidebar — descreve cenário, época, regras do mundo. - (Opcional) Clique em um personagem para editar nome, personalidade resumida ou anexar um
.mdde detalhes ampliados. O badge📄ao lado do nome indica que o personagem tem detalhes carregados. - No painel do Mestre (rodapé), digite o assunto inicial da conversa e pressione
Enter(ou clique em Injetar). - Toque ▶ para autoplay ou ⏭ para avançar fala por fala.
- Edite qualquer fala pelo botão ✎ (ou duplo-clique). Cuidado: editar uma fala antiga descarta as posteriores — você regenera com Play/Skip.
- Para redirecionar o rumo da conversa: injete uma nova instrução invisível no painel do Mestre a qualquer momento.
- Exporte tudo (personagens + mundo + histórico) como
.mdclicando em ⤓ no header.
| Ação | Atalho |
|---|---|
| Injetar prompt | Enter no campo do Mestre |
| Quebra de linha no prompt | Shift+Enter |
| Rollback de 1 fala | Clique em ⏮ |
| Rollback de 5 falas | Shift + clique em ⏮ |
| Editar fala | ✎ no header da mensagem ou duplo-clique no texto |
Botão ⚙ no header. Permite:
- Trocar entre provedores: Mock (sem custo), Haiku 4.5 (padrão, rápido), Sonnet 4.6 ou Opus 4.7.
- Verificar status da API key (configurada / não configurada / backend offline).
Tudo persiste em localStorage automaticamente:
narrative-engine:state:v1— personagens, histórico, contexto do mundo, autoplay flagnarrative-engine:settings:v1— provider escolhido
O botão ↺ no header reseta a sessão (volta aos personagens iniciais e apaga o histórico).
narrative-engine/
├── server/ Backend Express
│ ├── index.ts Rotas HTTP + validação
│ └── anthropic.ts SDK + construção do prompt + structured outputs
├── src/
│ ├── engine/ Engine narrativa pura (sem React, testada)
│ │ ├── NarrativeEngine.ts
│ │ └── types.ts
│ └── ui/ Frontend React
│ ├── App.tsx
│ ├── main.tsx
│ ├── useEngine.ts Hook que envolve a engine + autoplay + LLM
│ ├── settings.ts Settings store (localStorage)
│ ├── mockSpeech.ts Gerador mock
│ ├── llmGenerator.ts Cliente do backend
│ ├── exportSession.ts Exportador para .md
│ ├── styles.css
│ └── components/
│ ├── CharacterEditor.tsx
│ ├── MessageHistory.tsx
│ ├── MasterControls.tsx
│ ├── Settings.tsx
│ └── WorldContextPanel.tsx
├── tests/ Jest (engine core)
│ └── NarrativeEngine.test.ts
├── .env.example
├── Dockerfile
├── docker-compose.yml
├── vite.config.ts
├── jest.config.cjs
├── tsconfig.json
├── package.json
└── README.md
Todos rodam dentro do container via docker exec -u fly -w /narrative-engine narrative-engine <script>:
| Script | O que faz |
|---|---|
npm run dev:all |
Sobe Vite (3000) e Express (4000) em paralelo via concurrently |
npm run dev |
Só o Vite |
npm run server |
Só o Express (com tsx --watch para reload em mudanças em server/) |
npm run build |
Build de produção do frontend (Vite) |
npm run preview |
Serve o build de produção localmente |
npm test |
Roda a suíte Jest da engine |
| Variável | Obrigatória? | Descrição |
|---|---|---|
ANTHROPIC_API_KEY |
Sim (exceto modo mock) | Chave da API Anthropic |
PORT |
Não (default 4000) |
Porta do backend Express. Se alterar, ajuste o proxy em vite.config.ts. |
O .env é lido pelo backend via tsx --env-file=.env no script server. Reinicie o backend após editar o .env — o tsx --watch só observa arquivos .ts.
docker exec -u fly -w /narrative-engine narrative-engine npm testA suíte cobre o comportamento da engine narrativa:
- Rollback de N falas (e limites)
- Edição pontual com truncamento das falas posteriores
- Injeção de master prompts (IDs únicos,
hiddenFromUI) - CRUD de personagens (incluindo guard contra remover personagem com histórico)
- Contexto do mundo (set/clear)
O frontend não tem suíte automatizada — a validação visual roda pelo Vite no browser durante o desenvolvimento.
O system prompt enviado à Anthropic é construído por server/anthropic.ts → buildSystem() na seguinte ordem:
CONTEXTO DO MUNDO
<conteúdo do worldContext .md, se houver>
Você é <Nome>.
PERSONALIDADE
<personalidade resumida>
DETALHES SOBRE VOCÊ
<conteúdo do details .md, se houver>
REGRAS DO PODCAST
- Não saiba que é uma IA
- Responda apenas a próxima fala, em primeira pessoa, sem prefixo
- ...
O histórico (lista de falas + master prompts) vai em uma única user message, formatada como diálogo (Nome: fala, [Diretor]: instrução). A resposta é forçada a um JSON estruturado ({fala: string, tones: string[]}) via output_config.format.json_schema — sem precisar de parsing fuzzy.
O UID do container não bate com o do host. Ajuste o Dockerfile:
RUN userdel -r ubuntu 2>/dev/null || true \
&& useradd -m -u <SEU_UID_NO_HOST> -s /bin/bash flyDepois recrie o container:
docker compose down -v
docker compose up -d --buildProvavelmente sobrou um processo antigo do Vite ou do Express. Mate todos:
docker exec -u root narrative-engine bash -c 'pkill -KILL -f "concurrently|tsx|vite"'E suba novamente com npm run dev:all.
O backend lê o .env na inicialização. Reinicie o backend manualmente — o tsx --watch não monitora mudanças em .env.
Esse é o comportamento esperado. A especificação define que a edição "recalcula as gerações futuras com base na nova versão do passado". Para preservar o controle de gasto de tokens, o sistema trunca as falas posteriores e exige Play/Skip para regenerar.
Em raros casos (especialmente após muitos kills mal feitos), o watcher pode parar. Mate o backend e suba de novo:
docker exec -u root narrative-engine bash -c 'pkill -KILL -f "tsx"'
docker exec -u fly -w /narrative-engine narrative-engine npm run dev:all