nothumanallowed 13.5.57 → 13.5.59
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 +78 -1
- package/package.json +1 -1
- package/src/commands/ui.mjs +148 -0
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +608 -17
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# NotHumanAllowed
|
|
2
2
|
|
|
3
|
-
**38 specialized AI agents,
|
|
3
|
+
**38 specialized AI agents, 80 tools, Studio visual workflows, WebCraft full-stack builder — all local, all free.** Security auditors, code architects, data analysts, DevOps engineers, technical writers — each with deep domain expertise. Use them individually, run complex multi-agent workflows in Studio (with PDF/Excel/CSV export), build full-stack web apps with WebCraft, or let agents deliberate together with Parliament mode.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -40,6 +40,83 @@ EmailAgent → WebSearchAgent → WriterAgent
|
|
|
40
40
|
- **Parliament mode** — enable for 2+ specialist agents to cross-read and deliberate: R1 (independent), R2 (agents read each other), R3 (HERALD mediation), convergence score
|
|
41
41
|
- Open `nha ui` → click **Studio** in the sidebar
|
|
42
42
|
|
|
43
|
+
### Studio Export
|
|
44
|
+
|
|
45
|
+
When a workflow completes, Studio provides three export formats:
|
|
46
|
+
|
|
47
|
+
- **PDF** — full structured report with all agent outputs, typography, token counters
|
|
48
|
+
- **Excel (XLSX)** — professional multi-sheet workbook via SheetJS: one sheet per agent, auto-detected numeric columns with formatting, alternating row colors, freeze panes, auto-column widths, index sheet with token summary. Data tables are extracted from Markdown output automatically.
|
|
49
|
+
- **CSV** — all Markdown tables from the report merged into a single file
|
|
50
|
+
|
|
51
|
+
Export buttons appear in the result panel and in the toolbar after each run.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## WebCraft — Full-Stack Web Apps from a Chat
|
|
56
|
+
|
|
57
|
+
WebCraft is a full-stack web app builder embedded in `nha ui`. Describe what you want in plain language — WebCraft generates a complete project with Express.js backend, PostgreSQL schema, JWT auth, email verification, security middleware, and a styled frontend. Everything runs locally with a live sandbox.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Open nha ui → click WebCraft in the sidebar
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### How it works
|
|
64
|
+
|
|
65
|
+
1. **Describe your project** in the chat (or pick an example: MySaaS, MyShop, MyBlog, MyPortfolio...)
|
|
66
|
+
2. **WebCraft generates** all files: `server/`, `public/`, `db/migrations/`, `.env.example`, `package.json`, nginx config
|
|
67
|
+
3. **Click ▶ Sandbox** — runs `npm install && node server/index.js` in an isolated process, live on a local port
|
|
68
|
+
4. **Chat with the agent** to modify, fix, or extend anything — the agent edits files directly on disk, you see diffs in real time
|
|
69
|
+
|
|
70
|
+
### WebCraft Agent
|
|
71
|
+
|
|
72
|
+
An AI assistant permanently available in the chat panel. Powered by Liara (Qwen3 32B, free) or your own API key.
|
|
73
|
+
|
|
74
|
+
**What it can do:**
|
|
75
|
+
- Edit files surgically (old → new string replace) or rewrite them completely
|
|
76
|
+
- Read any project file for context
|
|
77
|
+
- Auto-fix `MODULE_NOT_FOUND` and common require() path errors
|
|
78
|
+
- Restart the sandbox after fixes
|
|
79
|
+
- Process attached screenshots or PDFs (vision) to debug visual issues
|
|
80
|
+
|
|
81
|
+
**Context files** (created automatically for every project, editable via sidebar):
|
|
82
|
+
| File | Type | Purpose |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `skills/memory.md` | memory | Architecture decisions, stack choices, developer preferences |
|
|
85
|
+
| `skills/liara.md` | provider | Calibrate AI tone, code style, constraints |
|
|
86
|
+
| `skills/skills.md` | skill | Reusable patterns, snippets, API integrations |
|
|
87
|
+
|
|
88
|
+
Add more skill files (unlimited) for specific integrations (Stripe, email templates, etc.).
|
|
89
|
+
|
|
90
|
+
### Developer Tools (sidebar toolbar)
|
|
91
|
+
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|---|---|
|
|
94
|
+
| **Diff viewer** | After every agent edit, see before/after for each changed file — color-coded, collapsible |
|
|
95
|
+
| **Syntax check** ✅ | Runs `node --check` on all JS files, reports errors instantly |
|
|
96
|
+
| **Search** 🔍 | Grep across all project files — click a result to jump to that file |
|
|
97
|
+
| **Snapshot** 💾 | Save a full point-in-time backup of all files. Restore any snapshot with one click |
|
|
98
|
+
| **Plan mode** | Type `/plan your request` — agent proposes a plan first, you approve before any file is touched |
|
|
99
|
+
| **Auto-fix** | Sandbox errors (MODULE_NOT_FOUND etc.) trigger automatic Liara fix attempts (3 free, unlimited with own key) |
|
|
100
|
+
|
|
101
|
+
### Example session
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
You: "Add a contact form with SMTP email and honeypot spam protection"
|
|
105
|
+
Agent: → edits server/routes/api.js (add /contact POST route)
|
|
106
|
+
→ edits server/services/email.js (add sendContactEmail)
|
|
107
|
+
→ edits public/index.html (add form HTML)
|
|
108
|
+
→ edits public/js/main.js (add form JS with honeypot)
|
|
109
|
+
[Diff viewer shows 4 files changed]
|
|
110
|
+
[Syntax check: ✅ all files valid]
|
|
111
|
+
[Sandbox restarted automatically]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
You: "/plan refactor auth to use refresh token rotation"
|
|
116
|
+
Agent: → proposes plan (3 files, 6 changes) — no edits yet
|
|
117
|
+
→ you click Approve → agent executes
|
|
118
|
+
```
|
|
119
|
+
|
|
43
120
|
## Daily Operations (PAO)
|
|
44
121
|
|
|
45
122
|
Connect Gmail + Calendar. 5 specialist agents analyze your day.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.59",
|
|
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
|
@@ -4697,6 +4697,154 @@ REGOLE CRITICHE:
|
|
|
4697
4697
|
return;
|
|
4698
4698
|
}
|
|
4699
4699
|
|
|
4700
|
+
// POST /api/studio/webcraft/snapshot { projectName } → { ok, snapshot }
|
|
4701
|
+
// Creates a timestamped snapshot of all project files (excludes node_modules)
|
|
4702
|
+
if (pathname === '/api/studio/webcraft/snapshot' && method === 'POST') {
|
|
4703
|
+
const body = await parseBody(req);
|
|
4704
|
+
const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4705
|
+
if (!projName) { sendJSON(res, 400, { error: 'projectName required' }); return; }
|
|
4706
|
+
const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
|
|
4707
|
+
const snapDir = path.join(projDir, '.snapshots');
|
|
4708
|
+
if (!fs.existsSync(snapDir)) fs.mkdirSync(snapDir, { recursive: true });
|
|
4709
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
4710
|
+
const snapPath = path.join(snapDir, ts + '.json');
|
|
4711
|
+
const snapshot = {};
|
|
4712
|
+
const walkSnap = (dir, base) => {
|
|
4713
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
4714
|
+
if (entry.name === 'node_modules' || entry.name === '.snapshots' || entry.name.startsWith('.')) continue;
|
|
4715
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
4716
|
+
if (entry.isDirectory()) { walkSnap(path.join(dir, entry.name), rel); }
|
|
4717
|
+
else {
|
|
4718
|
+
try { snapshot[rel] = fs.readFileSync(path.join(dir, entry.name), 'utf8'); } catch(_) {}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
};
|
|
4722
|
+
walkSnap(projDir, '');
|
|
4723
|
+
fs.writeFileSync(snapPath, JSON.stringify({ ts, files: snapshot }), 'utf8');
|
|
4724
|
+
// Keep only last 10 snapshots
|
|
4725
|
+
const allSnaps = fs.readdirSync(snapDir).filter(f => f.endsWith('.json')).sort();
|
|
4726
|
+
if (allSnaps.length > 10) {
|
|
4727
|
+
for (const old of allSnaps.slice(0, allSnaps.length - 10)) {
|
|
4728
|
+
try { fs.unlinkSync(path.join(snapDir, old)); } catch(_) {}
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
sendJSON(res, 200, { ok: true, snapshot: ts, fileCount: Object.keys(snapshot).length });
|
|
4732
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4733
|
+
return;
|
|
4734
|
+
}
|
|
4735
|
+
|
|
4736
|
+
// GET /api/studio/webcraft/snapshots/:name → { snapshots: [{ts, fileCount}] }
|
|
4737
|
+
if (pathname.startsWith('/api/studio/webcraft/snapshots/') && method === 'GET') {
|
|
4738
|
+
const projName = decodeURIComponent(pathname.replace('/api/studio/webcraft/snapshots/', '')).replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4739
|
+
const snapDir = path.join(os.homedir(), '.nha', 'webcraft', projName, '.snapshots');
|
|
4740
|
+
if (!fs.existsSync(snapDir)) { sendJSON(res, 200, { snapshots: [] }); return; }
|
|
4741
|
+
const snaps = fs.readdirSync(snapDir).filter(f => f.endsWith('.json')).sort().reverse().map(f => {
|
|
4742
|
+
try {
|
|
4743
|
+
const data = JSON.parse(fs.readFileSync(path.join(snapDir, f), 'utf8'));
|
|
4744
|
+
return { ts: data.ts, fileCount: Object.keys(data.files || {}).length };
|
|
4745
|
+
} catch(_) { return null; }
|
|
4746
|
+
}).filter(Boolean);
|
|
4747
|
+
sendJSON(res, 200, { snapshots: snaps });
|
|
4748
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4749
|
+
return;
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
// POST /api/studio/webcraft/restore { projectName, ts } → { ok, restored }
|
|
4753
|
+
if (pathname === '/api/studio/webcraft/restore' && method === 'POST') {
|
|
4754
|
+
const body = await parseBody(req);
|
|
4755
|
+
const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4756
|
+
const ts = (body.ts || '').replace(/[^0-9T\-]/g, '');
|
|
4757
|
+
if (!projName || !ts) { sendJSON(res, 400, { error: 'projectName and ts required' }); return; }
|
|
4758
|
+
const snapPath = path.join(os.homedir(), '.nha', 'webcraft', projName, '.snapshots', ts + '.json');
|
|
4759
|
+
if (!fs.existsSync(snapPath)) { sendJSON(res, 404, { error: 'snapshot not found' }); return; }
|
|
4760
|
+
const data = JSON.parse(fs.readFileSync(snapPath, 'utf8'));
|
|
4761
|
+
const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
|
|
4762
|
+
let restored = 0;
|
|
4763
|
+
for (const [rel, content] of Object.entries(data.files || {})) {
|
|
4764
|
+
const fp = path.join(projDir, rel);
|
|
4765
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
|
4766
|
+
fs.writeFileSync(fp, content, 'utf8');
|
|
4767
|
+
restored++;
|
|
4768
|
+
}
|
|
4769
|
+
sendJSON(res, 200, { ok: true, restored });
|
|
4770
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
// POST /api/studio/webcraft/syntax-check { projectName } → { results: [{file, ok, error}] }
|
|
4775
|
+
if (pathname === '/api/studio/webcraft/syntax-check' && method === 'POST') {
|
|
4776
|
+
const body = await parseBody(req);
|
|
4777
|
+
const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4778
|
+
if (!projName) { sendJSON(res, 400, { error: 'projectName required' }); return; }
|
|
4779
|
+
const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
|
|
4780
|
+
const { execFile } = await import('child_process');
|
|
4781
|
+
const { promisify } = await import('util');
|
|
4782
|
+
const execFileAsync = promisify(execFile);
|
|
4783
|
+
const jsFiles = [];
|
|
4784
|
+
const walkJs = (dir, base) => {
|
|
4785
|
+
if (!fs.existsSync(dir)) return;
|
|
4786
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
4787
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
4788
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
4789
|
+
if (entry.isDirectory()) { walkJs(path.join(dir, entry.name), rel); }
|
|
4790
|
+
else if (entry.name.endsWith('.js') || entry.name.endsWith('.mjs')) { jsFiles.push(rel); }
|
|
4791
|
+
}
|
|
4792
|
+
};
|
|
4793
|
+
walkJs(projDir, '');
|
|
4794
|
+
const results = [];
|
|
4795
|
+
for (const rel of jsFiles) {
|
|
4796
|
+
const fp = path.join(projDir, rel);
|
|
4797
|
+
try {
|
|
4798
|
+
await execFileAsync(process.execPath, ['--check', fp], { timeout: 5000 });
|
|
4799
|
+
results.push({ file: rel, ok: true });
|
|
4800
|
+
} catch(e) {
|
|
4801
|
+
const errMsg = (e.stderr || e.message || '').split('\n')[0].replace(fp, rel);
|
|
4802
|
+
results.push({ file: rel, ok: false, error: errMsg });
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
sendJSON(res, 200, { results });
|
|
4806
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
// POST /api/studio/webcraft/grep { projectName, query } → { matches: [{file, line, lineNum, match}] }
|
|
4811
|
+
if (pathname === '/api/studio/webcraft/grep' && method === 'POST') {
|
|
4812
|
+
const body = await parseBody(req);
|
|
4813
|
+
const projName = (body.projectName || '').replace(/[^a-zA-Z0-9_-]/g, '');
|
|
4814
|
+
const query = (body.query || '').slice(0, 200);
|
|
4815
|
+
if (!projName || !query) { sendJSON(res, 400, { error: 'projectName and query required' }); return; }
|
|
4816
|
+
const projDir = path.join(os.homedir(), '.nha', 'webcraft', projName);
|
|
4817
|
+
const matches = [];
|
|
4818
|
+
let queryRe;
|
|
4819
|
+
try { queryRe = new RegExp(query, 'gi'); } catch(_) { queryRe = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }
|
|
4820
|
+
const walkGrep = (dir, base) => {
|
|
4821
|
+
if (!fs.existsSync(dir)) return;
|
|
4822
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
4823
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
4824
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
4825
|
+
if (entry.isDirectory()) { walkGrep(path.join(dir, entry.name), rel); }
|
|
4826
|
+
else {
|
|
4827
|
+
const ext = entry.name.split('.').pop();
|
|
4828
|
+
if (!['js','mjs','ts','html','css','json','md','sql','env','conf'].includes(ext)) continue;
|
|
4829
|
+
try {
|
|
4830
|
+
const lines = fs.readFileSync(path.join(dir, entry.name), 'utf8').split('\n');
|
|
4831
|
+
lines.forEach((line, idx) => {
|
|
4832
|
+
queryRe.lastIndex = 0;
|
|
4833
|
+
if (queryRe.test(line)) {
|
|
4834
|
+
matches.push({ file: rel, lineNum: idx + 1, line: line.trim().slice(0, 200) });
|
|
4835
|
+
if (matches.length >= 100) return;
|
|
4836
|
+
}
|
|
4837
|
+
});
|
|
4838
|
+
} catch(_) {}
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4842
|
+
walkGrep(projDir, '');
|
|
4843
|
+
sendJSON(res, 200, { matches: matches.slice(0, 100) });
|
|
4844
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4700
4848
|
// ── Studio: Parliament deliberation (SSE streaming) ──────────────────
|
|
4701
4849
|
// Implements the Legion DeliberationEngine protocol adapted for Studio:
|
|
4702
4850
|
// Round 1 outputs already exist (from normal workflow steps).
|
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.5.
|
|
8
|
+
export const VERSION = '13.5.59';
|
|
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
|
@@ -4586,6 +4586,286 @@ function downloadStudioPDF() {
|
|
|
4586
4586
|
doGeneratePdf();
|
|
4587
4587
|
}
|
|
4588
4588
|
|
|
4589
|
+
// ── Studio Export: CSV ────────────────────────────────────────────────────────
|
|
4590
|
+
function extractMarkdownTables(md) {
|
|
4591
|
+
var NL = String.fromCharCode(10);
|
|
4592
|
+
var lines = md.split(NL);
|
|
4593
|
+
var tables = [];
|
|
4594
|
+
var current = null;
|
|
4595
|
+
for (var i = 0; i < lines.length; i++) {
|
|
4596
|
+
var l = lines[i].trim();
|
|
4597
|
+
if (l.charAt(0) === '|' && l.lastIndexOf('|') > 0) {
|
|
4598
|
+
if (/^\|[\s\-|:]+\|$/.test(l)) { continue; } // separator
|
|
4599
|
+
var cells = l.split('|').slice(1,-1).map(function(c){ return c.trim(); });
|
|
4600
|
+
if (!current) { current = { headers: cells, rows: [] }; }
|
|
4601
|
+
else { current.rows.push(cells); }
|
|
4602
|
+
} else {
|
|
4603
|
+
if (current && current.rows.length > 0) { tables.push(current); }
|
|
4604
|
+
current = null;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
if (current && current.rows.length > 0) tables.push(current);
|
|
4608
|
+
return tables;
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
function tableToCsvString(table) {
|
|
4612
|
+
var NL = String.fromCharCode(10);
|
|
4613
|
+
function escCell(v) {
|
|
4614
|
+
if (v === undefined || v === null) return '';
|
|
4615
|
+
var s = String(v).replace(new RegExp('"', 'g'), '""');
|
|
4616
|
+
if (s.indexOf(',') >= 0 || s.indexOf('"') >= 0 || s.indexOf(NL) >= 0) return '"' + s + '"';
|
|
4617
|
+
return s;
|
|
4618
|
+
}
|
|
4619
|
+
var rows = [table.headers].concat(table.rows);
|
|
4620
|
+
return rows.map(function(r){ return r.map(escCell).join(','); }).join(NL);
|
|
4621
|
+
}
|
|
4622
|
+
|
|
4623
|
+
function downloadStudioCSV() {
|
|
4624
|
+
var nodes = (studioState.nodes || []).filter(function(n){ return n.output && n.output !== '(no output)'; });
|
|
4625
|
+
var allTables = [];
|
|
4626
|
+
nodes.forEach(function(n) {
|
|
4627
|
+
var tbls = extractMarkdownTables(n.output || '');
|
|
4628
|
+
tbls.forEach(function(t, i){ allTables.push({ agent: (n.label||n.agent), idx: i+1, table: t }); });
|
|
4629
|
+
});
|
|
4630
|
+
// Also check synthesis result
|
|
4631
|
+
if (studioState.result) {
|
|
4632
|
+
var tbls2 = extractMarkdownTables(studioState.result);
|
|
4633
|
+
tbls2.forEach(function(t, i){ allTables.push({ agent: 'Synthesis', idx: i+1, table: t }); });
|
|
4634
|
+
}
|
|
4635
|
+
if (allTables.length === 0) { alert('Nessuna tabella trovata nel report. Chiedi agli agenti di produrre dati in formato tabella Markdown.'); return; }
|
|
4636
|
+
var NL = String.fromCharCode(10);
|
|
4637
|
+
var csvParts = allTables.map(function(entry) {
|
|
4638
|
+
return '# ' + entry.agent + (allTables.length > 1 ? ' — Tabella ' + entry.idx : '') + NL + tableToCsvString(entry.table);
|
|
4639
|
+
});
|
|
4640
|
+
var csv = csvParts.join(NL + NL);
|
|
4641
|
+
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
4642
|
+
var fname = (studioState.task || 'NHA-Studio').slice(0,50).replace(/[^a-z0-9\s]/gi,'').trim().replace(/\s+/g,'-') + '.csv';
|
|
4643
|
+
var a = document.createElement('a');
|
|
4644
|
+
a.href = URL.createObjectURL(blob);
|
|
4645
|
+
a.download = fname;
|
|
4646
|
+
a.click();
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
// ── Studio Export: Excel (XLSX via SheetJS) ───────────────────────────────────
|
|
4650
|
+
var _xlsxLoaded = false;
|
|
4651
|
+
var _xlsxLoading = false;
|
|
4652
|
+
|
|
4653
|
+
function loadXLSX(cb) {
|
|
4654
|
+
if (_xlsxLoaded && window.XLSX) { cb(); return; }
|
|
4655
|
+
if (_xlsxLoading) { setTimeout(function(){ loadXLSX(cb); }, 200); return; }
|
|
4656
|
+
_xlsxLoading = true;
|
|
4657
|
+
var s = document.createElement('script');
|
|
4658
|
+
s.src = 'https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js';
|
|
4659
|
+
s.onload = function() { _xlsxLoaded = true; _xlsxLoading = false; cb(); };
|
|
4660
|
+
s.onerror = function() { _xlsxLoading = false; alert('Errore caricamento SheetJS. Controlla la connessione.'); };
|
|
4661
|
+
document.head.appendChild(s);
|
|
4662
|
+
}
|
|
4663
|
+
|
|
4664
|
+
function downloadStudioXLSX() {
|
|
4665
|
+
loadXLSX(function() { _doGenerateXLSX(); });
|
|
4666
|
+
}
|
|
4667
|
+
|
|
4668
|
+
function _doGenerateXLSX() {
|
|
4669
|
+
var XLSX = window.XLSX;
|
|
4670
|
+
if (!XLSX) { alert('SheetJS non disponibile.'); return; }
|
|
4671
|
+
|
|
4672
|
+
var nodes = (studioState.nodes || []).filter(function(n){ return n.output && n.output !== '(no output)' && n.agent !== 'CanvasAgent'; });
|
|
4673
|
+
var task = studioState.task || 'NHA Studio Report';
|
|
4674
|
+
var today = new Date();
|
|
4675
|
+
var dateStr = today.toLocaleDateString('it-IT');
|
|
4676
|
+
var wb = XLSX.utils.book_new();
|
|
4677
|
+
|
|
4678
|
+
// ── ACCENT COLORS per agente ──────────────────────────────────────────────
|
|
4679
|
+
var AGENT_COLORS = ['4F46E5','0891B2','059669','D97706','DC2626','7C3AED','0284C7','BE185D','0D9488','CA8A04'];
|
|
4680
|
+
|
|
4681
|
+
// ── Helper: cell style fabbrica ──────────────────────────────────────────
|
|
4682
|
+
function headerStyle(hexFg) {
|
|
4683
|
+
return {
|
|
4684
|
+
font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 11, name: 'Calibri' },
|
|
4685
|
+
fill: { patternType: 'solid', fgColor: { rgb: hexFg || '4F46E5' } },
|
|
4686
|
+
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
|
|
4687
|
+
border: {
|
|
4688
|
+
top: { style: 'thin', color: { rgb: 'CCCCCC' } },
|
|
4689
|
+
bottom: { style: 'thin', color: { rgb: 'CCCCCC' } },
|
|
4690
|
+
left: { style: 'thin', color: { rgb: 'CCCCCC' } },
|
|
4691
|
+
right: { style: 'thin', color: { rgb: 'CCCCCC' } }
|
|
4692
|
+
}
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
function dataStyle(even) {
|
|
4696
|
+
return {
|
|
4697
|
+
font: { sz: 10, name: 'Calibri' },
|
|
4698
|
+
fill: even ? { patternType: 'solid', fgColor: { rgb: 'F3F4F6' } } : { patternType: 'none' },
|
|
4699
|
+
alignment: { vertical: 'center', wrapText: true },
|
|
4700
|
+
border: {
|
|
4701
|
+
top: { style: 'hair', color: { rgb: 'E5E7EB' } },
|
|
4702
|
+
bottom: { style: 'hair', color: { rgb: 'E5E7EB' } },
|
|
4703
|
+
left: { style: 'hair', color: { rgb: 'E5E7EB' } },
|
|
4704
|
+
right: { style: 'hair', color: { rgb: 'E5E7EB' } }
|
|
4705
|
+
}
|
|
4706
|
+
};
|
|
4707
|
+
}
|
|
4708
|
+
function titleStyle(hex) {
|
|
4709
|
+
return {
|
|
4710
|
+
font: { bold: true, sz: 14, name: 'Calibri', color: { rgb: hex || '4F46E5' } },
|
|
4711
|
+
fill: { patternType: 'solid', fgColor: { rgb: 'F8F9FC' } },
|
|
4712
|
+
alignment: { horizontal: 'left', vertical: 'center' }
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
function metaStyle() {
|
|
4716
|
+
return { font: { sz: 10, italic: true, color: { rgb: '6B7280' }, name: 'Calibri' } };
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
// ── Helper: parse numeric value ───────────────────────────────────────────
|
|
4720
|
+
function parseNum(v) {
|
|
4721
|
+
var s = String(v).replace(/[€$£%,\s]/g,'').trim();
|
|
4722
|
+
var n = parseFloat(s);
|
|
4723
|
+
return isNaN(n) ? null : n;
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
// ── Helper: aggiunge un foglio tabella da markdown ────────────────────────
|
|
4727
|
+
function addTableSheet(sheetName, agentLabel, tables, colorHex) {
|
|
4728
|
+
var ws = {};
|
|
4729
|
+
var maxCol = 0;
|
|
4730
|
+
var rowNum = 0;
|
|
4731
|
+
|
|
4732
|
+
// Title row
|
|
4733
|
+
ws['A' + (rowNum+1)] = { v: agentLabel, t: 's', s: titleStyle(colorHex) };
|
|
4734
|
+
rowNum++;
|
|
4735
|
+
// Meta row
|
|
4736
|
+
ws['A' + (rowNum+1)] = { v: 'Generato da NHA Studio il ' + dateStr, t: 's', s: metaStyle() };
|
|
4737
|
+
ws['B' + (rowNum+1)] = { v: 'Task: ' + task.slice(0,80), t: 's', s: metaStyle() };
|
|
4738
|
+
rowNum++;
|
|
4739
|
+
rowNum++; // blank
|
|
4740
|
+
|
|
4741
|
+
tables.forEach(function(table, ti) {
|
|
4742
|
+
if (tables.length > 1) {
|
|
4743
|
+
ws['A' + (rowNum+1)] = { v: 'Tabella ' + (ti+1), t: 's', s: { font: { bold: true, sz: 11, name: 'Calibri', color: { rgb: colorHex } } } };
|
|
4744
|
+
rowNum++;
|
|
4745
|
+
}
|
|
4746
|
+
// Detect numeric columns
|
|
4747
|
+
var isNumericCol = table.headers.map(function(_, ci) {
|
|
4748
|
+
return table.rows.every(function(r){ return r[ci] === undefined || r[ci] === '' || parseNum(r[ci]) !== null; });
|
|
4749
|
+
});
|
|
4750
|
+
// Header row
|
|
4751
|
+
var colCount = table.headers.length;
|
|
4752
|
+
table.headers.forEach(function(h, ci) {
|
|
4753
|
+
var col = String.fromCharCode(65 + ci);
|
|
4754
|
+
ws[col + (rowNum+1)] = { v: h, t: 's', s: headerStyle(colorHex) };
|
|
4755
|
+
if (ci > maxCol) maxCol = ci;
|
|
4756
|
+
});
|
|
4757
|
+
rowNum++;
|
|
4758
|
+
// Data rows
|
|
4759
|
+
table.rows.forEach(function(row, ri) {
|
|
4760
|
+
row.forEach(function(cell, ci) {
|
|
4761
|
+
var col = String.fromCharCode(65 + ci);
|
|
4762
|
+
var num = isNumericCol[ci] ? parseNum(cell) : null;
|
|
4763
|
+
var addr = col + (rowNum+1);
|
|
4764
|
+
if (num !== null && cell !== '') {
|
|
4765
|
+
ws[addr] = { v: num, t: 'n', z: num % 1 !== 0 ? '#,##0.00' : '#,##0', s: dataStyle(ri % 2 === 0) };
|
|
4766
|
+
} else {
|
|
4767
|
+
ws[addr] = { v: cell || '', t: 's', s: dataStyle(ri % 2 === 0) };
|
|
4768
|
+
}
|
|
4769
|
+
});
|
|
4770
|
+
rowNum++;
|
|
4771
|
+
});
|
|
4772
|
+
rowNum++; // blank between tables
|
|
4773
|
+
});
|
|
4774
|
+
|
|
4775
|
+
// Set sheet range
|
|
4776
|
+
var lastCol = String.fromCharCode(65 + maxCol);
|
|
4777
|
+
ws['!ref'] = 'A1:' + lastCol + (rowNum + 1);
|
|
4778
|
+
|
|
4779
|
+
// Column widths (auto-estimate from content)
|
|
4780
|
+
var colWidths = [];
|
|
4781
|
+
for (var ci = 0; ci <= maxCol; ci++) {
|
|
4782
|
+
var maxW = 12;
|
|
4783
|
+
tables.forEach(function(table) {
|
|
4784
|
+
if (table.headers[ci]) maxW = Math.max(maxW, table.headers[ci].length + 2);
|
|
4785
|
+
table.rows.forEach(function(r){ if (r[ci]) maxW = Math.max(maxW, Math.min(String(r[ci]).length + 2, 50)); });
|
|
4786
|
+
});
|
|
4787
|
+
colWidths.push({ wch: maxW });
|
|
4788
|
+
}
|
|
4789
|
+
ws['!cols'] = colWidths;
|
|
4790
|
+
|
|
4791
|
+
// Row heights
|
|
4792
|
+
var rowH = [];
|
|
4793
|
+
for (var ri2 = 0; ri2 < rowNum; ri2++) rowH.push({ hpt: ri2 < 3 ? 22 : 18 });
|
|
4794
|
+
ws['!rows'] = rowH;
|
|
4795
|
+
|
|
4796
|
+
// Freeze top rows (title + meta + header row of first table)
|
|
4797
|
+
ws['!freeze'] = { xSplit: 0, ySplit: 4, topLeftCell: 'A5' };
|
|
4798
|
+
|
|
4799
|
+
XLSX.utils.book_append_sheet(wb, ws, sheetName.slice(0,31));
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// ── Foglio INDICE ─────────────────────────────────────────────────────────
|
|
4803
|
+
var wsIdx = {};
|
|
4804
|
+
wsIdx['A1'] = { v: 'NHA Studio Report', t: 's', s: titleStyle('4F46E5') };
|
|
4805
|
+
wsIdx['A2'] = { v: task, t: 's', s: { font: { sz: 12, name: 'Calibri', bold: true } } };
|
|
4806
|
+
wsIdx['A3'] = { v: 'Generato il ' + dateStr + ' con NHA Studio', t: 's', s: metaStyle() };
|
|
4807
|
+
wsIdx['A5'] = { v: 'Agente', t: 's', s: headerStyle('4F46E5') };
|
|
4808
|
+
wsIdx['B5'] = { v: 'Tabelle', t: 's', s: headerStyle('4F46E5') };
|
|
4809
|
+
wsIdx['C5'] = { v: 'Token In', t: 's', s: headerStyle('4F46E5') };
|
|
4810
|
+
wsIdx['D5'] = { v: 'Token Out', t: 's', s: headerStyle('4F46E5') };
|
|
4811
|
+
var idxRow = 5;
|
|
4812
|
+
var hasAnyTable = false;
|
|
4813
|
+
nodes.forEach(function(n, ni) {
|
|
4814
|
+
var tables = extractMarkdownTables(n.output || '');
|
|
4815
|
+
if (tables.length > 0) hasAnyTable = true;
|
|
4816
|
+
idxRow++;
|
|
4817
|
+
var co = AGENT_COLORS[ni % AGENT_COLORS.length];
|
|
4818
|
+
wsIdx['A' + idxRow] = { v: (n.label||n.agent), t: 's', s: dataStyle(ni % 2 === 0) };
|
|
4819
|
+
wsIdx['B' + idxRow] = { v: tables.length, t: 'n', s: dataStyle(ni % 2 === 0) };
|
|
4820
|
+
wsIdx['C' + idxRow] = { v: n.tokensIn || 0, t: 'n', z: '#,##0', s: dataStyle(ni % 2 === 0) };
|
|
4821
|
+
wsIdx['D' + idxRow] = { v: n.tokensOut || 0, t: 'n', z: '#,##0', s: dataStyle(ni % 2 === 0) };
|
|
4822
|
+
});
|
|
4823
|
+
wsIdx['!ref'] = 'A1:D' + (idxRow + 1);
|
|
4824
|
+
wsIdx['!cols'] = [{ wch: 28 }, { wch: 10 }, { wch: 12 }, { wch: 12 }];
|
|
4825
|
+
XLSX.utils.book_append_sheet(wb, wsIdx, 'Indice');
|
|
4826
|
+
|
|
4827
|
+
// ── Un foglio per ogni agente con tabelle ─────────────────────────────────
|
|
4828
|
+
var sheetCount = 0;
|
|
4829
|
+
nodes.forEach(function(n, ni) {
|
|
4830
|
+
var tables = extractMarkdownTables(n.output || '');
|
|
4831
|
+
if (tables.length === 0) return;
|
|
4832
|
+
var colorHex = AGENT_COLORS[ni % AGENT_COLORS.length];
|
|
4833
|
+
var sheetName = (n.label || n.agent).slice(0,28);
|
|
4834
|
+
addTableSheet(sheetName, n.label || n.agent, tables, colorHex);
|
|
4835
|
+
sheetCount++;
|
|
4836
|
+
});
|
|
4837
|
+
|
|
4838
|
+
// ── Foglio Risultato Finale (testo libero come tabella a singola colonna) ──
|
|
4839
|
+
if (studioState.result) {
|
|
4840
|
+
var synTables = extractMarkdownTables(studioState.result);
|
|
4841
|
+
if (synTables.length > 0) {
|
|
4842
|
+
addTableSheet('Sintesi', 'Sintesi Finale', synTables, '059669');
|
|
4843
|
+
sheetCount++;
|
|
4844
|
+
} else {
|
|
4845
|
+
// Export free text as single-column sheet with line-by-line rows
|
|
4846
|
+
var wsText = {};
|
|
4847
|
+
wsText['A1'] = { v: 'Sintesi Finale', t: 's', s: titleStyle('059669') };
|
|
4848
|
+
wsText['A2'] = { v: task.slice(0,80), t: 's', s: metaStyle() };
|
|
4849
|
+
var NL5 = String.fromCharCode(10);
|
|
4850
|
+
var lines = (studioState.result || '').split(NL5);
|
|
4851
|
+
lines.forEach(function(line, li) {
|
|
4852
|
+
wsText['A' + (li+4)] = { v: line.replace(new RegExp('[*_#]','g'), '').replace(new RegExp(String.fromCharCode(96),'g'), ''), t: 's', s: dataStyle(li % 2 === 0) };
|
|
4853
|
+
});
|
|
4854
|
+
wsText['!ref'] = 'A1:A' + (lines.length + 4);
|
|
4855
|
+
wsText['!cols'] = [{ wch: 90 }];
|
|
4856
|
+
XLSX.utils.book_append_sheet(wb, wsText, 'Sintesi');
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
if (sheetCount === 0 && !studioState.result) {
|
|
4861
|
+
alert('Nessuna tabella trovata. Chiedi agli agenti di produrre dati in formato tabella Markdown per generare Excel.');
|
|
4862
|
+
return;
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4865
|
+
var fname = task.slice(0,50).replace(/[^a-z0-9\s]/gi,'').trim().replace(/\s+/g,'-') + '-NHAStudio.xlsx';
|
|
4866
|
+
XLSX.writeFile(wb, fname, { bookType: 'xlsx', type: 'binary', cellStyles: true });
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4589
4869
|
function renderStudioResult() {
|
|
4590
4870
|
var el = document.getElementById('studioResult');
|
|
4591
4871
|
if (!el) return;
|
|
@@ -4598,14 +4878,19 @@ function renderStudioResult() {
|
|
|
4598
4878
|
var tokLine = (studioTokens && (studioTokens.in > 0 || studioTokens.out > 0))
|
|
4599
4879
|
? '<div style="margin-top:8px;font-size:11px;color:var(--dim);font-family:var(--mono)">⬆ ' + (studioTokens.in||0).toLocaleString() + ' token in ⬇ ' + (studioTokens.out||0).toLocaleString() + ' token out • <strong style="color:var(--green)">' + ((studioTokens.in||0)+(studioTokens.out||0)).toLocaleString() + '</strong> totale</div>'
|
|
4600
4880
|
: '';
|
|
4601
|
-
var dlBtn = '<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--border);display:flex;align-items:center;gap:
|
|
4602
|
-
'<button onclick="downloadStudioPDF()" title="
|
|
4603
|
-
'<
|
|
4881
|
+
var dlBtn = '<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px;flex-wrap:wrap">' +
|
|
4882
|
+
'<button onclick="downloadStudioPDF()" title="Report completo come PDF" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:linear-gradient(135deg,#4f46e5,#2563eb);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(79,70,229,.35)">⤓ PDF</button>' +
|
|
4883
|
+
'<button onclick="downloadStudioXLSX()" title="Esporta tabelle come Excel professionale (SheetJS)" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(5,150,105,.35)">📊 Excel</button>' +
|
|
4884
|
+
'<button onclick="downloadStudioCSV()" title="Esporta tabelle come CSV" style="display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:linear-gradient(135deg,#0891b2,#0369a1);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(8,145,178,.35)">📋 CSV</button>' +
|
|
4604
4885
|
'</div>';
|
|
4605
4886
|
el.innerHTML = '<div class="studio-result__title">✓ ' + t('workflow_complete') + '</div>' + body + tokLine + dlBtn;
|
|
4606
|
-
// Show/hide inline
|
|
4887
|
+
// Show/hide inline export buttons in the prompt bar
|
|
4607
4888
|
var inlinePdfBtn = document.getElementById('studioInlinePdfBtn');
|
|
4608
4889
|
if (inlinePdfBtn) inlinePdfBtn.style.display = 'inline-flex';
|
|
4890
|
+
var inlineXlsxBtn = document.getElementById('studioInlineXlsxBtn');
|
|
4891
|
+
if (inlineXlsxBtn) inlineXlsxBtn.style.display = 'inline-flex';
|
|
4892
|
+
var inlineCsvBtn = document.getElementById('studioInlineCsvBtn');
|
|
4893
|
+
if (inlineCsvBtn) inlineCsvBtn.style.display = 'inline-flex';
|
|
4609
4894
|
// Update canvas button style: bright green when canvas exists, dimmed otherwise
|
|
4610
4895
|
var canvasBtn = document.getElementById('studioCanvasBtn');
|
|
4611
4896
|
if (canvasBtn) {
|
|
@@ -5962,7 +6247,9 @@ function renderStudio(el) {
|
|
|
5962
6247
|
'<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' : '') + '>📎</button>' +
|
|
5963
6248
|
'<button id="studioRunBtn" class="studio-run-btn" onclick="runStudio()" style="flex:1" ' + (studioState.running ? 'disabled' : '') + '>' + t('run') + '</button>' +
|
|
5964
6249
|
'<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') + '">■ ' + t('stop') + '</button>' +
|
|
5965
|
-
'<button id="studioInlinePdfBtn" onclick="downloadStudioPDF()" title="
|
|
6250
|
+
'<button id="studioInlinePdfBtn" onclick="downloadStudioPDF()" title="Download PDF" 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)">⤓ PDF</button>' +
|
|
6251
|
+
'<button id="studioInlineXlsxBtn" onclick="downloadStudioXLSX()" title="Export Excel" style="display:' + (studioState.result ? 'inline-flex' : 'none') + ';align-items:center;gap:5px;padding:8px 12px;background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;box-shadow:0 2px 6px rgba(5,150,105,.35)">📊 Excel</button>' +
|
|
6252
|
+
'<button id="studioInlineCsvBtn" onclick="downloadStudioCSV()" title="Export CSV" style="display:' + (studioState.result ? 'inline-flex' : 'none') + ';align-items:center;gap:5px;padding:8px 10px;background:linear-gradient(135deg,#0891b2,#0369a1);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;box-shadow:0 2px 6px rgba(8,145,178,.35)">CSV</button>' +
|
|
5966
6253
|
'<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' : '') + '>↻</button>' +
|
|
5967
6254
|
'</div>' +
|
|
5968
6255
|
'</div>' +
|
|
@@ -6252,6 +6539,13 @@ var wcChatRunning = false;
|
|
|
6252
6539
|
var wcChatAttachments = []; // [{name, mimeType, base64, size}]
|
|
6253
6540
|
var _wcAutoFixAttempts = 0;
|
|
6254
6541
|
var _wcAutoFixTimer = null;
|
|
6542
|
+
var _wcPlanPending = null; // null | { plan: string, message: string } — plan mode waiting for approval
|
|
6543
|
+
var _wcDiffQueue = []; // [{file, before, after}] diffs from last agent run
|
|
6544
|
+
var _wcGrepOpen = false; // grep panel visible
|
|
6545
|
+
var _wcGrepQuery = '';
|
|
6546
|
+
var _wcGrepResults = [];
|
|
6547
|
+
var _wcSyntaxResults = []; // [{file, ok, error}]
|
|
6548
|
+
var _wcSnapshots = []; // [{ts, fileCount}]
|
|
6255
6549
|
// Skills state
|
|
6256
6550
|
var wcSkills = []; // [{name, content, type}] type: 'skill'|'memory'|'provider'
|
|
6257
6551
|
var wcSkillModal = null; // null | {mode:'edit'|'new', idx:number|null, name, content, type, generating}
|
|
@@ -6348,11 +6642,17 @@ function renderWebCraft(el) {
|
|
|
6348
6642
|
'<div style="font-size:9px;color:var(--dim);margin-top:4px">'+t('wc_required_hint')+'</div>' +
|
|
6349
6643
|
'</div>' +
|
|
6350
6644
|
wcSkillsPanelHtml() +
|
|
6645
|
+
wcSnapshotsPanelHtml() +
|
|
6351
6646
|
(wcState.running ?
|
|
6352
6647
|
'<div style="width:100%;padding:11px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:12px;text-align:center">⏳ '+t('wc_generating')+'...</div>'
|
|
6353
6648
|
: '') +
|
|
6354
6649
|
(wcState.generatedFiles.length > 0 && !wcState.running ?
|
|
6355
|
-
'<
|
|
6650
|
+
'<div style="display:flex;gap:6px;flex-wrap:wrap">' +
|
|
6651
|
+
'<button onclick="wcDownloadZip()" style="flex:1;padding:9px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:11px;font-weight:600;cursor:pointer">⇩ ZIP</button>' +
|
|
6652
|
+
'<button onclick="wcRunSyntaxCheck()" title="Controlla errori sintassi JS" style="padding:9px 10px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--dim);font-size:11px;cursor:pointer" title="Syntax check">✅</button>' +
|
|
6653
|
+
'<button onclick="wcToggleGrep()" title="Cerca nel codice" style="padding:9px 10px;background:'+(_wcGrepOpen?'var(--greendim)':'var(--bg3)')+';border:1px solid '+(_wcGrepOpen?'var(--green3)':'var(--border2)')+';border-radius:8px;color:'+(_wcGrepOpen?'var(--green)':'var(--dim)')+';font-size:11px;cursor:pointer">🔍</button>' +
|
|
6654
|
+
'<button onclick="wcManualSnapshot()" title="Salva snapshot" style="padding:9px 10px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--dim);font-size:11px;cursor:pointer">💾</button>' +
|
|
6655
|
+
'</div>' +
|
|
6356
6656
|
'<button onclick="wcStartSandbox()" id="wcSandboxBtn" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;font-weight:600;cursor:pointer">▶ '+t('wc_sandbox_start')+'</button>'
|
|
6357
6657
|
: '') +
|
|
6358
6658
|
'</div>' +
|
|
@@ -6372,6 +6672,9 @@ function renderWebCraft(el) {
|
|
|
6372
6672
|
'<div style="flex:1;min-height:0;overflow:hidden">' +
|
|
6373
6673
|
(wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
|
|
6374
6674
|
'</div>' +
|
|
6675
|
+
(wcMainTab !== 'projects' ? wcPlanBannerHtml() : '') +
|
|
6676
|
+
(wcMainTab !== 'projects' ? wcGrepPanelHtml() : '') +
|
|
6677
|
+
(wcMainTab !== 'projects' ? wcDiffPanelHtml() : '') +
|
|
6375
6678
|
wcChatPanelHtml() +
|
|
6376
6679
|
'</div>' +
|
|
6377
6680
|
wcSkillModalHtml();
|
|
@@ -6671,6 +6974,14 @@ function wcChatPanelHtml() {
|
|
|
6671
6974
|
msg.attachments.map(function(a){ return '<span style="background:var(--bg3);border:1px solid var(--border2);border-radius:5px;padding:2px 7px;font-size:10px;color:var(--dim)">📎 '+wcEsc(a.name)+'</span>'; }).join('') +
|
|
6672
6975
|
'</div>';
|
|
6673
6976
|
}
|
|
6977
|
+
} else if (msg.role === 'system') {
|
|
6978
|
+
// System messages: compact notices (snapshot, syntax check, etc.)
|
|
6979
|
+
messagesHtml += '<div style="margin:2px 12px;padding:4px 10px;background:var(--bg3);border-left:2px solid var(--border2);border-radius:4px;font-size:10px;color:var(--dim)">' + (msg.text||'') + '</div>';
|
|
6980
|
+
if (msg.syntaxErrors && msg.syntaxErrors.length) {
|
|
6981
|
+
messagesHtml += '<div style="margin:2px 12px">' + msg.syntaxErrors.map(function(e2){
|
|
6982
|
+
return '<div style="font-size:10px;font-family:var(--mono);color:#f87171;padding:2px 0">✕ ' + wcEsc(e2.file) + ': ' + wcEsc(e2.error) + '</div>';
|
|
6983
|
+
}).join('') + '</div>';
|
|
6984
|
+
}
|
|
6674
6985
|
} else {
|
|
6675
6986
|
var toolBadges = (msg.tools || []).map(function(tool){
|
|
6676
6987
|
var icon = tool.op === 'edit' ? '✎' : (tool.op === 'write' ? '➕' : '👁');
|
|
@@ -6713,9 +7024,9 @@ function wcChatPanelHtml() {
|
|
|
6713
7024
|
'</div>'
|
|
6714
7025
|
: '<div style="padding:4px 12px 0;font-size:10px;color:var(--dim)">📄 <strong style="color:var(--green)">'+wcEsc(wcState.projectName)+'</strong> — scrivi per modificare o migliorare il progetto</div>';
|
|
6715
7026
|
|
|
6716
|
-
return '<div style="border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;flex-direction:column">' +
|
|
7027
|
+
return '<div style="border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;flex-direction:column;min-height:220px">' +
|
|
6717
7028
|
// Messages
|
|
6718
|
-
'<div id="wcChatMessages" style="max-height:
|
|
7029
|
+
'<div id="wcChatMessages" style="max-height:240px;overflow-y:auto;padding:6px 0">' +
|
|
6719
7030
|
messagesHtml +
|
|
6720
7031
|
'</div>' +
|
|
6721
7032
|
// Attachments
|
|
@@ -6728,7 +7039,7 @@ function wcChatPanelHtml() {
|
|
|
6728
7039
|
'📎' +
|
|
6729
7040
|
'<input type="file" id="wcFileInput" multiple accept="image/*,.pdf" style="display:none" onchange="wcHandleFileAttach(this)">' +
|
|
6730
7041
|
'</label>' +
|
|
6731
|
-
'<textarea id="wcChatInput" rows="
|
|
7042
|
+
'<textarea id="wcChatInput" rows="4" placeholder="'+placeholder+'" '+(inputDisabled?'disabled':'')+' style="flex:1;padding:8px 10px;font-size:12px;border-radius:8px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:vertical;min-height:80px;line-height:1.5;font-family:inherit" onkeydown="wcChatKeydown(event)"></textarea>' +
|
|
6732
7043
|
'<button onclick="wcChatSend()" '+(inputDisabled?'disabled':'')+' style="padding:8px 14px;background:var(--green3);border:none;border-radius:8px;color:var(--bg);font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0;height:38px">'+sendBtnLabel+'</button>' +
|
|
6733
7044
|
'</div>' +
|
|
6734
7045
|
'</div>';
|
|
@@ -6777,23 +7088,60 @@ async function wcChatSend() {
|
|
|
6777
7088
|
|
|
6778
7089
|
if (!msg && wcChatAttachments.length === 0) return;
|
|
6779
7090
|
if (wcChatRunning || wcState.running) return;
|
|
7091
|
+
if (inputEl) inputEl.value = '';
|
|
7092
|
+
|
|
7093
|
+
// Plan mode: if message starts with "/plan " or contains "plan:" keyword, ask agent for plan first
|
|
7094
|
+
var planMode = msg.toLowerCase().startsWith('/plan ') || msg.toLowerCase().startsWith('piano: ');
|
|
7095
|
+
if (planMode) {
|
|
7096
|
+
var planMsg = msg.replace(new RegExp('^/plan[ ]*', 'i'),'').replace(new RegExp('^piano:[ ]*', 'i'),'');
|
|
7097
|
+
wcChat.push({ role: 'user', text: msg });
|
|
7098
|
+
renderWebCraft(document.getElementById('content'));
|
|
7099
|
+
// Ask agent to produce only a plan, no edits
|
|
7100
|
+
await wcExecuteAgentCall(
|
|
7101
|
+
'[MODALITA PIANO] Descrivi in dettaglio cosa modificheresti per: "' + planMsg + '". ' +
|
|
7102
|
+
'Elenca i file che toccheresti e cosa faresti in ciascuno. NON applicare nessuna modifica ancora. ' +
|
|
7103
|
+
'Rispondi con il piano in bullet list.',
|
|
7104
|
+
false, planMsg
|
|
7105
|
+
);
|
|
7106
|
+
return;
|
|
7107
|
+
}
|
|
6780
7108
|
|
|
6781
7109
|
var attachCopy = wcChatAttachments.slice();
|
|
6782
7110
|
wcChatAttachments = [];
|
|
6783
|
-
wcChatRunning = true;
|
|
6784
7111
|
wcChat.push({ role: 'user', text: msg, attachments: attachCopy });
|
|
6785
|
-
if (inputEl) inputEl.value = '';
|
|
6786
7112
|
renderWebCraft(document.getElementById('content'));
|
|
6787
7113
|
wcScrollChatToBottom();
|
|
6788
7114
|
|
|
7115
|
+
// Auto-snapshot before first agent call in a session
|
|
7116
|
+
if (_wcAutoFixAttempts === 0 && wcChat.filter(function(c){ return c.role==='user'; }).length === 1) {
|
|
7117
|
+
wcTakeSnapshot().then(function(ts) {
|
|
7118
|
+
if (ts) wcChat.push({ role: 'system', text: '💾 Snapshot automatico salvato (' + ts.slice(0,16).replace('T',' ') + ')' });
|
|
7119
|
+
renderWebCraft(document.getElementById('content'));
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
|
|
7123
|
+
await wcExecuteAgentCall(msg, false, null, attachCopy);
|
|
7124
|
+
}
|
|
7125
|
+
|
|
7126
|
+
// Core agent call — separated so plan mode and normal mode share the same SSE pipeline
|
|
7127
|
+
async function wcExecuteAgentCall(message, isPlanExec, planOrigMsg, attachments) {
|
|
7128
|
+
if (wcChatRunning) return;
|
|
7129
|
+
wcChatRunning = true;
|
|
7130
|
+
renderWebCraft(document.getElementById('content'));
|
|
7131
|
+
|
|
7132
|
+
// Track file state BEFORE edits for diff
|
|
7133
|
+
var filesBefore = {};
|
|
7134
|
+
wcState.generatedFiles.forEach(function(f) { filesBefore[f.name] = f.content; });
|
|
7135
|
+
_wcDiffQueue = [];
|
|
7136
|
+
|
|
6789
7137
|
try {
|
|
6790
7138
|
var r = await fetch(API + '/api/studio/webcraft/agent', {
|
|
6791
7139
|
method: 'POST',
|
|
6792
7140
|
headers: { 'Content-Type': 'application/json' },
|
|
6793
7141
|
body: JSON.stringify({
|
|
6794
7142
|
projectName: wcState.projectName,
|
|
6795
|
-
message:
|
|
6796
|
-
attachments:
|
|
7143
|
+
message: message,
|
|
7144
|
+
attachments: (attachments || []).map(function(a){ return { name: a.name, mimeType: a.mimeType, base64: a.base64 }; })
|
|
6797
7145
|
})
|
|
6798
7146
|
});
|
|
6799
7147
|
|
|
@@ -6811,6 +7159,7 @@ async function wcChatSend() {
|
|
|
6811
7159
|
var reader2 = r.body.getReader();
|
|
6812
7160
|
var dec = new TextDecoder();
|
|
6813
7161
|
var buf = '';
|
|
7162
|
+
var anyEdits = false;
|
|
6814
7163
|
while (true) {
|
|
6815
7164
|
var res = await reader2.read();
|
|
6816
7165
|
if (res.done) break;
|
|
@@ -6829,16 +7178,32 @@ async function wcChatSend() {
|
|
|
6829
7178
|
wcScrollChatToBottom();
|
|
6830
7179
|
} else if (ev.type === 'tool') {
|
|
6831
7180
|
agentMsg.tools.push({ op: ev.op, path: ev.path, result: ev.result });
|
|
6832
|
-
// Update file in generatedFiles if edited/written
|
|
6833
7181
|
if ((ev.op === 'edit' || ev.op === 'write') && ev.result === 'ok') {
|
|
6834
|
-
|
|
7182
|
+
anyEdits = true;
|
|
6835
7183
|
wcChat[wcChat.length-1] = agentMsg;
|
|
6836
7184
|
renderWebCraft(document.getElementById('content'));
|
|
6837
7185
|
}
|
|
6838
7186
|
} else if (ev.type === 'done') {
|
|
7187
|
+
// Plan mode: detect if response is a plan (no tool edits), show approval banner
|
|
7188
|
+
if (planOrigMsg && !anyEdits) {
|
|
7189
|
+
_wcPlanPending = { plan: agentMsg.text, originalMessage: planOrigMsg };
|
|
7190
|
+
}
|
|
6839
7191
|
wcChatRunning = false;
|
|
6840
|
-
if (ev.changed) {
|
|
6841
|
-
|
|
7192
|
+
if (ev.changed) {
|
|
7193
|
+
// Build diffs before reloading
|
|
7194
|
+
var changedFiles = (agentMsg.tools || []).filter(function(t2){ return t2.op === 'edit' || t2.op === 'write'; }).map(function(t2){ return t2.path; });
|
|
7195
|
+
await wcReloadProjectFiles();
|
|
7196
|
+
// Build diffs from before/after
|
|
7197
|
+
changedFiles.forEach(function(fname) {
|
|
7198
|
+
var after = wcState.generatedFiles.find(function(f){ return f.name === fname; });
|
|
7199
|
+
if (after) _wcDiffQueue.push({ file: fname, before: filesBefore[fname] || '', after: after.content });
|
|
7200
|
+
});
|
|
7201
|
+
// Auto syntax-check after edits
|
|
7202
|
+
if (changedFiles.some(function(f){ return f.endsWith('.js') || f.endsWith('.mjs'); })) {
|
|
7203
|
+
setTimeout(wcRunSyntaxCheck, 500);
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
// Persist chat
|
|
6842
7207
|
fetch(API + '/api/studio/webcraft/projects/chat/save', {
|
|
6843
7208
|
method: 'POST',
|
|
6844
7209
|
headers: {'Content-Type':'application/json'},
|
|
@@ -7033,6 +7398,218 @@ function wcAddField() {
|
|
|
7033
7398
|
}
|
|
7034
7399
|
function wcSetFile(i) { wcState.activeFile = i; renderWebCraft(document.getElementById('content')); }
|
|
7035
7400
|
|
|
7401
|
+
// ── WebCraft: Diff Viewer ─────────────────────────────────────────────────────
|
|
7402
|
+
function wcDiffLines(before, after) {
|
|
7403
|
+
var NL = String.fromCharCode(10);
|
|
7404
|
+
var bLines = (before || '').split(NL);
|
|
7405
|
+
var aLines = (after || '').split(NL);
|
|
7406
|
+
var html = '';
|
|
7407
|
+
var maxLen = Math.max(bLines.length, aLines.length);
|
|
7408
|
+
var bi = 0, ai = 0;
|
|
7409
|
+
while (bi < bLines.length || ai < aLines.length) {
|
|
7410
|
+
var bL = bLines[bi], aL = aLines[ai];
|
|
7411
|
+
if (bL === aL) {
|
|
7412
|
+
html += '<div style="font-family:var(--mono);font-size:10px;padding:1px 8px;color:var(--dim);white-space:pre-wrap"> ' + wcEsc(bL||'') + '</div>';
|
|
7413
|
+
bi++; ai++;
|
|
7414
|
+
} else if (bi >= bLines.length) {
|
|
7415
|
+
html += '<div style="font-family:var(--mono);font-size:10px;padding:1px 8px;background:#0a3a1a;color:#4ade80;white-space:pre-wrap">+' + wcEsc(aL||'') + '</div>';
|
|
7416
|
+
ai++;
|
|
7417
|
+
} else if (ai >= aLines.length) {
|
|
7418
|
+
html += '<div style="font-family:var(--mono);font-size:10px;padding:1px 8px;background:#3a0a0a;color:#f87171;white-space:pre-wrap">-' + wcEsc(bL||'') + '</div>';
|
|
7419
|
+
bi++;
|
|
7420
|
+
} else {
|
|
7421
|
+
html += '<div style="font-family:var(--mono);font-size:10px;padding:1px 8px;background:#3a0a0a;color:#f87171;white-space:pre-wrap">-' + wcEsc(bL||'') + '</div>';
|
|
7422
|
+
html += '<div style="font-family:var(--mono);font-size:10px;padding:1px 8px;background:#0a3a1a;color:#4ade80;white-space:pre-wrap">+' + wcEsc(aL||'') + '</div>';
|
|
7423
|
+
bi++; ai++;
|
|
7424
|
+
}
|
|
7425
|
+
if (bi > maxLen + 50 && ai > maxLen + 50) break; // safety
|
|
7426
|
+
}
|
|
7427
|
+
return html;
|
|
7428
|
+
}
|
|
7429
|
+
|
|
7430
|
+
function wcDiffPanelHtml() {
|
|
7431
|
+
if (_wcDiffQueue.length === 0) return '';
|
|
7432
|
+
var items = _wcDiffQueue.map(function(d, di) {
|
|
7433
|
+
var addedLines = (d.after||'').split(String.fromCharCode(10)).length - (d.before||'').split(String.fromCharCode(10)).length;
|
|
7434
|
+
var sign = addedLines >= 0 ? '+' : '';
|
|
7435
|
+
return '<details style="border:1px solid var(--border);border-radius:6px;margin-bottom:6px;background:var(--bg3)">' +
|
|
7436
|
+
'<summary style="padding:7px 10px;cursor:pointer;font-size:11px;font-family:var(--mono);color:var(--text);list-style:none;display:flex;align-items:center;gap:8px">' +
|
|
7437
|
+
'<span style="color:var(--green);font-size:10px">▲</span>' +
|
|
7438
|
+
'<span style="flex:1">' + wcEsc(d.file) + '</span>' +
|
|
7439
|
+
'<span style="color:' + (addedLines >= 0 ? '#4ade80' : '#f87171') + ';font-size:10px">' + sign + addedLines + ' linee</span>' +
|
|
7440
|
+
'</summary>' +
|
|
7441
|
+
'<div style="max-height:200px;overflow-y:auto;border-top:1px solid var(--border)">' + wcDiffLines(d.before, d.after) + '</div>' +
|
|
7442
|
+
'</details>';
|
|
7443
|
+
}).join('');
|
|
7444
|
+
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-top:8px">' +
|
|
7445
|
+
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
|
|
7446
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px">🔌 Diff — ' + _wcDiffQueue.length + ' file modificati</div>' +
|
|
7447
|
+
'<button onclick="wcClearDiff()" style="font-size:10px;background:none;border:none;color:var(--dim);cursor:pointer">✕ Chiudi</button>' +
|
|
7448
|
+
'</div>' +
|
|
7449
|
+
items +
|
|
7450
|
+
'</div>';
|
|
7451
|
+
}
|
|
7452
|
+
|
|
7453
|
+
// ── WebCraft: Snapshot / Rollback ─────────────────────────────────────────────
|
|
7454
|
+
async function wcManualSnapshot() {
|
|
7455
|
+
var ts = await wcTakeSnapshot();
|
|
7456
|
+
if (ts) {
|
|
7457
|
+
wcChat.push({ role: 'system', text: '💾 Snapshot salvato (' + ts.slice(0,16).replace('T',' ') + ')' });
|
|
7458
|
+
await wcLoadSnapshots();
|
|
7459
|
+
renderWebCraft(document.getElementById('content'));
|
|
7460
|
+
}
|
|
7461
|
+
}
|
|
7462
|
+
|
|
7463
|
+
async function wcTakeSnapshot() {
|
|
7464
|
+
if (!wcState.projectName) return null;
|
|
7465
|
+
try {
|
|
7466
|
+
var r = await fetch(API + '/api/studio/webcraft/snapshot', {
|
|
7467
|
+
method: 'POST',
|
|
7468
|
+
headers: {'Content-Type':'application/json'},
|
|
7469
|
+
body: JSON.stringify({ projectName: wcState.projectName })
|
|
7470
|
+
});
|
|
7471
|
+
if (r.ok) { var d = await r.json(); return d.snapshot; }
|
|
7472
|
+
} catch(_) {}
|
|
7473
|
+
return null;
|
|
7474
|
+
}
|
|
7475
|
+
|
|
7476
|
+
async function wcLoadSnapshots() {
|
|
7477
|
+
if (!wcState.projectName) return;
|
|
7478
|
+
try {
|
|
7479
|
+
var r = await fetch(API + '/api/studio/webcraft/snapshots/' + encodeURIComponent(wcState.projectName));
|
|
7480
|
+
if (r.ok) { var d = await r.json(); _wcSnapshots = d.snapshots || []; renderWebCraft(document.getElementById('content')); }
|
|
7481
|
+
} catch(_) {}
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7484
|
+
async function wcRestoreSnapshot(ts) {
|
|
7485
|
+
if (!confirm('Ripristinare lo snapshot del ' + ts.replace('T',' ').replace(/-/g,':').slice(0,16) + '? I file attuali verranno sovrascritti.')) return;
|
|
7486
|
+
try {
|
|
7487
|
+
var r = await fetch(API + '/api/studio/webcraft/restore', {
|
|
7488
|
+
method: 'POST',
|
|
7489
|
+
headers: {'Content-Type':'application/json'},
|
|
7490
|
+
body: JSON.stringify({ projectName: wcState.projectName, ts: ts })
|
|
7491
|
+
});
|
|
7492
|
+
if (r.ok) {
|
|
7493
|
+
wcChat.push({ role: 'agent', text: 'Snapshot ripristinato (' + ts + '). Ricarico i file...' });
|
|
7494
|
+
await wcReloadProjectFiles();
|
|
7495
|
+
}
|
|
7496
|
+
} catch(_) {}
|
|
7497
|
+
}
|
|
7498
|
+
|
|
7499
|
+
function wcSnapshotsPanelHtml() {
|
|
7500
|
+
if (_wcSnapshots.length === 0) return '';
|
|
7501
|
+
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px">' +
|
|
7502
|
+
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:8px">💾 Snapshot</div>' +
|
|
7503
|
+
_wcSnapshots.slice(0,5).map(function(s) {
|
|
7504
|
+
var label = s.ts.replace('T',' ').replace(/-/g,':').slice(0,16);
|
|
7505
|
+
return '<div style="display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid var(--border);font-size:10px">' +
|
|
7506
|
+
'<span style="flex:1;color:var(--dim);font-family:var(--mono)">' + label + '</span>' +
|
|
7507
|
+
'<span style="color:var(--dim)">' + s.fileCount + 'f</span>' +
|
|
7508
|
+
'<button onclick="wcRestoreSnapshot(' + JSON.stringify(s.ts) + ')" style="padding:2px 8px;background:var(--bg3);border:1px solid var(--border2);border-radius:4px;color:var(--dim);font-size:10px;cursor:pointer">↻</button>' +
|
|
7509
|
+
'</div>';
|
|
7510
|
+
}).join('') +
|
|
7511
|
+
'</div>';
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
// ── WebCraft: Syntax Check ────────────────────────────────────────────────────
|
|
7515
|
+
async function wcRunSyntaxCheck() {
|
|
7516
|
+
if (!wcState.projectName) return;
|
|
7517
|
+
try {
|
|
7518
|
+
var r = await fetch(API + '/api/studio/webcraft/syntax-check', {
|
|
7519
|
+
method: 'POST',
|
|
7520
|
+
headers: {'Content-Type':'application/json'},
|
|
7521
|
+
body: JSON.stringify({ projectName: wcState.projectName })
|
|
7522
|
+
});
|
|
7523
|
+
if (r.ok) {
|
|
7524
|
+
var d = await r.json();
|
|
7525
|
+
_wcSyntaxResults = d.results || [];
|
|
7526
|
+
var errors = _wcSyntaxResults.filter(function(x){ return !x.ok; });
|
|
7527
|
+
if (errors.length > 0) {
|
|
7528
|
+
wcChat.push({ role: 'system', text: '⚠ Syntax check: ' + errors.length + ' errore/i trovato/i. Clicca "Fix" per correggere automaticamente.', syntaxErrors: errors });
|
|
7529
|
+
} else {
|
|
7530
|
+
wcChat.push({ role: 'system', text: '✓ Syntax check: tutti i file JS sono validi.' });
|
|
7531
|
+
}
|
|
7532
|
+
renderWebCraft(document.getElementById('content'));
|
|
7533
|
+
wcScrollChatToBottom();
|
|
7534
|
+
}
|
|
7535
|
+
} catch(_) {}
|
|
7536
|
+
}
|
|
7537
|
+
|
|
7538
|
+
// ── WebCraft: Grep / Search ───────────────────────────────────────────────────
|
|
7539
|
+
async function wcRunGrep() {
|
|
7540
|
+
var el = document.getElementById('wcGrepInput');
|
|
7541
|
+
var q = el ? el.value.trim() : _wcGrepQuery;
|
|
7542
|
+
if (!q || !wcState.projectName) return;
|
|
7543
|
+
_wcGrepQuery = q;
|
|
7544
|
+
try {
|
|
7545
|
+
var r = await fetch(API + '/api/studio/webcraft/grep', {
|
|
7546
|
+
method: 'POST',
|
|
7547
|
+
headers: {'Content-Type':'application/json'},
|
|
7548
|
+
body: JSON.stringify({ projectName: wcState.projectName, query: q })
|
|
7549
|
+
});
|
|
7550
|
+
if (r.ok) { var d = await r.json(); _wcGrepResults = d.matches || []; renderWebCraft(document.getElementById('content')); }
|
|
7551
|
+
} catch(_) {}
|
|
7552
|
+
}
|
|
7553
|
+
|
|
7554
|
+
function wcGrepPanelHtml() {
|
|
7555
|
+
if (!_wcGrepOpen) return '';
|
|
7556
|
+
var resultsHtml = _wcGrepResults.length > 0
|
|
7557
|
+
? _wcGrepResults.map(function(m) {
|
|
7558
|
+
return '<div style="padding:4px 8px;border-bottom:1px solid var(--border);cursor:pointer" onclick="wcJumpToFile(' + JSON.stringify(m.file) + ')">' +
|
|
7559
|
+
'<span style="font-size:10px;color:var(--green);font-family:var(--mono)">' + wcEsc(m.file) + ':' + m.lineNum + '</span>' +
|
|
7560
|
+
'<pre style="margin:2px 0 0;font-size:10px;color:var(--text);white-space:pre-wrap;overflow:hidden;max-height:30px">' + wcEsc(m.line) + '</pre>' +
|
|
7561
|
+
'</div>';
|
|
7562
|
+
}).join('')
|
|
7563
|
+
: '<div style="font-size:11px;color:var(--dim);padding:8px">Nessun risultato.</div>';
|
|
7564
|
+
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-top:8px">' +
|
|
7565
|
+
'<div style="display:flex;gap:6px;margin-bottom:8px">' +
|
|
7566
|
+
'<input id="wcGrepInput" value="'+wcEsc(_wcGrepQuery)+'" placeholder="Cerca nel codice..." onkeydown="wcGrepKeydown(event)" style="flex:1;padding:6px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);font-family:var(--mono)">' +
|
|
7567
|
+
'<button onclick="wcRunGrep()" style="padding:6px 12px;background:var(--green3);border:none;border-radius:6px;color:var(--bg);font-size:11px;font-weight:700;cursor:pointer">🔍</button>' +
|
|
7568
|
+
'<button onclick="wcCloseGrep()" style="padding:6px 8px;background:none;border:1px solid var(--border);border-radius:6px;color:var(--dim);cursor:pointer">×</button>' +
|
|
7569
|
+
'</div>' +
|
|
7570
|
+
(_wcGrepResults.length > 0 ? '<div style="font-size:9px;color:var(--dim);margin-bottom:4px">' + _wcGrepResults.length + ' risultati</div>' : '') +
|
|
7571
|
+
'<div style="max-height:200px;overflow-y:auto">' + resultsHtml + '</div>' +
|
|
7572
|
+
'</div>';
|
|
7573
|
+
}
|
|
7574
|
+
|
|
7575
|
+
function wcClearDiff() { _wcDiffQueue = []; renderWebCraft(document.getElementById('content')); }
|
|
7576
|
+
function wcCloseGrep() { _wcGrepOpen = false; renderWebCraft(document.getElementById('content')); }
|
|
7577
|
+
function wcGrepKeydown(e) { if (e.key === 'Enter') wcRunGrep(); }
|
|
7578
|
+
|
|
7579
|
+
function wcJumpToFile(fname) {
|
|
7580
|
+
var idx = wcState.generatedFiles.findIndex(function(f){ return f.name === fname; });
|
|
7581
|
+
if (idx >= 0) { wcState.activeFile = idx; wcRightTab = 'files'; renderWebCraft(document.getElementById('content')); }
|
|
7582
|
+
}
|
|
7583
|
+
|
|
7584
|
+
function wcToggleGrep() { _wcGrepOpen = !_wcGrepOpen; renderWebCraft(document.getElementById('content')); }
|
|
7585
|
+
|
|
7586
|
+
// ── WebCraft: Plan Mode ───────────────────────────────────────────────────────
|
|
7587
|
+
function wcPlanBannerHtml() {
|
|
7588
|
+
if (!_wcPlanPending) return '';
|
|
7589
|
+
return '<div style="background:#1a2a1a;border:1px solid var(--green3);border-radius:10px;padding:14px;margin-bottom:8px">' +
|
|
7590
|
+
'<div style="font-size:11px;font-weight:700;color:var(--green);margin-bottom:8px">📌 Piano proposto — approva per eseguire</div>' +
|
|
7591
|
+
'<pre style="font-size:11px;color:var(--text);white-space:pre-wrap;margin:0 0 10px;max-height:120px;overflow-y:auto;font-family:var(--mono)">' + wcEsc(_wcPlanPending.plan) + '</pre>' +
|
|
7592
|
+
'<div style="display:flex;gap:8px">' +
|
|
7593
|
+
'<button onclick="wcApprovePlan()" style="padding:7px 18px;background:var(--green3);border:none;border-radius:7px;color:var(--bg);font-size:12px;font-weight:700;cursor:pointer">✓ Esegui</button>' +
|
|
7594
|
+
'<button onclick="wcRejectPlan()" style="padding:7px 14px;background:none;border:1px solid var(--border2);border-radius:7px;color:var(--dim);font-size:12px;cursor:pointer">✕ Annulla</button>' +
|
|
7595
|
+
'</div>' +
|
|
7596
|
+
'</div>';
|
|
7597
|
+
}
|
|
7598
|
+
|
|
7599
|
+
async function wcApprovePlan() {
|
|
7600
|
+
if (!_wcPlanPending) return;
|
|
7601
|
+
var msg = _wcPlanPending.originalMessage;
|
|
7602
|
+
_wcPlanPending = null;
|
|
7603
|
+
await wcExecuteAgentCall(msg + String.fromCharCode(10) + '[Piano approvato — procedi con le modifiche]', false);
|
|
7604
|
+
}
|
|
7605
|
+
|
|
7606
|
+
function wcRejectPlan() {
|
|
7607
|
+
wcChat.push({ role: 'agent', text: 'Piano annullato. Dimmi se vuoi modificare la richiesta.' });
|
|
7608
|
+
_wcPlanPending = null;
|
|
7609
|
+
renderWebCraft(document.getElementById('content'));
|
|
7610
|
+
wcScrollChatToBottom();
|
|
7611
|
+
}
|
|
7612
|
+
|
|
7036
7613
|
async function wcGenerate() {
|
|
7037
7614
|
if (wcState.running) return;
|
|
7038
7615
|
var desc = wcState.description;
|
|
@@ -7332,6 +7909,20 @@ async function wcStartSandbox() {
|
|
|
7332
7909
|
wcState.sandbox.running = false;
|
|
7333
7910
|
wcState.sandbox.error = evt.msg;
|
|
7334
7911
|
renderWebCraft(document.getElementById('content'));
|
|
7912
|
+
// Auto-fix: try to detect MODULE_NOT_FOUND in crash message and fix it
|
|
7913
|
+
var errMsg = evt.msg || '';
|
|
7914
|
+
var modMatch = errMsg.match(new RegExp("Cannot find module '([^']+)'")) ||
|
|
7915
|
+
errMsg.match(new RegExp('Cannot find module "([^"]+)"'));
|
|
7916
|
+
if (modMatch && _wcAutoFixAttempts < 3) {
|
|
7917
|
+
_wcAutoFixAttempts++;
|
|
7918
|
+
wcTriggerAutoFix(modMatch[1]);
|
|
7919
|
+
} else if (errMsg && !modMatch && _wcAutoFixAttempts < 3) {
|
|
7920
|
+
// Generic crash: post error to chat so agent can see it
|
|
7921
|
+
_wcAutoFixAttempts++;
|
|
7922
|
+
wcChat.push({ role: 'system', text: '❌ Errore sandbox: ' + errMsg });
|
|
7923
|
+
renderWebCraft(document.getElementById('content'));
|
|
7924
|
+
wcScrollChatToBottom();
|
|
7925
|
+
}
|
|
7335
7926
|
}
|
|
7336
7927
|
} catch(_) {}
|
|
7337
7928
|
}
|