sagadeck 1.7.1 → 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.
Files changed (46) hide show
  1. package/README.md +40 -0
  2. package/docs/ANDAMENTO.md +122 -0
  3. package/docs/REFERENCIA.md +58 -0
  4. package/package.json +2 -1
  5. package/src/ai/deck-ai.js +13 -4
  6. package/src/ai/map-analysis-run.js +119 -0
  7. package/src/ai/map-data.js +221 -0
  8. package/src/build.js +6 -2
  9. package/src/export/browser.js +23 -1
  10. package/src/export/pptx.js +2 -1
  11. package/src/export/shots.js +3 -1
  12. package/src/layouts.js +10 -0
  13. package/src/map-analysis.js +106 -0
  14. package/src/map-config.js +83 -0
  15. package/src/map-routing.js +82 -0
  16. package/src/map-services.js +60 -0
  17. package/src/map.js +261 -0
  18. package/src/runtime/base.css +20 -0
  19. package/src/runtime/map.js +234 -0
  20. package/src/runtime/runtime.js +1 -1
  21. package/src/runtime/vendor/LEAFLET-LICENSE.txt +26 -0
  22. package/src/runtime/vendor/geo-contornos.json +1 -0
  23. package/src/runtime/vendor/leaflet.css +661 -0
  24. package/src/runtime/vendor/leaflet.js +6 -0
  25. package/src/studio/ai-chat-route.js +20 -0
  26. package/src/studio/demo-decks.js +3 -1
  27. package/src/studio/geo-routes.js +138 -0
  28. package/src/studio/layout-samples.js +71 -0
  29. package/src/studio/project.js +1 -1
  30. package/src/studio/public/ai-settings.js +4 -1
  31. package/src/studio/public/app.js +7 -3
  32. package/src/studio/public/explorer.js +23 -8
  33. package/src/studio/public/index.html +4 -0
  34. package/src/studio/public/library.html +2 -0
  35. package/src/studio/public/library.js +6 -1
  36. package/src/studio/public/map-data.js +154 -0
  37. package/src/studio/public/map-editor.js +287 -0
  38. package/src/studio/public/map-fields.js +21 -0
  39. package/src/studio/public/map-settings.js +84 -0
  40. package/src/studio/public/slide-form.js +1 -1
  41. package/src/studio/public/style.css +13 -0
  42. package/src/studio/public/ui-icons.js +1 -1
  43. package/src/studio/public/viewers.js +2 -1
  44. package/src/studio/public/visual-editor.js +1 -1
  45. package/src/studio/server.js +10 -0
  46. package/src/studio/tour.js +76 -0
@@ -243,7 +243,7 @@
243
243
  // ------------------------------------------------------------------ planilha como no Excel
244
244
  const colName = (i) => { let s = ""; i++; while (i > 0) { const r = (i - 1) % 26; s = String.fromCharCode(65 + r) + s; i = Math.floor((i - 1) / 26); } return s; };
245
245
  const looksNum = (v) => /^\s*[-+]?(R\$\s*)?(\d{1,3}(\.\d{3})+(,\d+)?|\d+([.,]\d+)?)\s*%?\s*$/.test(String(v ?? ""));
246
- function sheetGrid(host, { rows, editable = false, hasHeader = false, maxRender = 3000, onChange, types = [] } = {}) {
246
+ function sheetGrid(host, { rows, editable = false, hasHeader = false, maxRender = 3000, onChange, onSelect, types = [] } = {}) {
247
247
  let data = rows.map((r) => [...r]);
248
248
  const undo = [], redo = [];
249
249
  let sel = { r: 0, c: 0, r2: 0, c2: 0 }, editing = null;
@@ -267,6 +267,7 @@
267
267
  const [r1, r2] = [Math.min(sel.r, sel.r2), Math.max(sel.r, sel.r2)], [c1, c2] = [Math.min(sel.c, sel.c2), Math.max(sel.c, sel.c2)];
268
268
  for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) cell(r, c)?.classList.add("sel");
269
269
  const cur = cell(sel.r, sel.c); cur?.classList.add("cur");
270
+ onSelect?.(sel.r, sel.c); // quem abriu a planilha acompanha a linha (ex.: o mapa abre o cartão do ponto)
270
271
  for (let c = c1; c <= c2; c++) table.querySelector(`thead th[data-col="${c}"]`)?.classList.add("on");
271
272
  for (let r = r1; r <= r2; r++) table.querySelector(`tbody th[data-row="${r}"]`)?.classList.add("on");
272
273
  cur?.scrollIntoView({ block: "nearest", inline: "nearest" });
@@ -257,7 +257,7 @@
257
257
  if(!root.dataset.visualEvents){
258
258
  root.dataset.visualEvents='true';
259
259
  root.addEventListener('pointerdown',e=>{
260
- if(e.button!==0 || e.target.closest('.science-plot,input,textarea'))return;
260
+ if(e.button!==0 || e.target.closest('.science-plot,.map-live,input,textarea'))return; // gráfico e mapa ao vivo recebem o próprio clique
261
261
  if(e.target.closest('[contenteditable="true"]')?.dataset.writing)return; // escrevendo: o clique é do cursor
262
262
  const el=objectAt(e.clientX,e.clientY)||(locked(e.target.closest('[data-vkey]'))?null:e.target.closest('[data-vkey]'));
263
263
  if(!el){
@@ -6,6 +6,7 @@ import {chatErrorResult,installErrorResponses} from './errors.js';
6
6
  import { slideCopyRoutes } from "./slide-copy-routes.js";
7
7
  import { publicScript } from "./public-files.js";
8
8
  import { iaRoutes } from "./ia-routes.js";
9
+ import { geoRoutes } from "./geo-routes.js";
9
10
  import { codeWatch } from "./code-version.js";
10
11
  import {meetingRoutes,meetingPreview} from '../meeting.js';
11
12
  import { codeRoutes } from "./code-routes.js";
@@ -493,6 +494,13 @@ export function createStudioServer(deckPath = null, opts = {}) {
493
494
  res.end(fs.readFileSync(path.join(RUNTIME_DIR, pathname === "/diagram.js" ? "diagram.js" : "vendor/mermaid.min.js")));
494
495
  return;
495
496
  }
497
+ // mapa ao vivo (src/runtime/map.js): o Leaflet e os contornos só são pedidos quando um slide de mapa monta
498
+ if (["/map.js", "/leaflet.js", "/leaflet.css", "/geo-contornos.json"].includes(pathname)) {
499
+ const type = pathname.endsWith(".css") ? "text/css" : pathname.endsWith(".json") ? "application/json" : "application/javascript";
500
+ res.writeHead(200, { "Content-Type": `${type}; charset=utf-8` }); // sem cache: depois de atualizar, o mapa novo vale na hora
501
+ res.end(fs.readFileSync(path.join(RUNTIME_DIR, pathname === "/map.js" ? "map.js" : `vendor${pathname}`)));
502
+ return;
503
+ }
496
504
  if (["/science.js", "/formula.js", "/calc.js", "/plotly.min.js"].includes(pathname)) {
497
505
  res.writeHead(200, {"Content-Type":"application/javascript; charset=utf-8"});
498
506
  res.end(fs.readFileSync(path.join(RUNTIME_DIR, pathname === "/plotly.min.js" ? "vendor/plotly.min.js" : pathname.slice(1))));
@@ -895,6 +903,8 @@ export function createStudioServer(deckPath = null, opts = {}) {
895
903
 
896
904
  // Configurar IA (provedor, chave, modelos desta máquina): src/studio/ia-routes.js
897
905
  if (pathname.startsWith("/api/ia") && await iaRoutes({ req, res, pathname, opts, readJSON })) return;
906
+ // Configurar mapa, estado dos serviços e coordenadas pelo endereço: src/studio/geo-routes.js
907
+ if (pathname.startsWith("/api/mapa") && await geoRoutes({ req, res, pathname, opts, readJSON, W, isBundledTemplate })) return;
898
908
 
899
909
  if (pathname === "/api/ai/status" && req.method === "GET") {
900
910
  const cfg = llmConfig();
@@ -0,0 +1,76 @@
1
+ // "Conheça o SagaDeck": a apresentação que todo usuário acha na biblioteca, com tudo o que dá para fazer. Monta-se
2
+ // sozinha dos exemplos de layout (layout-samples.js): layout novo entra aqui sem ninguém lembrar (engine.test.js
3
+ // confere). Cada slide diz o nome do recurso e a frase para pedir à IA. Abre em prévia, como os modelos da vitrine.
4
+ import { LAYOUT_INFO, LAYOUT_SAMPLES, LAYOUT_ASK } from "./layout-samples.js";
5
+
6
+ export const TOUR_TITLE = "Conheça o SagaDeck";
7
+
8
+ // os grupos na ordem em que fazem sentido para quem está conhecendo; layout que não estiver em nenhum entra em "Mais"
9
+ const GROUPS = [
10
+ ["Abrir e fechar", "capa, seções, agenda e encerramento", ["cover", "section", "agenda", "end", "references"]],
11
+ ["Uma ideia por vez", "frases, números e palavras que ocupam o palco", ["statement", "headline", "quote", "definition", "number", "kinetic", "scenography"]],
12
+ ["Organizar o conteúdo", "listas, cartões, etapas, comparações e linhas do tempo", ["list", "cards", "mosaic", "ribbon", "bento", "split", "compare", "matrix", "steps", "timeline", "calendar", "hub", "infographic", "pyramid", "funnel"]],
13
+ ["Números e dados", "indicadores, gráficos, tabelas e painéis", ["stats", "chart", "table", "map", "onepage", "status", "poster"]],
14
+ ["Imagem e vídeo", "fotos, vídeos e foco guiado", ["image", "full", "spotlight", "carousel", "video"]],
15
+ ["Com a plateia", "perguntas, enquetes e experiências interativas", ["question", "poll", "decisionlab", "playground", "portal"]],
16
+ ["Código e tecnologia", "código, APIs ao vivo, Git e diagramas", ["code", "codewalk", "codelab", "api", "duel", "terminals", "turns", "diagram", "graphlab", "algo", "dossier"]],
17
+ ["Aula e ciência", "fórmulas que viram curva, calculadoras e exercícios", ["science", "calc", "solution"]],
18
+ ["Do seu jeito", "monte o slide livremente", ["blocks", "canvas"]],
19
+ ];
20
+
21
+ // o que não é layout, mas a pessoa precisa saber que existe
22
+ const EXTRAS = [
23
+ ["sparkles", "Criar com IA", "De um tema, de um arquivo (PDF, Word, planilha, PowerPoint) ou de um link: a IA monta a apresentação inteira."],
24
+ ["message-circle", "Conversar com a IA", "No chat, peça mudanças no slide ou no deck todo; cada mudança vira uma versão que dá para desfazer."],
25
+ ["search-check", "Leitura crítica", "A IA revisa a apresentação e aponta o que melhorar, slide a slide, com Aplicar e Ignorar."],
26
+ ["file-up", "Importar PowerPoint", "Traga um .pptx e continue editando aqui, com as imagens e a ordem do original."],
27
+ ["table", "Gráfico da planilha", "Cole do Excel ou use a planilha do projeto: o Studio sugere gráficos e atualiza quando o arquivo muda."],
28
+ ["palette", "Temas e paletas", "Troque o visual inteiro com um clique; o conteúdo se rearruma sozinho."],
29
+ ["presentation", "Modo apresentador", "Notas, próximo slide, cronômetro e caneta para desenhar ao vivo."],
30
+ ["download", "PDF e PowerPoint", "Exporte para PDF ou para um PowerPoint editável, com as animações de clique."],
31
+ ["link", "Compartilhar link", "Um link só de leitura, para quem não precisa editar."],
32
+ ];
33
+
34
+ // os exemplos de navegação (hub, cartões) apontam para slides que não existem aqui: o link sai, o desenho fica
35
+ const semLinks = (v) => (Array.isArray(v) ? v.map(semLinks) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).filter(([k]) => k !== "goto").map(([k, x]) => [k, semLinks(x)])) : v);
36
+
37
+ const asked = (ask) => `Peça à IA: “${ask}”`;
38
+
39
+ export function tourDeck() {
40
+ const all = Object.keys(LAYOUT_SAMPLES);
41
+ const used = new Set(GROUPS.flatMap(([, , names]) => names));
42
+ const groups = [...GROUPS.map(([title, sub, names]) => [title, sub, names.filter((n) => LAYOUT_SAMPLES[n])])];
43
+ const rest = all.filter((n) => !used.has(n));
44
+ if (rest.length) groups.push(["Mais recursos", "os layouts mais novos", rest]);
45
+
46
+ const slides = [{
47
+ layout: "cover", kicker: "SAGADECK · TOUR", title: TOUR_TITLE,
48
+ subtitle: "Tudo o que dá para fazer, um slide por recurso. Cada um diz como pedir à IA.",
49
+ figure: { icon: "compass", size: 320 },
50
+ notes: "Navegue à vontade: nada aqui cria arquivo. Embaixo de cada slide (nestas notas) está o nome do recurso e a frase para pedir à IA na sua apresentação.",
51
+ }];
52
+ groups.forEach(([title, sub, names], i) => {
53
+ if (!names.length) return;
54
+ slides.push({ layout: "section", number: i + 1, kicker: `${names.length} recurso${names.length === 1 ? "" : "s"}`, title, subtitle: sub });
55
+ for (const name of names) {
56
+ const sample = semLinks(structuredClone(LAYOUT_SAMPLES[name]));
57
+ const [label, desc] = LAYOUT_INFO[name] || [name, ""];
58
+ const ask = LAYOUT_ASK[name] || "";
59
+ slides.push({
60
+ ...sample,
61
+ layout: name,
62
+ tour: name,
63
+ source: `**${label}**: ${desc}. ${asked(ask)}`,
64
+ // as notas ficam visíveis logo abaixo do slide no Studio (e na visão do apresentador): valem para todo layout,
65
+ // inclusive os que não desenham o rodapé (capa, citação, vídeo…)
66
+ notes: [`${label}: ${desc}.`, asked(ask), sample.notes].filter(Boolean).join("\n\n"),
67
+ });
68
+ }
69
+ });
70
+ slides.push({ layout: "section", number: groups.length + 1, kicker: "Além dos slides", title: "O que mais o SagaDeck faz", subtitle: "IA, importação, exportação e apresentação" });
71
+ slides.push({ layout: "cards", title: "Com a IA", cols: 3, items: EXTRAS.slice(0, 3).map(([icon, title, text]) => ({ icon, title, text })) });
72
+ slides.push({ layout: "cards", title: "Com os seus arquivos", cols: 3, items: EXTRAS.slice(3, 6).map(([icon, title, text]) => ({ icon, title, text })) });
73
+ slides.push({ layout: "cards", title: "Na hora de apresentar", cols: 3, items: EXTRAS.slice(6).map(([icon, title, text]) => ({ icon, title, text })) });
74
+ slides.push({ layout: "end", title: "Agora é a sua vez.", subtitle: "Crie uma apresentação e peça à IA o que você viu aqui." });
75
+ return { title: TOUR_TITLE, theme: "sinal", duration: 30, maxWords: 140, slides };
76
+ }