Skip to content

PedroRodrigues/NarrativeEngine

Repository files navigation

Narrative Engine

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.


Funcionalidades

Controle do diretor

  • 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.

Personagens

  • 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).

Geração via LLM

  • 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.

Persistência e exportação

  • Sessão completa (personagens, histórico, contexto do mundo) em localStorage.
  • Exportação para .md com cabeçalho, personagens, detalhes, master prompts e histórico — pronto para arquivar ou compartilhar.

Stack

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}.


Arquitetura

┌─────────────────────────────────────────────────────────────┐
│  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.


Pré-requisitos

  • Docker e Docker Compose
  • Porta 3000 livre no host
  • Chave da API Anthropic (console.anthropic.com) — opcional se você quiser usar apenas o gerador mock

Nota sobre WSL/Linux: o Dockerfile cria o usuário fly com UID 1000 para casar com o usuário padrão do host. Se no seu host o UID for diferente, ajuste no Dockerfile antes do build (useradd -m -u <SEU_UID>).


Setup passo a passo

1. Clonar o repositório

git clone https://github.com/PedroRodrigues/NarrativeEngine.git
cd NarrativeEngine

2. Subir o container

docker compose up -d --build

O docker-compose.yml monta o diretório atual em /narrative-engine dentro do container e mantém node_modules em um volume separado.

3. Instalar dependências (uma vez)

docker exec -u fly -w /narrative-engine narrative-engine npm install

Se 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_modules

4. Configurar a API key

cp .env.example .env

Edite .env e cole sua chave:

ANTHROPIC_API_KEY=sk-ant-...

Pule este passo se for usar apenas o provider mock.

5. Subir os processos de desenvolvimento

docker exec -u fly -w /narrative-engine narrative-engine npm run dev:all

Esse comando sobe, em paralelo:

  • Vite na porta 3000 (frontend + HMR)
  • Express na porta 4000 (backend, com tsx --watch para reload automático ao editar server/*.ts)

6. Abrir a aplicação

Navegue até http://localhost:3000.


Como usar

Fluxo básico

  1. Personagens iniciais já vêm preenchidos (Gorila e Albinão — caso de teste padrão).
  2. (Opcional) Carregue um arquivo .md de contexto do mundo na sidebar — descreve cenário, época, regras do mundo.
  3. (Opcional) Clique em um personagem para editar nome, personalidade resumida ou anexar um .md de detalhes ampliados. O badge 📄 ao lado do nome indica que o personagem tem detalhes carregados.
  4. No painel do Mestre (rodapé), digite o assunto inicial da conversa e pressione Enter (ou clique em Injetar).
  5. Toque para autoplay ou para avançar fala por fala.
  6. Edite qualquer fala pelo botão (ou duplo-clique). Cuidado: editar uma fala antiga descarta as posteriores — você regenera com Play/Skip.
  7. Para redirecionar o rumo da conversa: injete uma nova instrução invisível no painel do Mestre a qualquer momento.
  8. Exporte tudo (personagens + mundo + histórico) como .md clicando em no header.

Atalhos

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

Configurações

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).

Persistência

Tudo persiste em localStorage automaticamente:

  • narrative-engine:state:v1 — personagens, histórico, contexto do mundo, autoplay flag
  • narrative-engine:settings:v1 — provider escolhido

O botão no header reseta a sessão (volta aos personagens iniciais e apaga o histórico).


Estrutura de pastas

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

Scripts disponíveis

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áveis de ambiente (.env)

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.


Testes

docker exec -u fly -w /narrative-engine narrative-engine npm test

A 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.


Configuração do prompt enviado ao LLM

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.


Troubleshooting

EACCES ao rodar npm install

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 fly

Depois recrie o container:

docker compose down -v
docker compose up -d --build

"Port 3000 is in use"

Provavelmente 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.

Status mostra "API key não configurada" mesmo com .env preenchido

O backend lê o .env na inicialização. Reinicie o backend manualmente — o tsx --watch não monitora mudanças em .env.

Edição de fala antiga apagou as posteriores

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.

tsx --watch não recarrega

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors