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.
- package/README.md +40 -0
- package/docs/ANDAMENTO.md +122 -0
- package/docs/REFERENCIA.md +58 -0
- package/package.json +2 -1
- package/src/ai/deck-ai.js +13 -4
- package/src/ai/map-analysis-run.js +119 -0
- package/src/ai/map-data.js +221 -0
- package/src/build.js +6 -2
- 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/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/runtime/base.css +20 -0
- package/src/runtime/map.js +234 -0
- package/src/runtime/runtime.js +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/demo-decks.js +3 -1
- package/src/studio/geo-routes.js +138 -0
- package/src/studio/layout-samples.js +71 -0
- package/src/studio/project.js +1 -1
- package/src/studio/public/ai-settings.js +4 -1
- package/src/studio/public/app.js +7 -3
- package/src/studio/public/explorer.js +23 -8
- package/src/studio/public/index.html +4 -0
- package/src/studio/public/library.html +2 -0
- package/src/studio/public/library.js +6 -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 +10 -0
- 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){
|
package/src/studio/server.js
CHANGED
|
@@ -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
|
+
}
|