sagadeck 1.8.0 → 1.10.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 +25 -8
- package/SKILL.md +10 -3
- package/bin/sagadeck.js +23 -0
- package/docs/ANDAMENTO.md +482 -7
- package/docs/PLANO-MELHORIAS.md +180 -0
- package/docs/REFERENCIA.md +178 -111
- package/package.json +35 -4
- package/src/ai/commands.js +2 -2
- package/src/ai/context.js +7 -2
- package/src/ai/deck-ai.js +4 -2
- package/src/ai/ia-check.js +127 -0
- package/src/ai/ia-config.js +2 -2
- package/src/ai/llm.js +5 -1
- package/src/ai/map-analysis-run.js +1 -1
- package/src/ai/quality.js +1 -1
- package/src/ai/video-generation.js +70 -25
- package/src/api-client.js +42 -10
- package/src/build.js +46 -14
- package/src/code-highlight.js +19 -2
- package/src/code-language.js +13 -0
- package/src/elements.js +39 -1
- package/src/export/pptx.js +7 -2
- package/src/export/shots.js +7 -2
- package/src/fiscal/normalize.js +19 -0
- package/src/fs-retry.js +37 -2
- package/src/import/office.js +5 -4
- package/src/layouts.js +104 -99
- package/src/lessons.js +1 -1
- package/src/library.js +7 -7
- package/src/map-analysis.js +4 -1
- package/src/map-config.js +10 -4
- package/src/map.js +21 -8
- package/src/mcp/server.js +17 -0
- package/src/motion.js +20 -6
- package/src/purpose.js +1 -1
- package/src/runtime/api-core.js +122 -11
- package/src/runtime/api-ui.js +2 -1
- package/src/runtime/base.css +88 -332
- package/src/runtime/fit.js +3 -2
- package/src/runtime/fonts/amatic-sc.css +3 -0
- package/src/runtime/fonts/bebas-neue.css +2 -0
- package/src/runtime/fonts/cabin-sketch.css +2 -0
- package/src/runtime/fonts/fraunces.css +3 -0
- package/src/runtime/fonts/gelasio.css +3 -0
- package/src/runtime/fonts/ibm-plex-mono.css +4 -0
- package/src/runtime/fonts/index.json +10 -1
- package/src/runtime/fonts/itim.css +2 -0
- package/src/runtime/fonts/montserrat.css +2 -0
- package/src/runtime/fonts/space-grotesk.css +2 -0
- package/src/runtime/map-background.js +91 -0
- package/src/runtime/map.js +41 -9
- package/src/runtime/runtime.js +81 -100
- package/src/runtime/skins/aurora-dev.css +39 -0
- package/src/runtime/skins/caderno.css +33 -0
- package/src/runtime/skins/cartaz.css +27 -0
- package/src/runtime/skins/infografico.css +29 -0
- package/src/runtime/skins/lousa.css +22 -0
- package/src/runtime/skins/musgo.css +42 -0
- package/src/runtime/skins/terminal-pro.css +33 -0
- package/src/runtime/vendor/MAPLIBRE-LEAFLET-LICENSE.txt +15 -0
- package/src/runtime/vendor/MAPLIBRE-LICENSE.txt +116 -0
- package/src/runtime/vendor/geo-contornos.json +1 -1
- package/src/runtime/vendor/maplibre-gl.css +1 -0
- package/src/runtime/vendor/maplibre-gl.js +825 -0
- package/src/runtime/vendor/maplibre-leaflet.js +234 -0
- package/src/runtime/vendor/maplibre-worker.js +10 -0
- package/src/studio/ai-chat-route.js +26 -7
- package/src/studio/assets/imagens/tour-mapa-botao.jpg +0 -0
- package/src/studio/assets/imagens/tour-mapa-editor.jpg +0 -0
- package/src/studio/command-approval.js +2 -3
- package/src/studio/demo-decks.js +4 -2
- package/src/studio/developer-starters.js +46 -0
- package/src/studio/export-jobs.js +86 -0
- package/src/studio/exporting.js +36 -18
- package/src/studio/feature-demos.js +144 -0
- package/src/studio/ia-routes.js +9 -0
- package/src/studio/layout-samples.js +9 -18
- package/src/studio/library-routes.js +7 -9
- package/src/studio/project.js +1 -0
- package/src/studio/public/about.js +68 -0
- package/src/studio/public/ai-settings.js +2 -2
- package/src/studio/public/app.js +43 -52
- package/src/studio/public/chat.js +5 -1
- package/src/studio/public/developer-authoring.css +12 -0
- package/src/studio/public/developer-authoring.js +306 -0
- package/src/studio/public/export-status.js +154 -0
- package/src/studio/public/generation-guide.js +42 -0
- package/src/studio/public/index.html +31 -10
- package/src/studio/public/library.css +48 -3
- package/src/studio/public/library.html +5 -1
- package/src/studio/public/library.js +150 -41
- package/src/studio/public/map-data.js +7 -3
- package/src/studio/public/map-editor.js +625 -236
- package/src/studio/public/map-settings.js +3 -1
- package/src/studio/public/slide-form.js +27 -29
- package/src/studio/public/style.css +56 -9
- package/src/studio/public/ui-icons.js +1 -1
- package/src/studio/server.js +29 -14
- package/src/studio/snapshot.js +8 -4
- package/src/studio/template-collections.js +1 -1
- package/src/studio/tour.js +89 -7
- package/src/themes.js +202 -0
- package/templates/cenario-e-ciencia.yaml +1 -21
- package/templates/ensaio-api.yaml +3 -3
- package/templates/cenario/Texto no cen/303/241rio.yaml" +0 -171
- package/templates/cenario/imagens/cidade.svg +0 -30
- package/templates/cenario/imagens/pessoa.svg +0 -15
package/src/motion.js
CHANGED
|
@@ -5,13 +5,17 @@ const number=(x,f,min,max)=>Math.min(max,Math.max(min,Number.isFinite(Number(x))
|
|
|
5
5
|
const color=(x,f)=>/^#?[0-9a-f]{6}$/i.test(x||'')?'#'+String(x).replace('#',''):f;
|
|
6
6
|
export function motionOptions(value,colors={}){
|
|
7
7
|
const o=typeof value==='string'?{type:value}:value||{};
|
|
8
|
-
if(!['wireframe','hologram','trace','requests','flow','chat','terminal'].includes(o.type))throw new Error('motion.type: wireframe, hologram, trace, requests, flow, chat ou
|
|
8
|
+
if(!['wireframe','hologram','trace','requests','flow','chat','terminal','console'].includes(o.type))throw new Error('motion.type: wireframe, hologram, trace, requests, flow, chat, terminal ou console.');
|
|
9
|
+
// console: janela de terminal com cenas (comando digitado letra a letra, depois a saída linha a linha), em ciclo
|
|
10
|
+
const con=o.type==='console';
|
|
11
|
+
const scenes=(Array.isArray(o.scenes)?o.scenes:con&&Array.isArray(o.lines)?o.lines:[]).slice(0,12).map(sc=>typeof sc==='string'?{cmd:sc,out:[]}:{cmd:String(sc?.cmd??''),out:(Array.isArray(sc?.out)?sc.out:String(sc?.out??'').split('\n')).map(String).filter(x=>x!=='').slice(0,8)}).filter(sc=>sc.cmd);
|
|
12
|
+
if(con&&!scenes.length)throw new Error('motion console: scenes: [{cmd: "comando", out: ["linha de saída", ...]}].');
|
|
9
13
|
const aliases={fg:'ink',bg:'paper',hi:'accent',em:'alert'};
|
|
10
14
|
const resolved=(v,f)=>color(colors[aliases[v]||v]||v,f);
|
|
11
15
|
const trace=(Array.isArray(o.path)&&o.path.length>=2?o.path:[[.05,.5],[.95,.5]]).slice(0,64).map(p=>[number(Array.isArray(p)?p[0]:p?.x,0,0,1),number(Array.isArray(p)?p[1]:p?.y,0,0,1)]);
|
|
12
16
|
const transparent=o.background==='transparent';
|
|
13
17
|
const layers=(Array.isArray(o.layers)&&o.layers.length?o.layers:[{shape:o.shape,x:.5,y:.5,scale:.24}]).slice(0,6).map(l=>({shape:['sphere','cube','plane','ring'].includes(l.shape)?l.shape:'sphere',x:number(l.x,.5,-.5,1.5),y:number(l.y,.5,-.5,1.5),scale:number(l.scale,.24,.03,1),speed:number(l.speed,1,.1,4),rotation:number(l.rotation,0,-10,10)}));
|
|
14
|
-
return {type:o.type,path:trace,closed:o.closed===true,pulses:Math.round(number(o.pulses,2,1,8)),lineWidth:number(o.lineWidth,1.2,.5,6),layers,opacity:number(o.opacity,.7,.05,1),lines:Array.isArray(o.lines)?o.lines.map(String):String(o.code||'').split('\n'),fontSize:number(o.fontSize,16,10,40),shape:o.shape==='cube'?'cube':'sphere',speed:number(o.speed,1,.1,8),recognition:number(o.recognition,1,0,10),thinking:number(o.thinking,.8,0,10),read:number(o.read,2,0,15),typing:number(o.typing
|
|
18
|
+
return {type:o.type,path:trace,closed:o.closed===true,pulses:Math.round(number(o.pulses,2,1,8)),lineWidth:number(o.lineWidth,1.2,.5,6),layers,opacity:number(o.opacity,.7,.05,1),lines:Array.isArray(o.lines)?o.lines.map(String):String(o.code||'').split('\n'),fontSize:number(o.fontSize,con?20:16,10,40),shape:o.shape==='cube'?'cube':'sphere',speed:number(o.speed,1,.1,8),recognition:number(o.recognition,1,0,10),thinking:number(o.thinking,.8,0,10),read:number(o.read,2,0,15),typing:number(o.typing,con?.016:.035,.005,.2),scenes,hold:number(o.hold,3.2,.5,30),caption:String(o.caption||''),user:String(o.user||''),assistant:String(o.assistant||''),background:transparent?'transparent':resolved(o.background,con?color(colors.surfaceDark||colors.surface,'#12160f'):'#111e30'),foreground:resolved(o.foreground,con?color(colors.ink,'#ece8db'):transparent?color(colors.ink,'#182642'):'#b5c9db'),accent:resolved(o.accent,con?color(colors.accent,'#f0b24a'):color(colors.alert,'#cc092f')),secondary:resolved(o.secondary,con?color(colors.c2||colors.accent,'#a9c79b'):transparent?color(colors.accent,'#3769e8'):'#aa9ccf'),muted:color(colors.muted,'#9fa89a'),line:color(colors.line,'#2b352c'),alert:color(colors.alert,'#e07a55'),manual:o.start!=='auto',finish:['next','video','hold'].includes(o.finish)?o.finish:'hold'};
|
|
15
19
|
}
|
|
16
20
|
function drawTrace(ctx,o,t,w,h){
|
|
17
21
|
const points=o.path.map(p=>[p[0]*w,p[1]*h]);if(o.closed)points.push(points[0]);
|
|
@@ -26,11 +30,21 @@ function scene(o){
|
|
|
26
30
|
const message=(kind)=>parent.postMessage({sagaMotion:kind},'*');
|
|
27
31
|
const chat=document.querySelector('.chat'),input=document.querySelector('.input'),thread=document.querySelector('.thread'),status=document.querySelector('.status');
|
|
28
32
|
function reset(){started=false;phase='idle';elapsed=0;chat.hidden=true;input.textContent='';thread.replaceChildren();status.textContent='';}
|
|
29
|
-
window.__sagaMotion=()=>({active,reduced,started,serial,phase});message('ready');window.addEventListener('message',e=>{if(e.source!==parent)return;if(e.data?.sagaMotion==='poster'){still=e.data.active!==false;recompute();if(reduced&&o.type==='terminal')document.querySelector('.feed').textContent=o.lines.join('\n');}if(e.data?.sagaMotion==='active')active=!!e.data.active;if(e.data?.sagaMotion==='reset')reset();if(e.data?.sagaMotion==='start'){reset();started=true;active=true;phase='recognize';chat.hidden=false;}});
|
|
33
|
+
window.__sagaMotion=()=>({active,reduced,started,serial,phase});message('ready');window.addEventListener('message',e=>{if(e.source!==parent)return;if(e.data?.sagaMotion==='poster'){still=e.data.active!==false;recompute();if(reduced&&o.type==='terminal')document.querySelector('.feed').textContent=o.lines.join('\n');if(reduced&&o.type==='console')consoleFull();}if(e.data?.sagaMotion==='active')active=!!e.data.active;if(e.data?.sagaMotion==='reset')reset();if(e.data?.sagaMotion==='start'){reset();started=true;active=true;phase='recognize';chat.hidden=false;}});
|
|
30
34
|
if(o.type==='chat'&&o.manual){const request=e=>{if(!active)return;if(e.type==='keydown'&&![' ','Enter','ArrowRight','PageDown'].includes(e.key))return;e.preventDefault();message('start-request');};document.addEventListener('click',request);document.addEventListener('keydown',request);}
|
|
31
35
|
if(o.type==='chat'&&!o.manual){started=true;phase='recognize';chat.hidden=false;}
|
|
32
36
|
function bubble(text,kind){const el=document.createElement('div');el.className='bubble '+kind;el.textContent=text;thread.append(el);return el;}
|
|
33
37
|
let terminalElapsed=0;const terminalText=o.lines.join('\n');
|
|
38
|
+
let ci=0,cphase='wait',ct=0,shown='';const conBody=document.querySelector('.con-body'),conNum=document.querySelector('.con-n');
|
|
39
|
+
function showConsole(cmdLen,outN){const sc=o.scenes[ci],key=ci+':'+cmdLen+':'+outN;if(!sc||key===shown)return;shown=key;const cmd=document.createElement('p');cmd.className='cmd';const pr=document.createElement('span');pr.className='pr';cmd.append(pr,document.createTextNode(sc.cmd.slice(0,cmdLen)));if(cmdLen<sc.cmd.length){const cur=document.createElement('span');cur.className='cur';cmd.append(cur);}const outs=sc.out.slice(0,outN).map((l,i)=>{const e=document.createElement('p');e.className='out'+(i===0?' first':'');const arr=document.createElement('span');arr.className='arr';e.append(arr,document.createTextNode(l));return e;});conBody.replaceChildren(cmd,...outs);conNum.textContent=String(ci+1).padStart(2,'0')+'/'+String(o.scenes.length).padStart(2,'0');}
|
|
40
|
+
function consoleFull(){ci=0;cphase='wait';ct=0;showConsole(o.scenes[0].cmd.length,o.scenes[0].out.length);}
|
|
41
|
+
function tickConsole(dt){const sc=o.scenes[ci];if(reduced){consoleFull();return;}ct+=dt;
|
|
42
|
+
if(cphase==='wait'){showConsole(0,0);if(ct>=.3){cphase='type';ct=0;}}
|
|
43
|
+
else if(cphase==='type'){const n=Math.min(sc.cmd.length,Math.floor(ct/o.typing));showConsole(n,0);if(n>=sc.cmd.length){cphase='gap';ct=0;}}
|
|
44
|
+
else if(cphase==='gap'){showConsole(sc.cmd.length,0);if(ct>=.38){cphase='out';ct=0;}}
|
|
45
|
+
else if(cphase==='out'){const n=Math.min(sc.out.length,1+Math.floor(ct/.26));showConsole(sc.cmd.length,n);if(n>=sc.out.length&&ct>=Math.max(0,sc.out.length-1)*.26){cphase='hold';ct=0;}}
|
|
46
|
+
else if(cphase==='hold'&&ct>=o.hold){ci=(ci+1)%o.scenes.length;cphase='wait';ct=0;}}
|
|
47
|
+
if(o.type==='console'){if(reduced)consoleFull();else showConsole(0,0);}
|
|
34
48
|
let bot;
|
|
35
49
|
function tickChat(dt){if(!started||phase==='done')return;elapsed+=dt;
|
|
36
50
|
if(phase==='recognize'&&elapsed>=o.recognition){phase='input';elapsed=0;}
|
|
@@ -40,7 +54,7 @@ function scene(o){
|
|
|
40
54
|
else if(phase==='read'&&elapsed>=o.read){phase='done';message('done');}}
|
|
41
55
|
function requestLine(){serial++;const ops=['GET /silent-cloud','POST /paper-moon','PUT /silver-orbit','PATCH /soft-vector','GET /tiny-comet','DELETE /ghost-frame'];const el=document.createElement('div');el.textContent=String(serial).padStart(6,'0')+' '+ops[(serial*7+Math.floor(serial/9))%ops.length].padEnd(23,' ')+' '+(serial*2654435761>>>0).toString(16)+' '+(17+serial*37%981)+'ms 200 OK';document.querySelector('.feed').append(el);while(document.querySelector('.feed').children.length>14)document.querySelector('.feed').firstChild.remove();document.querySelector('.feed').scrollTop=1e6;}
|
|
42
56
|
if(o.type==='requests'){for(let i=0;i<8;i++)requestLine();setInterval(()=>{if(active&&!document.hidden&&!reduced){requestLine();requestLine();}},Math.max(35,240/o.speed));}
|
|
43
|
-
function draw(ts){requestAnimationFrame(draw);if(!active||document.hidden){previous=ts;return;}const dt=Math.min(.1,(ts-(previous||ts))/1000);previous=ts;if(o.type==='chat'){tickChat(dt);return;}if(o.type==='terminal'){terminalElapsed+=dt*o.speed;const count=reduced?terminalText.length:Math.floor(terminalElapsed/o.typing);document.querySelector('.feed').textContent=terminalText.slice(0,count)+(count<terminalText.length?'_':'');document.querySelector('.feed').scrollTop=1e6;if(count>terminalText.length+3/o.typing)terminalElapsed=0;return;}if(o.type==='requests'||(reduced&&serial))return;serial++;
|
|
57
|
+
function draw(ts){requestAnimationFrame(draw);if(!active||document.hidden){previous=ts;return;}const dt=Math.min(.1,(ts-(previous||ts))/1000);previous=ts;if(o.type==='chat'){tickChat(dt);return;}if(o.type==='console'){tickConsole(dt*o.speed);return;}if(o.type==='terminal'){terminalElapsed+=dt*o.speed;const count=reduced?terminalText.length:Math.floor(terminalElapsed/o.typing);document.querySelector('.feed').textContent=terminalText.slice(0,count)+(count<terminalText.length?'_':'');document.querySelector('.feed').scrollTop=1e6;if(count>terminalText.length+3/o.typing)terminalElapsed=0;return;}if(o.type==='requests'||(reduced&&serial))return;serial++;
|
|
44
58
|
const w=main.clientWidth,h=main.clientHeight,dpr=devicePixelRatio||1;if(canvas.width!==Math.round(w*dpr)||canvas.height!==Math.round(h*dpr)){canvas.width=Math.round(w*dpr);canvas.height=Math.round(h*dpr);}ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,w,h);const t=reduced?0:(ts-epoch)/1000*o.speed,r=Math.min(w*.25,h*.32),cy=h*.55;if(o.type==='hologram'){drawHologram(ctx,o,t,w,h);return;}if(o.type==='trace'){drawTrace(ctx,o,t,w,h);return;}
|
|
45
59
|
function p(x,y,z){let a=t*.15+.4,xx=x*Math.cos(a)+z*Math.sin(a),zz=-x*Math.sin(a)+z*Math.cos(a),yy=y*Math.cos(.3)-zz*Math.sin(.3);const f=4/(4+zz);return[w/2+xx*r*f,cy+yy*r*f];}
|
|
46
60
|
function line(v,c){ctx.beginPath();v.forEach((q,i)=>{let a=p(...q);i?ctx.lineTo(...a):ctx.moveTo(...a);});ctx.strokeStyle=c;ctx.lineWidth=.9;ctx.stroke();}
|
|
@@ -53,6 +67,6 @@ function scene(o){
|
|
|
53
67
|
}
|
|
54
68
|
// still: o deck pediu movimento nenhum (motion: none). A cena já nasce parada e completa; antes ela só sabia pela
|
|
55
69
|
// mensagem da página, que sob carga chegava depois de o terminal começar a digitar.
|
|
56
|
-
export function motionHTML(value,colors,{still=false}={}){const o={...motionOptions(value,colors),...(still?{still:true}:{})};const css=`html,body{height:100%;background:${o.type
|
|
57
|
-
const html=`<main><canvas aria-hidden="true"></canvas><div class="caption">${esc(o.type==='chat'?'':o.caption)}</div><div class="feed" aria-hidden="true"></div><section class="chat" hidden><div class="chat-title">${esc(o.caption||'ASSISTENTE')}</div><div class="thread" aria-live="polite"></div><div class="status"></div><div class="input" aria-label="Mensagem digitada"></div></section></main>`;
|
|
70
|
+
export function motionHTML(value,colors,{still=false}={}){const o={...motionOptions(value,colors),...(still?{still:true}:{})};const css=`html,body{height:100%;background:${['chat','console'].includes(o.type)?'transparent':o.background};color:${o.foreground};overflow:hidden}main{height:100%;position:relative}canvas{position:absolute;inset:0;width:100%;height:100%}.caption{position:absolute;top:12px;left:16px;color:${o.secondary};font:11px monospace;letter-spacing:1.5px}.feed{position:absolute;top:36px;left:16px;right:16px;bottom:12px;overflow:hidden;font:${o.type==='terminal'?o.fontSize:12}px/1.5 monospace;white-space:pre}.chat{position:absolute;inset:0;border:1px solid ${o.secondary};border-radius:16px;padding:18px;background:${o.background};display:flex;flex-direction:column;gap:12px}.chat[hidden]{display:none}.thread{flex:1;overflow:auto;display:flex;flex-direction:column;gap:12px}.bubble{font:18px/1.45 system-ui;max-width:90%;padding:10px 14px;border-radius:10px;white-space:pre-wrap}.user{align-self:flex-end;background:#263a51}.assistant{align-self:flex-start}.status{font:14px system-ui;color:${o.secondary};min-height:18px}.input{border:1px solid #51617b;border-radius:9px;min-height:48px;padding:12px;font:17px system-ui;white-space:pre-wrap}.chat-title{font:12px system-ui;letter-spacing:2px;color:${o.secondary}}`+(o.type==='console'?`.caption{display:none}.con{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);max-height:100%;display:flex;flex-direction:column;border:1px solid ${o.line};border-radius:14px;overflow:hidden;background:${o.background};font-family:'IBM Plex Mono','Cascadia Mono',Consolas,'SF Mono',Menlo,monospace}.con-bar{flex:none;display:flex;align-items:center;gap:9px;height:44px;padding:0 18px;border-bottom:1px solid ${o.line}}.con-bar i{width:13px;height:13px;border-radius:50%;opacity:.72}.con-bar i:nth-child(1){background:${o.alert}}.con-bar i:nth-child(2){background:${o.accent}}.con-bar i:nth-child(3){background:${o.secondary}}.con-cap{margin-left:10px;font-size:13px;letter-spacing:2.5px;text-transform:uppercase;color:${o.muted};white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.con-n{margin-left:auto;font-size:13px;color:${o.muted}}.con-body{flex:1;min-height:calc(${o.fontSize}px * 1.6 * 6 + 36px);overflow:hidden;padding:18px 22px;font-size:${o.fontSize}px;line-height:1.6}.con-body p{margin:0 0 .4em;white-space:pre-wrap;word-break:break-all}.cmd{color:${o.foreground}}.pr{display:inline-block;width:.42em;height:.42em;margin:0 .85em .12em .1em;border-top:.13em solid ${o.accent};border-right:.13em solid ${o.accent};transform:rotate(45deg)}.cur{display:inline-block;width:.55em;height:1.05em;margin-left:2px;vertical-align:-.18em;background:${o.accent};animation:bl 1s steps(1) infinite}@keyframes bl{50%{opacity:0}}.out{color:${o.muted}}.out.first{color:${o.secondary}}.arr{position:relative;display:inline-block;width:.8em;height:0;margin-right:.7em;vertical-align:.3em;border-top:.09em solid ${o.muted}}.arr::before{content:"";position:absolute;left:0;top:-.24em;width:.4em;height:.4em;border-left:.09em solid ${o.muted};border-bottom:.09em solid ${o.muted};transform:translateY(-.03em) rotate(45deg)}@media(prefers-reduced-motion:reduce){.cur{animation:none}}`:'');
|
|
71
|
+
const html=`<main><canvas aria-hidden="true"></canvas><div class="caption">${esc(o.type==='chat'?'':o.caption)}</div><div class="feed" aria-hidden="true"></div><section class="chat" hidden><div class="chat-title">${esc(o.caption||'ASSISTENTE')}</div><div class="thread" aria-live="polite"></div><div class="status"></div><div class="input" aria-label="Mensagem digitada"></div></section>${o.type==='console'?`<div class="con"><div class="con-bar"><i></i><i></i><i></i><span class="con-cap">${esc(o.caption)}</span><span class="con-n"></span></div><div class="con-body" aria-hidden="true"></div></div>`:''}</main>`;
|
|
58
72
|
const doc=playgroundDocument({html,css,javascript:`${drawHologram.toString()}\n${drawTrace.toString()}\n(${scene.toString()})(${JSON.stringify(o)});`});return `<iframe data-motion="${o.type}" data-motion-manual="${o.manual}" data-motion-finish="${o.finish}" title="${esc(o.caption||'Detalhe animado')}" sandbox="allow-scripts" style="width:100%;height:100%;border:0" srcdoc="${esc(doc)}"></iframe>`;}
|
package/src/purpose.js
CHANGED
|
@@ -12,7 +12,7 @@ export const PURPOSES = {
|
|
|
12
12
|
executiva: { label: "Executiva (antigo)", hint: "", maxWords: 40, dense: false, legacy: true },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const LAYOUT_WORDS = { onepage: 120, status: 90, solution: 140, calc: 90, algo: 60 }; // densos por natureza (uma página com tudo; exercício resolvido se revela aos poucos)
|
|
15
|
+
const LAYOUT_WORDS = { onepage: 120, status: 90, solution: 140, calc: 90, algo: 60, calendar: 240 }; // densos por natureza (uma página com tudo; exercício resolvido se revela aos poucos)
|
|
16
16
|
|
|
17
17
|
// Limite de palavras na tela de um slide: o do slide > o do deck > o do propósito (ou do layout, se maior) > 40
|
|
18
18
|
export function wordLimit(s = {}, spec = {}, layout = s.layout) {
|
package/src/runtime/api-core.js
CHANGED
|
@@ -35,11 +35,45 @@
|
|
|
35
35
|
|
|
36
36
|
// {{nome}} em qualquer texto do pedido (URL, cabeçalhos, corpo). Sem valor: fica como está.
|
|
37
37
|
const VAR = /\{\{\s*([\w.-]+)\s*\}\}/g;
|
|
38
|
+
function renderBody(body, vars, encoding) {
|
|
39
|
+
if (typeof body !== "string" || !["json", "urlencoded"].includes(encoding)) return render(body, vars);
|
|
40
|
+
if (encoding === "urlencoded") return body.replace(VAR, (all, key) => vars && vars[key] != null ? encodeURIComponent(String(vars[key])) : all);
|
|
41
|
+
// Interpola no texto original, sem converter números ou reformatar o JSON. Dentro de uma
|
|
42
|
+
// string escapa só o valor; fora dela, insere um literal JSON. Marcador sem valor continua.
|
|
43
|
+
let result = "", quoted = false;
|
|
44
|
+
for (let at = 0; at < body.length; at++) {
|
|
45
|
+
if (body.startsWith("{{", at)) {
|
|
46
|
+
const marker = body.slice(at).match(/^\{\{\s*([\w.-]+)\s*\}\}/);
|
|
47
|
+
if (marker && vars && vars[marker[1]] != null) {
|
|
48
|
+
const value = vars[marker[1]];
|
|
49
|
+
result += quoted ? JSON.stringify(String(value)).slice(1, -1) : JSON.stringify(value);
|
|
50
|
+
at += marker[0].length - 1; continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const char = body[at]; result += char;
|
|
54
|
+
if (quoted && char === "\\" && at + 1 < body.length) { result += body[++at]; continue; }
|
|
55
|
+
if (char === '"') quoted = !quoted;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function renderURL(url, vars, encoding) {
|
|
60
|
+
if (encoding !== "query") return render(url, vars);
|
|
61
|
+
const text = String(url), query = text.indexOf("?"), fragment = text.indexOf("#");
|
|
62
|
+
return text.replace(VAR, (all, key, at) => {
|
|
63
|
+
if (!vars || vars[key] == null) return all;
|
|
64
|
+
const value = String(vars[key]);
|
|
65
|
+
return query >= 0 && at > query && (fragment < 0 || at < fragment) ? encodeURIComponent(value) : value;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
38
68
|
function render(value, vars) {
|
|
39
69
|
if (typeof value === "string") return value.replace(VAR, (all, k) => (vars && vars[k] != null ? String(vars[k]) : all));
|
|
40
70
|
if (Array.isArray(value)) return value.map((x) => render(x, vars));
|
|
41
71
|
if (value && typeof value === "object" && value.__py) return value;
|
|
42
|
-
if (value && typeof value === "object") {
|
|
72
|
+
if (value && typeof value === "object") {
|
|
73
|
+
const o = {};
|
|
74
|
+
for (const [k, v] of Object.entries(value)) o[render(k, vars)] = k === "body" && value.bodyEncoding ? renderBody(v, vars, value.bodyEncoding) : k === "url" && value.urlEncoding ? renderURL(v, vars, value.urlEncoding) : render(v, vars);
|
|
75
|
+
return o;
|
|
76
|
+
}
|
|
43
77
|
return value;
|
|
44
78
|
}
|
|
45
79
|
function missing(value, vars) {
|
|
@@ -82,6 +116,8 @@
|
|
|
82
116
|
url: String(req.url || ""),
|
|
83
117
|
headers: req.headers && typeof req.headers === "object" ? req.headers : {},
|
|
84
118
|
body: req.body,
|
|
119
|
+
...(["json", "urlencoded"].includes(req.bodyEncoding) ? { bodyEncoding: req.bodyEncoding } : {}),
|
|
120
|
+
...(req.urlEncoding === "query" ? { urlEncoding: "query" } : {}),
|
|
85
121
|
form: req.form && typeof req.form === "object" ? req.form : null, // multipart: { campo: "@file" | texto }
|
|
86
122
|
auth: req.auth !== false,
|
|
87
123
|
},
|
|
@@ -202,12 +238,12 @@
|
|
|
202
238
|
}
|
|
203
239
|
|
|
204
240
|
function curlCmd(r, tokenVar, extra, fileName) {
|
|
205
|
-
const parts = [`curl -s${extra || ""}${r.method !== "GET" || r.body != null || r.form ? ` -X ${r.method}` : ""} ${dq(r.url)}`];
|
|
241
|
+
const parts = [`curl -s${extra || ""}${r.method !== "GET" || r.body != null || r.form ? ` -X ${r.method}` : ""} ${r._urlShell || dq(r.url)}`];
|
|
206
242
|
if (r.auth) parts.push(`-H "Authorization: Bearer $${tokenVar}"`);
|
|
207
|
-
for (const [k, v] of Object.entries(r.headers || {})) parts.push(`-H ${dq(k + ": " + v)}`);
|
|
243
|
+
for (const [k, v] of Object.entries(r.headers || {})) parts.push(`-H ${r._headerShell?.[k] || dq(k + ": " + v)}`);
|
|
208
244
|
if (r.form) for (const [k, v] of Object.entries(r.form)) parts.push(`-F ${dq(k + "=" + (v === "@file" ? "@" + (fileName || "arquivo") : v))}`);
|
|
209
245
|
else if (isJsonBody(r.body)) { parts.push(`-H "Content-Type: application/json"`); parts.push(`-d ${shq(JSON.stringify(r.body, null, 2))}`); }
|
|
210
|
-
else if (r.body != null) parts.push(`-d ${shq(r.body)}`);
|
|
246
|
+
else if (r.body != null) parts.push(r._stdinBody ? "--data-binary @-" : `-d ${r._bodyShell || shq(r.body)}`);
|
|
211
247
|
return parts.join(" \\\n ");
|
|
212
248
|
}
|
|
213
249
|
|
|
@@ -217,7 +253,70 @@
|
|
|
217
253
|
const secretsIn = (v) => { const out = new Set(); JSON.stringify(v || "").replace(/\{\{\s*secret\.([\w-]+)\s*\}\}/g, (_, k) => out.add(k)); return [...out]; };
|
|
218
254
|
const fileName = (a, vars) => (vars && vars["file.name"]) || (a.file ? String(a.file).split(/[\\/]/).pop() : "arquivo");
|
|
219
255
|
const usesB64 = (a) => JSON.stringify(a.request.body || "").includes("{{file.base64}}");
|
|
256
|
+
// Para cURL importado, expressões são montadas por contexto. As partes literais continuam
|
|
257
|
+
// literais no shell, e JSON/form/query escapam o segredo só na hora de rodar o exemplo.
|
|
258
|
+
function secretParts(text, encoding) {
|
|
259
|
+
text = String(text); const parts = []; let last = 0, scan = 0, quoted = false;
|
|
260
|
+
const query = text.indexOf("?"), fragment = text.indexOf("#");
|
|
261
|
+
const pattern = /\{\{\s*secret\.([\w-]+)\s*\}\}/g; let match;
|
|
262
|
+
while ((match = pattern.exec(text))) {
|
|
263
|
+
if (encoding === "json") for (; scan < match.index; scan++) { if (quoted && text[scan] === "\\") scan++; else if (text[scan] === '"') quoted = !quoted; }
|
|
264
|
+
if (match.index > last) parts.push(text.slice(last, match.index));
|
|
265
|
+
const context = encoding === "json" ? (quoted ? "json-string" : "json-literal") : encoding === "urlencoded" || (encoding === "query" && query >= 0 && match.index > query && (fragment < 0 || match.index < fragment)) ? "urlencoded" : "raw";
|
|
266
|
+
parts.push({ name: secretEnv(match[1]), context }); last = pattern.lastIndex; scan = last;
|
|
267
|
+
}
|
|
268
|
+
if (last < text.length || !parts.length) parts.push(text.slice(last));
|
|
269
|
+
return parts;
|
|
270
|
+
}
|
|
271
|
+
function secretExpression(text, encoding, language) {
|
|
272
|
+
return secretParts(text, encoding).map(part => {
|
|
273
|
+
if (typeof part === "string") return JSON.stringify(part);
|
|
274
|
+
const value = language === "python" ? `os.environ[${JSON.stringify(part.name)}]` : `segredo(${JSON.stringify(part.name)})`;
|
|
275
|
+
if (part.context === "json-string") return language === "python" ? `json.dumps(${value}, ensure_ascii=False)[1:-1]` : `JSON.stringify(${value}).slice(1, -1)`;
|
|
276
|
+
if (part.context === "json-literal") return language === "python" ? `json.dumps(${value}, ensure_ascii=False)` : `JSON.stringify(${value})`;
|
|
277
|
+
if (part.context === "urlencoded") return language === "python" ? `quote(${value}, safe="-_.!~*'()")` : `encodeURIComponent(${value})`;
|
|
278
|
+
return value;
|
|
279
|
+
}).join(" + ");
|
|
280
|
+
}
|
|
281
|
+
function curlImported(a, vars) {
|
|
282
|
+
const L = Lines(false), name = fileName(a, vars), helpers = [], helperNames = new Map();
|
|
283
|
+
const cleanVars = Object.fromEntries(Object.entries(vars || {}).filter(([key]) => !key.startsWith("secret.")));
|
|
284
|
+
const shell = (text, encoding) => secretParts(text, encoding).map(part => {
|
|
285
|
+
if (typeof part === "string") return shq(part);
|
|
286
|
+
if (part.context === "raw") return `"$${part.name}"`;
|
|
287
|
+
const key = `${part.name}:${part.context}`;
|
|
288
|
+
let helper = helperNames.get(key);
|
|
289
|
+
if (!helper) {
|
|
290
|
+
helper = `SAGA_SECRET_${helpers.length + 1}`; helperNames.set(key, helper);
|
|
291
|
+
const expression = part.context === "urlencoded" ? "encodeURIComponent(valor)" : part.context === "json-string" ? "JSON.stringify(valor).slice(1,-1)" : "JSON.stringify(valor)";
|
|
292
|
+
const script = `const valor=process.env[${JSON.stringify(part.name)}];if(valor==null)throw new Error(${JSON.stringify("Defina " + part.name)});process.stdout.write(${expression});`;
|
|
293
|
+
helpers.push(`${helper}=$(node -e ${shq(script)})`);
|
|
294
|
+
}
|
|
295
|
+
return `"$${helper}"`;
|
|
296
|
+
}).join("");
|
|
297
|
+
const prepare = original => {
|
|
298
|
+
const r = render(original, { ...cleanVars, "file.name": name });
|
|
299
|
+
r._urlShell = shell(r.url, r.urlEncoding);
|
|
300
|
+
r._headerShell = Object.fromEntries(Object.entries(r.headers || {}).map(([key, value]) => [key, shell(`${key}: ${value}`, "raw")]));
|
|
301
|
+
if (r.body != null) {
|
|
302
|
+
if (typeof r.body === "object") r.body = JSON.stringify(r.body, null, 2);
|
|
303
|
+
r._bodyShell = shell(r.body, r.bodyEncoding);
|
|
304
|
+
r._stdinBody = true; // stdin preserva UTF-8 também com curl.exe no Windows
|
|
305
|
+
}
|
|
306
|
+
return r;
|
|
307
|
+
};
|
|
308
|
+
const req = prepare(a.request), check = a.polling ? prepare(render(a.polling.check, { ...cleanVars, id: "$ID" })) : null;
|
|
309
|
+
if (helpers.length) { L.add("# Bash + Node.js: segredos são escapados sem alterar o JSON ou a query."); helpers.forEach(line => L.add(line)); L.blank(); }
|
|
310
|
+
const command = (r, extra = "") => (r._stdinBody ? `printf '%s' ${r._bodyShell} | \\\n` : "") + curlCmd(r, a.tokenVar, extra, name);
|
|
311
|
+
if (a.mode === "polling") {
|
|
312
|
+
L.add("# 1. inicia", "start").add(command(req), "start");
|
|
313
|
+
L.add(`# guarde ${a.polling.id} da resposta em ID`).blank();
|
|
314
|
+
L.add("# 2. consulta o andamento", "poll").add(command(check), "poll");
|
|
315
|
+
} else L.add(command(req, a.mode === "stream" ? "N" : "") + (a.audio ? ` \\\n --output ${shq(a.audio)}` : ""), "start");
|
|
316
|
+
return L.out();
|
|
317
|
+
}
|
|
220
318
|
function curl(a, vars) {
|
|
319
|
+
if (a.request.bodyEncoding || a.request.urlEncoding) return curlImported(a, vars);
|
|
221
320
|
const L = Lines(false);
|
|
222
321
|
const name = fileName(a, vars);
|
|
223
322
|
const sec = Object.fromEntries(secretsIn(a.request).map((k) => ["secret." + k, MARK(k)]));
|
|
@@ -242,14 +341,14 @@
|
|
|
242
341
|
|
|
243
342
|
function pyCall(name, r, extra, ind) {
|
|
244
343
|
ind = ind || "";
|
|
245
|
-
const args = [
|
|
344
|
+
const args = [r._urlExpr || dq(r.url)];
|
|
246
345
|
if (r.auth || Object.keys(r.headers || {}).length) args.push("headers=HEADERS");
|
|
247
346
|
if (r.form) {
|
|
248
347
|
const files = Object.entries(r.form).filter(([, v]) => v === "@file"), data = Object.entries(r.form).filter(([, v]) => v !== "@file");
|
|
249
348
|
if (files.length) args.push(`files={${files.map(([k]) => `${JSON.stringify(k)}: open(${dq(r._fileName || "arquivo")}, "rb")`).join(", ")}}`);
|
|
250
349
|
if (data.length) args.push(`data={${data.map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(String(v))}`).join(", ")}}`);
|
|
251
350
|
} else if (isJsonBody(r.body)) args.push(`json=${pyLiteral(r.body, ind + " ")}`);
|
|
252
|
-
else if (r.body != null) args.push(`data=${JSON.stringify(String(r.body))}`);
|
|
351
|
+
else if (r.body != null) args.push(`data=${r.bodyEncoding ? `(${r._bodyExpr || JSON.stringify(String(r.body))}).encode("utf-8")` : r._bodyExpr || JSON.stringify(String(r.body))}`);
|
|
253
352
|
args.push(...(extra || []));
|
|
254
353
|
return `${name} = requests.${r.method.toLowerCase()}(\n` + args.map((x) => `${ind} ${x},`).join("\n") + `\n${ind})`;
|
|
255
354
|
}
|
|
@@ -260,7 +359,7 @@
|
|
|
260
359
|
Object.assign(h, r.headers || {});
|
|
261
360
|
if (!Object.keys(h).length) return;
|
|
262
361
|
const val = (v) => { const m = String(v).match(/^\{\{\s*secret\.([\w-]+)\s*\}\}$/); return m ? `os.environ[${JSON.stringify(String(m[1]).toUpperCase().replace(/[^A-Z0-9_]/g, "_"))}]` : JSON.stringify(String(v)); };
|
|
263
|
-
const body = Object.entries(h).map(([k, v]) => ` ${JSON.stringify(k)}: ${v === "__TOKEN__" ? 'f"Bearer {TOKEN}"' : val(v)},`).join("\n");
|
|
362
|
+
const body = Object.entries(h).map(([k, v]) => ` ${JSON.stringify(k)}: ${v === "__TOKEN__" ? 'f"Bearer {TOKEN}"' : r.urlEncoding || r.bodyEncoding ? secretExpression(v, "raw", "python") : val(v)},`).join("\n");
|
|
264
363
|
L.add(`HEADERS = {\n${body}\n}`, null, "cabeçalhos enviados em toda chamada");
|
|
265
364
|
}
|
|
266
365
|
|
|
@@ -271,6 +370,12 @@
|
|
|
271
370
|
const toPy = (v) => (v === "{{file.base64}}" ? { __py: "CONTEUDO" } : typeof v === "string" && /^\{\{\s*secret\.([\w-]+)\s*\}\}$/.test(v) ? { __py: `os.environ[${JSON.stringify(secretEnv(v.match(/secret\.([\w-]+)/)[1]))}]` } : Array.isArray(v) ? v.map(toPy) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, toPy(x)])) : v);
|
|
272
371
|
const req = { ...render({ ...a.request, body: toPy(a.request.body) }, { ...vars, "file.name": name }), _fileName: name };
|
|
273
372
|
const imports = ["import os"];
|
|
373
|
+
if (req.urlEncoding || req.bodyEncoding) {
|
|
374
|
+
req._urlExpr = secretExpression(req.url, req.urlEncoding, "python");
|
|
375
|
+
if (typeof req.body === "string") req._bodyExpr = secretExpression(req.body, req.bodyEncoding, "python");
|
|
376
|
+
if (req.bodyEncoding === "json") imports.push("import json");
|
|
377
|
+
if (req.urlEncoding || req.bodyEncoding === "urlencoded") imports.push("from urllib.parse import quote");
|
|
378
|
+
}
|
|
274
379
|
if (b64) imports.push("import base64");
|
|
275
380
|
if (a.mode === "polling") imports.push("import time");
|
|
276
381
|
if (a.mode === "stream") imports.push("import json");
|
|
@@ -368,9 +473,9 @@
|
|
|
368
473
|
opts.push(`body: JSON.stringify(${jsLiteral(r.body, " ")})`);
|
|
369
474
|
} else {
|
|
370
475
|
if (hasHeaders) opts.push("headers: HEADERS");
|
|
371
|
-
if (r.body != null) opts.push(`body: ${JSON.stringify(String(r.body))}`);
|
|
476
|
+
if (r.body != null) opts.push(`body: ${r._bodyExpr || JSON.stringify(String(r.body))}`);
|
|
372
477
|
}
|
|
373
|
-
const target = url || dq(r.url);
|
|
478
|
+
const target = url || r._urlExpr || dq(r.url);
|
|
374
479
|
L.add(opts.length ? `const ${name} = await fetch(${target}, {\n${opts.map((o) => " " + o + ",").join("\n")}\n});` : `const ${name} = await fetch(${target});`, tag, why);
|
|
375
480
|
L.add(`if (!${name}.ok) throw new Error(\`HTTP \${${name}.status}: \${await ${name}.text()}\`);`, tag, "para aqui se deu erro (4xx/5xx)");
|
|
376
481
|
}
|
|
@@ -380,15 +485,21 @@
|
|
|
380
485
|
const b64 = usesB64(a);
|
|
381
486
|
const toJs = (v) => (v === "{{file.base64}}" ? { __js: "CONTEUDO" } : jsSecret(v) || (Array.isArray(v) ? v.map(toJs) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, toJs(x)])) : v));
|
|
382
487
|
const req = { ...render({ ...a.request, body: toJs(a.request.body) }, { ...vars, "file.name": name }), _fileName: name };
|
|
488
|
+
const imported = !!(req.urlEncoding || req.bodyEncoding);
|
|
489
|
+
if (imported) {
|
|
490
|
+
req._urlExpr = secretExpression(req.url, req.urlEncoding, "javascript");
|
|
491
|
+
if (typeof req.body === "string") req._bodyExpr = secretExpression(req.body, req.bodyEncoding, "javascript");
|
|
492
|
+
}
|
|
383
493
|
const fsUse = [b64 || req.form ? "readFile" : "", a.audio ? "writeFile" : ""].filter(Boolean);
|
|
384
494
|
L.add("// Node.js 18+ · salve como exemplo.mjs e rode: node exemplo.mjs");
|
|
385
495
|
if (fsUse.length) L.add(`import { ${fsUse.join(", ")} } from "node:fs/promises";`);
|
|
386
496
|
L.blank();
|
|
497
|
+
if (imported && secretsIn(a.request).length) L.add('const segredo = (nome) => { const valor = process.env[nome]; if (valor == null) throw new Error(`Defina ${nome}`); return valor; };').blank();
|
|
387
498
|
const auth = req.auth || !!(a.polling && a.polling.check.auth);
|
|
388
499
|
if (auth) L.add(`const TOKEN = process.env.${a.tokenVar};`, null, "o token de acesso vem de uma variável de ambiente, nunca escrito no código").blank();
|
|
389
500
|
const h = [];
|
|
390
501
|
if (auth) h.push(" Authorization: `Bearer ${TOKEN}`,");
|
|
391
|
-
for (const [k, v] of Object.entries(req.headers || {})) h.push(` ${JSON.stringify(k)}: ${jsSecret(v) ? jsSecret(v).__js : JSON.stringify(String(v))},`);
|
|
502
|
+
for (const [k, v] of Object.entries(req.headers || {})) h.push(` ${JSON.stringify(k)}: ${imported ? secretExpression(v, "raw", "javascript") : jsSecret(v) ? jsSecret(v).__js : JSON.stringify(String(v))},`);
|
|
392
503
|
if (h.length) L.add(`const HEADERS = {\n${h.join("\n")}\n};`, null, "cabeçalhos enviados em toda chamada").blank();
|
|
393
504
|
if (b64) L.add(`const CONTEUDO = (await readFile(${dq(name)})).toString("base64");`, "start", "o arquivo, em base64, vai dentro do JSON").blank();
|
|
394
505
|
const out = (expr, why) => {
|
|
@@ -525,6 +636,6 @@
|
|
|
525
636
|
|
|
526
637
|
// o slide mexe com arquivo? (upload @file ou {{file.…}})
|
|
527
638
|
const usesFile = (s) => { const a = s && s._normalized ? s : normalize(s); return !!a.file || a.mic || !!(a.request.form && Object.values(a.request.form).includes("@file")) || /\{\{\s*file\./.test(JSON.stringify(a.request)); };
|
|
528
|
-
const api = { isType, cosine, usesFile, parsePath, get, set, render, missing, envKind, mask, normalize, key, code, pyLiteral, pyPath, LANGS };
|
|
639
|
+
const api = { isType, cosine, usesFile, parsePath, get, set, render, renderBody, renderURL, missing, envKind, mask, normalize, key, code, pyLiteral, pyPath, LANGS };
|
|
529
640
|
g.SagadeckApiCore = api;
|
|
530
641
|
})(typeof window !== "undefined" ? window : globalThis);
|
package/src/runtime/api-ui.js
CHANGED
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
const bodyTxt = bodyEl ? bodyEl.value : "";
|
|
62
62
|
let body;
|
|
63
63
|
if (bodyTxt.trim() !== "" && !cfg.request.form) { // upload: a aba Corpo só mostra os campos do formulário
|
|
64
|
-
|
|
64
|
+
if (cfg.request.bodyEncoding && typeof cfg.request.body === "string") body = bodyTxt;
|
|
65
|
+
else try { body = JSON.parse(bodyTxt); }
|
|
65
66
|
catch (e) {
|
|
66
67
|
if (typeof cfg.request.body === "string") body = bodyTxt;
|
|
67
68
|
else throw new Error("O corpo não é um JSON válido: " + e.message);
|