nothumanallowed 13.2.78 → 13.2.80

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "13.2.78",
3
+ "version": "13.2.80",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '13.2.78';
8
+ export const VERSION = '13.2.80';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -81,6 +81,34 @@ function renderMd(raw) {
81
81
  // Close lists on blank or non-list line
82
82
  if (inUl) { out.push('</ul>'); inUl = false; }
83
83
  if (inOl) { out.push('</ol>'); inOl = false; }
84
+ // Markdown table: line starting with | and containing at least two |
85
+ if (l.charAt(0) === '|' && l.lastIndexOf('|') > 0) {
86
+ // Separator row (---|---) — skip, handled via <thead>
87
+ if (/^\|[\s\-|:]+\|$/.test(l.trim())) continue;
88
+ var cells = l.split('|').slice(1,-1).map(function(c){ return c.trim(); });
89
+ // Check if next line is a separator → this is a header row
90
+ var nextL = lines[i+1] ? lines[i+1].trim() : '';
91
+ var isHeader = /^\|[\s\-|:]+\|$/.test(nextL);
92
+ if (isHeader) {
93
+ out.push('<table class="md-table"><thead><tr>' + cells.map(function(c){ return '<th>'+c+'</th>'; }).join('') + '</tr></thead><tbody>');
94
+ } else {
95
+ // Check if we need to open tbody (no header case)
96
+ var prevOut = out[out.length-1] || '';
97
+ if (prevOut.indexOf('<tbody>') === -1 && prevOut.indexOf('<tr>') === -1) {
98
+ out.push('<table class="md-table"><tbody>');
99
+ }
100
+ out.push('<tr>' + cells.map(function(c){ return '<td>'+c+'</td>'; }).join('') + '</tr>');
101
+ // Close table if next line is not a table row
102
+ var nextL2 = lines[i+1] ? lines[i+1].trim() : '';
103
+ if (!nextL2 || nextL2.charAt(0) !== '|') { out.push('</tbody></table>'); }
104
+ }
105
+ continue;
106
+ }
107
+ // Close open table if we hit a non-table line
108
+ var lastOut = out[out.length-1] || '';
109
+ if (lastOut.indexOf('<tr>') !== -1 && lastOut.indexOf('</table>') === -1) {
110
+ out.push('</tbody></table>');
111
+ }
84
112
  // Blockquote > text
85
113
  var bqm = l.match(/^&gt; (.+)/);
86
114
  if (bqm) { out.push('<blockquote class="md-bq">'+bqm[1]+'</blockquote>'); continue; }
@@ -3250,6 +3278,8 @@ function studioReset() {
3250
3278
  if (ta) ta.value = '';
3251
3279
  var tb = document.getElementById('studioTokenBar');
3252
3280
  if (tb) tb.textContent = '';
3281
+ var inlinePdfBtn = document.getElementById('studioInlinePdfBtn');
3282
+ if (inlinePdfBtn) inlinePdfBtn.style.display = 'none';
3253
3283
  renderStudioNodes();
3254
3284
  renderStudioLog();
3255
3285
  renderStudioResult();
@@ -3388,68 +3418,221 @@ function downloadStudioPDF() {
3388
3418
  return;
3389
3419
  }
3390
3420
 
3391
- // Build sections for each agent
3421
+ // ── Markdown → HTML for PDF (full support: tables, lists, headers, inline) ──
3392
3422
  function mdToPdfHtml(raw) {
3393
- var lines = raw.split(String.fromCharCode(10));
3423
+ var NL2 = String.fromCharCode(10);
3424
+ var lines = raw.split(NL2);
3394
3425
  var out = '';
3395
- var inList = false;
3426
+ var inUl = false, inOl = false, inTable = false, inTbody = false;
3427
+ function closeAll() {
3428
+ if (inUl) { out += '</ul>'; inUl = false; }
3429
+ if (inOl) { out += '</ol>'; inOl = false; }
3430
+ if (inTable) { if (inTbody) { out += '</tbody>'; inTbody = false; } out += '</table>'; inTable = false; }
3431
+ }
3432
+ function inlineFormat(t) {
3433
+ t = t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
3434
+ t = t.replace(new RegExp('[*][*]([^*]+)[*][*]','g'),'<strong>$1</strong>');
3435
+ t = t.replace(new RegExp('[*]([^*]+)[*]','g'),'<em>$1</em>');
3436
+ t = t.replace(/~~([^~]+)~~/g,'<del>$1</del>');
3437
+ return t;
3438
+ }
3396
3439
  for (var li = 0; li < lines.length; li++) {
3397
- var line = lines[li]
3398
- .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
3399
- // Inline bold: **text**
3400
- line = line.replace(new RegExp('[*][*]([^*]+)[*][*]','g'),'<strong>$1</strong>');
3401
- // Inline italic: *text*
3402
- line = line.replace(new RegExp('[*]([^*]+)[*]','g'),'<em>$1</em>');
3403
- if (line.slice(0,4) === '### ') { if(inList){out+='</ul>';inList=false;} out += '<h3>' + line.slice(4) + '</h3>'; continue; }
3404
- if (line.slice(0,3) === '## ') { if(inList){out+='</ul>';inList=false;} out += '<h2>' + line.slice(3) + '</h2>'; continue; }
3405
- if (line.slice(0,2) === '# ') { if(inList){out+='</ul>';inList=false;} out += '<h2>' + line.slice(2) + '</h2>'; continue; }
3406
- if (line.slice(0,2) === '- ' || line.slice(0,2) === '* ') {
3407
- if (!inList) { out += '<ul>'; inList = true; }
3408
- out += '<li>' + line.slice(2) + '</li>';
3440
+ var line = lines[li];
3441
+ var trimmed = line.trim();
3442
+ // Headers
3443
+ if (trimmed.slice(0,4) === '### ') { closeAll(); out += '<h3>' + inlineFormat(trimmed.slice(4)) + '</h3>'; continue; }
3444
+ if (trimmed.slice(0,3) === '## ') { closeAll(); out += '<h2>' + inlineFormat(trimmed.slice(3)) + '</h2>'; continue; }
3445
+ if (trimmed.slice(0,2) === '# ') { closeAll(); out += '<h1>' + inlineFormat(trimmed.slice(2)) + '</h1>'; continue; }
3446
+ // Horizontal rule
3447
+ if (/^---+$/.test(trimmed)) { closeAll(); out += '<hr>'; continue; }
3448
+ // Markdown table
3449
+ if (trimmed.charAt(0) === '|' && trimmed.lastIndexOf('|') > 0) {
3450
+ // Separator row signals end of header
3451
+ if (/^\|[\s\-|:]+\|$/.test(trimmed)) {
3452
+ if (inTable) { out += '</thead><tbody>'; inTbody = true; }
3453
+ continue;
3454
+ }
3455
+ var cells = trimmed.split('|').slice(1,-1).map(function(c){ return inlineFormat(c.trim()); });
3456
+ var nextTrimmed = lines[li+1] ? lines[li+1].trim() : '';
3457
+ var nextIsSep = /^\|[\s\-|:]+\|$/.test(nextTrimmed);
3458
+ if (!inTable) {
3459
+ out += '<table>';
3460
+ inTable = true;
3461
+ if (nextIsSep) { out += '<thead>'; inTbody = false; }
3462
+ else { out += '<tbody>'; inTbody = true; }
3463
+ }
3464
+ var tag = (!inTbody) ? 'th' : 'td';
3465
+ out += '<tr>' + cells.map(function(c){ return '<'+tag+'>'+c+'</'+tag+'>'; }).join('') + '</tr>';
3409
3466
  continue;
3410
3467
  }
3411
- if (inList) { out += '</ul>'; inList = false; }
3412
- if (line.trim() === '') { out += '</p><p>'; } else { out += line + '<br>'; }
3468
+ // Close table if not a table row
3469
+ if (inTable) { if (inTbody) { out += '</tbody>'; inTbody = false; } out += '</table>'; inTable = false; }
3470
+ // Unordered list
3471
+ if (/^[\-\*] /.test(trimmed)) {
3472
+ if (inOl) { out += '</ol>'; inOl = false; }
3473
+ if (!inUl) { out += '<ul>'; inUl = true; }
3474
+ out += '<li>' + inlineFormat(trimmed.slice(2)) + '</li>';
3475
+ continue;
3476
+ }
3477
+ // Ordered list
3478
+ var olMatch = trimmed.match(/^\d+\. (.+)/);
3479
+ if (olMatch) {
3480
+ if (inUl) { out += '</ul>'; inUl = false; }
3481
+ if (!inOl) { out += '<ol>'; inOl = true; }
3482
+ out += '<li>' + inlineFormat(olMatch[1]) + '</li>';
3483
+ continue;
3484
+ }
3485
+ // Close lists
3486
+ if (inUl) { out += '</ul>'; inUl = false; }
3487
+ if (inOl) { out += '</ol>'; inOl = false; }
3488
+ // Blank line
3489
+ if (trimmed === '') { out += '<div style="height:6px"></div>'; continue; }
3490
+ // Paragraph
3491
+ out += '<p>' + inlineFormat(trimmed) + '</p>';
3413
3492
  }
3414
- if (inList) out += '</ul>';
3415
- return '<p>' + out + '</p>';
3493
+ closeAll();
3494
+ return out;
3416
3495
  }
3417
- var sectionsHtml = nodes.map(function(n) {
3418
- if (!n.output || n.output === '(no output)' || n.agent === 'CanvasAgent') return '';
3419
- return '<div class="section"><div class="agent-label">' + (n.icon||'') + ' ' + esc(n.label||n.agent) + '</div><div class="section-body">' + mdToPdfHtml(n.output) + '</div></div>';
3496
+
3497
+ // ── Collect workflow metadata ─────────────────────────────────────────────
3498
+ var activeNodes = nodes.filter(function(n){ return n.output && n.output !== '(no output)' && n.agent !== 'CanvasAgent'; });
3499
+ var totalTokensIn = studioTokens ? (studioTokens.in || 0) : 0;
3500
+ var totalTokensOut = studioTokens ? (studioTokens.out || 0) : 0;
3501
+ var agentNames = activeNodes.map(function(n){ return (n.icon||'') + ' ' + esc(n.label||n.agent); });
3502
+ var nowTime = new Date().toLocaleTimeString('it-IT', {hour:'2-digit',minute:'2-digit'});
3503
+
3504
+ // ── Section HTML ──────────────────────────────────────────────────────────
3505
+ var sectionsHtml = activeNodes.map(function(n, idx) {
3506
+ var agentColor = ['#4f46e5','#0891b2','#059669','#d97706','#dc2626','#7c3aed','#0284c7'][idx % 7];
3507
+ return '<div class="section">' +
3508
+ '<div class="agent-header" style="border-left-color:' + agentColor + '">' +
3509
+ '<span class="agent-icon">' + (n.icon||'&#9632;') + '</span>' +
3510
+ '<div><div class="agent-name">' + esc(n.label||n.agent) + '</div>' +
3511
+ '<div class="agent-sub">' + esc(n.agent) + ' &nbsp;&#183;&nbsp; Step ' + (idx+1) + ' di ' + activeNodes.length + '</div></div>' +
3512
+ '</div>' +
3513
+ '<div class="section-body">' + mdToPdfHtml(n.output) + '</div>' +
3514
+ '</div>';
3420
3515
  }).join('');
3421
3516
 
3422
- // Include canvas if present (as an embedded iframe screenshot fallback note)
3423
- var canvasNote = studioState.canvas ? ["<div class='section canvas-note'><div class='agent-label'>&#9632; Canvas Report</div><div class='section-body'><p><em>Il Canvas HTML e disponibile in Studio. Aprire il pannello Canvas e usare la funzione stampa del browser per includerlo.</em></p></div></div>"].join("") : "";
3424
-
3425
- var html = '<!DOCTYPE html><html lang="it"><head><meta charset="UTF-8"><title>' + esc(task) + '</title><style>' +
3426
- 'body{font-family:"Helvetica Neue",Arial,sans-serif;color:#111;background:#fff;margin:0;padding:0}' +
3427
- '.cover{background:#0d0d0d;color:#fff;padding:60px 50px;page-break-after:always}' +
3428
- '.cover h1{font-size:28px;font-weight:700;margin:0 0 12px;color:#00ff41}' +
3429
- '.cover .meta{font-size:13px;color:#aaa;margin-top:8px}' +
3430
- '.cover .task{font-size:15px;color:#e0e0e0;margin-top:20px;line-height:1.6;max-width:700px}' +
3431
- '.cover .brand{font-size:11px;color:#555;margin-top:40px;letter-spacing:2px;text-transform:uppercase}' +
3432
- '.toc{padding:40px 50px;border-bottom:1px solid #e0e0e0;page-break-after:always}' +
3433
- '.toc h2{font-size:14px;text-transform:uppercase;letter-spacing:2px;color:#555;margin-bottom:16px}' +
3434
- '.toc ol{margin:0;padding-left:20px;font-size:13px;line-height:2}' +
3435
- '.section{padding:36px 50px;border-bottom:1px solid #f0f0f0;page-break-inside:avoid}' +
3517
+ // ── Full HTML document ────────────────────────────────────────────────────
3518
+ var html = '<!DOCTYPE html><html lang="it"><head><meta charset="UTF-8"><title>' + esc(task) + '</title>' +
3519
+ '<link rel="preconnect" href="https://fonts.googleapis.com">' +
3520
+ '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">' +
3521
+ '<style>' +
3522
+ '*{box-sizing:border-box;margin:0;padding:0}' +
3523
+ 'body{font-family:"Inter",system-ui,sans-serif;color:#1e1e2e;background:#fff;font-size:13px;line-height:1.7}' +
3524
+
3525
+ // Cover
3526
+ '.cover{background:linear-gradient(135deg,#1e1b4b 0%,#312e81 40%,#1e3a5f 100%);color:#fff;padding:64px 60px 56px;page-break-after:always;position:relative;overflow:hidden}' +
3527
+ '.cover::before{content:"";position:absolute;top:-80px;right:-80px;width:360px;height:360px;background:radial-gradient(circle,rgba(99,102,241,.25) 0%,transparent 70%);pointer-events:none}' +
3528
+ '.cover-brand{font-size:10px;font-weight:600;letter-spacing:3px;text-transform:uppercase;color:rgba(255,255,255,.5);margin-bottom:32px;display:flex;align-items:center;gap:8px}' +
3529
+ '.cover-brand::before{content:"";display:inline-block;width:24px;height:2px;background:#6366f1}' +
3530
+ '.cover h1{font-size:30px;font-weight:800;line-height:1.25;color:#fff;margin-bottom:20px;max-width:680px}' +
3531
+ '.cover-task-label{font-size:10px;font-weight:600;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,.4);margin-bottom:8px}' +
3532
+ '.cover-task{font-size:14px;color:rgba(255,255,255,.8);line-height:1.65;max-width:660px;font-style:italic;padding:14px 18px;background:rgba(255,255,255,.07);border-radius:8px;border-left:3px solid #6366f1}' +
3533
+
3534
+ // Stats bar
3535
+ '.cover-stats{display:flex;gap:0;margin-top:40px;border-top:1px solid rgba(255,255,255,.12);padding-top:28px}' +
3536
+ '.stat{flex:1;padding-right:28px;border-right:1px solid rgba(255,255,255,.1)}' +
3537
+ '.stat:last-child{border-right:none;padding-right:0;padding-left:28px}' +
3538
+ '.stat:not(:first-child){padding-left:28px}' +
3539
+ '.stat-value{font-size:22px;font-weight:800;color:#fff;line-height:1}' +
3540
+ '.stat-label{font-size:10px;font-weight:500;letter-spacing:1.5px;text-transform:uppercase;color:rgba(255,255,255,.45);margin-top:5px}' +
3541
+
3542
+ // Workflow bar
3543
+ '.workflow-bar{padding:28px 60px;background:#f8f7ff;border-bottom:1px solid #e8e5ff;display:flex;align-items:center;gap:0;flex-wrap:wrap}' +
3544
+ '.wf-step{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:#4f46e5;white-space:nowrap}' +
3545
+ '.wf-arrow{color:#c7c2f0;margin:0 6px;font-size:14px}' +
3546
+ '.wf-label{font-size:9px;font-weight:500;letter-spacing:1.5px;text-transform:uppercase;color:#9c97c7;margin-right:16px}' +
3547
+
3548
+ // TOC
3549
+ '.toc{padding:36px 60px;border-bottom:1px solid #eee;page-break-after:always}' +
3550
+ '.toc-title{font-size:10px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#9ca3af;margin-bottom:18px}' +
3551
+ '.toc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px}' +
3552
+ '.toc-item{display:flex;align-items:center;gap:10px;padding:10px 14px;background:#f9f9fc;border-radius:8px;border:1px solid #ede9fe}' +
3553
+ '.toc-num{width:22px;height:22px;border-radius:50%;background:#4f46e5;color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}' +
3554
+ '.toc-name{font-size:12px;font-weight:600;color:#1e1e2e}' +
3555
+
3556
+ // Sections
3557
+ '.section{padding:36px 60px;border-bottom:1px solid #f0f0f5;page-break-inside:avoid}' +
3436
3558
  '.section:last-child{border-bottom:none}' +
3437
- '.agent-label{font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#888;font-weight:700;margin-bottom:14px}' +
3438
- '.section-body{font-size:13px;line-height:1.8;color:#222}' +
3439
- '.section-body h2{font-size:16px;font-weight:700;color:#111;margin:20px 0 8px}' +
3440
- '.section-body h3{font-size:14px;font-weight:600;color:#333;margin:16px 0 6px}' +
3441
- '.section-body ul{margin:8px 0;padding-left:20px}' +
3559
+ '.agent-header{display:flex;align-items:center;gap:14px;margin-bottom:20px;padding-left:14px;border-left:3px solid #4f46e5}' +
3560
+ '.agent-icon{font-size:22px;line-height:1}' +
3561
+ '.agent-name{font-size:14px;font-weight:700;color:#1e1e2e}' +
3562
+ '.agent-sub{font-size:10px;font-weight:500;color:#9ca3af;letter-spacing:.5px;margin-top:2px}' +
3563
+ '.section-body{font-size:13px;line-height:1.75;color:#374151}' +
3564
+ '.section-body h1{font-size:18px;font-weight:700;color:#1e1e2e;margin:20px 0 10px;border-bottom:1px solid #e5e7eb;padding-bottom:6px}' +
3565
+ '.section-body h2{font-size:15px;font-weight:700;color:#1e1e2e;margin:18px 0 8px}' +
3566
+ '.section-body h3{font-size:13px;font-weight:600;color:#4f46e5;margin:14px 0 6px}' +
3567
+ '.section-body p{margin:0 0 10px}' +
3568
+ '.section-body ul{margin:8px 0 10px 18px;list-style:disc}' +
3569
+ '.section-body ol{margin:8px 0 10px 18px}' +
3442
3570
  '.section-body li{margin-bottom:4px}' +
3443
- '.section-body strong{font-weight:700}' +
3444
- '.section-body p{margin:0 0 12px}' +
3445
- '.canvas-note{background:#f9f9f9}' +
3446
- '.footer-bar{padding:20px 50px;background:#f9f9f9;font-size:10px;color:#aaa;text-align:center;letter-spacing:1px}' +
3447
- '@media print{body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.cover{page-break-after:always}.toc{page-break-after:always}}' +
3571
+ '.section-body strong{font-weight:700;color:#1e1e2e}' +
3572
+ '.section-body em{color:#6366f1;font-style:italic}' +
3573
+ '.section-body table{width:100%;border-collapse:collapse;margin:14px 0;font-size:12px}' +
3574
+ '.section-body th{background:#f0eeff;color:#4f46e5;font-weight:700;text-align:left;padding:8px 12px;border:1px solid #e0d9ff;font-size:11px;letter-spacing:.3px}' +
3575
+ '.section-body td{padding:7px 12px;border:1px solid #ede9fe;color:#374151}' +
3576
+ '.section-body tr:nth-child(even) td{background:#f9f8ff}' +
3577
+ '.section-body hr{border:none;border-top:1px solid #e5e7eb;margin:16px 0}' +
3578
+ '.section-body blockquote{border-left:3px solid #6366f1;padding:8px 14px;background:#f5f3ff;border-radius:0 6px 6px 0;color:#4f46e5;font-style:italic;margin:10px 0}' +
3579
+
3580
+ // Footer
3581
+ '.footer-bar{padding:18px 60px;background:#f8f7ff;border-top:2px solid #e8e5ff;display:flex;justify-content:space-between;align-items:center}' +
3582
+ '.footer-left{font-size:10px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#9c97c7}' +
3583
+ '.footer-right{font-size:10px;color:#b8b4d4}' +
3584
+
3585
+ '@media print{' +
3586
+ 'body{-webkit-print-color-adjust:exact;print-color-adjust:exact}' +
3587
+ '.cover{page-break-after:always}' +
3588
+ '.toc{page-break-after:always}' +
3589
+ '.section{page-break-inside:avoid}' +
3590
+ '}' +
3448
3591
  '</style></head><body>' +
3449
- '<div class="cover"><div class="brand">NotHumanAllowed — NHA Studio</div><h1>' + esc(task.length > 80 ? task.slice(0,80)+'...' : task) + '</h1><div class="meta">Generato il ' + today + ' &nbsp;·&nbsp; ' + nodes.filter(function(n){return n.agent!=='CanvasAgent'}).length + ' agenti</div><div class="task">' + esc(task) + '</div></div>' +
3450
- '<div class="toc"><h2>Indice</h2><ol>' + nodes.filter(function(n){return n.output&&n.output!=='(no output)'&&n.agent!=='CanvasAgent'}).map(function(n){return '<li>' + esc(n.label||n.agent) + '</li>';}).join('') + '</ol></div>' +
3451
- sectionsHtml + canvasNote +
3452
- '<div class="footer-bar">NHA Studio &nbsp;·&nbsp; nothumanallowed.com &nbsp;·&nbsp; ' + today + '</div>' +
3592
+
3593
+ // ── Cover ────────────────────────────────────────────────────────────────
3594
+ '<div class="cover">' +
3595
+ '<div class="cover-brand">NotHumanAllowed &nbsp; NHA Studio</div>' +
3596
+ '<h1>' + esc(task.length > 90 ? task.slice(0,90)+'...' : task) + '</h1>' +
3597
+ '<div class="cover-task-label">Workflow richiesto</div>' +
3598
+ '<div class="cover-task">' + esc(task) + '</div>' +
3599
+ '<div class="cover-stats">' +
3600
+ '<div class="stat"><div class="stat-value">' + activeNodes.length + '</div><div class="stat-label">Agenti eseguiti</div></div>' +
3601
+ '<div class="stat"><div class="stat-value">' + today + '</div><div class="stat-label">Data generazione</div></div>' +
3602
+ '<div class="stat"><div class="stat-value">' + nowTime + '</div><div class="stat-label">Ora</div></div>' +
3603
+ (totalTokensIn > 0 ? '<div class="stat"><div class="stat-value">' + (totalTokensIn + totalTokensOut).toLocaleString() + '</div><div class="stat-label">Token totali</div></div>' : '') +
3604
+ '</div>' +
3605
+ '</div>' +
3606
+
3607
+ // ── Workflow bar ─────────────────────────────────────────────────────────
3608
+ '<div class="workflow-bar">' +
3609
+ '<span class="wf-label">Workflow:</span>' +
3610
+ activeNodes.map(function(n, idx){
3611
+ return '<span class="wf-step">' + (n.icon||'') + ' ' + esc(n.label||n.agent) + '</span>' +
3612
+ (idx < activeNodes.length-1 ? '<span class="wf-arrow">&#8594;</span>' : '');
3613
+ }).join('') +
3614
+ '</div>' +
3615
+
3616
+ // ── TOC ──────────────────────────────────────────────────────────────────
3617
+ '<div class="toc">' +
3618
+ '<div class="toc-title">Indice dei contenuti</div>' +
3619
+ '<div class="toc-grid">' +
3620
+ activeNodes.map(function(n, idx){
3621
+ return '<div class="toc-item"><div class="toc-num">' + (idx+1) + '</div><div class="toc-name">' + esc(n.label||n.agent) + '</div></div>';
3622
+ }).join('') +
3623
+ '</div>' +
3624
+ '</div>' +
3625
+
3626
+ // ── Sections ─────────────────────────────────────────────────────────────
3627
+ sectionsHtml +
3628
+
3629
+ // ── Footer ───────────────────────────────────────────────────────────────
3630
+ '<div class="footer-bar">' +
3631
+ '<span class="footer-left">NHA Studio &nbsp;&#183;&nbsp; nothumanallowed.com</span>' +
3632
+ '<span class="footer-right">' + today + ' ' + nowTime +
3633
+ (totalTokensIn > 0 ? ' &nbsp;&#183;&nbsp; ' + totalTokensIn.toLocaleString() + ' token in / ' + totalTokensOut.toLocaleString() + ' out' : '') +
3634
+ '</span>' +
3635
+ '</div>' +
3453
3636
  '</body></html>';
3454
3637
 
3455
3638
  // Use Blob URL to avoid popup blockers — opens in new tab, user can Cmd+P to print as PDF
@@ -3474,8 +3657,17 @@ function renderStudioResult() {
3474
3657
  var body = hasCanvas
3475
3658
  ? '<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap"><span style="color:var(--dim);font-size:13px">&#10003; ' + t('canvas_generated') + '</span><button onclick="openCanvasPanel()" style="padding:6px 14px;background:var(--greendim);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;cursor:pointer;font-weight:700">&#x25A3; ' + t('canvas_open') + '</button></div>'
3476
3659
  : '<div class="md-body">' + renderMd(studioState.result) + '</div>';
3477
- var dlBtn = '<button onclick="downloadStudioPDF()" title="Scarica il workflow come PDF" style="margin-top:10px;padding:6px 14px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:11px;cursor:pointer;font-family:var(--mono);letter-spacing:.5px">&#x2913; Download PDF</button>';
3478
- el.innerHTML = '<div class="studio-result__title">&#10003; ' + t('workflow_complete') + '</div>' + body + dlBtn;
3660
+ var tokLine = (studioTokens && (studioTokens.in > 0 || studioTokens.out > 0))
3661
+ ? '<div style="margin-top:8px;font-size:11px;color:var(--dim);font-family:var(--mono)">&#x2B06; ' + (studioTokens.in||0).toLocaleString() + ' token in &nbsp;&#x2B07; ' + (studioTokens.out||0).toLocaleString() + ' token out &nbsp;&#x2022;&nbsp; <strong style="color:var(--green)">' + ((studioTokens.in||0)+(studioTokens.out||0)).toLocaleString() + '</strong> totale</div>'
3662
+ : '';
3663
+ var dlBtn = '<div style="margin-top:12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap">' +
3664
+ '<button onclick="downloadStudioPDF()" title="Scarica il workflow come PDF" style="display:inline-flex;align-items:center;gap:6px;padding:8px 18px;background:linear-gradient(135deg,#4f46e5,#2563eb);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;letter-spacing:.3px;box-shadow:0 2px 8px rgba(79,70,229,.35)">&#x2913; Download PDF</button>' +
3665
+ '<span style="font-size:11px;color:var(--dim)">Scarica il workflow completo come documento PDF</span>' +
3666
+ '</div>';
3667
+ el.innerHTML = '<div class="studio-result__title">&#10003; ' + t('workflow_complete') + '</div>' + body + tokLine + dlBtn;
3668
+ // Show/hide inline PDF button in the prompt bar
3669
+ var inlinePdfBtn = document.getElementById('studioInlinePdfBtn');
3670
+ if (inlinePdfBtn) inlinePdfBtn.style.display = 'inline-flex';
3479
3671
  // Update canvas button style: bright green when canvas exists, dimmed otherwise
3480
3672
  var canvasBtn = document.getElementById('studioCanvasBtn');
3481
3673
  if (canvasBtn) {
@@ -4005,6 +4197,7 @@ function renderStudio(el) {
4005
4197
  '<button onclick="document.getElementById(\\x27studioFileInput\\x27).click()" title="Attach PDF or image" style="padding:8px 10px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:15px" ' + (studioState.running ? 'disabled' : '') + '>&#128206;</button>' +
4006
4198
  '<button id="studioRunBtn" class="studio-run-btn" onclick="runStudio()" style="flex:1" ' + (studioState.running ? 'disabled' : '') + '>' + t('run') + '</button>' +
4007
4199
  '<button id="studioStopBtn" onclick="stopStudio()" title="' + t('stop') + '" style="padding:8px 14px;background:#7f1d1d;border:1px solid #ef4444;border-radius:8px;color:#ef4444;cursor:pointer;font-size:13px;font-weight:700;white-space:nowrap;' + (studioState.running ? '' : 'display:none') + '">&#9632; ' + t('stop') + '</button>' +
4200
+ '<button id="studioInlinePdfBtn" onclick="downloadStudioPDF()" title="Scarica PDF del risultato" style="display:' + (studioState.result ? 'inline-flex' : 'none') + ';align-items:center;gap:5px;padding:8px 12px;background:linear-gradient(135deg,#4f46e5,#2563eb);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;box-shadow:0 2px 6px rgba(79,70,229,.35)">&#x2913; PDF</button>' +
4008
4201
  '<button onclick="studioReset()" title="' + t('reset') + '" style="padding:8px 12px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--dim);cursor:pointer;font-size:16px;line-height:1" ' + (studioState.running ? 'disabled' : '') + '>&#8635;</button>' +
4009
4202
  '</div>' +
4010
4203
  '</div>' +
@@ -4619,6 +4812,11 @@ input:focus,textarea:focus{border-color:var(--green3)}
4619
4812
  .md-body em{font-style:italic;color:var(--dim)}
4620
4813
  .md-body del{text-decoration:line-through;color:var(--dim)}
4621
4814
  .md-body a{color:var(--cyan);text-decoration:underline}
4815
+ .md-body .md-table{width:100%;border-collapse:collapse;margin:10px 0 14px;font-size:13px}
4816
+ .md-body .md-table th{background:var(--bg3);color:var(--bright);font-weight:600;padding:7px 12px;text-align:left;border:1px solid var(--border2);white-space:nowrap}
4817
+ .md-body .md-table td{padding:6px 12px;border:1px solid var(--border);color:var(--text);vertical-align:top}
4818
+ .md-body .md-table tbody tr:nth-child(odd){background:rgba(0,0,0,0.15)}
4819
+ .md-body .md-table tbody tr:hover{background:rgba(0,255,65,0.04)}
4622
4820
 
4623
4821
  /* ---- CHAT bubble markdown tweaks ---- */
4624
4822
  .msg--assistant .msg__bubble{white-space:normal}