sagadeck 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -1
- package/SKILL.md +3 -3
- package/bin/sagadeck.js +2 -2
- package/docs/ANDAMENTO.md +162 -4
- package/docs/EXPERIENCIAS.md +1 -1
- package/docs/REFERENCIA.md +59 -1
- package/package.json +2 -1
- package/src/ai/deck-ai.js +13 -4
- package/src/ai/ia-config.js +17 -2
- package/src/ai/llm.js +6 -4
- package/src/ai/map-analysis-run.js +119 -0
- package/src/ai/map-data.js +221 -0
- package/src/ai/variety.js +1 -1
- package/src/build.js +6 -2
- package/src/experiences.js +1 -1
- package/src/export/browser.js +23 -1
- package/src/export/pptx.js +2 -1
- package/src/export/shots.js +3 -1
- package/src/layouts.js +10 -0
- package/src/library.js +2 -2
- package/src/map-analysis.js +106 -0
- package/src/map-config.js +83 -0
- package/src/map-routing.js +82 -0
- package/src/map-services.js +60 -0
- package/src/map.js +261 -0
- package/src/mcp/server.js +4 -4
- package/src/runtime/base.css +21 -1
- package/src/runtime/code-lab.js +10 -10
- package/src/runtime/map.js +234 -0
- package/src/runtime/runtime.js +1 -1
- package/src/runtime/skins/aluminio.css +1 -1
- package/src/runtime/skins/prata.css +1 -1
- package/src/runtime/vendor/LEAFLET-LICENSE.txt +26 -0
- package/src/runtime/vendor/geo-contornos.json +1 -0
- package/src/runtime/vendor/leaflet.css +661 -0
- package/src/runtime/vendor/leaflet.js +6 -0
- package/src/studio/ai-chat-route.js +20 -0
- package/src/studio/banner.js +1 -1
- package/src/studio/demo-decks.js +3 -1
- package/src/studio/geo-routes.js +138 -0
- package/src/studio/ia-routes.js +4 -1
- package/src/studio/layout-samples.js +71 -0
- package/src/studio/project.js +1 -1
- package/src/studio/public/ai-settings.js +15 -2
- package/src/studio/public/app.js +13 -7
- package/src/studio/public/chat.js +1 -1
- package/src/studio/public/explorer.js +23 -8
- package/src/studio/public/index.html +4 -0
- package/src/studio/public/library.css +2 -1
- package/src/studio/public/library.html +2 -0
- package/src/studio/public/library.js +10 -1
- package/src/studio/public/map-data.js +154 -0
- package/src/studio/public/map-editor.js +287 -0
- package/src/studio/public/map-fields.js +21 -0
- package/src/studio/public/map-settings.js +84 -0
- package/src/studio/public/slide-form.js +1 -1
- package/src/studio/public/style.css +13 -0
- package/src/studio/public/ui-icons.js +1 -1
- package/src/studio/public/viewers.js +2 -1
- package/src/studio/public/visual-editor.js +1 -1
- package/src/studio/server.js +13 -1
- package/src/studio/tour.js +76 -0
- package/src/templates/scaffold.js +5 -5
- package/src/themes.js +5 -5
- package/templates/{exemplo-keynote.yaml → exemplo-prata.yaml} +6 -6
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Você (ou uma IA) escreve um arquivo `.yaml` com o conteúdo. O sagadeck gera:
|
|
|
9
9
|
| **`.pdf`** | um slide por página, para mandar depois da palestra |
|
|
10
10
|
| **`- roteiro.pdf`** | roteiro do apresentador: miniatura de cada slide + fala + interações + relógio planejado |
|
|
11
11
|
|
|
12
|
-
Ele vem com 11 temas (incluindo
|
|
12
|
+
Ele vem com 11 temas (incluindo prata limpo e respirável, artesanal desenhado à mão, azul elétrico vivo e pop alegre), 23 layouts (incluindo KPIs e fluxos de processo), 2.100+ ícones, pictogramas e cenas geradas na hora, diagramas, 7 tipos de gráfico, e um **fiscal automático** que detecta texto estourado, sobreposição, contraste baixo e slides com texto demais ("anti-sono").
|
|
13
13
|
|
|
14
14
|
## Instalação
|
|
15
15
|
|
|
@@ -150,6 +150,46 @@ ilustrar" e `--no-images` proíbe. Imagens só são geradas para os slides que a
|
|
|
150
150
|
|
|
151
151
|
Todo YAML vindo do LLM é validado (renderiza cada slide); se falhar, o erro volta para o LLM corrigir (até 3 tentativas). Decks gerados passam por uma rodada de enxugamento quando o fiscal anti-sono reclamaria.
|
|
152
152
|
|
|
153
|
+
## Mapa (opcional)
|
|
154
|
+
|
|
155
|
+
O slide de mapa (`layout: map`) mostra pontos, linhas e áreas sobre um mapa ao vivo: sensores, medidores, unidades de
|
|
156
|
+
atendimento, cinemas, trajetos, bairros, o que for. Os dados vêm de uma planilha do projeto (CSV com latitude e
|
|
157
|
+
longitude, ou só com o endereço) ou de arquivos GeoJSON, GPX e KML. **Nada aqui é obrigatório**: quem não usa mapa
|
|
158
|
+
não precisa configurar coisa nenhuma, e o sagadeck não pede nada à internet por causa dele.
|
|
159
|
+
|
|
160
|
+
**Sem chave nenhuma, já funciona** com os serviços públicos gratuitos do OpenStreetMap:
|
|
161
|
+
|
|
162
|
+
| serviço | para quê | padrão (sem configurar) |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| mapa de fundo | as ruas por baixo das camadas | OpenStreetMap |
|
|
165
|
+
| endereço e coordenada | **Achar coordenadas** de uma planilha só com endereço | Nominatim (1 endereço por segundo) |
|
|
166
|
+
| busca de lugares | achar cinemas, escolas, unidades de saúde… pelo nome do tipo | Overpass |
|
|
167
|
+
| rotas | "seguir as ruas" ao desenhar, distância e tempo | OSRM (servidor de demonstração) |
|
|
168
|
+
|
|
169
|
+
**Chave opcional: OpenRouteService**, para rotas a pé, de carro e de bicicleta e para a **área alcançável em X
|
|
170
|
+
minutos**. É gratuita:
|
|
171
|
+
1. Crie a conta em [openrouteservice.org](https://openrouteservice.org) e confirme o e-mail.
|
|
172
|
+
2. No painel, gere um token do plano gratuito (Standard).
|
|
173
|
+
3. No Studio, **Configurar mapa** (no alto da biblioteca, ou no formulário do slide de mapa) › Rotas › **Usar o
|
|
174
|
+
OpenRouteService**, cole a chave e clique em **Testar**.
|
|
175
|
+
|
|
176
|
+
A configuração fica só nesta máquina, em `~/.sagadeck/mapa.json` (como a da IA); a chave nunca volta inteira para a
|
|
177
|
+
página e nunca vai para dentro da apresentação. Também vale pôr a chave numa variável de ambiente e indicar o nome dela
|
|
178
|
+
em `keyEnv` (no servidor, por exemplo):
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{ "rotas": { "provedor": "openrouteservice", "url": "https://api.openrouteservice.org", "keyEnv": "OPENROUTESERVICE_API_KEY" } }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Os serviços públicos têm regras de uso que o sagadeck respeita sozinho: identifica cada pedido, mostra a atribuição
|
|
185
|
+
"© colaboradores do OpenStreetMap", busca um endereço por segundo, guarda o que já achou e nunca baixa mapa fora da
|
|
186
|
+
tela. Eles são para uso leve e não comercial; no trabalho, prefira o servidor de mapas da empresa ou um provedor com
|
|
187
|
+
chave (os endereços se trocam em **Configurar mapa**). Antes de mandar os endereços de uma planilha sua para um
|
|
188
|
+
serviço de fora, o sagadeck pergunta.
|
|
189
|
+
|
|
190
|
+
Se a rede barrar um serviço (autorização negada, proxy, bloqueio), o sagadeck faz **uma tentativa e para**: avisa
|
|
191
|
+
na tela, as camadas continuam desenhadas e nada é tentado de novo até você clicar em **Tentar de novo**.
|
|
192
|
+
|
|
153
193
|
## Desenvolvimento
|
|
154
194
|
|
|
155
195
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -120,7 +120,7 @@ O agente no terminal deve seguir o fluxo:
|
|
|
120
120
|
## Decisão de Arquitetura: Mesmo Repositório vs. Repositório Separado
|
|
121
121
|
|
|
122
122
|
### Por que o Estúdio deve ficar no **mesmo repositório** (`sagadeck`):
|
|
123
|
-
1. **Fonte Única da Verdade**: Os layouts (incluindo `stats` para KPIs e `steps` para processos), os 11 temas visuais (incluindo `prata`
|
|
123
|
+
1. **Fonte Única da Verdade**: Os layouts (incluindo `stats` para KPIs e `steps` para processos), os 11 temas visuais (incluindo `prata` limpo e respirável, `rabisco` artesanal pintado à mão, `oceano` azul elétrico vivo, `pop` alegre/chiclete e `aurora` neon), as regras de tipografia e as rotinas de verificação geométrica residem no mesmo código (`src/layouts.js`, `src/themes.js`, `src/fiscal/autofix.js`). Qualquer alteração ou novo layout adicionado ao compilador fica disponível **instantaneamente** no estúdio visual.
|
|
124
124
|
2. **Zero Descompasso de Versões**: Em repositórios separados, quando o formato YAML ou os temas do SagaDeck evoluem, a interface visual quebra ou fica desatualizada até que alguém publique e atualize dependências externas.
|
|
125
125
|
3. **Experiência de Uso (DX) Imediata**: O desenvolvedor ou agente clona o repositório ou roda `npm install -g sagadeck` e tem **tudo num só lugar**: linha de comando (`sagadeck build`), estúdio visual (`sagadeck studio`), servidor de agentes (`sagadeck mcp`) e fiscal (`sagadeck check`).
|
|
126
126
|
4. **Leve e Modular**: A interface web foi construída com tecnologias nativas leves (Vanilla JS, CSS moderno e servidor HTTP embutido do Node.js sem frameworks pesados). Isso mantém o pacote pequeno (~1.1 MB empacotado) sem impactar o desempenho do compilador.
|
|
@@ -130,7 +130,7 @@ O agente no terminal deve seguir o fluxo:
|
|
|
130
130
|
## Novos Temas Visuais & Recursos Anti-Textão
|
|
131
131
|
|
|
132
132
|
### 1. Temas Modernos, Vivos, Jovens e Clean
|
|
133
|
-
- **`prata`**: **
|
|
133
|
+
- **`prata`**: **Limpo e espaçoso**. Jovem, clean, respirável e sofisticado (levado a sério). Fundo cinza prata acetinado (`#F5F5F7`), texto em ardósia/preto profundo (`#1D1D1F`), cinza titânio intermediário (`#86868B`), divisores finos (`#D2D2D7`) e azul vivo (`#0071E3`). Cartelas com efeito vidro fosco / acabamento acetinado com sombra ultra-suave e tipografia do sistema / Inter.
|
|
134
134
|
- **`rabisco`**: Estilo artesanal e lousa com fontes desenhadas à mão (`'Caveat'`, `'Patrick Hand'`, `'Ink Free'`), bordas orgânicas de caderno, sombras desenhadas, fitas adesivas/post-its e ícones em estilo doodle. Ideal quando se quer uma apresentação calorosa, amigável e ilustrada.
|
|
135
135
|
- **`oceano`**: Azul elétrico ultra vibrante (`#0062FF`), ciano neon (`#00C6FF`), tipografia moderna punchy e fundo luminoso de alta energia. Perfeito para tech, startups e produtos dinâmicos.
|
|
136
136
|
- **`pop`**: Alegre, colorido e chiclete com paleta super animada (roxo, rosa chiclete, menta, sol) e cantos ultra-arredondados (26px).
|
|
@@ -146,7 +146,7 @@ Para apresentações de alto impacto sem "muralhas de texto":
|
|
|
146
146
|
- **Sem bloqueios de robô**: Modelos de IA não devem tentar raspar o Google via navegadores headless (o Google bloqueia com CAPTCHA/bot detection). O SagaDeck inclui pesquisa nativa via DuckDuckGo com `sagadeck search "<termo>"` e a ferramenta MCP `sagadeck_web_search`.
|
|
147
147
|
- **Controle Flexível de Fontes**:
|
|
148
148
|
- **Quando o usuário pedir fontes citadas**: Inclua `source: "Fonte: Nome do Relatório, Ano"` nos slides com métricas (`stats`, `number`, `chart`, `split`) e a URL completa no roteiro do apresentador (`notes:`).
|
|
149
|
-
- **Quando o usuário pedir sem fontes (ou estilo clean
|
|
149
|
+
- **Quando o usuário pedir sem fontes (ou estilo clean)**: Omita o campo `source:`, mantendo o slide limpo e desobstruído.
|
|
150
150
|
- **Garantia para Modelos Econômicos**: Qualquer modelo (inclusive os menores como Haiku, Flash ou GPT-4o-mini) deve sempre finalizar com o comando `sagadeck autofix <deck.yaml>`. Isso corrige problemas cobertos pelas regras; depois confira a renderização, porque não garante perfeição geométrica ou visual.
|
|
151
151
|
|
|
152
152
|
## Recursos recentes para autoria por IA
|
package/bin/sagadeck.js
CHANGED
|
@@ -52,7 +52,7 @@ const HELP = `sagadeck — YAML -> apresentação (HTML animado + PowerPoint edi
|
|
|
52
52
|
(imagens: peça no briefing; --images = "você decide onde ilustrar", --no-images = nenhuma)
|
|
53
53
|
sagadeck napkin <texto|arquivo> [-o deck.yaml] [--rules] texto bruto -> slide visual (LLM se houver; --rules força as regras)
|
|
54
54
|
sagadeck imagens <deck.yaml> gera as imagens pedidas com image_prompt: no YAML (modelo de imagem)
|
|
55
|
-
sagadeck scaffold <nome> [--theme=prata] [--type=pitch|
|
|
55
|
+
sagadeck scaffold <nome> [--theme=prata] [--type=lancamento|pitch|palestra] gera esqueleto narrativo pronto (economiza 80% de tokens)
|
|
56
56
|
sagadeck new <nome> [--topic=T] [--theme=x] cria um deck de exemplo NA BIBLIOTECA (<biblioteca>/<tópico>/<nome>/)
|
|
57
57
|
sagadeck build <deck.yaml> gera <deck>.html (abre no navegador; P = modo apresentador)
|
|
58
58
|
sagadeck check <deck.yaml> procura texto estourado, sobreposição, contraste, excesso de texto
|
|
@@ -202,7 +202,7 @@ async function main() {
|
|
|
202
202
|
const { generateScaffold } = await import("../src/templates/scaffold.js");
|
|
203
203
|
const YAML = (await import("yaml")).default;
|
|
204
204
|
const theme = flags.theme || "prata";
|
|
205
|
-
const type = flags.type || "
|
|
205
|
+
const type = flags.type || "lancamento";
|
|
206
206
|
const title = flags.title || (args[0] ? path.basename(args[0], path.extname(args[0])) : "Nova Apresentação");
|
|
207
207
|
const author = flags.author || "Seu Nome";
|
|
208
208
|
const spec = generateScaffold({ title, theme, type, author });
|
package/docs/ANDAMENTO.md
CHANGED
|
@@ -12,10 +12,10 @@ começar. Trabalho em curso vai para o GitHub (commit + push) a cada etapa, nunc
|
|
|
12
12
|
|
|
13
13
|
**Para quem pega isto numa sessão nova, sem a conversa de origem.** O projeto é o **sagadeck**:
|
|
14
14
|
- Repositório GitHub `naruminho/sagadeck`, clonado em `C:\Users\narum\src\sagadeck`, branch `main`.
|
|
15
|
-
- Publicado no npm como `sagadeck` (1.7.
|
|
15
|
+
- Publicado no npm como `sagadeck` (1.7.1: a IA diz por que o `ia.json` não serve e, no servidor, não mostra a configuração; o PyPI parou na 1.5.0).
|
|
16
16
|
- Ferramenta Node (ESM) que transforma apresentações escritas em YAML em HTML/PPTX.
|
|
17
17
|
- Tem um editor web, o Studio (`sagadeck studio`, porta 3517).
|
|
18
|
-
- Tem uma IA (
|
|
18
|
+
- Tem uma IA embutida (provedor direto, configurado em `~/.sagadeck/ia.json` pela tela Configurar IA) que cria e edita decks, inclusive a partir de PDFs de
|
|
19
19
|
artigos científicos para apresentar em congresso.
|
|
20
20
|
|
|
21
21
|
Os itens abaixo vieram de duas rodadas de 08/10/2026, ambas descritas nas seções logo abaixo deste diário:
|
|
@@ -42,6 +42,164 @@ datadas abaixo). O resto fica para depois e só entra se for pedido. Antes de co
|
|
|
42
42
|
ler o conteúdo força o download. Conferir se listar a biblioteca (`openLibrary`, `/api/library`) lê o YAML de
|
|
43
43
|
cada deck; se lê, listar só pelo nome/metadado e abrir o conteúdo sob demanda.
|
|
44
44
|
|
|
45
|
+
## Frente: mapa (planejada em 10/10/2026, em andamento)
|
|
46
|
+
|
|
47
|
+
Pedido de Narumi, depois de muita conversa. **Leia antes de mexer.** Recurso de nicho, mas completo: mostrar e
|
|
48
|
+
explicar algo geográfico (pontos de interesse de qualquer tipo, linhas, áreas), com a IA montando a partir de dados.
|
|
49
|
+
Não é um QGIS: análise pesada fica lá; aqui é apresentar bem, com contas simples feitas pelo código.
|
|
50
|
+
|
|
51
|
+
**Decisões (não reabrir):**
|
|
52
|
+
- **Internet não é limitação.** Recurso online é bem-vindo e fica disponível onde funciona; onde não funciona, some ou
|
|
53
|
+
avisa. Nada de "assar/chapar" o mapa de fundo no deck (ela odiou): mapa **ao vivo** (Leaflet, embutido no pacote).
|
|
54
|
+
- **Nada bancário nem de setor nenhum**: pontos de interesse genéricos (sensor, hidrômetro, placa solar, cinema,
|
|
55
|
+
órgão público…). Exemplos, REFERENCIA e testes com casos variados.
|
|
56
|
+
- **A planilha é a fonte, o mapa é a vista**: pontos em CSV do projeto (`contexto/*.csv`, importa XLSX); linhas e
|
|
57
|
+
polígonos em GeoJSON do projeto (importa GPX/KML). O slide diz qual arquivo e quais colunas (posição, cor, tamanho,
|
|
58
|
+
rótulo). A mesma planilha serve tabela e gráfico.
|
|
59
|
+
- **Carregamento preguiçoso** em quatro níveis: o Leaflet só entra no HTML de deck com mapa; o mapa só liga (e só pede
|
|
60
|
+
tiles) quando o slide aparece; o editor de mapa no Studio vem por import dinâmico; os módulos de serviço no servidor
|
|
61
|
+
só no primeiro pedido. Teste para os três primeiros.
|
|
62
|
+
- **Uma tentativa e para**: erro de autorização ou bloqueio (401, 403, 407, recusa, certificado, até 429) marca o
|
|
63
|
+
serviço indisponível na sessão; só volta com "Tentar de novo" ou reabrindo o Studio. Tiles: se os primeiros falham
|
|
64
|
+
sem nenhum sucesso, a camada de fundo desliga e aparece aviso (camadas continuam). **Sem teste proativo** de
|
|
65
|
+
serviço: o primeiro uso real é o teste. Teste: serviço com 403 recebe exatamente um pedido.
|
|
66
|
+
- **Pedidos saem do navegador no Studio local** (mesmo caminho/proxy do Google Maps, que abre na rede do trabalho; o
|
|
67
|
+
Node não usa o proxy do sistema). No multiusuário, do servidor (a chave não vai para a página dos outros).
|
|
68
|
+
- **Serviços padrão gratuitos**, todos configuráveis em **Configurar mapa** (`~/.sagadeck/mapa.json`, como o ia.json):
|
|
69
|
+
tiles do OpenStreetMap, Nominatim (endereço↔coordenada), Overpass (busca de lugares/linhas/áreas), OSRM
|
|
70
|
+
(rotas, encaixe de GPS) e OpenRouteService (rotas e isócronas; chave da Narumi já está no mapa.json desta máquina,
|
|
71
|
+
nunca no repositório). Respeitar as políticas: atribuição visível "© colaboradores do OpenStreetMap", identificação
|
|
72
|
+
do sagadeck, Nominatim 1/s e cache obrigatório (lote grande: avisar e sugerir provedor com chave), nada de baixar
|
|
73
|
+
tiles fora da tela. Públicos de OSRM/Overpass são não comerciais: para uso no trabalho, servidor da empresa ou chave.
|
|
74
|
+
- **A IA**: recebe no prompt o estado de cada serviço (funcionando, não usado, bloqueado) e decide; o código recusa
|
|
75
|
+
sem pedido de rede o que está bloqueado. Pergunta antes de mandar **dados da pessoa** a serviço de fora (primeira vez
|
|
76
|
+
por planilha) e antes de lote grande (tempo estimado). Geometria nunca inventada: vem de dados, do OSM ou do cálculo.
|
|
77
|
+
Testes: mock (encanamento) e ao vivo (decisão).
|
|
78
|
+
- PDF/PPTX capturam o mapa como está na tela na exportação; camada por clique vira animação no PPTX.
|
|
79
|
+
|
|
80
|
+
**Entregas (uma branch/PR cada, com testes e CI verde):**
|
|
81
|
+
1. **Feita (10/10).** Slide `map`: motor `src/map.js` (CSV com colunas de posição achadas pelo nome, GeoJSON, GPX,
|
|
82
|
+
KML, `areas` por UF/país com os contornos embutidos `src/runtime/vendor/geo-contornos.json`; cor por categoria
|
|
83
|
+
na paleta do tema `c1…c5` ou escala por número, tamanho, rótulo, cartão; prévia SVG para miniaturas), mapa ao vivo
|
|
84
|
+
`src/runtime/map.js` (Leaflet 1.9.4 embutido por `scripts/vendor-map.mjs`, só em deck com mapa; monta só no slide
|
|
85
|
+
atual; testa **um** tile antes de ligar o fundo e, se falhar, nenhum outro mapa da sessão tenta; legenda que liga e
|
|
86
|
+
desliga; camada por clique com marcadores `data-step` e câmera por camada), configuração da máquina
|
|
87
|
+
`src/map-config.js` (`~/.sagadeck/mapa.json`; só o fundo vai para o deck), exportação abrindo o deck com mapa por
|
|
88
|
+
endereço local (Referer) e esperando o mapa de cada slide (`settleSlide`), formulário `map-fields.js`, REFERENCIA,
|
|
89
|
+
exemplo de layout (entra no Conheça o SagaDeck). Testes: `test/map.test.js` (motor, apresentação com servidor de
|
|
90
|
+
tiles falso, 403 recebe exatamente um pedido, exportação) e `studio.test.js` (inserir mapa, monta, trocar fundo
|
|
91
|
+
salva). Achados no caminho: coordenada "-46.702" lida como milhar pelo leitor de números (agora `coordNum`); a
|
|
92
|
+
classe `widget` na caixa fazia o runtime trocar o conteúdo; rota do `map.js` com cache servia código velho.
|
|
93
|
+
Pendente para depois: geocodificar `at: "Recife"` (entrega 2) e contornos de reserva quando o fundo falha.
|
|
94
|
+
2. **Feita (10/10).** A tela de dados é a planilha da aba Arquivos (`sheetGrid`, já editável e colando do Excel),
|
|
95
|
+
com as ferramentas de mapa (`src/studio/public/map-data.js`): **Pôr no mapa** (adivinha cor pela coluna de
|
|
96
|
+
categorias que se repetem e rótulo pela de nome; `.xlsx` ganha um CSV ao lado), **Achar coordenadas** (pergunta o
|
|
97
|
+
plano antes e mostra quantos endereços, para qual serviço e quanto tempo; só envia com o sim; progresso; o que não
|
|
98
|
+
achou fica listado; cache em `.sagadeck/geocodificacao.json`, consentimento em `.sagadeck/mapa-consentimento.json`)
|
|
99
|
+
e a **seleção ligada** (clicar no ponto seleciona a linha; selecionar a linha abre o cartão). Servidor:
|
|
100
|
+
`src/map-services.js` (pedidos identificados `sagadeck/versão`, uma tentativa e para, estado de cada serviço) e
|
|
101
|
+
`src/studio/geo-routes.js` (`/api/mapa`, `/api/mapa/tentar`, `/api/mapa/geocode` em ndjson). Tela **Configurar
|
|
102
|
+
mapa** (`map-settings.js`, botão no formulário do mapa). Decisão: a busca de endereço sai do servidor do Studio
|
|
103
|
+
(a política do Nominatim pede identificação, que o navegador não deixa mudar); os tiles saem do navegador.
|
|
104
|
+
Testes: `test/map-data.test.js`. Fica para depois: abrir GeoJSON como tabela.
|
|
105
|
+
3. **Feita (10/10).** Editor `src/studio/public/map-editor.js`, carregado só no clique em **Editar no mapa** (botão e
|
|
106
|
+
barra no `#canvas-viewport`, fora do slide escalado): Mover (arrastar ponto atualiza a linha da planilha; clicar
|
|
107
|
+
numa linha/área abre os vértices: arrastar, apagar, dividir; juntar linhas pelas pontas mais próximas), Ponto
|
|
108
|
+
(linha nova na planilha da camada, endereço pelo `/reverse`), Linha e Área (GeoJSON da camada ou
|
|
109
|
+
`contexto/desenhos.geojson`; "Seguir as ruas" a pé/carro/bicicleta pelo `/api/mapa/rota`), Régua, Usar esta vista
|
|
110
|
+
(grava `view` no slide); comprimento e área ao vivo. O Studio guarda a vista entre redesenhos (`keepView`). Rotas,
|
|
111
|
+
endereço de um ponto e área alcançável em `src/map-routing.js` (OpenRouteService com chave, testado ao vivo com a
|
|
112
|
+
chave da Narumi: 817 m a pé, isócrona de 5 min; ou OSRM). Chave do mapa como a da IA: tela Configurar mapa com
|
|
113
|
+
"Usar o OpenRouteService" e Testar, botão na biblioteca, `keyEnv` (variável de ambiente, para o servidor). README
|
|
114
|
+
com a seção "Mapa (opcional)". Achados: o editor visual capturava o clique no mapa (agora ignora `.map-live`); o
|
|
115
|
+
projeto não gravava `.geojson/.gpx/.kml` como texto. Testes: `test/map-editor.test.js` e `map-data.test.js`.
|
|
116
|
+
Retomada: PR #178 mergeado após CI verde no Ubuntu e no Windows. A corrida do arrastar passou; a falha restante era
|
|
117
|
+
uma releitura ao clicar na aba de planilha já ativa, que substituía a grade e apagava a seleção feita pelo mapa.
|
|
118
|
+
O clique agora mantém a aba ativa. Regressão com resposta atrasada reproduziu a falha antes da correção e passou
|
|
119
|
+
depois; mapa/planilha e editor: 6 testes passando. Suíte completa: 950 passaram, 22 pulados, zero falhas.
|
|
120
|
+
4. **Feita (10/10).** A IA que edita o deck pede dados com um bloco `mapa:` (como o `ver:` do material; nenhuma
|
|
121
|
+
chamada a mais quando o pedido não é de mapa): `lugares` e `linhas` (Overpass, com o filtro de tags que ela
|
|
122
|
+
escolhe e o centro achado no Nominatim), `contorno` (Nominatim com polígono), `web` (a pesquisa existente + uma
|
|
123
|
+
extração que só usa o que leu, com a URL de cada linha; as coordenadas vêm depois) e `coordenadas` (planilha da
|
|
124
|
+
pessoa; sem `confirmado: true`, o código pergunta "Posso enviar?" com opções e não envia nada). O Studio executa
|
|
125
|
+
(`src/ai/map-data.js`), grava em `contexto/` com a coluna `fonte` e chama a IA de novo (até 2 rodadas). O prompt
|
|
126
|
+
(`mapDataPrompt`) leva o estado de cada serviço; bloqueado não recebe pedido e a IA fica sabendo. Testes:
|
|
127
|
+
`test/map-ai.test.js` (encanamento, LLM falso) e 3 ao vivo em `test/ai-live.test.js` (pede `amenity=cinema` perto
|
|
128
|
+
de Campinas; não se dá o sim sozinha; com a busca bloqueada, não pede e oferece alternativa), todos passando com o
|
|
129
|
+
modelo de verdade. Conferido com o OpenStreetMap real: 5 cinemas num raio de 8 km do centro de Campinas (o
|
|
130
|
+
Overpass público às vezes responde 504 por sobrecarga; o sagadeck registra e não insiste).
|
|
131
|
+
5. **Feita (10/10), no mesmo PR da 4.** Contas puras em `src/map-analysis.js` (haversine, mais próximo, raio, ponto em
|
|
132
|
+
polígono com buraco, distância a linha, círculo, faixa em volta de linha para desenhar, "onde instalar" por grade e
|
|
133
|
+
escolha gulosa) e as ações do bloco `mapa:` em `src/ai/map-analysis-run.js`: `mais_proximo` (CSV com
|
|
134
|
+
`mais_proximo` e `distancia_m`), `contar` (por área ou raio, grava o círculo), `faixa` (GeoJSON da faixa e quantos
|
|
135
|
+
pontos dentro, pela distância exata à linha), `sugerir` (CSV de sugestões, avisando que é geométrico), `rota`
|
|
136
|
+
(distância e tempo), `alcance` (isócronas, só com OpenRouteService) e `encaixar` (OSRM `/match`, amostrando até 100
|
|
137
|
+
pontos). A IA só narra os números. Achado: pontos todos alinhados deixavam o "onde instalar" sem resposta (retângulo
|
|
138
|
+
de altura zero). Testes: `test/map-analysis.test.js` e o ao vivo "qual loja fica mais perto de cada pedido" (pede
|
|
139
|
+
`mais_proximo`).
|
|
140
|
+
|
|
141
|
+
**Publicação 1.8.0 (retomada em 10/10):** versão preparada em `package.json` e lockfile, incluindo as cinco entregas
|
|
142
|
+
e o Conheça o SagaDeck. O #178 já foi mergeado; falta concluir a CI e mergear o #179, conferir a CI da `main`, criar a
|
|
143
|
+
release e atualizar a instalação local e o Oracle. No Oracle, o ambiente e os dois `mapa.json` já estão preparados
|
|
144
|
+
com `OPENROUTESERVICE_API_KEY` (modo 600, dono de cada serviço); os serviços aguardam o código da release para
|
|
145
|
+
reiniciar. Os quatro testes de mapa com o modelo real passaram novamente. Suíte completa da 1.8.0: 982 testes,
|
|
146
|
+
956 passaram, 26 pulados, zero falhas; mapa/editor/exportação/leitores e regras: 22 testes direcionados passaram.
|
|
147
|
+
Na revisão com cliques reais, a planilha cobria o mapa inteiro e a legenda interceptava um ponto no enquadramento
|
|
148
|
+
automático. Corrigidos: a planilha fica ao lado do mapa (sugestões de gráfico recolhidas, com botão para abrir), a
|
|
149
|
+
seleção funciona nas duas direções e o enquadramento reserva espaço para a legenda. O teste reproduziu a obstrução
|
|
150
|
+
antes da correção e agora clica no ponto e na célula, sem disparar eventos sintéticos.
|
|
151
|
+
|
|
152
|
+
## Conheça o SagaDeck — 10/10/2026
|
|
153
|
+
|
|
154
|
+
Pergunta de Narumi: a pessoa nem sabe o que dá para gerar. Agora a biblioteca tem, na lateral, **Conheça o
|
|
155
|
+
SagaDeck** (também na vitrine, em Recursos do SagaDeck): uma apresentação com um slide por recurso, agrupada em
|
|
156
|
+
seções, que abre em prévia (nada é gravado; "Usar como base" cria a cópia em Modelos).
|
|
157
|
+
|
|
158
|
+
- `src/studio/tour.js` monta-se sozinha dos exemplos de layout (`LAYOUT_SAMPLES`): layout novo entra sem ninguém
|
|
159
|
+
lembrar; layout fora dos grupos cai em "Mais recursos". Fecha com o que não é layout (criar e conversar com a IA,
|
|
160
|
+
leitura crítica, importar PowerPoint, gráfico da planilha, temas, apresentador, PDF/PowerPoint, link).
|
|
161
|
+
- Cada slide traz nas **notas** (visíveis abaixo do slide no Studio e na visão do apresentador) o nome, a descrição e
|
|
162
|
+
a frase para pedir à IA (`LAYOUT_ASK` em `layout-samples.js`, exemplos de áreas variadas); no rodapé (`source`)
|
|
163
|
+
também, nos layouts que o desenham. Os links de navegação dos exemplos saem (apontariam para slides inexistentes).
|
|
164
|
+
- Testes: `engine.test.js` (todo layout tem pedido e aparece no tour, com nome e pedido; renderiza) e
|
|
165
|
+
`library-ui.test.js` (a lateral abre em prévia e nada é criado na biblioteca).
|
|
166
|
+
|
|
167
|
+
## Sem citar o programa de apresentações da maçã (1.7.1) — 10/10/2026
|
|
168
|
+
|
|
169
|
+
Pedido de Narumi: o código e a documentação não citam o programa de apresentações da maçã, a empresa nem os
|
|
170
|
+
aparelhos dela. O tema `prata` passa a se descrever como "limpo e espaçoso"; o `aluminio`, como "lançamento de
|
|
171
|
+
aparelho premium". O exemplo virou `templates/exemplo-prata.yaml`, e o tipo padrão do `scaffold` (CLI e MCP) virou
|
|
172
|
+
`lancamento` (tipo desconhecido continua caindo nele). Saiu também o slogan da empresa do esqueleto. Ficam só os nomes
|
|
173
|
+
técnicos (a fonte do sistema no CSS, a identificação de navegador da pesquisa). `guards.test.js` falha se voltar
|
|
174
|
+
(olha os arquivos do repositório, menos bibliotecas de terceiros); regra no `CLAUDE.md`.
|
|
175
|
+
|
|
176
|
+
## No servidor, a configuração da IA não sai para quem usa (1.7.1) — 10/10/2026
|
|
177
|
+
|
|
178
|
+
No multiusuário (portal do Oracle), `/api/ia` e `/api/ai/status` mostravam a qualquer usuário o provedor, o endereço,
|
|
179
|
+
os modelos, o caminho do `ia.json` e os 4 últimos caracteres da chave, embora ele não pudesse editar nada.
|
|
180
|
+
|
|
181
|
+
- `/api/ia` no multiusuário devolve só `{ editable: false, configured }`; `/api/ai/status`, só `{ available,
|
|
182
|
+
configured, server: true }`.
|
|
183
|
+
- O Configurar IA, no servidor, vira um aviso: quem configura é quem administra, e se a IA está configurada. O
|
|
184
|
+
indicador do editor diz "A IA deste servidor está ligada" (ou "não está respondendo / não está configurada. Avise
|
|
185
|
+
quem administra"), sem endereço nem modelo; o progresso do chat diz "Enviando para a IA".
|
|
186
|
+
- Teste (`test/library-ui.test.js`): com a IA configurada no servidor, nada de chave, modelo, endereço ou arquivo nas
|
|
187
|
+
respostas nem na página do editor; o diálogo não tem campos. Falha sem a mudança.
|
|
188
|
+
|
|
189
|
+
## ia.json ilegível diz o motivo — 10/10/2026
|
|
190
|
+
|
|
191
|
+
Na migração do Oracle (o modelrelay saiu; os serviços `sagadeck` e `sagadeck-dev` falam direto com o OpenRouter, cada
|
|
192
|
+
um com seu `ia.json`), o arquivo foi criado como root e o serviço, que roda com usuário próprio, não conseguia lê-lo:
|
|
193
|
+
o sagadeck dizia só "IA não configurada", como se o arquivo nem existisse.
|
|
194
|
+
|
|
195
|
+
- `readIA` (`src/ai/ia-config.js`) separa "não existe" (só não configurada) de "existe e não serve": sem permissão,
|
|
196
|
+
ilegível, JSON quebrado, sem `url`. O motivo vai em `llmConfig().problem`.
|
|
197
|
+
- Aparece ao subir (`studioBanner`, a linha "IA: não configurada. …", que é o que quem administra vê no log), no erro
|
|
198
|
+
da chamada à IA, em `/api/ia` e `/api/ai/status`, na faixa da biblioteca, no Configurar IA e no indicador do editor.
|
|
199
|
+
- A faixa da biblioteca quebra o texto longo (o caminho do arquivo empurrava o botão para baixo de outro elemento).
|
|
200
|
+
- Testes: `test/ia-config.test.js` (pasta no lugar do arquivo, sem permissão fora do Windows, JSON quebrado, sem
|
|
201
|
+
endereço) e `test/library-ui.test.js` (faixa e Configurar IA dizem "Não deu para ler"). Falham sem a correção.
|
|
202
|
+
|
|
45
203
|
## CI verde de novo (e a regra: merge só com a CI verde) — 09/10/2026
|
|
46
204
|
|
|
47
205
|
A CI do GitHub estava vermelha havia dias (desde antes da 1.4.0) e 13 PRs foram mergeados e a 1.5.0 publicada assim.
|
|
@@ -788,8 +946,8 @@ raciocínio, a mesma conferência sai em 1–2 s e acha o que importa (título r
|
|
|
788
946
|
resposta parava no meio perdia os itens → os que faltaram vão de novo, um por vez. Respostas que não serviram
|
|
789
947
|
ficam em `.sagadeck/transform/falhas/` (as 40 últimas).
|
|
790
948
|
- Casos de 02/10 (decks em `Pedidos de teste/`):
|
|
791
|
-
- **
|
|
792
|
-
|
|
949
|
+
- **Prateado**: o `prata` achado sem graça. Tema novo **`aluminio`**: a cara
|
|
950
|
+
de lançamento de aparelho premium (prata metálico com brilho de estúdio, alumínio escovado, título
|
|
793
951
|
cromado com a cor sólida em `color` para o fiscal medir, disco de metal polido na capa e na seção, cartões de
|
|
794
952
|
alumínio com chanfro, seção em azul-pacífico, escuro em grafite).
|
|
795
953
|
- **história dos videogames em 5 slides, temática, o último como mapa de Mario Kart visto de cima**: saiu no tema
|
package/docs/EXPERIENCIAS.md
CHANGED
|
@@ -6,7 +6,7 @@ Na biblioteca, escolha uma experiência para abrir um roteiro pronto e editável
|
|
|
6
6
|
|---|---|---|
|
|
7
7
|
| Sala de decisão | Estratégia e diretoria | Grafite e ouro, gráficos limpos, recomendação → evidências → riscos → decisão |
|
|
8
8
|
| Matéria de capa | História e investigação | Serifas, papel claro, enquadramentos amplos, pistas e virada narrativa |
|
|
9
|
-
| Modo espetáculo | Palco e
|
|
9
|
+
| Modo espetáculo | Palco e palestras | Contraste luminoso, escala monumental, pausas, escolhas da plateia e revelação por clique |
|
|
10
10
|
| Clube criativo | Workshop | Formas primárias, composição geométrica, desafios e timer |
|
|
11
11
|
| Laboratório vivo | Programação | Código por etapas, saída ilustrativa, screenshot com focos e previsão antes da resposta |
|
|
12
12
|
| Essencial | Produto e comunicação objetiva | Espaço vazio, uma ideia por vez, tipografia discreta e comparação |
|
package/docs/REFERENCIA.md
CHANGED
|
@@ -96,6 +96,7 @@ Tabela em markdown no meio de um texto (`problem`, `body`, `text`, passo do `sol
|
|
|
96
96
|
| `timeline` | `title, events: [{when, title, text, tag}], highlight, after, build` | linha do tempo |
|
|
97
97
|
| `chart` | `title, chart: {…}, side (texto ou elemento), chartHeight` | gráfico + comentário |
|
|
98
98
|
| `table` | `title, head, rows, style, color, highlight, total, side, caption, source` | tabela de verdade nas cores do tema |
|
|
99
|
+
| `map` | `kicker, title, basemap, layers: [{name, points \| geojson \| gpx \| kml \| areas, color, size, label, colors, popup, step, view}], build, view, caption` | **mapa ao vivo** com pontos, linhas e áreas (sensores, medidores, unidades de atendimento, cinemas, trajetos, regiões…): os dados vêm da planilha do projeto ou de arquivos GeoJSON/GPX/KML; ver [Mapa](#mapa-map) |
|
|
99
100
|
| `compare` | `title, left: {label, value, title, text, items, figure, hl}, right: {…}, vs, after, build` | A × B |
|
|
100
101
|
| `matrix` | `title, x: [esq, dir], y: [cima, baixo], cells: [4 × {title, text, example, hl}], build` | matriz 2×2 |
|
|
101
102
|
| `question` | `question, options: [texto ou {key, text, sub}], keys, cols, timer, hint, optionSize` | pergunta para a plateia (com timer) |
|
|
@@ -563,7 +564,7 @@ Linhas que começam com PALAVRAS EM MAIÚSCULAS seguidas de `:` viram etiquetas
|
|
|
563
564
|
|
|
564
565
|
Como no PowerPoint, são duas escolhas independentes:
|
|
565
566
|
|
|
566
|
-
- **Tema** (`theme`): fontes, raio das bordas, textura e uma **pele** própria que rearruma capa, seção e encerramento, muda o jeito dos slides de conteúdo (título, cartões, marcadores da lista, citação, número grande, frase) e põe ornamentos (`sinal`: faixa zebrada e selo no chapéu; `editorial`: fios e capa centralizada; `noite`: moldura fina; `bauhaus`: círculo, quadrado e triângulo; `terminal`: janela com barra e prompt; `jornal`: fios de jornal e manchete sublinhada; `rabisco`: fitas adesivas e títulos inclinados; `oceano`: ondas e chapéu em pílula; `pop`: adesivos e sombra chapada; `aurora`: brilhos e traço em gradiente; `prata`: limpo e centralizado
|
|
567
|
+
- **Tema** (`theme`): fontes, raio das bordas, textura e uma **pele** própria que rearruma capa, seção e encerramento, muda o jeito dos slides de conteúdo (título, cartões, marcadores da lista, citação, número grande, frase) e põe ornamentos (`sinal`: faixa zebrada e selo no chapéu; `editorial`: fios e capa centralizada; `noite`: moldura fina; `bauhaus`: círculo, quadrado e triângulo; `terminal`: janela com barra e prompt; `jornal`: fios de jornal e manchete sublinhada; `rabisco`: fitas adesivas e títulos inclinados; `oceano`: ondas e chapéu em pílula; `pop`: adesivos e sombra chapada; `aurora`: brilhos e traço em gradiente; `prata`: limpo e centralizado; `relevo`: ficção científica sóbria (sala de controle de filme, sem néon): grafite profundo, curvas de nível ao fundo, cantoneiras de mira, título leve embaixo à esquerda, rótulos técnicos em monoespaçada; combina com ilustração 3D realista (terreno, cidade, água) e gráfico 3D (`science` com superfície); `arcade`: videogame e fliperama, tela de tubo com linhas de varredura, neon, título em pixel no meio e chão de tijolos de jogo de plataforma; `aluminio`: prata metálico de lançamento de aparelho premium, título cromado embaixo à esquerda e um disco de metal polido; `manual` e `manual-noite`: documentação técnica, sem ornamento, código e texto corrido legíveis — o par claro/escuro do mesmo arranjo, para material de consulta, tutorial e apostila: `manual` imprime bem, `manual-noite` descansa a vista). Nos slides de conteúdo: `sinal` barra de aviso e placas; `editorial` fios finos e citação centralizada; `noite` tudo centralizado e cartões vazados; `bauhaus` faixas de cor primária e círculos; `terminal` título como comando e cartões-janela; `jornal` fios duplos e colunas; `oceano` cartões flutuando e balões; `pop` contorno grosso e sombra dura; `prata` centralizado e sem caixa; `aluminio` cartões de alumínio com chanfro de luz; `relevo` painéis de vidro fosco com traço gelo; `arcade` cartões de 8 bits com borda de pixel e sombra dura; `rabisco` tracejados e números circulados; `aurora` vidro com brilho. Trocar o tema já muda o arranjo: não reescreva slides só para "combinar" com o tema.
|
|
567
568
|
- **Paleta** (`palette`): só as cores, em qualquer tema. `tinta`, `floresta`, `mar`, `entardecer`, `lavanda`, `grafite`, `neon`, `areia`, `cereja`, `corporativo`, `luzquente`, ou as suas: `palette: { paper: "F4F1EA", ink: "161616", accent: "D7263D", alert: "1B998B" }`. Os quatro tons saem dessas quatro cores, com contraste garantido. A paleta troca também o fundo: em tema escuro por natureza (`relevo`, `arcade`, `aurora`, `noite`, `terminal`), uma paleta de papel claro (`safira`, `corporativo`, `tinta`…) deixa o tema claro; para manter o visual escuro que o pedido quer, não ponha paleta (ou use uma de papel escuro, como `neon`).
|
|
568
569
|
- **Paletas de família** (para identidade de marca sem cansar a vista): a cor forte da marca fica só no detalhe (`alert`, a ênfase pontual) e a página trabalha com os parentes mais agradáveis dela (`accent` e `family`). `rubi` (rosas, magenta e vinho; vermelho só no detalhe), `ametista` (roxos e lilases), `tangerina` (laranja com azul-marinho), `safira` (azuis com um toque âmbar), `esmeralda` (verdes). Na sua: `palette: { paper: "FFFFFF", ink: "3B2B33", accent: "B83A6E", alert: "CC092F", family: ["F9DCE5", "EFA3BC", "D9668F", "7E2349"] }` (de 2 a 8 parentes). `family` pinta as séries extras dos gráficos e os grupos e ramos dos diagramas. Pedido de "cores da empresa X": monte uma paleta de família assim, com a cor forte em `alert`. Para clima futurista de bom gosto (luz quente de prédios e shows de drones, nunca neon barato): `luzquente` — brancos quentes e âmbar que funcionam no claro e, no `tone: dark`, viram HUD âmbar sobre carvão quente.
|
|
569
570
|
|
|
@@ -665,6 +666,63 @@ Como o slide mestre do PowerPoint: `master:` no deck desenha em todo slide o que
|
|
|
665
666
|
|
|
666
667
|
Quando melhorar ou reescrever uma apresentação que já existia (principalmente uma importada), marque cada slide que mudou para a pessoa validar: `review: { status: alterado, note: "o que mudou, em uma frase", original: 7 }` (`original` = o número do slide no original) ou `review: { status: novo, note: "por que entrou" }` para slide que não existia. O Studio mostra um selo na miniatura, uma faixa no slide com **Ver original**, **Aceitar** e **Desfazer** (volta ao slide de `original/original.yaml`; slide novo sai) e a lista em Revisar › **Mudanças**. A transformação da apresentação inteira ainda usa dois estados: `pendente` (a proposta ficou ao lado do original porque faltava algo dele; aceitar tira o original, desfazer tira a proposta) e `revisar` (a conferência pela foto achou um problema de desenho). A apresentação e a exportação não mostram as marcas. Nunca apague dados do original: números, nomes, fórmulas, fontes e figuras específicas (um mapa, um experimento de uma cidade) continuam, só mais claros.
|
|
667
668
|
|
|
669
|
+
## Mapa (`map`)
|
|
670
|
+
|
|
671
|
+
Mapa ao vivo (ruas por baixo, arrastável e com zoom ao apresentar) com **camadas** por cima. A planilha é a fonte e o
|
|
672
|
+
mapa é a vista: os pontos vêm de um CSV do projeto (normalmente em `contexto/`), e as linhas e áreas de GeoJSON, GPX
|
|
673
|
+
(rastro de GPS) ou KML. Serve para qualquer ponto de interesse (sensor, medidor, placa solar, unidade de atendimento,
|
|
674
|
+
escola, cinema…) e qualquer traçado ou área (rede, trajeto, bairro, área de obra).
|
|
675
|
+
|
|
676
|
+
```yaml
|
|
677
|
+
- layout: map
|
|
678
|
+
kicker: Monitoramento
|
|
679
|
+
title: Onde estão os ==sensores==
|
|
680
|
+
basemap: claro # ruas (padrão) | claro | escuro | satelite (se configurado) | nenhum
|
|
681
|
+
build: true # uma camada por clique
|
|
682
|
+
layers:
|
|
683
|
+
- name: Sensores # nome na legenda
|
|
684
|
+
points: contexto/sensores.csv # colunas de latitude e longitude achadas pelo nome (lat/latitude, lon/longitude)
|
|
685
|
+
color: status # coluna: categoria vira cor (legenda clicável); número vira escala
|
|
686
|
+
colors: { ativo: s3, sem sinal: alert } # cor de cada categoria (opcional)
|
|
687
|
+
size: vazao # coluna numérica vira o tamanho do ponto (opcional)
|
|
688
|
+
label: nome # rótulo fixo ao lado de cada ponto (até 60 pontos)
|
|
689
|
+
popup: [nome, status, vazao] # campos do cartão ao clicar (padrão: as primeiras colunas)
|
|
690
|
+
- name: Rede de água
|
|
691
|
+
geojson: contexto/rede.geojson # linhas e polígonos; ou gpx: / kml:
|
|
692
|
+
color: material
|
|
693
|
+
view: fit # quando a camada aparece, a câmera enquadra ela
|
|
694
|
+
- name: Consumo por estado
|
|
695
|
+
areas: { SP: 320, RJ: 140, MG: 150 } # siglas de UF ou códigos de país (BR, PT…): pinta pela escala
|
|
696
|
+
legend: mil m³
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
- `points:` também aceita a lista no próprio slide: `[{ nome: Centro, lat: -23.55, lon: -46.63, status: ativo }]`.
|
|
700
|
+
Uma coluna só com "lat, lon" vale (`coordenadas`, `posicao`). Use ponto ou vírgula decimal; nunca invente
|
|
701
|
+
coordenadas: elas vêm da planilha, do arquivo ou de uma busca. Linha sem posição válida fica de fora (o fiscal avisa).
|
|
702
|
+
- `color:` aceita também uma cor fixa (`s1`, `em`, `hi`, `alert`, `#1F6FB2`). `step` (número) diz em que clique a
|
|
703
|
+
camada aparece; `view` da camada (`fit` ou `{ center: [lat, lon], zoom }`) leva a câmera até ela nesse clique.
|
|
704
|
+
- `view:` do slide fixa o enquadramento (`{ center: [-23.55, -46.63], zoom: 13 }`); sem ele, o mapa enquadra os dados.
|
|
705
|
+
- Planilha só com endereço (sem latitude e longitude): no Studio, a planilha aberta (aba Arquivos) tem **Achar
|
|
706
|
+
coordenadas**, que pergunta antes de mandar os endereços para o serviço de fora e grava as colunas `latitude` e
|
|
707
|
+
`longitude`; **Pôr no mapa** cria o slide a partir dela. Sugira esses botões em vez de inventar coordenadas.
|
|
708
|
+
Com um slide de mapa selecionado, a planilha fica ao lado dele: clicar num ponto seleciona a linha e selecionar
|
|
709
|
+
uma linha abre o cartão do ponto. O enquadramento automático deixa os pontos livres da legenda.
|
|
710
|
+
- **Editar no mapa** (botão na prévia do slide, no Studio): a pessoa clica para criar pontos (vão para a planilha da
|
|
711
|
+
camada, com o endereço preenchido), desenha linhas e áreas (vão para o GeoJSON da camada; "Seguir as ruas" liga os
|
|
712
|
+
cliques pelo serviço de rotas), arrasta pontos e vértices, divide e junta linhas, mede com a régua e grava o
|
|
713
|
+
enquadramento ("Usar esta vista"). Quando a pessoa quiser marcar algo à mão no mapa, indique esse botão.
|
|
714
|
+
- **Dados de fora** (lugares de um tipo, linhas como ciclovias, o contorno de um lugar com nome, uma lista da web,
|
|
715
|
+
coordenadas da planilha da pessoa): peça com o bloco `mapa:` (as regras vêm no prompt); o Studio busca, grava os
|
|
716
|
+
arquivos em `contexto/` com a fonte de cada linha e te devolve o que gravou. Nunca invente coordenada nem contorno.
|
|
717
|
+
- **Análises** (o mais próximo e a distância, quantos num raio ou numa área, faixa em volta de uma linha, onde
|
|
718
|
+
instalar o próximo, rota com tempo, área alcançável em X minutos, rastro de GPS encaixado nas ruas): também pelo
|
|
719
|
+
bloco `mapa:`; o Studio calcula e devolve os números. Nunca calcule distância, contagem ou tempo de cabeça.
|
|
720
|
+
- Serviços (Configurar mapa, opcional): sem chave, tudo funciona com os serviços públicos do OpenStreetMap; a chave
|
|
721
|
+
gratuita do OpenRouteService só acrescenta rotas a pé/de carro/de bicicleta confiáveis e área alcançável.
|
|
722
|
+
- Ao apresentar: clicar num ponto, linha ou área abre o cartão com os dados; clicar na legenda liga e desliga uma
|
|
723
|
+
categoria ou a camada inteira. O fundo vem da internet; sem rede, aparece um aviso e as camadas continuam. No PDF e
|
|
724
|
+
no PowerPoint o mapa sai como estava na tela.
|
|
725
|
+
|
|
668
726
|
## Tabelas (`table`)
|
|
669
727
|
|
|
670
728
|
Tabela é tabela, nunca imagem: dados de um livro ou de um PowerPoint que vieram como figura viram `table` quando os valores estão legíveis (o sagadeck alinha os números à direita, com algarismos de mesma largura, e pinta nas cores do tema). Layout `table` (o slide inteiro) ou o elemento `{ table: {…} }` em qualquer lugar (`content`, `add`, `canvas`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sagadeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Gerador de apresentações: YAML -> HTML animado + PowerPoint editável, com temas e figuras geradas na hora.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@material-symbols/svg-400": "^0.47.5",
|
|
34
34
|
"esbuild": "^0.28.2",
|
|
35
35
|
"jsqr": "^1.4.0",
|
|
36
|
+
"leaflet": "1.9.4",
|
|
36
37
|
"mermaid": "^11.17.2"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
package/src/ai/deck-ai.js
CHANGED
|
@@ -782,7 +782,7 @@ export function applyPatch(base, patch) {
|
|
|
782
782
|
const STUDIO_KEYS = ["layout", "notes", "time", "id", "uid", "review", "original", "master", "visualEdits", "auto", "fiscalOk", "from", "image_prompt", "image_ref", "density", "deco", "theme", "palette", "tone",
|
|
783
783
|
"bg", "fg", "background", "backgroundStyle", "footer", "header", "transition", "steps", "markStyle", "maxWords", "fit", "titleAs", "context"];
|
|
784
784
|
const FREE_TEXT_KEYS = new Set(["code", "mermaid", "svg", "html", "request", "realtime", "body", "headers", "response", "notes", "consulta", "output", "json"]);
|
|
785
|
-
const PATCH_WORDS = new Set(["slides", "insert", "delete", "edit", "deck", "variants", "test", "ver"]);
|
|
785
|
+
const PATCH_WORDS = new Set(["slides", "insert", "delete", "edit", "deck", "variants", "test", "ver", "mapa"]);
|
|
786
786
|
let knownKeysCache = null;
|
|
787
787
|
function knownSlideKeys() {
|
|
788
788
|
if (knownKeysCache) return knownKeysCache;
|
|
@@ -925,6 +925,13 @@ function parseEditText(text, base) {
|
|
|
925
925
|
const raw = parseYaml(yaml);
|
|
926
926
|
if (!raw || typeof raw !== "object") throw new Error("O bloco yaml precisa ser um objeto com deck/slides/insert/delete (ou variants).");
|
|
927
927
|
if (raw.variants) return parseVariants(raw.variants, base, prose);
|
|
928
|
+
// mapa: [ações] → a IA precisa de dados geográficos (lugares, linhas, contornos, web, coordenadas); quem busca é o
|
|
929
|
+
// Studio (src/ai/map-data.js), que chama de novo com o que gravou
|
|
930
|
+
if (raw.mapa) {
|
|
931
|
+
const acoes = [].concat(raw.mapa?.acoes || raw.mapa).filter((a) => a && typeof a === "object" && a.tipo);
|
|
932
|
+
if (!acoes.length) throw new Error("mapa: mande a lista de ações (tipo: lugares | linhas | contorno | web | coordenadas).");
|
|
933
|
+
return { spec: base, prose, changed: [], geo: acoes.slice(0, 4) };
|
|
934
|
+
}
|
|
928
935
|
// ver: [arquivos] → a IA quer OLHAR o material (recorte, página inteira) antes de responder; quem mostra é o Studio
|
|
929
936
|
if (raw.ver || raw.look) {
|
|
930
937
|
const files = [].concat(raw.ver || raw.look).map((f) => String(f || "").trim()).filter(Boolean);
|
|
@@ -974,7 +981,7 @@ function commandEnvNames(apiContext) {
|
|
|
974
981
|
|
|
975
982
|
// Chat lateral do Studio: aplica um pedido em linguagem natural ao deck.
|
|
976
983
|
export async function editDeck({ spec, instruction, targetSlide = null, issues = [], images = false, imageOptions = {}, history = [], onProgress,
|
|
977
|
-
visuals = [], renderNotes = [], apiContext = null, drawCheck = null, runCommand = null, materials = [], deferImages = false, maxImages, styles = null, signal, reviewCheck = null, reviewDepth = 0, repairSlides = null }) {
|
|
984
|
+
visuals = [], renderNotes = [], apiContext = null, drawCheck = null, runCommand = null, materials = [], deferImages = false, maxImages, styles = null, signal, reviewCheck = null, reviewDepth = 0, repairSlides = null, mapData = null }) {
|
|
978
985
|
const deck = promptSpec(spec);
|
|
979
986
|
const { slides: _slides, ...numbered } = deck;
|
|
980
987
|
const slidesYaml = deck.slides.map((s, i) => `# ── slide ${i + 1} ──\n${YAML.stringify([s], { indent: 2 })}`).join("");
|
|
@@ -1012,7 +1019,7 @@ Antes de responder, verifique (e siga as Regras de edição):
|
|
|
1012
1019
|
: text;
|
|
1013
1020
|
const convo = conversationFor(history);
|
|
1014
1021
|
const messages = [
|
|
1015
|
-
{ role: "system", content: `${systemPrompt({ images, ...(maxImages ? { maxImages } : {}) })}\n\n${AUTOMATION_NOTES}\n\n${EDIT_RULES}\n\n${CONVERSATION_RULES}\n\n${PATCH_FORMAT}\n\n${VARIANTS_FORMAT}\n\n${API_RULES}\n\n${runCommand ? runCommand.description || `${COMMAND_RULES}\n${VIDEO_COMMAND_RULES}${commandEnvNames(apiContext)}` : "Comandos: indisponíveis aqui (só no Studio local, com a apresentação salva na biblioteca). Não peça run:."}` },
|
|
1022
|
+
{ role: "system", content: `${systemPrompt({ images, ...(maxImages ? { maxImages } : {}) })}\n\n${AUTOMATION_NOTES}\n\n${EDIT_RULES}\n\n${CONVERSATION_RULES}\n\n${PATCH_FORMAT}\n\n${VARIANTS_FORMAT}\n\n${API_RULES}\n\n${runCommand ? runCommand.description || `${COMMAND_RULES}\n${VIDEO_COMMAND_RULES}${commandEnvNames(apiContext)}` : "Comandos: indisponíveis aqui (só no Studio local, com a apresentação salva na biblioteca). Não peça run:."}${mapData ? `\n\n${mapData}` : ""}` },
|
|
1016
1023
|
// a conversa deste deck: o que a pessoa disse lá atrás (compactado) + as últimas trocas inteiras
|
|
1017
1024
|
...(convo.memory ? [{ role: "user", content: convo.memory }, { role: "assistant", content: "Certo, levo isso em conta." }] : []),
|
|
1018
1025
|
...convo.recent,
|
|
@@ -1020,7 +1027,7 @@ Antes de responder, verifique (e siga as Regras de edição):
|
|
|
1020
1027
|
];
|
|
1021
1028
|
let motifObjected = false;
|
|
1022
1029
|
const drawWarned = new Set();
|
|
1023
|
-
const { spec: edited, prose, attempts, changed = [], talk, options = [], variants, imagesDropped, visionRouted, test = [], commands = [], transform, style, review, look } =
|
|
1030
|
+
const { spec: edited, prose, attempts, changed = [], talk, options = [], variants, imagesDropped, visionRouted, test = [], commands = [], transform, style, review, look, geo } =
|
|
1024
1031
|
await askUntilValid(messages, async (t) => {
|
|
1025
1032
|
const parsed = parseEditText(t, spec);
|
|
1026
1033
|
if (repairSlides && (parsed.spec.slides.length !== spec.slides.length || parsed.changed?.some(i => !repairSlides.includes(i)))) throw new Error('A revisão só pode corrigir os slides indicados, sem inserir, excluir ou alterar outros slides.');
|
|
@@ -1043,6 +1050,8 @@ Antes de responder, verifique (e siga as Regras de edição):
|
|
|
1043
1050
|
if (transform) return { reply: prose || "Vou transformar a apresentação.", spec, actions, targetSlide, transform };
|
|
1044
1051
|
// quer ver o material antes de responder: o Studio mostra as imagens e chama de novo (src/studio/ai-chat-route.js)
|
|
1045
1052
|
if (look) return { reply: prose || "Vou olhar o material.", spec, actions, targetSlide, look };
|
|
1053
|
+
// precisa de dados para o mapa: o Studio busca e chama de novo (src/studio/ai-chat-route.js)
|
|
1054
|
+
if (geo) return { reply: prose || "Vou buscar os dados para o mapa.", spec, actions, targetSlide, geo };
|
|
1046
1055
|
// estilo (Brand Kit): quem aplica/salva é o servidor, com a biblioteca
|
|
1047
1056
|
if (style) return { reply: prose || "Certo.", spec, actions, targetSlide, style };
|
|
1048
1057
|
// versões para escolher: nada muda até a pessoa escolher uma
|
package/src/ai/ia-config.js
CHANGED
|
@@ -40,10 +40,25 @@ export function cleanIA(raw) {
|
|
|
40
40
|
return out.url || out.adaptador ? out : null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// lê o arquivo e, quando ele existe mas não serve, diz por quê (sem isso, um ia.json de outro dono, que o serviço não
|
|
44
|
+
// consegue ler, aparecia só como "IA não configurada"). Sem arquivo não é problema: é só não configurada.
|
|
45
|
+
export function readIA(file = iaFile()) {
|
|
46
|
+
let text;
|
|
47
|
+
try { text = fs.readFileSync(file, "utf8"); }
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (e.code === "ENOENT") return { config: null, problem: "" };
|
|
50
|
+
if (e.code === "EACCES" || e.code === "EPERM") return { config: null, problem: `O arquivo ${file} existe, mas este processo não tem permissão para lê-lo. Confira o dono e as permissões dele.` };
|
|
51
|
+
return { config: null, problem: `Não deu para ler ${file} (${e.code || e.message}).` };
|
|
52
|
+
}
|
|
53
|
+
let raw;
|
|
54
|
+
try { raw = JSON.parse(text); } catch (e) { return { config: null, problem: `O arquivo ${file} não é um JSON válido (${e.message}).` }; }
|
|
55
|
+
const config = cleanIA(raw);
|
|
56
|
+
return config ? { config, problem: "" } : { config: null, problem: `O arquivo ${file} não tem o endereço do provedor ("url").` };
|
|
45
57
|
}
|
|
46
58
|
|
|
59
|
+
export const loadIA = (file = iaFile()) => readIA(file).config;
|
|
60
|
+
export const iaProblem = (file = iaFile()) => readIA(file).problem;
|
|
61
|
+
|
|
47
62
|
// grava de forma atômica, só para quem é dono da conta (a chave mora aqui)
|
|
48
63
|
export function saveIA(cfg, file = iaFile()) {
|
|
49
64
|
const clean = cleanIA(cfg);
|
package/src/ai/llm.js
CHANGED
|
@@ -17,7 +17,7 @@ import { mediaPrompt } from './art-direction.js';
|
|
|
17
17
|
import { currentUsage, recordUsage } from './usage.js';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { pathToFileURL } from 'node:url';
|
|
20
|
-
import { iaFile,
|
|
20
|
+
import { iaFile, readIA, keyOf, migrateModelrelay } from './ia-config.js';
|
|
21
21
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
22
22
|
|
|
23
23
|
export class LLMError extends Error {
|
|
@@ -38,7 +38,7 @@ let migrated = false;
|
|
|
38
38
|
export function llmConfig(env = process.env) {
|
|
39
39
|
const fromEnv = !!env.SAGADECK_LLM_URL;
|
|
40
40
|
if (!fromEnv && !migrated) { migrated = true; try { migrateModelrelay({ env, file: iaFile(env) }); } catch { /* segue sem */ } }
|
|
41
|
-
const ia = fromEnv ? null :
|
|
41
|
+
const { config: ia, problem } = fromEnv ? { config: null, problem: "" } : readIA(iaFile(env));
|
|
42
42
|
const m = ia?.models || {};
|
|
43
43
|
return {
|
|
44
44
|
url: (env.SAGADECK_LLM_URL || ia?.url || "").replace(/\/+$/, ""),
|
|
@@ -51,6 +51,8 @@ export function llmConfig(env = process.env) {
|
|
|
51
51
|
headers: ia?.headers || {},
|
|
52
52
|
adaptador: ia?.adaptador || "",
|
|
53
53
|
source: fromEnv ? "variável" : ia ? "arquivo" : "nenhuma",
|
|
54
|
+
// o ia.json existe mas não serve (sem permissão, JSON quebrado): o motivo, para não dizer só "não configurada"
|
|
55
|
+
problem,
|
|
54
56
|
timeoutMs: Number(env.SAGADECK_LLM_TIMEOUT || 180) * 1000,
|
|
55
57
|
firstTimeoutMs: Number(env.SAGADECK_LLM_FIRST_TIMEOUT || 600) * 1000,
|
|
56
58
|
};
|
|
@@ -79,7 +81,7 @@ async function send(cfg, url, init) {
|
|
|
79
81
|
if (typeof fn !== "function") throw new LLMError(`O adaptador ${cfg.adaptador} não exporta uma função padrão (url, init) => Response.`);
|
|
80
82
|
return fn(url, init);
|
|
81
83
|
}
|
|
82
|
-
const notConfigured = () => new LLMError("A IA não está configurada: no Studio, abra Configurar IA (ou crie ~/.sagadeck/ia.json).", { code: "AI_NOT_CONFIGURED" });
|
|
84
|
+
const notConfigured = (cfg) => new LLMError(cfg?.problem ? `A IA não está configurada. ${cfg.problem}` : "A IA não está configurada: no Studio, abra Configurar IA (ou crie ~/.sagadeck/ia.json).", { code: "AI_NOT_CONFIGURED" });
|
|
83
85
|
const howToFix = "Confira o provedor e a chave em Configurar IA, no Studio.";
|
|
84
86
|
|
|
85
87
|
// Disponibilidade com cache curto, para o Studio decidir entre LLM e as regras determinísticas.
|
|
@@ -182,7 +184,7 @@ async function chatOnce(messages, opts = {}) {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
async function chatOnceRaw(messages, { model, temperature, maxTokens, onDelta, signal, reasoningOff, cfg = llmConfig() } = {}) {
|
|
185
|
-
if (!llmConfigured(cfg)) throw notConfigured();
|
|
187
|
+
if (!llmConfigured(cfg)) throw notConfigured(cfg);
|
|
186
188
|
const body = { model: resolveModel(cfg, model || cfg.textModel), messages };
|
|
187
189
|
if (reasoningOff) body.reasoning = { enabled: false };
|
|
188
190
|
if (temperature !== undefined) body.temperature = temperature;
|