nothumanallowed 13.4.8 → 13.4.9
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 +1 -1
- package/src/commands/ui.mjs +122 -24
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +136 -185
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.4.
|
|
3
|
+
"version": "13.4.9",
|
|
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/commands/ui.mjs
CHANGED
|
@@ -3230,6 +3230,69 @@ ${rawText.slice(0, 18000)}`;
|
|
|
3230
3230
|
const files = await withTimeout(gd.listFiles(config, '', 10), 'DriveAgent');
|
|
3231
3231
|
toolData = typeof files === 'string' ? files : JSON.stringify(files);
|
|
3232
3232
|
} catch (e) { toolData = `Drive read failed: ${e.message}`; }
|
|
3233
|
+
|
|
3234
|
+
} else if (agent === 'FileReaderAgent') {
|
|
3235
|
+
// Reads local files/directories mentioned in the task or step prompt
|
|
3236
|
+
sendToken('[Reading local files...] ');
|
|
3237
|
+
try {
|
|
3238
|
+
// Extract paths from task + stepPrompt (absolute paths, ~/ paths, named dirs like Desktop/Downloads)
|
|
3239
|
+
const pathPatterns = [
|
|
3240
|
+
/([~/][^\s"'`,;]+\.[a-zA-Z0-9]{1,10})/g, // file with extension
|
|
3241
|
+
/([~/][^\s"'`,;]{3,})/g, // any path starting with / or ~
|
|
3242
|
+
];
|
|
3243
|
+
const homedir = (await import('os')).homedir();
|
|
3244
|
+
const foundPaths = new Set();
|
|
3245
|
+
for (const re of pathPatterns) {
|
|
3246
|
+
const text = task + ' ' + stepPrompt + ' ' + (context || '');
|
|
3247
|
+
let m;
|
|
3248
|
+
while ((m = re.exec(text)) !== null) {
|
|
3249
|
+
const p = m[1].replace(/^~/, homedir).replace(/\/$/, '');
|
|
3250
|
+
if (p.length > 2) foundPaths.add(p);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
// Also resolve named directories: Desktop, Downloads, Documents
|
|
3254
|
+
const namedDirRe = /\b(Desktop|Downloads|Documents|Documenti|Scrivania|Scaricati)\b/i;
|
|
3255
|
+
const namedMatch = (task + ' ' + stepPrompt).match(namedDirRe);
|
|
3256
|
+
const dirMap = { desktop: 'Desktop', scrivania: 'Desktop', downloads: 'Downloads', scaricati: 'Downloads', documents: 'Documents', documenti: 'Documents' };
|
|
3257
|
+
if (namedMatch) foundPaths.add(path.join(homedir, dirMap[namedMatch[1].toLowerCase()] || namedMatch[1]));
|
|
3258
|
+
|
|
3259
|
+
if (foundPaths.size === 0) {
|
|
3260
|
+
// Fallback: list Desktop
|
|
3261
|
+
foundPaths.add(path.join(homedir, 'Desktop'));
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
const parts = [];
|
|
3265
|
+
for (const p of foundPaths) {
|
|
3266
|
+
try {
|
|
3267
|
+
const result = await withTimeout(executeTool('file_list', { path: p }, config), 8000);
|
|
3268
|
+
parts.push(`## Directory: ${p}\n${typeof result === 'string' ? result : JSON.stringify(result)}`);
|
|
3269
|
+
} catch {
|
|
3270
|
+
try {
|
|
3271
|
+
const result = await withTimeout(executeTool('file_read', { path: p, lines: 300 }, config), 8000);
|
|
3272
|
+
parts.push(`## File: ${p}\n${typeof result === 'string' ? result : JSON.stringify(result)}`);
|
|
3273
|
+
} catch (e2) { parts.push(`## ${p}: could not read (${e2.message})`); }
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
toolData = parts.join('\n\n') || 'No files found at the specified paths.';
|
|
3277
|
+
} catch (e) { toolData = `File read failed: ${e.message}`; }
|
|
3278
|
+
|
|
3279
|
+
} else if (agent === 'CodeExecutorAgent') {
|
|
3280
|
+
// Generates and executes code (Python/JS/TS) to answer data questions
|
|
3281
|
+
sendToken('[Preparing code execution...] ');
|
|
3282
|
+
try {
|
|
3283
|
+
// Ask LLM to write the code first
|
|
3284
|
+
const codeGenSys = `You are a code generator. Write a ${(/python/i.test(stepPrompt) || /dati|data|analisi|analysis|calcol/i.test(stepPrompt)) ? 'Python' : 'JavaScript'} script that answers the following task. Output ONLY the raw code, no markdown fences, no explanation.`;
|
|
3285
|
+
const codeGenUser = `Task: ${stepPrompt}\n\nAvailable context data:\n${(context || '').slice(0, 4000)}`;
|
|
3286
|
+
const codeGenConfig = Object.assign({}, config, { thinking: 'off' });
|
|
3287
|
+
sendToken('[Generating code...] ');
|
|
3288
|
+
const generatedCode = await withTimeout(callLLM(codeGenConfig, codeGenSys, codeGenUser, { max_tokens: 2000 }), 30000);
|
|
3289
|
+
// Strip markdown fences if LLM added them
|
|
3290
|
+
const cleanCode = generatedCode.replace(/^```[\w]*\n?/m, '').replace(/\n?```\s*$/m, '').trim();
|
|
3291
|
+
const lang = /^import |^from |^print\(|^def |^class |^#!.*python/m.test(cleanCode) ? 'python' : 'javascript';
|
|
3292
|
+
sendToken(`[Executing ${lang} code...] `);
|
|
3293
|
+
const execResult = await withTimeout(executeTool('execute_code', { language: lang, code: cleanCode, timeout: 30 }, config), 45000);
|
|
3294
|
+
toolData = `## Generated code (${lang}):\n\`\`\`${lang}\n${cleanCode.slice(0, 2000)}\n\`\`\`\n\n## Execution result:\n${typeof execResult === 'string' ? execResult : JSON.stringify(execResult)}`;
|
|
3295
|
+
} catch (e) { toolData = `Code execution failed: ${e.message}`; }
|
|
3233
3296
|
}
|
|
3234
3297
|
|
|
3235
3298
|
// ── Build system prompt with real tool data ───────────────────
|
|
@@ -3239,37 +3302,72 @@ ${rawText.slice(0, 18000)}`;
|
|
|
3239
3302
|
const today = new Date().toISOString().split('T')[0];
|
|
3240
3303
|
const isCanvasAgent = agent === 'CanvasAgent';
|
|
3241
3304
|
// Tool-data agents: fetch real live data and use buildSystemPrompt (tool calls allowed)
|
|
3242
|
-
const isLiveDataAgent = ['CalendarAgent','EmailAgent','GitHubAgent','NotionAgent','SlackAgent','DriveAgent','BrowserAgent','WebSearchAgent','ResearchAgent'].includes(agent);
|
|
3305
|
+
const isLiveDataAgent = ['CalendarAgent','EmailAgent','GitHubAgent','NotionAgent','SlackAgent','DriveAgent','BrowserAgent','WebSearchAgent','ResearchAgent','FileReaderAgent','CodeExecutorAgent'].includes(agent);
|
|
3243
3306
|
|
|
3244
3307
|
// ── Canvas HTML template — built server-side, guaranteed CSS ─────
|
|
3245
3308
|
// The LLM outputs ONLY the <body> inner HTML (no <html>, no <style>)
|
|
3246
3309
|
// Server wraps it in the full template. This prevents the model from ignoring CSS.
|
|
3247
|
-
const NHA_CSS = `*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Inter',system-ui,sans-serif;background:#0d0d14;color:#f0f0f5;min-height:100vh;padding:24px;font-size:14px;line-height:1.65}a{color:#22d3ee;text-decoration:underline}a:hover{color:#818cf8}strong{color:#f0f0f5;font-weight:700}em{color:#a5b4fc;font-style:italic}u{text-decoration-color:#ef4444;text-underline-offset:2px}blockquote{border-left:3px solid #6366f1;padding:10px 16px;margin:12px 0;background:#15151f;border-radius:0 8px 8px 0;color:#8b8b9e;font-style:italic}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
-
|
|
3256
|
-
|
|
3257
|
-
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
-
|
|
3262
|
-
|
|
3263
|
-
- .
|
|
3264
|
-
|
|
3265
|
-
-
|
|
3266
|
-
|
|
3310
|
+
const NHA_CSS = `*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Inter',system-ui,sans-serif;background:#0d0d14;color:#f0f0f5;min-height:100vh;padding:24px;font-size:14px;line-height:1.65}a{color:#22d3ee;text-decoration:underline}a:hover{color:#818cf8}strong{color:#f0f0f5;font-weight:700}em{color:#a5b4fc;font-style:italic}u{text-decoration-color:#ef4444;text-underline-offset:2px}blockquote{border-left:3px solid #6366f1;padding:10px 16px;margin:12px 0;background:#15151f;border-radius:0 8px 8px 0;color:#8b8b9e;font-style:italic}
|
|
3311
|
+
/* ── HEADER ── */
|
|
3312
|
+
.header{background:linear-gradient(135deg,#4f46e5 0%,#06b6d4 100%);border-radius:16px;padding:28px 36px;margin-bottom:20px}.header h1{font-size:24px;font-weight:800;color:#fff;margin-bottom:6px}.header p{font-size:13px;color:rgba(255,255,255,.85);margin:0}.meta{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}.meta span{background:rgba(255,255,255,.18);border-radius:20px;padding:3px 12px;font-size:11px;color:#fff;font-weight:500}
|
|
3313
|
+
/* ── GRID / CARDS ── */
|
|
3314
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:20px}.card{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px}.card-label{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#6366f1;font-weight:700;margin-bottom:8px}.card h3{font-size:20px;font-weight:700;color:#f0f0f5;margin-bottom:4px}.card p{font-size:12px;color:#8b8b9e;margin:0}
|
|
3315
|
+
/* ── SECTIONS ── */
|
|
3316
|
+
.section{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:22px;margin-bottom:16px;break-inside:avoid;page-break-inside:avoid}.section-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:16px}.section h3{font-size:15px;font-weight:600;color:#f0f0f5;margin-bottom:6px;margin-top:14px}.section p{font-size:13px;color:#8b8b9e;line-height:1.7;margin-bottom:10px}
|
|
3317
|
+
/* ── LISTS ── */
|
|
3318
|
+
ul{list-style:none;padding:0;margin:8px 0}ul li{padding:4px 0 4px 18px;position:relative;font-size:13px;color:#8b8b9e}ul li::before{content:'›';position:absolute;left:0;color:#6366f1;font-weight:700}ol{padding-left:20px;margin:8px 0}ol li{padding:4px 0;font-size:13px;color:#8b8b9e;line-height:1.6}
|
|
3319
|
+
/* ── PRIORITY LIST ── */
|
|
3320
|
+
.priority-list{display:flex;flex-direction:column;gap:8px}.priority-item{display:flex;align-items:flex-start;gap:12px;padding:12px;background:#1c1c28;border-radius:8px;break-inside:avoid}.priority-num{width:26px;height:26px;border-radius:50%;background:#6366f1;color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}.priority-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:3px}.priority-item p{font-size:12px;color:#8b8b9e;line-height:1.5;margin:0}
|
|
3321
|
+
/* ── SOURCE ITEMS ── */
|
|
3322
|
+
.source-item{padding:14px;background:#1c1c28;border-radius:8px;margin-bottom:10px;border-left:3px solid #6366f1;break-inside:avoid}.source-item h4{font-size:13px;font-weight:600;color:#f0f0f5;margin-bottom:4px}.source-item p{font-size:12px;color:#8b8b9e;line-height:1.6;margin:4px 0}.source-item a{font-size:11px}
|
|
3323
|
+
/* ── BAR CHARTS ── */
|
|
3324
|
+
.bar-row{margin-bottom:10px;break-inside:avoid}.bar-label{font-size:12px;color:#8b8b9e;margin-bottom:4px;display:flex;justify-content:space-between}.bar-track{background:#1c1c28;border-radius:4px;height:8px;overflow:hidden}.bar-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#6366f1,#22d3ee)}
|
|
3325
|
+
/* ── DATA TABLES ── */
|
|
3326
|
+
.data-table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px;border-radius:8px;overflow:hidden}.data-table th{background:#1e1b4b;color:#a5b4fc;font-weight:700;padding:10px 14px;text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.8px;border-bottom:2px solid #2a2a38}.data-table td{padding:9px 14px;color:#c4c4d4;border-bottom:1px solid #1e1e2c;vertical-align:top}.data-table tr:nth-child(even) td{background:#12121e}.data-table tr:hover td{background:#1c1c2c}.data-table td strong{color:#f0f0f5}
|
|
3327
|
+
/* ── CHART CONTAINERS ── */
|
|
3328
|
+
.chart-wrap{background:#15151f;border:1px solid #2a2a38;border-radius:12px;padding:18px;margin-bottom:16px;break-inside:avoid}.chart-title{font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:#22d3ee;font-weight:700;margin-bottom:12px}.chart-canvas{width:100%!important;max-height:300px}
|
|
3329
|
+
/* ── BADGES / TAGS ── */
|
|
3330
|
+
.badge-high{display:inline-block;background:#7f1d1d;color:#ef4444;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-med{display:inline-block;background:#713f12;color:#f59e0b;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-low{display:inline-block;background:#14532d;color:#34d399;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}.badge-info{display:inline-block;background:#1e1b4b;color:#a5b4fc;border-radius:12px;padding:2px 10px;font-size:10px;font-weight:700;margin-right:4px}
|
|
3331
|
+
/* ── MISC ── */
|
|
3332
|
+
.divider{border:none;border-top:1px solid #2a2a38;margin:16px 0}.footer{text-align:center;padding:18px;font-size:11px;color:#4a4a5e;margin-top:8px}
|
|
3333
|
+
/* ── PRINT / PDF ── */
|
|
3334
|
+
@media print{*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;color-adjust:exact!important}body{background:#fff!important;color:#111!important;padding:0!important}.header{background:linear-gradient(135deg,#4f46e5,#06b6d4)!important}.header h1,.header p,.meta span{color:#fff!important}.section,.card,.chart-wrap,.source-item,.priority-item,.data-table,.bar-row{break-inside:avoid;page-break-inside:avoid}.section{background:#f6f8ff!important;border:1px solid #dde1f0!important}.section-title,.chart-title{color:#4f46e5!important}.section p,.section h3{color:#1a1a2e!important}ul li,ol li{color:#374151!important}ul li::before{color:#4f46e5!important}.card{background:#f0f3fc!important;border:1px solid #dde1f0!important}.card h3{color:#1a1a2e!important}.card p{color:#374151!important}.priority-item,.source-item{background:#eef0f8!important}.data-table th{background:#e8eaf6!important;color:#4f46e5!important}.data-table td{color:#374151!important;border-bottom:1px solid #dde1f0!important}.data-table tr:nth-child(even) td{background:#f4f6fb!important}.bar-track{background:#e0e4ef!important}.footer{color:#9ca3af!important}a{color:#4f46e5!important}}`;
|
|
3335
|
+
|
|
3336
|
+
const wrapInNHATemplate = (bodyHtml, title) => `<!DOCTYPE html><html lang="${langCode}"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>${(title||'NHA Report').replace(/</g,'<')}</title><style>${NHA_CSS}</style><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"><\/script></head><body>${bodyHtml}</body></html>`;
|
|
3337
|
+
|
|
3338
|
+
const canvasSystemPrompt = `You are a professional HTML dashboard generator. Output ONLY the HTML content that goes INSIDE the <body> tag. Do NOT output <!DOCTYPE>, <html>, <head>, <style>, or any wrapper tags — the CSS and Chart.js are already provided.
|
|
3339
|
+
|
|
3340
|
+
AVAILABLE CSS CLASSES:
|
|
3341
|
+
- .header > h1, p, .meta > span (gradient header banner)
|
|
3342
|
+
- .grid > .card > .card-label, h3, p (KPI stat grid)
|
|
3343
|
+
- .section > .section-title, h3, p (content sections with auto page-break)
|
|
3344
|
+
- .source-item > h4, p, a (news/email source items)
|
|
3345
|
+
- .priority-list > .priority-item > .priority-num, h4, p (ranked action list)
|
|
3346
|
+
- .bar-row > .bar-label, .bar-track > .bar-fill[style="width:X%"] (CSS bar charts)
|
|
3347
|
+
- .data-table (full HTML table: thead > tr > th, tbody > tr > td)
|
|
3348
|
+
- .chart-wrap > .chart-title + <canvas id="cN" class="chart-canvas"> (Chart.js chart)
|
|
3349
|
+
- .badge-high .badge-med .badge-low .badge-info (colored badges)
|
|
3350
|
+
- ul > li, ol > li, blockquote, .divider, .footer
|
|
3351
|
+
|
|
3352
|
+
TABLES — use whenever data has rows and columns:
|
|
3353
|
+
<div class="section"><div class="section-title">TABLE TITLE</div>
|
|
3354
|
+
<table class="data-table"><thead><tr><th>Col1</th><th>Col2</th></tr></thead><tbody><tr><td>val</td><td>val</td></tr></tbody></table></div>
|
|
3355
|
+
|
|
3356
|
+
CHARTS — Chart.js is loaded. Use for any quantitative comparison, trend, distribution:
|
|
3357
|
+
<div class="chart-wrap"><div class="chart-title">CHART TITLE</div>
|
|
3358
|
+
<canvas id="c1" class="chart-canvas"></canvas></div>
|
|
3359
|
+
<script>new Chart(document.getElementById('c1'),{type:'bar',data:{labels:['A','B','C'],datasets:[{label:'Value',data:[10,20,15],backgroundColor:['#6366f1','#22d3ee','#f59e0b']}]},options:{responsive:true,plugins:{legend:{labels:{color:'#f0f0f5'}}},scales:{x:{ticks:{color:'#8b8b9e'},grid:{color:'#1e1e2c'}},y:{ticks:{color:'#8b8b9e'},grid:{color:'#1e1e2c'}}}}});<\/script>
|
|
3360
|
+
Chart types: 'bar' | 'line' | 'pie' | 'doughnut' | 'radar'. Use unique ids: c1, c2, c3...
|
|
3361
|
+
For pie/doughnut omit scales. For line use borderColor instead of backgroundColor.
|
|
3267
3362
|
|
|
3268
3363
|
RULES:
|
|
3269
3364
|
- Language: ${language}. ALL text must be in ${language}.
|
|
3270
|
-
- Use REAL data from the input —
|
|
3271
|
-
- URLs
|
|
3272
|
-
- Use .
|
|
3365
|
+
- Use REAL data from the input — NEVER invent numbers or fabricate data
|
|
3366
|
+
- URLs: ALWAYS wrap in <a href="URL" target="_blank">text</a>
|
|
3367
|
+
- Use .data-table for any tabular data (comparisons, metrics, lists with 2+ columns)
|
|
3368
|
+
- Use Chart.js for any numeric data (percentages, counts, trends, rankings)
|
|
3369
|
+
- Use .grid>.card for KPI numbers at the top
|
|
3370
|
+
- Use .priority-list for action items
|
|
3273
3371
|
- Output must start with <div class="header"> and end with <div class="footer">`;
|
|
3274
3372
|
|
|
3275
3373
|
// ── Handle PDF/image attachment on first step ─────────────────
|
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.4.
|
|
8
|
+
export const VERSION = '13.4.9';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -3548,33 +3548,42 @@ function renderStudioNodes() {
|
|
|
3548
3548
|
el.innerHTML = '<div class="studio-canvas__empty"><div class="studio-canvas__empty-icon">⚙</div><div>Describe a task and click Run</div></div>';
|
|
3549
3549
|
return;
|
|
3550
3550
|
}
|
|
3551
|
-
// ──
|
|
3552
|
-
// Renders all pipeline nodes as
|
|
3553
|
-
//
|
|
3554
|
-
// Orchestrator walks between desks scolding agents.
|
|
3551
|
+
// ── Parliament-style office scene ──────────────────────────────────────────
|
|
3552
|
+
// Renders all pipeline nodes as the same parliament office scene:
|
|
3553
|
+
// agents at desks, orchestrator walking + sbraita (speech bubble yelling).
|
|
3555
3554
|
var hasActive = studioState.nodes.some(function(n){ return n.status === \x27running\x27; });
|
|
3556
3555
|
var hasDone = studioState.nodes.some(function(n){ return n.status === \x27done\x27; });
|
|
3557
3556
|
var showMaster = hasActive || hasDone;
|
|
3558
3557
|
|
|
3559
|
-
//
|
|
3558
|
+
// Sbraita phrases — orchestrator yells at agents
|
|
3559
|
+
var sbraitaPhrases = [
|
|
3560
|
+
\x27PIU VELOCE!!!\x27, \x27FOCUS!!!\x27, \x27DEADLINE!!!\x27, \x27CHE FAI??!\x27,
|
|
3561
|
+
\x27MUOVITI!!\x27, \x27NO ERRORI!\x27, \x27ADESSO!!\x27, \x27SBRIGATI!\x27
|
|
3562
|
+
];
|
|
3563
|
+
var activeNode = studioState.nodes.find(function(n){ return n.status === \x27running\x27; });
|
|
3564
|
+
var sbIdx = activeNode ? Math.abs((activeNode.label||activeNode.agent||\x27x\x27).charCodeAt(0)) % sbraitaPhrases.length : 0;
|
|
3565
|
+
var sbraitaText = sbraitaPhrases[sbIdx];
|
|
3566
|
+
|
|
3567
|
+
// Build desks row — same card structure as parliament
|
|
3560
3568
|
var desksHtml2 = \x27\x27;
|
|
3561
|
-
studioState.nodes.forEach(function(n
|
|
3569
|
+
studioState.nodes.forEach(function(n) {
|
|
3562
3570
|
var isActive = n.status === \x27running\x27;
|
|
3563
3571
|
var isDone = n.status === \x27done\x27;
|
|
3564
3572
|
var isErr = n.status === \x27error\x27;
|
|
3565
|
-
desksHtml2 += \x27<div class="
|
|
3573
|
+
desksHtml2 += \x27<div class="prl-desk\x27+(isActive?\x27 prl-desk--active\x27:\x27\x27)+(isDone?\x27 wf-desk--done\x27:\x27\x27)+(isErr?\x27 wf-desk--err\x27:\x27\x27)+\x27" style="--dc:#6366f1;min-width:78px;cursor:pointer" data-agent-label="\x27+esc(n.label||n.agent)+\x27" onclick="studioScrollToAgent(this.getAttribute(String.fromCharCode(100,97,116,97,45,97,103,101,110,116,45,108,97,98,101,108)))" title="Vai al log di \x27+esc(n.label||n.agent)+\x27">\x27;
|
|
3566
3574
|
if (isActive) desksHtml2 += \x27<div class="prl-action-bubble prl-action-bubble--active" style="font-size:8px;padding:2px 6px">\u2026lavora</div>\x27;
|
|
3567
3575
|
else if (isDone) desksHtml2 += \x27<div class="prl-action-bubble" style="background:#0a2010;border-color:#22c55e;color:#4ade80;font-size:8px;padding:2px 6px">\u2714 fatto</div>\x27;
|
|
3568
3576
|
else if (isErr) desksHtml2 += \x27<div class="prl-action-bubble" style="background:#200a0a;border-color:#ef4444;color:#f87171;font-size:8px;padding:2px 6px">\u2715 errore</div>\x27;
|
|
3577
|
+
else desksHtml2 += \x27<div class="prl-action-bubble" style="font-size:8px;padding:2px 6px;opacity:.5">in attesa</div>\x27;
|
|
3569
3578
|
desksHtml2 += buildWorkflowChar(n);
|
|
3570
|
-
desksHtml2 += \x27<div class="
|
|
3579
|
+
desksHtml2 += \x27<div class="prl-desk-name" style="color:\x27+(isDone?\x27#4ade80\x27:(isActive?\x27#a5b4fc\x27:(isErr?\x27#f87171\x27:\x27#6b7280\x27)))+\x27;max-width:78px;white-space:normal;word-break:break-word;text-align:center" title="\x27+esc(n.label)+\x27">\x27+esc(n.label)+\x27</div>\x27;
|
|
3571
3580
|
desksHtml2 += \x27</div>\x27;
|
|
3572
3581
|
n._rendered = true;
|
|
3573
3582
|
});
|
|
3574
3583
|
|
|
3575
|
-
// ── Master orchestrator SVG
|
|
3584
|
+
// ── Master orchestrator SVG ─────────────────────────────────────────────────
|
|
3576
3585
|
var masterColor3 = \x27#818cf8\x27;
|
|
3577
|
-
var masterSvg2 = \x27<svg viewBox="0 0 60 90" width="52" height="78" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 0 10px \x27+masterColor3+\x27aa)">\x27+
|
|
3586
|
+
var masterSvg2 = \x27<svg viewBox="0 0 60 90" width="52" height="78" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,sans-serif" style="filter:drop-shadow(0 0 10px \x27+masterColor3+\x27aa)">\x27+
|
|
3578
3587
|
\x27<path d="M22 55 C21 63 19 72 18 77 C17 80 18 82 21 82 C23 82 24 80 24 77 C25 71 25 62 26 55 Z" fill="#1e1c4a" class="prl-master-leg-l"/>\x27+
|
|
3579
3588
|
\x27<path d="M28 55 C29 63 31 72 32 77 C33 80 32 82 29 82 C27 82 26 80 26 77 C25 71 25 62 24 55 Z" fill="#1e1c4a" class="prl-master-leg-r"/>\x27+
|
|
3580
3589
|
\x27<path d="M16 79 C14 79 13 81 14 83 C15 85 18 85 21 84 C23 83 24 82 23 80 C22 79 19 79 16 79" fill="#0a0a14"/>\x27+
|
|
@@ -3590,7 +3599,7 @@ function renderStudioNodes() {
|
|
|
3590
3599
|
\x27<path d="M33 35 L36 33 L37 36 L34 37 Z" fill="\x27+masterColor3+\x2799"/>\x27+
|
|
3591
3600
|
\x27<circle cx="25" cy="42" r="1.2" fill="\x27+masterColor3+\x27aa"/>\x27+
|
|
3592
3601
|
\x27<circle cx="20" cy="36" r="2.5" fill="#0d0d1e" stroke="\x27+masterColor3+\x2799" stroke-width="1"/>\x27+
|
|
3593
|
-
\x27<text x="20" y="39" text-anchor="middle" font-size="4" fill="\x27+masterColor3+\x27">N</text>\x27+
|
|
3602
|
+
\x27<text x="20" y="39.5" text-anchor="middle" font-size="4" fill="\x27+masterColor3+\x27" font-family="system-ui,sans-serif">N</text>\x27+
|
|
3594
3603
|
\x27<g class="prl-master-arm-l">\x27+
|
|
3595
3604
|
\x27<path d="M13 34 C8 37 6 42 6 46 C6 49 9 50 11 49 C13 48 13 45 14 41 C15 38 14 35 13 34" fill="#252450"/>\x27+
|
|
3596
3605
|
\x27<path d="M6 46 C4 48 4 51 5 53 C6 55 9 55 10 53 C11 51 10 48 10 46 Z" fill="#d4a97a"/>\x27+
|
|
@@ -3624,18 +3633,49 @@ function renderStudioNodes() {
|
|
|
3624
3633
|
\x27<circle cx="30.5" cy="15" r="1.3" fill="#0a0a18"/>\x27+
|
|
3625
3634
|
\x27<circle cx="20.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
|
|
3626
3635
|
\x27<circle cx="31.5" cy="13.7" r=".9" fill="rgba(255,255,255,.95)"/>\x27+
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3636
|
+
// Angry expression when active (furrowed brows + open mouth)
|
|
3637
|
+
(hasActive ?
|
|
3638
|
+
\x27<path d="M19.5 23 Q25 26 30.5 23" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
|
|
3639
|
+
\x27<path d="M20 23.5 Q25 26 30 23.5 Q25 25 20 23.5" fill="#fff" opacity=".6"/>\x27+
|
|
3640
|
+
// Open mouth / yelling
|
|
3641
|
+
\x27<ellipse cx="25" cy="24.5" rx="3" ry="1.5" fill="#5a1a0a" opacity=".7"/>\x27
|
|
3642
|
+
:
|
|
3643
|
+
\x27<path d="M19.5 24 Q25 27.5 30.5 24" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
|
|
3644
|
+
\x27<path d="M20 24.5 Q25 27 30 24.5 Q25 26.5 20 24.5" fill="#fff" opacity=".7"/>\x27
|
|
3645
|
+
)+
|
|
3646
|
+
\x27<circle cx="25" cy="-4" r="9" fill="\x27+masterColor3+\x2722" stroke="\x27+masterColor3+\x2766" stroke-width="1.5"/>\x27+
|
|
3647
|
+
\x27<path d="M25 -13 L30 -4 L25 5 L20 -4 Z" fill="\x27+masterColor3+\x27" stroke="\x27+masterColor3+\x27cc" stroke-width=".8"/>\x27+
|
|
3648
|
+
\x27<path d="M25 -13 L30 -4 L25 -3 L20 -4 Z" fill="\x27+masterColor3+\x27aa"/>\x27+
|
|
3631
3649
|
\x27</svg>\x27;
|
|
3632
3650
|
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3651
|
+
// Speech bubble "sbraita" when there is an active agent
|
|
3652
|
+
var bubbleHtml = showMaster && hasActive
|
|
3653
|
+
? (\x27<div class="wf-sbraita-bubble">\x27+sbraitaText+\x27</div>\x27)
|
|
3654
|
+
: \x27\x27;
|
|
3655
|
+
|
|
3656
|
+
var masterWfHtml = showMaster ? (
|
|
3657
|
+
\x27<div class="prl-master prl-master-walk wf-master" style="bottom:20px">\x27+
|
|
3658
|
+
bubbleHtml+
|
|
3659
|
+
masterSvg2+
|
|
3660
|
+
\x27<div class="prl-master-label" style="color:\x27+masterColor3+\x27;font-size:8px">Orchestratore</div>\x27+
|
|
3661
|
+
\x27</div>\x27
|
|
3662
|
+
) : \x27\x27;
|
|
3663
|
+
|
|
3664
|
+
// Phase label
|
|
3665
|
+
var doneCount = studioState.nodes.filter(function(n){return n.status===\x27done\x27;}).length;
|
|
3666
|
+
var totalCount = studioState.nodes.length;
|
|
3667
|
+
var phaseLabel2 = hasActive
|
|
3668
|
+
? (\x27Workflow in esecuzione \u2014 \x27+doneCount+\x27/\x27+totalCount+\x27 completati\x27)
|
|
3669
|
+
: (doneCount===totalCount && totalCount>0 ? \x27Workflow completato\x27 : \x27Workflow pianificato\x27);
|
|
3670
|
+
var phaseColor2 = hasActive ? \x27#6366f1\x27 : (doneCount===totalCount && totalCount>0 ? \x27#22c55e\x27 : \x27#6b7280\x27);
|
|
3671
|
+
|
|
3672
|
+
var html = \x27<div class="prl-wrap" style="border-color:\x27+phaseColor2+\x2744;padding-bottom:16px">\x27+
|
|
3673
|
+
\x27<div class="prl-header"><span class="prl-phase-chip" style="--pc:\x27+phaseColor2+\x27">\x27+phaseLabel2+\x27</span></div>\x27+
|
|
3674
|
+
\x27<div class="prl-office wf-office" style="min-height:160px">\x27+
|
|
3636
3675
|
\x27<div class="prl-office-floor"></div>\x27+
|
|
3637
|
-
\x27<div class="
|
|
3676
|
+
\x27<div class="prl-desks-row" style="justify-content:center;flex-wrap:wrap;gap:10px;padding-bottom:8px">\x27+desksHtml2+\x27</div>\x27+
|
|
3638
3677
|
masterWfHtml+
|
|
3678
|
+
\x27</div>\x27+
|
|
3639
3679
|
\x27</div>\x27;
|
|
3640
3680
|
|
|
3641
3681
|
el.innerHTML = html;
|
|
@@ -3973,172 +4013,55 @@ function downloadStudioPDF() {
|
|
|
3973
4013
|
'</div>' +
|
|
3974
4014
|
'</body></html>';
|
|
3975
4015
|
|
|
3976
|
-
// Generate PDF
|
|
3977
|
-
//
|
|
3978
|
-
|
|
4016
|
+
// ── Generate PDF via browser native print dialog ──────────────────────────
|
|
4017
|
+
// Renders the full NHA report HTML in a hidden iframe, waits for Chart.js to
|
|
4018
|
+
// initialize all charts, then calls window.print() on the iframe.
|
|
4019
|
+
// The CSS @media print block (already in NHA_CSS) handles all styling:
|
|
4020
|
+
// - Light backgrounds for readability on paper
|
|
4021
|
+
// - break-inside:avoid on .section/.card/.priority-item/.data-table
|
|
4022
|
+
// - -webkit-print-color-adjust:exact for gradient headers and charts
|
|
4023
|
+
// This approach never cuts text in the middle of a paragraph.
|
|
3979
4024
|
function doGeneratePdf() {
|
|
3980
4025
|
var btn2 = document.getElementById('studioInlinePdfBtn');
|
|
3981
4026
|
var dlBtn2 = document.querySelector('button[onclick="downloadStudioPDF()"]');
|
|
3982
4027
|
function setBusy(b) {
|
|
3983
|
-
if (btn2) { btn2.disabled = b; btn2.textContent = b ? '
|
|
3984
|
-
if (dlBtn2) { dlBtn2.disabled = b; dlBtn2.textContent = b ? '
|
|
4028
|
+
if (btn2) { btn2.disabled = b; btn2.textContent = b ? 'Apertura stampa...' : '\u2913 PDF'; }
|
|
4029
|
+
if (dlBtn2) { dlBtn2.disabled = b; dlBtn2.textContent = b ? 'Apertura stampa...' : '\u2913 Download PDF'; }
|
|
3985
4030
|
}
|
|
3986
4031
|
setBusy(true);
|
|
3987
4032
|
|
|
3988
|
-
//
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
'.priority-item p{color:#374151!important}' +
|
|
4010
|
-
'.source-item{background:#eef0f8!important;border-left-color:#4f46e5!important}' +
|
|
4011
|
-
'.source-item h4{color:#1a1a2e!important}' +
|
|
4012
|
-
'.source-item p{color:#374151!important}' +
|
|
4013
|
-
'.bar-track{background:#e0e4ef!important}' +
|
|
4014
|
-
'.footer{color:#9ca3af!important}' +
|
|
4015
|
-
'a{color:#4f46e5!important}' +
|
|
4016
|
-
'.section,.card,.priority-item,.source-item,.bar-row{break-inside:avoid;page-break-inside:avoid}' +
|
|
4017
|
-
'h1,h2,h3,h4{break-after:avoid;page-break-after:avoid}' +
|
|
4018
|
-
'.header{break-after:avoid;page-break-after:avoid}' +
|
|
4019
|
-
'.section-body h4{font-size:12px;font-weight:600;color:#6366f1!important;margin:10px 0 4px;text-transform:uppercase;letter-spacing:.4px}' +
|
|
4020
|
-
'</style>';
|
|
4021
|
-
var pdfHtml2 = html.replace('</head>', lightOverride + '</head>');
|
|
4022
|
-
|
|
4023
|
-
// Build hidden iframe at 794px (A4 width at 96dpi = 210mm)
|
|
4024
|
-
var iframe = document.createElement('iframe');
|
|
4025
|
-
iframe.style.cssText = 'position:fixed;left:-9999px;top:0;width:794px;height:1px;border:none;visibility:hidden';
|
|
4026
|
-
document.body.appendChild(iframe);
|
|
4027
|
-
var ifrDoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
4028
|
-
ifrDoc.open(); ifrDoc.write(pdfHtml2); ifrDoc.close();
|
|
4029
|
-
iframe.onload = function() {
|
|
4030
|
-
var ifrBody = ifrDoc.body;
|
|
4031
|
-
var totalH = Math.max(ifrBody.scrollHeight, ifrBody.offsetHeight, ifrDoc.documentElement.scrollHeight);
|
|
4032
|
-
iframe.style.height = totalH + 'px';
|
|
4033
|
-
|
|
4034
|
-
// Collect smart page-break candidates using offsetTop (document-relative, not viewport-relative).
|
|
4035
|
-
// We use offsetTop + offsetHeight of each .section element so breaks land after each agent section.
|
|
4036
|
-
var breakCandidates = [0];
|
|
4037
|
-
var sectionEls = ifrDoc.querySelectorAll('.section,.cover,.toc,.workflow-bar');
|
|
4038
|
-
for (var si2 = 0; si2 < sectionEls.length; si2++) {
|
|
4039
|
-
var el2 = sectionEls[si2];
|
|
4040
|
-
// Walk up to get absolute offsetTop within the iframe document
|
|
4041
|
-
var absTop = 0; var cur = el2;
|
|
4042
|
-
while (cur && cur !== ifrDoc.body) { absTop += cur.offsetTop; cur = cur.offsetParent; }
|
|
4043
|
-
breakCandidates.push(absTop); // start of section (new page begins here)
|
|
4044
|
-
breakCandidates.push(absTop + el2.offsetHeight); // end of section
|
|
4045
|
-
}
|
|
4046
|
-
breakCandidates.push(totalH);
|
|
4047
|
-
breakCandidates.sort(function(a,b){ return a-b; });
|
|
4048
|
-
// Deduplicate
|
|
4049
|
-
breakCandidates = breakCandidates.filter(function(v,i,a){ return i===0||v!==a[i-1]; });
|
|
4050
|
-
|
|
4051
|
-
// Scale: 3x on HiDPI screens, minimum 2.5x for sharp text at A4
|
|
4052
|
-
var renderScale = Math.max(2.5, Math.min(3, window.devicePixelRatio * 1.5));
|
|
4053
|
-
window.html2canvas(ifrBody, {
|
|
4054
|
-
scale: renderScale,
|
|
4055
|
-
useCORS: true,
|
|
4056
|
-
allowTaint: true,
|
|
4057
|
-
backgroundColor: '#ffffff',
|
|
4058
|
-
width: 794,
|
|
4059
|
-
windowWidth: 794,
|
|
4060
|
-
scrollX: 0,
|
|
4061
|
-
scrollY: 0,
|
|
4062
|
-
logging: false,
|
|
4063
|
-
imageTimeout: 15000,
|
|
4064
|
-
ignoreElements: function(el){ return el.tagName === 'SCRIPT' || el.tagName === 'NOSCRIPT'; }
|
|
4065
|
-
}).then(function(canvas) {
|
|
4066
|
-
var pdf = new window.jspdf.jsPDF({orientation:'portrait', unit:'pt', format:'a4', compress:true});
|
|
4067
|
-
var pageW = pdf.internal.pageSize.getWidth(); // 595.28pt
|
|
4068
|
-
var pageH = pdf.internal.pageSize.getHeight(); // 841.89pt
|
|
4069
|
-
var margin = 28; // pt — ~10mm margins
|
|
4070
|
-
var usableW = pageW - margin * 2;
|
|
4071
|
-
var usableH = pageH - margin * 2;
|
|
4072
|
-
|
|
4073
|
-
// px per rendered page: canvas.width / usableW gives canvas-px per pt;
|
|
4074
|
-
// usableH (pt) * that ratio = canvas px per A4 usable page height
|
|
4075
|
-
var pxPerPt = canvas.width / usableW;
|
|
4076
|
-
var maxSliceH = Math.floor(usableH * pxPerPt); // max canvas px per page
|
|
4077
|
-
|
|
4078
|
-
// Convert DOM break candidates to canvas px coordinates
|
|
4079
|
-
// (DOM px * renderScale because html2canvas renders at renderScale)
|
|
4080
|
-
var canvasBreaks = breakCandidates.map(function(domPx){ return Math.round(domPx * renderScale); });
|
|
4081
|
-
|
|
4082
|
-
// Smart page-break slicer: each slice ends at the nearest break candidate that fits within maxSliceH.
|
|
4083
|
-
// Falls back to hard-cut only when a single section is taller than one full page.
|
|
4084
|
-
var yCanvas = 0;
|
|
4085
|
-
var pageNum = 0;
|
|
4086
|
-
while (yCanvas < canvas.height) {
|
|
4087
|
-
if (pageNum > 0) pdf.addPage();
|
|
4088
|
-
var maxEnd = yCanvas + maxSliceH;
|
|
4089
|
-
// Find the largest break candidate <= maxEnd (that is also > yCanvas)
|
|
4090
|
-
var bestBreak = -1;
|
|
4091
|
-
for (var bi2 = 0; bi2 < canvasBreaks.length; bi2++) {
|
|
4092
|
-
var bp = canvasBreaks[bi2];
|
|
4093
|
-
if (bp > yCanvas && bp <= maxEnd) { bestBreak = bp; }
|
|
4094
|
-
}
|
|
4095
|
-
// If no break found (section taller than a page), hard-cut at maxEnd
|
|
4096
|
-
var sliceEnd = (bestBreak > yCanvas) ? bestBreak : Math.min(maxEnd, canvas.height);
|
|
4097
|
-
var thisSlice = sliceEnd - yCanvas;
|
|
4098
|
-
if (thisSlice <= 0) break; // safety guard
|
|
4099
|
-
|
|
4100
|
-
var sliceCanvas = document.createElement('canvas');
|
|
4101
|
-
sliceCanvas.width = canvas.width;
|
|
4102
|
-
sliceCanvas.height = thisSlice;
|
|
4103
|
-
var ctx = sliceCanvas.getContext('2d');
|
|
4104
|
-
ctx.fillStyle = '#ffffff';
|
|
4105
|
-
ctx.fillRect(0, 0, sliceCanvas.width, sliceCanvas.height);
|
|
4106
|
-
ctx.drawImage(canvas, 0, yCanvas, canvas.width, thisSlice, 0, 0, canvas.width, thisSlice);
|
|
4107
|
-
var sliceData = sliceCanvas.toDataURL('image/png');
|
|
4108
|
-
// Proportional height in pt: thisSlice / pxPerPt
|
|
4109
|
-
var sliceImgH = thisSlice / pxPerPt;
|
|
4110
|
-
pdf.addImage(sliceData, 'PNG', margin, margin, usableW, sliceImgH, '', 'FAST');
|
|
4111
|
-
yCanvas = sliceEnd;
|
|
4112
|
-
pageNum++;
|
|
4113
|
-
}
|
|
4114
|
-
pdf.save(pdfFileName);
|
|
4115
|
-
document.body.removeChild(iframe);
|
|
4116
|
-
setBusy(false);
|
|
4117
|
-
}).catch(function(e2) {
|
|
4118
|
-
document.body.removeChild(iframe);
|
|
4033
|
+
// Open report in a new browser window and trigger print dialog.
|
|
4034
|
+
// The browser handles all page-break logic natively — no rasterization,
|
|
4035
|
+
// no text-cut artifacts. Charts render as vectors via Chart.js.
|
|
4036
|
+
// The @media print CSS block (in NHA_CSS) handles light-mode override.
|
|
4037
|
+
var printWin = window.open('', '_blank', 'width=900,height=700');
|
|
4038
|
+
if (!printWin) {
|
|
4039
|
+
// Popup blocked — fallback: open in current tab print
|
|
4040
|
+
setBusy(false);
|
|
4041
|
+
var fb = window.open(URL.createObjectURL(new Blob([html], {type:'text/html'})), '_blank');
|
|
4042
|
+
if (fb) { setTimeout(function(){ fb.print(); }, 1500); }
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
printWin.document.open();
|
|
4046
|
+
printWin.document.write(html);
|
|
4047
|
+
printWin.document.close();
|
|
4048
|
+
// Wait for Chart.js to render all charts before printing
|
|
4049
|
+
// (charts need ~500ms after DOMContentLoaded for animation frame)
|
|
4050
|
+
printWin.onload = function() {
|
|
4051
|
+
setTimeout(function() {
|
|
4052
|
+
printWin.focus();
|
|
4053
|
+
printWin.print();
|
|
4119
4054
|
setBusy(false);
|
|
4120
|
-
|
|
4121
|
-
|
|
4055
|
+
// Close the print window after a short delay (user may close it themselves)
|
|
4056
|
+
setTimeout(function(){ try { printWin.close(); } catch(e3){} }, 3000);
|
|
4057
|
+
}, 600);
|
|
4122
4058
|
};
|
|
4059
|
+
// Fallback if onload already fired
|
|
4060
|
+
setTimeout(function() {
|
|
4061
|
+
setBusy(false);
|
|
4062
|
+
}, 4000);
|
|
4123
4063
|
}
|
|
4124
|
-
|
|
4125
|
-
if (window.jspdf && window.html2canvas) {
|
|
4126
|
-
doGeneratePdf();
|
|
4127
|
-
} else {
|
|
4128
|
-
var loaded = 0;
|
|
4129
|
-
function onLibLoad() { loaded++; if (loaded >= 2) doGeneratePdf(); }
|
|
4130
|
-
if (!window.html2canvas) {
|
|
4131
|
-
var s1 = document.createElement('script');
|
|
4132
|
-
s1.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
|
|
4133
|
-
s1.onload = onLibLoad; document.head.appendChild(s1);
|
|
4134
|
-
} else { loaded++; }
|
|
4135
|
-
if (!window.jspdf) {
|
|
4136
|
-
var s2 = document.createElement('script');
|
|
4137
|
-
s2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
|
|
4138
|
-
s2.onload = onLibLoad; document.head.appendChild(s2);
|
|
4139
|
-
} else { loaded++; }
|
|
4140
|
-
if (loaded >= 2) doGeneratePdf();
|
|
4141
|
-
}
|
|
4064
|
+
doGeneratePdf();
|
|
4142
4065
|
}
|
|
4143
4066
|
|
|
4144
4067
|
function renderStudioResult() {
|
|
@@ -4339,10 +4262,22 @@ async function runStudio() {
|
|
|
4339
4262
|
if (!pb) return;
|
|
4340
4263
|
pb.style.display = \x27block\x27;
|
|
4341
4264
|
if (convergence != null) {
|
|
4342
|
-
|
|
4265
|
+
// Deliberation complete — return to normal flow, anchored in place
|
|
4266
|
+
pb.style.position = \x27\x27; pb.style.bottom = \x27\x27; pb.style.left = \x27\x27;
|
|
4267
|
+
pb.style.transform = \x27\x27; pb.style.width = \x27\x27;
|
|
4268
|
+
pb.style.top = \x27\x27; pb.style.zIndex = \x27\x27; pb.style.boxShadow = \x27\x27;
|
|
4269
|
+
pb.style.maxWidth = \x27\x27;
|
|
4343
4270
|
} else {
|
|
4344
|
-
|
|
4345
|
-
pb.style.
|
|
4271
|
+
// Fixed overlay at bottom of viewport so it never scrolls away
|
|
4272
|
+
pb.style.position = \x27fixed\x27;
|
|
4273
|
+
pb.style.bottom = \x2716px\x27;
|
|
4274
|
+
pb.style.left = \x2750%\x27;
|
|
4275
|
+
pb.style.transform = \x27translateX(-50%)\x27;
|
|
4276
|
+
pb.style.width = \x27calc(100vw - 32px)\x27;
|
|
4277
|
+
pb.style.maxWidth = \x27860px\x27;
|
|
4278
|
+
pb.style.top = \x27\x27;
|
|
4279
|
+
pb.style.zIndex = \x27500\x27;
|
|
4280
|
+
pb.style.boxShadow = \x270 8px 40px rgba(99,102,241,.5),0 2px 0 rgba(99,102,241,.2)\x27;
|
|
4346
4281
|
}
|
|
4347
4282
|
|
|
4348
4283
|
// ── OFFICE CARTOON ANIMATION ─────────────────────────────────────────
|
|
@@ -4603,7 +4538,7 @@ async function runStudio() {
|
|
|
4603
4538
|
var masterIcon = phase===\x27r3\x27 ? \x27\u26a1\x27 : (phase===\x27done\x27 ? \x27\u2714\x27 : \x27\u2666\x27);
|
|
4604
4539
|
var masterColor2 = {r1:\x27#818cf8\x27,r2:\x27#818cf8\x27,r3:\x27#f59e0b\x27,done:\x27#22c55e\x27}[phase]||\x27#818cf8\x27;
|
|
4605
4540
|
var masterAnim = (phase===\x27r1\x27) ? \x27prl-master-walk\x27 : (phase===\x27r2\x27 ? \x27prl-master-supervise\x27 : \x27\x27);
|
|
4606
|
-
var masterSvg = \x27<svg viewBox="0 0 60 90" width="56" height="86" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 0 12px \x27+masterColor2+\x27aa)">\x27+
|
|
4541
|
+
var masterSvg = \x27<svg viewBox="0 0 60 90" width="56" height="86" xmlns="http://www.w3.org/2000/svg" font-family="system-ui,sans-serif" style="filter:drop-shadow(0 0 12px \x27+masterColor2+\x27aa)">\x27+
|
|
4607
4542
|
// ════ LEGS (walking when R1) ════
|
|
4608
4543
|
// Left leg — trouser
|
|
4609
4544
|
\x27<path d="M22 55 C21 63 19 72 18 77 C17 80 18 82 21 82 C23 82 24 80 24 77 C25 71 25 62 26 55 Z" fill="#1e1c4a" class="prl-master-leg-l"/>\x27+
|
|
@@ -4713,10 +4648,19 @@ async function runStudio() {
|
|
|
4713
4648
|
// Confident smile
|
|
4714
4649
|
\x27<path d="M19.5 24 Q25 27.5 30.5 24" stroke="#8b4513" stroke-width="1.8" fill="none" stroke-linecap="round"/>\x27+
|
|
4715
4650
|
\x27<path d="M20 24.5 Q25 27 30 24.5 Q25 26.5 20 24.5" fill="#fff" opacity=".7"/>\x27+
|
|
4716
|
-
// Crown / authority
|
|
4717
|
-
\x27<
|
|
4718
|
-
|
|
4719
|
-
|
|
4651
|
+
// Crown / authority badge above head — pure SVG geometry (no font-dependent text)
|
|
4652
|
+
\x27<circle cx="25" cy="-4" r="9" fill="\x27+masterColor2+\x2722" stroke="\x27+masterColor2+\x2766" stroke-width="1.5"/>\x27+
|
|
4653
|
+
(phase===\x27r3\x27 ?
|
|
4654
|
+
// Lightning bolt for R3 mediation
|
|
4655
|
+
\x27<path d="M27 -12 L22 -4 L25.5 -4 L23 4 L29 -3 L25.5 -3 Z" fill="\x27+masterColor2+\x27" stroke="\x27+masterColor2+\x2788" stroke-width=".6" stroke-linejoin="round"/>\x27
|
|
4656
|
+
: phase===\x27done\x27 ?
|
|
4657
|
+
// Checkmark for done
|
|
4658
|
+
\x27<path d="M19 -4 L23.5 0.5 L31 -9" stroke="\x27+masterColor2+\x27" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>\x27
|
|
4659
|
+
:
|
|
4660
|
+
// Diamond (authority) for r1/r2
|
|
4661
|
+
\x27<path d="M25 -13 L30 -4 L25 5 L20 -4 Z" fill="\x27+masterColor2+\x27" stroke="\x27+masterColor2+\x27cc" stroke-width=".8"/>\x27+
|
|
4662
|
+
\x27<path d="M25 -13 L30 -4 L25 -3 L20 -4 Z" fill="\x27+masterColor2+\x27aa"/>\x27
|
|
4663
|
+
)+
|
|
4720
4664
|
\x27</svg>\x27;
|
|
4721
4665
|
|
|
4722
4666
|
var masterLabel2 = {r1:\x27Orchestratore\x27,r2:\x27Coordina\x27,r3:\x27HERALD\x27,done:\x27Completato\x27}[phase]||\x27MASTER\x27;
|
|
@@ -5260,6 +5204,8 @@ function renderStudio(el) {
|
|
|
5260
5204
|
{icon:'📷',name:'CanvasAgent',desc:'Generate HTML visual report'},
|
|
5261
5205
|
{icon:'🔒',name:'SecurityAgent',desc:'Security analysis'},
|
|
5262
5206
|
{icon:'🔧',name:'DevOpsAgent',desc:'Infrastructure analysis'},
|
|
5207
|
+
{icon:'💾',name:'CodeExecutorAgent',desc:'Run Python/JS/TS code in sandbox'},
|
|
5208
|
+
{icon:'📁',name:'FileReaderAgent',desc:'Read local files & directories'},
|
|
5263
5209
|
];
|
|
5264
5210
|
|
|
5265
5211
|
var toolsHtml = STUDIO_AGENTS.map(function(t){
|
|
@@ -5933,10 +5879,15 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
5933
5879
|
.wf-desk--done{filter:drop-shadow(0 0 6px #22c55e55)}
|
|
5934
5880
|
.wf-desk--err{filter:drop-shadow(0 0 6px #ef444455)}
|
|
5935
5881
|
.wf-desk-name{font-size:9px;font-weight:600;text-align:center;max-width:88px;word-break:break-word;line-height:1.3;padding:0 2px;margin-top:2px}
|
|
5936
|
-
.wf-master{position:absolute;bottom:
|
|
5882
|
+
.wf-master{position:absolute;bottom:20px;right:16px}
|
|
5883
|
+
/* Orchestrator speech bubble — sbraita */
|
|
5884
|
+
.wf-sbraita-bubble{position:absolute;top:-28px;left:50%;transform:translateX(-50%);background:#1a0a0a;border:1.5px solid #ef4444;color:#fca5a5;font-family:var(--mono);font-size:9px;font-weight:800;padding:3px 8px;border-radius:8px;white-space:nowrap;letter-spacing:.5px;animation:sbraitaPop .4s ease-in-out infinite alternate;pointer-events:none;z-index:4}
|
|
5885
|
+
.wf-sbraita-bubble::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:5px solid transparent;border-top-color:#ef4444}
|
|
5886
|
+
@keyframes sbraitaPop{0%{transform:translateX(-50%) scale(1) rotate(-2deg)}100%{transform:translateX(-50%) scale(1.06) rotate(2deg)}}
|
|
5937
5887
|
/* ── Parliament Office Cartoon ── */
|
|
5938
5888
|
.prl-wrap{background:#07070f;border:1.5px solid #6366f1;border-radius:14px;padding:14px 16px 12px;margin-bottom:16px;animation:stNodeIn .35s ease forwards;overflow:hidden}
|
|
5939
|
-
#studioParliamentBlock[style*="
|
|
5889
|
+
#studioParliamentBlock[style*="fixed"] .prl-wrap{animation:stNodeIn .35s ease forwards,parlPulse 2.2s ease-in-out infinite}
|
|
5890
|
+
#studioParliamentBlock[style*="fixed"]{backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
|
|
5940
5891
|
@keyframes parlPulse{0%,100%{border-color:#6366f1;box-shadow:none}50%{border-color:#818cf8;box-shadow:0 0 20px rgba(99,102,241,.3)}}
|
|
5941
5892
|
.prl-header{display:flex;align-items:center;margin-bottom:10px}
|
|
5942
5893
|
.prl-phase-chip{font-size:10px;font-weight:800;font-family:var(--mono);letter-spacing:.3px;color:var(--pc,#6366f1);background:rgba(99,102,241,.12);border:1px solid rgba(99,102,241,.35);border-radius:20px;padding:3px 12px;display:inline-block}
|