sagadeck 1.7.0 → 1.7.1
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 +1 -1
- package/SKILL.md +3 -3
- package/bin/sagadeck.js +2 -2
- package/docs/ANDAMENTO.md +40 -4
- package/docs/EXPERIENCIAS.md +1 -1
- package/docs/REFERENCIA.md +1 -1
- package/package.json +1 -1
- package/src/ai/ia-config.js +17 -2
- package/src/ai/llm.js +6 -4
- package/src/ai/variety.js +1 -1
- package/src/experiences.js +1 -1
- package/src/library.js +2 -2
- package/src/mcp/server.js +4 -4
- package/src/runtime/base.css +1 -1
- package/src/runtime/code-lab.js +10 -10
- package/src/runtime/skins/aluminio.css +1 -1
- package/src/runtime/skins/prata.css +1 -1
- package/src/studio/banner.js +1 -1
- package/src/studio/ia-routes.js +4 -1
- package/src/studio/public/ai-settings.js +11 -1
- package/src/studio/public/app.js +6 -4
- package/src/studio/public/chat.js +1 -1
- package/src/studio/public/library.css +2 -1
- package/src/studio/public/library.js +5 -1
- package/src/studio/server.js +3 -1
- package/src/templates/scaffold.js +5 -5
- package/src/themes.js +5 -5
- package/templates/{exemplo-keynote.yaml → exemplo-prata.yaml} +6 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Alumínio (lançamento de
|
|
1
|
+
/* Alumínio (lançamento de aparelho premium): prata metálico com brilho de luz de estúdio, alumínio escovado,
|
|
2
2
|
títulos cromados (a cor continua sólida em "color": o fiscal mede o contraste por ela), cartões de metal com
|
|
3
3
|
chanfro de luz; capa com o título embaixo à esquerda e um disco de metal polido à direita; o escuro é grafite */
|
|
4
4
|
/* o metal sai das cores do tom (--bg, --fg, --surface): com uma paleta por cima, ele acompanha */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Prata
|
|
1
|
+
/* Prata: nada de enfeite; muito respiro, tudo centralizado, título contido, número da seção discreto */
|
|
2
2
|
.th-prata .L-cover:not(:has(.cv-fig)) .cv-main,.th-prata .L-end:not(:has(.en-fig)) .en-main{align-items:center;text-align:center;gap:28px}
|
|
3
3
|
.th-prata :is(.L-cover,.L-end) .ttl{max-width:1300px}
|
|
4
4
|
.th-prata .kicker{color:var(--muted);letter-spacing:.02em}
|
package/src/studio/banner.js
CHANGED
|
@@ -7,7 +7,7 @@ const providerName = (ai) => Object.values(PROVIDERS).find((p) => p.url === ai.u
|
|
|
7
7
|
|
|
8
8
|
export function studioBanner({ version, url, library, ai = null, multiuser = false, usersDir = "", pid = process.pid, isLibrary = true }) {
|
|
9
9
|
const iaLine = !ai || !(ai.url || ai.adaptador)
|
|
10
|
-
? " IA: não configurada. No Studio, clique em IA desligada (ou Configurar IA na biblioteca) e informe provedor e chave."
|
|
10
|
+
? (ai?.problem ? ` IA: não configurada. ${ai.problem}` : " IA: não configurada. No Studio, clique em IA desligada (ou Configurar IA na biblioteca) e informe provedor e chave.")
|
|
11
11
|
: ` IA: ${providerName(ai)} · texto ${ai.textModel}${ai.source === "variável" ? " (pelas variáveis SAGADECK_LLM_*)" : ""}`;
|
|
12
12
|
return [
|
|
13
13
|
`✓ SagaDeck Studio ${version} pronto. Abra no navegador: ${url}${isLibrary ? " (biblioteca)" : ""}`,
|
package/src/studio/ia-routes.js
CHANGED
|
@@ -23,7 +23,10 @@ function draft(body, current) {
|
|
|
23
23
|
export async function iaRoutes({ req, res, pathname, opts, readJSON }) {
|
|
24
24
|
if (pathname === "/api/ia" && req.method === "GET") {
|
|
25
25
|
const cfg = llmConfig();
|
|
26
|
-
|
|
26
|
+
// no servidor, quem usa o portal não configura: só fica sabendo se a IA está configurada (provedor, endereço,
|
|
27
|
+
// modelos e o final da chave são de quem administra)
|
|
28
|
+
if (opts.multiuser) { json(res, 200, { editable: false, configured: !!(cfg.url || cfg.adaptador) }); return true; }
|
|
29
|
+
json(res, 200, { config: maskedIA(loadIA()), file: iaFile(), source: cfg.source, configured: !!(cfg.url || cfg.adaptador), problem: cfg.problem || "", providers: PROVIDERS, roles: ROLES, recommended: RECOMMENDED, editable: !opts.multiuser });
|
|
27
30
|
return true;
|
|
28
31
|
}
|
|
29
32
|
if ((pathname === "/api/ia" || pathname === "/api/ia/test") && req.method === "POST") {
|
|
@@ -21,6 +21,15 @@ window.SagaAISettings = (() => {
|
|
|
21
21
|
document.body.append(dialog);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// no servidor (multiusuário) a configuração é de quem administra e não vem para a página: só se está ligada
|
|
25
|
+
function serverNotice() {
|
|
26
|
+
dialog.innerHTML = `<h2>Configurar IA</h2>
|
|
27
|
+
<p>Neste servidor, quem configura a IA é quem administra.</p>
|
|
28
|
+
<p>${info.configured ? "A IA está configurada." : "A IA não está configurada. Avise quem administra o servidor."}</p>
|
|
29
|
+
<div class="row"><button type="button" data-close>Fechar</button></div>`;
|
|
30
|
+
dialog.querySelector("[data-close]").onclick = () => dialog.close();
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
function form() {
|
|
25
34
|
const c = info.config || {}, providers = info.providers || {};
|
|
26
35
|
const provider = c.provider && (providers[c.provider] || c.provider === "outro") ? c.provider : (c.url ? "outro" : "openrouter");
|
|
@@ -28,6 +37,7 @@ window.SagaAISettings = (() => {
|
|
|
28
37
|
<p>O sagadeck fala direto com o provedor que você escolher. A chave fica só nesta máquina, em <code>${esc(info.file)}</code>.</p>
|
|
29
38
|
${info.source === "variável" ? '<p class="warn">As variáveis SAGADECK_LLM_URL / SAGADECK_LLM_KEY estão definidas e valem por cima desta tela.</p>' : ""}
|
|
30
39
|
${info.editable ? "" : '<p class="warn">Neste servidor, quem configura a IA é quem administra.</p>'}
|
|
40
|
+
${info.problem ? `<p class="warn" data-problem>${esc(info.problem)}</p>` : ""}
|
|
31
41
|
${info.recommended ? `<div class="rec"><b>Recomendado para começar:</b> ${esc(providers[info.recommended.provider]?.label || info.recommended.provider)}, com ${esc(info.recommended.models.text)} (texto e visão), ${esc(info.recommended.models.image)} (imagem) e ${esc(info.recommended.models.search)} (busca). Se a IA não responder nem com isso, o problema é a rede ou a chave, não o modelo. Depois, troque pelo modelo que quiser. <button type="button" data-rec>Usar a recomendação</button></div>` : ""}
|
|
32
42
|
<label for="ai-cfg-provider">Provedor</label>
|
|
33
43
|
<select id="ai-cfg-provider" data-f="provider">${Object.entries(providers).map(([k, p]) => `<option value="${k}">${esc(p.label)}</option>`).join("")}<option value="outro">Outro compatível com a API da OpenAI</option></select>
|
|
@@ -79,7 +89,7 @@ window.SagaAISettings = (() => {
|
|
|
79
89
|
onSaved = typeof saved === "function" ? saved : () => {};
|
|
80
90
|
if (!dialog) build();
|
|
81
91
|
info = await (await fetch("api/ia")).json();
|
|
82
|
-
form();
|
|
92
|
+
if (info.editable) form(); else serverNotice();
|
|
83
93
|
if (!dialog.open) dialog.showModal();
|
|
84
94
|
},
|
|
85
95
|
};
|
package/src/studio/public/app.js
CHANGED
|
@@ -3044,9 +3044,11 @@
|
|
|
3044
3044
|
}
|
|
3045
3045
|
const on = !!state.ai.available;
|
|
3046
3046
|
dom.aiStatus.textContent = on ? "IA ligada" : "IA desligada";
|
|
3047
|
-
dom.aiStatus.title =
|
|
3048
|
-
?
|
|
3049
|
-
:
|
|
3047
|
+
dom.aiStatus.title = state.ai.server // no servidor, sem os detalhes (são de quem administra)
|
|
3048
|
+
? (on ? "A IA deste servidor está ligada." : state.ai.configured ? "A IA deste servidor não está respondendo. Avise quem administra." : "A IA deste servidor não está configurada. Avise quem administra.")
|
|
3049
|
+
: on ? `LLM em ${state.ai.url} · texto: ${state.ai.textModel} · imagem: ${state.ai.imageModel}`
|
|
3050
|
+
: state.ai.configured ? `Nenhum LLM respondendo em ${state.ai.url}. Clique para conferir o provedor e a chave.`
|
|
3051
|
+
: state.ai.problem ? `A IA não está configurada. ${state.ai.problem}` : "A IA não está configurada. Clique para informar provedor e chave.";
|
|
3050
3052
|
dom.aiStatus.classList.toggle("on", on);
|
|
3051
3053
|
dom.aiStatus.classList.toggle("off", !on);
|
|
3052
3054
|
}
|
|
@@ -3154,7 +3156,7 @@
|
|
|
3154
3156
|
}
|
|
3155
3157
|
dom.btnRunAiDeck.disabled = true;
|
|
3156
3158
|
const started = Date.now();
|
|
3157
|
-
let phase = `Enviando para ${state.ai.textModel || "
|
|
3159
|
+
let phase = `Enviando para ${state.ai.textModel || "a IA"}…`;
|
|
3158
3160
|
const show = () => {
|
|
3159
3161
|
dom.aiDeckStatus.textContent = `${phase} · ${Math.round((Date.now() - started) / 1000)}s`;
|
|
3160
3162
|
};
|
|
@@ -127,7 +127,7 @@ window.SagaChat = function ({ state, dom, escHtml, hydrateIcons, showToast, open
|
|
|
127
127
|
await fetch("api/ai/chat/cancel", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requestId: job.id }) }).catch(() => {});
|
|
128
128
|
};
|
|
129
129
|
const work = createProgressBubble(state.ai.available
|
|
130
|
-
? `Enviando para o LLM (${state.ai.textModel})…`
|
|
130
|
+
? (state.ai.textModel ? `Enviando para o LLM (${state.ai.textModel})…` : "Enviando para a IA…")
|
|
131
131
|
: "Verificando se a IA está no ar…");
|
|
132
132
|
dom.chatSend.disabled = false;
|
|
133
133
|
dom.chatSend.title = 'Parar processamento'; dom.chatSend.setAttribute('aria-label','Parar');
|
|
@@ -30,7 +30,8 @@ body.lib { display: grid; grid-template-columns: 218px minmax(0, 1fr); grid-temp
|
|
|
30
30
|
.lib-search kbd { border: 1px solid var(--ui-line); border-radius: 3px; font-size: 10px; padding: 0 4px; }
|
|
31
31
|
.ai-banner { display: flex; align-items: center; gap: 12px; margin: 10px 24px 0; padding: 10px 14px; border-radius: 10px; background: #fef3c7; color: #78350f; border: 1px solid #f59e0b; font-size: 13px; }
|
|
32
32
|
.ai-banner[hidden] { display: none; }
|
|
33
|
-
.ai-banner span { flex: 1; }
|
|
33
|
+
.ai-banner span { flex: 1; min-width: 0; overflow-wrap: anywhere; } /* o motivo pode trazer um caminho longo */
|
|
34
|
+
.ai-banner button { flex: none; }
|
|
34
35
|
.ai-banner .ic { width: 18px; height: 18px; flex: none; }
|
|
35
36
|
.lib-btn { display: inline-flex; justify-content: center; align-items: center; gap: 8px; min-height: 38px; padding: 0 15px; border-radius: 8px; border: 1px solid transparent; font-size: 12px; font-weight: 600; text-decoration: none; transition: background .15s, transform .15s; }
|
|
36
37
|
.lib-btn.primary { background: var(--brand-fill); color: var(--on-brand); box-shadow: 0 3px 8px #634ce916; }
|
|
@@ -61,7 +61,11 @@
|
|
|
61
61
|
// botão IA: provedor, chave e modelos desta máquina (ai-settings.js); no multiusuário quem configura é quem administra
|
|
62
62
|
// e, enquanto a IA não estiver configurada, a faixa "Configure a IA" (some ao salvar)
|
|
63
63
|
const aiSaved = () => { $("#ai-banner").hidden = true; };
|
|
64
|
-
api("api/ia").then(({ editable, configured
|
|
64
|
+
api("api/ia").then(({ editable, configured, problem }) => {
|
|
65
|
+
$("#btn-ai").hidden = !editable; $("#ai-banner").hidden = !editable || configured;
|
|
66
|
+
if (problem) $("#ai-banner span").textContent = `A IA não está configurada. ${problem}`; // o arquivo existe mas não serve
|
|
67
|
+
hydrate($("#ai-banner"));
|
|
68
|
+
}).catch(() => {});
|
|
65
69
|
$("#btn-ai").onclick = () => window.SagaAISettings.open(aiSaved);
|
|
66
70
|
$("#ai-banner-btn").onclick = () => window.SagaAISettings.open(aiSaved);
|
|
67
71
|
const openEditor = (id, present) => { location.href = `editor?deck=${encodeURIComponent(id)}${present ? "&present=1" : ""}`; };
|
package/src/studio/server.js
CHANGED
|
@@ -900,7 +900,9 @@ export function createStudioServer(deckPath = null, opts = {}) {
|
|
|
900
900
|
const cfg = llmConfig();
|
|
901
901
|
const available = await llmAvailable({ force: url.searchParams.has("refresh") });
|
|
902
902
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
903
|
-
|
|
903
|
+
// no servidor, só se está ligada: provedor e modelos são de quem administra (src/studio/ia-routes.js)
|
|
904
|
+
if (opts.multiuser) { res.end(JSON.stringify({ available, configured: llmConfigured(cfg), server: true })); return; }
|
|
905
|
+
res.end(JSON.stringify({ available, configured: llmConfigured(cfg), problem: cfg.problem || "", source: cfg.source, url: cfg.url, textModel: cfg.textModel, imageModel: cfg.imageModel }));
|
|
904
906
|
return;
|
|
905
907
|
}
|
|
906
908
|
|
|
@@ -6,7 +6,7 @@ export function generateScaffold({
|
|
|
6
6
|
title = "Minha Apresentação",
|
|
7
7
|
theme = "prata",
|
|
8
8
|
author = "Seu Nome",
|
|
9
|
-
type = "
|
|
9
|
+
type = "lancamento",
|
|
10
10
|
} = {}) {
|
|
11
11
|
const normType = String(type).toLowerCase();
|
|
12
12
|
|
|
@@ -243,7 +243,7 @@ export function generateScaffold({
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
// Padrão:
|
|
246
|
+
// Padrão: lançamento, limpo e com muito respiro (tema prata)
|
|
247
247
|
return {
|
|
248
248
|
title,
|
|
249
249
|
author,
|
|
@@ -252,7 +252,7 @@ export function generateScaffold({
|
|
|
252
252
|
slides: [
|
|
253
253
|
{
|
|
254
254
|
layout: "cover",
|
|
255
|
-
kicker: "
|
|
255
|
+
kicker: "Lançamento",
|
|
256
256
|
title: "Design com ==Propósito==.",
|
|
257
257
|
subtitle: "A harmonia entre simplicidade, respiro e precisão",
|
|
258
258
|
author,
|
|
@@ -355,14 +355,14 @@ export function generateScaffold({
|
|
|
355
355
|
text: "O padrão definitivo para criadores exigentes.",
|
|
356
356
|
rating: 5,
|
|
357
357
|
ratingLabel: "5.0 Pro",
|
|
358
|
-
tags: ["
|
|
358
|
+
tags: ["Lançamento", "Prata"],
|
|
359
359
|
},
|
|
360
360
|
],
|
|
361
361
|
notes: "> O tom escuro cria um momento teatral no palco.",
|
|
362
362
|
},
|
|
363
363
|
{
|
|
364
364
|
layout: "end",
|
|
365
|
-
title: "
|
|
365
|
+
title: "Simples assim.",
|
|
366
366
|
subtitle: "Obrigado.",
|
|
367
367
|
contacts: ["apresentacao@empresa.com", "suporte@empresa.com"],
|
|
368
368
|
notes: "> Conclusão elegante e contato.",
|
package/src/themes.js
CHANGED
|
@@ -270,7 +270,7 @@ export const THEMES = {
|
|
|
270
270
|
},
|
|
271
271
|
|
|
272
272
|
prata: {
|
|
273
|
-
label: "Prata —
|
|
273
|
+
label: "Prata — limpo e espacial: prata acetinado, muito respiro, tipografia do sistema / Inter limpa e elegante",
|
|
274
274
|
colors: {
|
|
275
275
|
paper: "F5F5F7", ink: "1D1D1F", accent: "0071E3", alert: "FF3B30",
|
|
276
276
|
muted: "86868B", line: "D2D2D7", surface: "FFFFFF", surfaceDark: "161617",
|
|
@@ -293,10 +293,10 @@ export const THEMES = {
|
|
|
293
293
|
},
|
|
294
294
|
radius: 20, deco: "silver",
|
|
295
295
|
},
|
|
296
|
-
// Alumínio: a cara
|
|
297
|
-
// cromados, grafite no escuro). O prata segue
|
|
296
|
+
// Alumínio: a cara de lançamento de aparelho premium (prata metálico, alumínio escovado, títulos
|
|
297
|
+
// cromados, grafite no escuro). O prata segue o branco limpo.
|
|
298
298
|
aluminio: {
|
|
299
|
-
label: "Alumínio — lançamento de
|
|
299
|
+
label: "Alumínio — lançamento de aparelho premium: prata metálico, títulos cromados, alumínio escovado e grafite",
|
|
300
300
|
colors: {
|
|
301
301
|
paper: "E4E5E9", ink: "1C1C1E", accent: "0066CC", alert: "D70015",
|
|
302
302
|
muted: "5E5E63", line: "C3C5CA", surface: "F4F5F7", surfaceDark: "2C2C2E",
|
|
@@ -305,7 +305,7 @@ export const THEMES = {
|
|
|
305
305
|
tones: {
|
|
306
306
|
light: { bg: "paper", fg: "ink", muted: "muted", line: "line", surface: "surface", hi: "D1D3D8", em: "accent", onHi: "ink" },
|
|
307
307
|
dark: { bg: "1C1C1E", fg: "F5F5F7", muted: "A1A1A6", line: "3A3A3C", surface: "surfaceDark", hi: "48484A", em: "2997FF", onHi: "FFFFFF" },
|
|
308
|
-
// azul-pacífico
|
|
308
|
+
// azul-pacífico metálico (a pele põe o brilho)
|
|
309
309
|
accent: { bg: "1D3A50", fg: "FFFFFF", muted: "C5D6E3", line: "3F6580", surface: "2A4C64", hi: "FFFFFF", em: "8FC1E3", onHi: "1D3A50" },
|
|
310
310
|
alert: { bg: "alert", fg: "FFFFFF", muted: "FFD1D4", line: "F0525F", surface: "B30012", hi: "FFFFFF", em: "FFFFFF", onHi: "alert" },
|
|
311
311
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
title: Inovação & Design
|
|
2
|
-
author:
|
|
2
|
+
author: Estúdio Prata
|
|
3
3
|
theme: prata
|
|
4
4
|
duration: 12
|
|
5
5
|
slides:
|
|
6
6
|
- layout: cover
|
|
7
|
-
kicker: Apresentação Especial ·
|
|
7
|
+
kicker: Apresentação Especial · Lançamento
|
|
8
8
|
title: Simplicidade com ==Impacto==.
|
|
9
9
|
subtitle: A estética do respiro, elegância e precisão visual
|
|
10
10
|
author: Design Team
|
|
@@ -79,12 +79,12 @@ slides:
|
|
|
79
79
|
title: "Padrão de Mercado"
|
|
80
80
|
text: "A referência definitiva para profissionais sérios."
|
|
81
81
|
rating: 5
|
|
82
|
-
ratingLabel: "5.0
|
|
83
|
-
tags: ["
|
|
82
|
+
ratingLabel: "5.0 Pro"
|
|
83
|
+
tags: ["Lançamento", "Prata", "Clean"]
|
|
84
84
|
|
|
85
85
|
- layout: end
|
|
86
|
-
title:
|
|
86
|
+
title: Simples assim.
|
|
87
87
|
subtitle: Obrigado.
|
|
88
88
|
contacts:
|
|
89
|
-
-
|
|
89
|
+
- design.org
|
|
90
90
|
- contato@design.org
|