nothumanallowed 16.0.34 → 16.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.34",
3
+ "version": "16.0.36",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '16.0.34';
8
+ export const VERSION = '16.0.36';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1268,9 +1268,30 @@ RULES:
1268
1268
  );
1269
1269
  } else {
1270
1270
  // Fallback for other providers — use old text-based <tool> system
1271
- // (kept for backward compatibility with Gemini, DeepSeek, etc.)
1271
+ // (kept for backward compatibility with Gemini, DeepSeek, Liara, etc.)
1272
1272
  let conversationHistory = [{ role: 'user', content: userContent }];
1273
1273
  emit({ type: 'text', token: 'Note: Using text-based tools (native tool calling not available for this provider).\n' });
1274
+ // Inject text-based tool-format instructions into the user message so the
1275
+ // LLM emits calls in a format the parser recognizes. The parser now ALSO
1276
+ // accepts OpenAI/Anthropic-style nude JSON, but the wrapper form is more
1277
+ // robust against truncation.
1278
+ const textBasedFormat = `
1279
+ TOOL CALL FORMAT (CRITICAL):
1280
+ You MUST emit tool calls using THIS exact format (one per line, on its own line):
1281
+
1282
+ <tool>{"op": "read", "path": "public/index.html"}</tool>
1283
+ <tool>{"op": "edit", "path": "public/index.html", "old": "exact old text", "new": "new text"}</tool>
1284
+ <tool>{"op": "write", "path": "public/css/main.css", "content": "body { ... }"}</tool>
1285
+ <tool>{"op": "check", "path": "server.js"}</tool>
1286
+
1287
+ Valid ops: read, edit, write, delete, list, search, run, check, sandbox
1288
+
1289
+ DO NOT write things like {"tool": "read_file", "args": {...}} — that format is for
1290
+ OpenAI-native providers, not for you. Use <tool>{"op": "read", "path": "..."}</tool>.
1291
+
1292
+ After ALL fixes are done, emit <done/> on its own line.
1293
+ `;
1294
+ conversationHistory[0].content = (typeof conversationHistory[0].content === 'string' ? conversationHistory[0].content : userContent) + '\n\n' + textBasedFormat;
1274
1295
  // Minimal old-style loop with <tool> tags — simplified
1275
1296
  for (let step = 0; step < MAX_STEPS; step++) {
1276
1297
  if (isAborted()) break;
@@ -1294,31 +1315,25 @@ RULES:
1294
1315
  // Check if agent signaled completion
1295
1316
  const isDone = stepResponse.includes('<done/>') || stepResponse.includes('<done />');
1296
1317
 
1297
- // Extract and execute ALL tool calls from this step
1298
- // First try matched pairs, then handle truncated (unclosed) tool tags
1299
- const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
1300
- let match;
1318
+ // Extract and execute ALL tool calls from this step.
1319
+ // Accept THREE wire formats (LLM providers vary wildly):
1320
+ // 1. NHA native: <tool>{"op":"read","path":"..."}</tool>
1321
+ // 2. OpenAI-style: {"tool":"read_file","args":{"file_path":"..."}}
1322
+ // 3. Anthropic-style: <tool>{"name":"read_file","input":{"path":"..."}}</tool>
1323
+ // Normalize all three into the internal {op, path, ...} shape before dispatch.
1324
+ const toolCalls = _extractAllToolCalls(stepResponse);
1301
1325
  const toolResults = [];
1302
- const matchedRanges = [];
1326
+ const matchedRanges = toolCalls.matchedRanges;
1303
1327
 
1304
- while ((match = toolRegex.exec(stepResponse)) !== null) {
1305
- matchedRanges.push([match.index, match.index + match[0].length]);
1328
+ for (const rawCall of toolCalls.calls) {
1306
1329
  let toolCall;
1307
1330
  try {
1308
- let raw = match[1].trim();
1309
- raw = raw.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
1310
- toolCall = JSON.parse(raw);
1311
- } catch {
1312
- // LLM often puts raw HTML/code inside JSON values without proper escaping.
1313
- // Extract fields manually using a robust regex-based parser.
1314
- try {
1315
- toolCall = _parseToolCallRobust(match[1].trim());
1316
- } catch (parseErr2) {
1317
- console.error('[TOOL-PARSE] JSON parse failed even after robust parse:', parseErr2.message, 'raw:', match[1].slice(0, 200));
1318
- toolResults.push({ op: 'error', result: 'JSON parse failed' });
1319
- emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
1320
- continue;
1321
- }
1331
+ toolCall = _normalizeToolCall(rawCall);
1332
+ } catch (parseErr) {
1333
+ console.error('[TOOL-PARSE] failed:', parseErr.message, 'raw:', JSON.stringify(rawCall).slice(0, 200));
1334
+ toolResults.push({ op: 'error', result: 'parse_failed' });
1335
+ emit({ type: 'tool', op: 'parse_error', path: '', result: 'parse_failed' });
1336
+ continue;
1322
1337
  }
1323
1338
 
1324
1339
  const { op, path: relPath, old: oldStr, new: newStr, content, newPath, query, glob: globPat, cmd } = toolCall;
@@ -3368,6 +3383,20 @@ ${original.slice(0, 12_000)}`;
3368
3383
  } catch (e) { console.error('[scan] CRASH:', e); sendError(res, 500, e.message); }
3369
3384
  });
3370
3385
 
3386
+ // ── Deterministic auto-repair (zero LLM, pure code) ────────────────────────
3387
+ // Handles the common bug classes that the LLM gets wrong:
3388
+ // 1. Mismatched/unclosed HTML tags → balance via tag stack
3389
+ // 2. Referenced files not found → create empty placeholders
3390
+ // No tool calling, no streaming, no LLM. Synchronous fixes.
3391
+ router.post('/api/studio/webcraft/auto-repair', async (req, res) => {
3392
+ try {
3393
+ const { projectName } = await parseBody(req);
3394
+ if (!projectName) return sendError(res, 400, 'projectName required');
3395
+ const result = autoRepairProject(projectName);
3396
+ sendJSON(res, 200, result);
3397
+ } catch (e) { sendError(res, 500, e.message); }
3398
+ });
3399
+
3371
3400
  // ── Syntax check ──────────────────────────────────────────────────────────
3372
3401
  router.post('/api/studio/webcraft/syntax-check', async (req, res) => {
3373
3402
  try {
@@ -3396,6 +3425,380 @@ function _safeName(name) {
3396
3425
  * This extracts "op", "path", "old", "new", "content", "query", "cmd" etc. by finding
3397
3426
  * the key-value boundaries manually.
3398
3427
  */
3428
+ // ─────────────────────────────────────────────────────────────────────────────
3429
+ // DETERMINISTIC AUTO-REPAIR
3430
+ // Fixes common bug classes (mismatched HTML tags, missing CSS/JS files) using
3431
+ // pure code — NO LLM call. The LLM-based "Fix" button is fragile when the
3432
+ // provider doesn't support native tool calling; this deterministic pass runs
3433
+ // first and resolves 80% of common project errors instantly.
3434
+ // ─────────────────────────────────────────────────────────────────────────────
3435
+
3436
+ const _HTML_VOID = new Set([
3437
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',
3438
+ 'param', 'source', 'track', 'wbr',
3439
+ ]);
3440
+
3441
+ /**
3442
+ * Balance HTML tags in `html` by tracking open/close via a pushdown stack.
3443
+ * Returns { fixed, balanced, edits } where:
3444
+ * - fixed: the corrected HTML string
3445
+ * - balanced: true if tags were already balanced or fixable, false if too broken
3446
+ * - edits: list of human-readable changes (for logging)
3447
+ */
3448
+ export function _balanceHtmlTags(html) {
3449
+ const edits = [];
3450
+ // Strip comments and scripts/styles to avoid false matches in their content
3451
+ const placeholders = [];
3452
+ let work = html
3453
+ .replace(/<!--[\s\S]*?-->/g, (m) => { placeholders.push(m); return `__NHA_PH_${placeholders.length - 1}__`; })
3454
+ .replace(/<(script|style)([^>]*)>([\s\S]*?)<\/\1>/gi, (m) => { placeholders.push(m); return `__NHA_PH_${placeholders.length - 1}__`; });
3455
+
3456
+ const tagRe = /<\/?([a-zA-Z][a-zA-Z0-9-]*)\b[^>]*?>/g;
3457
+ const stack = [];
3458
+ let outStr = '';
3459
+ let lastIdx = 0;
3460
+ let match;
3461
+
3462
+ while ((match = tagRe.exec(work)) !== null) {
3463
+ const tag = match[0];
3464
+ const name = match[1].toLowerCase();
3465
+ const isClose = tag.startsWith('</');
3466
+ const isSelfClose = tag.endsWith('/>') || _HTML_VOID.has(name);
3467
+
3468
+ outStr += work.slice(lastIdx, match.index);
3469
+
3470
+ if (isClose) {
3471
+ // Find matching open in stack
3472
+ let depth = stack.length - 1;
3473
+ let foundAt = -1;
3474
+ while (depth >= 0) {
3475
+ if (stack[depth].name === name) { foundAt = depth; break; }
3476
+ depth--;
3477
+ }
3478
+ if (foundAt === -1) {
3479
+ // Stray close — drop it
3480
+ edits.push(`Removed stray </${name}>`);
3481
+ // Skip writing this tag
3482
+ lastIdx = match.index + tag.length;
3483
+ continue;
3484
+ }
3485
+ // Auto-close anything above the match
3486
+ while (stack.length - 1 > foundAt) {
3487
+ const top = stack.pop();
3488
+ outStr += `</${top.name}>`;
3489
+ edits.push(`Auto-closed <${top.name}> before </${name}>`);
3490
+ }
3491
+ stack.pop();
3492
+ outStr += tag;
3493
+ } else if (isSelfClose) {
3494
+ outStr += tag;
3495
+ } else {
3496
+ stack.push({ name, idx: match.index });
3497
+ outStr += tag;
3498
+ }
3499
+ lastIdx = match.index + tag.length;
3500
+ }
3501
+ outStr += work.slice(lastIdx);
3502
+
3503
+ // Close any remaining open tags at the very end
3504
+ while (stack.length > 0) {
3505
+ const top = stack.pop();
3506
+ // Insert closing tags before </body> or </html> if present, else at end
3507
+ const bodyClose = outStr.lastIndexOf('</body>');
3508
+ const htmlClose = outStr.lastIndexOf('</html>');
3509
+ let insertAt = outStr.length;
3510
+ if (bodyClose !== -1 && top.name !== 'body' && top.name !== 'html') insertAt = bodyClose;
3511
+ else if (htmlClose !== -1 && top.name !== 'html') insertAt = htmlClose;
3512
+ outStr = outStr.slice(0, insertAt) + `</${top.name}>` + outStr.slice(insertAt);
3513
+ edits.push(`Auto-closed <${top.name}> at end of document`);
3514
+ }
3515
+
3516
+ // Restore placeholders
3517
+ const fixed = outStr.replace(/__NHA_PH_(\d+)__/g, (_, i) => placeholders[+i] || '');
3518
+ return { fixed, balanced: true, edits };
3519
+ }
3520
+
3521
+ /**
3522
+ * Extract <link href="..."> and <script src="..."> + <img src="..."> targets
3523
+ * from HTML. Returns relative paths that point inside the project.
3524
+ */
3525
+ function _extractHtmlAssetRefs(html) {
3526
+ const refs = [];
3527
+ const linkRe = /<link\b[^>]*\bhref\s*=\s*["']([^"']+)["'][^>]*>/gi;
3528
+ const scriptRe = /<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
3529
+ let m;
3530
+ while ((m = linkRe.exec(html)) !== null) refs.push({ kind: 'css', href: m[1] });
3531
+ while ((m = scriptRe.exec(html)) !== null) refs.push({ kind: 'js', href: m[1] });
3532
+ return refs;
3533
+ }
3534
+
3535
+ function _normalizeAssetPath(href, htmlRelPath) {
3536
+ // Strip query/hash
3537
+ let p = href.replace(/[?#].*$/, '');
3538
+ // Skip external (http://, https://, //, data:)
3539
+ if (/^(?:https?:)?\/\//.test(p) || p.startsWith('data:') || p.startsWith('//')) return null;
3540
+ // Absolute paths starting with / are project-root-relative
3541
+ if (p.startsWith('/')) return p.slice(1);
3542
+ // Relative paths — resolve against the HTML file's directory
3543
+ const htmlDir = path.dirname(htmlRelPath);
3544
+ return path.posix.normalize(path.posix.join(htmlDir, p));
3545
+ }
3546
+
3547
+ function _placeholderContent(kind, refPath) {
3548
+ if (kind === 'css') {
3549
+ return `/* nha-webcraft: auto-created placeholder for ${refPath}\n The HTML referenced this file but it didn't exist. This is a\n minimal stub to keep the sandbox bootable. Add real styles via chat. */\n\n/* base reset */\n* { box-sizing: border-box; }\nbody { margin: 0; font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif; }\n`;
3550
+ }
3551
+ if (kind === 'js') {
3552
+ return `// nha-webcraft: auto-created placeholder for ${refPath}\n// The HTML referenced this file but it didn't exist. This is a\n// minimal stub to keep the sandbox bootable. Add real logic via chat.\nconsole.log('[${refPath}] placeholder loaded');\n`;
3553
+ }
3554
+ return '';
3555
+ }
3556
+
3557
+ export function autoRepairProject(projectName) {
3558
+ const dir = ProjectStore.dir(projectName);
3559
+ if (!fs.existsSync(dir)) throw new Error('project not found');
3560
+
3561
+ const repairs = [];
3562
+ const filesRepaired = new Set();
3563
+ const filesCreated = [];
3564
+
3565
+ // Walk every HTML file in the project
3566
+ const stack = [dir];
3567
+ const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next']);
3568
+ while (stack.length) {
3569
+ const cur = stack.pop();
3570
+ let entries;
3571
+ try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; }
3572
+ for (const ent of entries) {
3573
+ if (skipDirs.has(ent.name) || ent.name.startsWith('.')) continue;
3574
+ const abs = path.join(cur, ent.name);
3575
+ if (ent.isDirectory()) { stack.push(abs); continue; }
3576
+ const ext = path.extname(ent.name).toLowerCase();
3577
+ if (ext !== '.html' && ext !== '.htm') continue;
3578
+
3579
+ const rel = path.relative(dir, abs).replace(/\\/g, '/');
3580
+ let html;
3581
+ try { html = fs.readFileSync(abs, 'utf-8'); } catch { continue; }
3582
+
3583
+ // Phase A: balance tags
3584
+ const { fixed, edits } = _balanceHtmlTags(html);
3585
+ if (fixed !== html && edits.length > 0) {
3586
+ fs.writeFileSync(abs, fixed, 'utf-8');
3587
+ filesRepaired.add(rel);
3588
+ repairs.push({ file: rel, kind: 'html-balance', edits });
3589
+ }
3590
+
3591
+ // Phase B: create missing referenced assets
3592
+ const refs = _extractHtmlAssetRefs(fixed);
3593
+ for (const ref of refs) {
3594
+ const target = _normalizeAssetPath(ref.href, rel);
3595
+ if (!target) continue;
3596
+ const targetAbs = path.join(dir, target);
3597
+ if (fs.existsSync(targetAbs)) continue;
3598
+ // Don't create node_modules / external dirs
3599
+ if (target.startsWith('node_modules/') || target.includes('..')) continue;
3600
+ try {
3601
+ ensureDir(path.dirname(targetAbs));
3602
+ fs.writeFileSync(targetAbs, _placeholderContent(ref.kind, ref.href), 'utf-8');
3603
+ filesCreated.push(target);
3604
+ repairs.push({ file: target, kind: 'missing-asset', source: rel, ref: ref.href });
3605
+ } catch {}
3606
+ }
3607
+ }
3608
+ }
3609
+
3610
+ return {
3611
+ ok: true,
3612
+ filesRepaired: [...filesRepaired],
3613
+ filesCreated,
3614
+ repairs,
3615
+ summary: `${filesRepaired.size} HTML balanced, ${filesCreated.length} placeholder files created`,
3616
+ };
3617
+ }
3618
+
3619
+ // ─────────────────────────────────────────────────────────────────────────────
3620
+
3621
+ // Map native tool names → internal NHA "op" codes. Both directions: the LLM
3622
+ // might emit either based on which provider's docs it was trained on.
3623
+ const _TOOL_NAME_TO_OP = new Map([
3624
+ ['read_file', 'read'], ['read', 'read'],
3625
+ ['edit_file', 'edit'], ['edit', 'edit'],
3626
+ ['create_file', 'write'], ['write', 'write'], ['write_file', 'write'],
3627
+ ['delete_file', 'delete'], ['delete', 'delete'],
3628
+ ['list_files', 'list'], ['list', 'list'],
3629
+ ['search_files', 'search'], ['search', 'search'],
3630
+ ['run_command', 'run'], ['run', 'run'], ['shell', 'run'],
3631
+ ['check_syntax', 'check'], ['check', 'check'],
3632
+ ['restart_sandbox', 'sandbox'], ['sandbox', 'sandbox'],
3633
+ ]);
3634
+
3635
+ // Map common param-name variants used across LLM providers.
3636
+ const _PARAM_ALIASES = {
3637
+ path: ['path', 'file_path', 'filepath', 'filename', 'file'],
3638
+ old: ['old', 'old_text', 'old_string', 'oldText'],
3639
+ new: ['new', 'new_text', 'new_string', 'newText', 'replacement'],
3640
+ content: ['content', 'text', 'body', 'file_content'],
3641
+ query: ['query', 'pattern', 'search', 'q'],
3642
+ glob: ['glob', 'pattern', 'file_pattern'],
3643
+ cmd: ['cmd', 'command', 'shell_command'],
3644
+ };
3645
+
3646
+ function _pickParam(obj, kind) {
3647
+ for (const alt of _PARAM_ALIASES[kind] || [kind]) {
3648
+ if (obj && Object.prototype.hasOwnProperty.call(obj, alt) && obj[alt] !== undefined) {
3649
+ return obj[alt];
3650
+ }
3651
+ }
3652
+ return undefined;
3653
+ }
3654
+
3655
+ /**
3656
+ * Extract every tool-call-like JSON blob from an LLM response. Handles:
3657
+ * - <tool>{...}</tool> — NHA native wrapper
3658
+ * - {"tool": "...", "args": {...}} — OpenAI-style nude JSON
3659
+ * - {"name": "...", "input": {...}} — Anthropic-style nude JSON
3660
+ * - ```json\n{...}\n``` — markdown-fenced JSON blocks
3661
+ *
3662
+ * Returns { calls: [raw objects], matchedRanges: [[start,end], ...] } so the
3663
+ * caller can blank out the consumed regions from the visible text stream.
3664
+ */
3665
+ export function _extractAllToolCalls(text) {
3666
+ const calls = [];
3667
+ const matchedRanges = [];
3668
+
3669
+ // Pass 1: <tool>...</tool> wrappers
3670
+ const wrapRe = /<tool>([\s\S]*?)<\/tool>/g;
3671
+ let m;
3672
+ while ((m = wrapRe.exec(text)) !== null) {
3673
+ matchedRanges.push([m.index, m.index + m[0].length]);
3674
+ try {
3675
+ let raw = m[1].trim().replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
3676
+ calls.push(JSON.parse(raw));
3677
+ } catch {
3678
+ try { calls.push(_parseToolCallRobust(m[1].trim())); } catch {}
3679
+ }
3680
+ }
3681
+
3682
+ // Pass 2: markdown-fenced JSON blocks ```json {...} ```
3683
+ const fenceRe = /```(?:json)?\s*\n([\s\S]*?)\n```/g;
3684
+ while ((m = fenceRe.exec(text)) !== null) {
3685
+ // Skip if already inside a <tool> match
3686
+ if (matchedRanges.some(([s, e]) => m.index >= s && m.index < e)) continue;
3687
+ const body = m[1].trim();
3688
+ if (!/^\s*\{/.test(body)) continue;
3689
+ try {
3690
+ const parsed = JSON.parse(body.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']'));
3691
+ if (_looksLikeToolCall(parsed)) {
3692
+ calls.push(parsed);
3693
+ matchedRanges.push([m.index, m.index + m[0].length]);
3694
+ }
3695
+ } catch {}
3696
+ }
3697
+
3698
+ // Pass 3: bare JSON objects with balanced braces — scan top-level.
3699
+ // Find every '{' candidate not already consumed, then walk forward to find
3700
+ // the matching '}' while respecting nested braces AND string literals.
3701
+ for (let i = 0; i < text.length; i++) {
3702
+ if (text[i] !== '{') continue;
3703
+ if (matchedRanges.some(([s, e]) => i >= s && i < e)) continue;
3704
+ // Must be at line start or after whitespace (avoid inline {expr}/object literals in code)
3705
+ let prev = i - 1;
3706
+ while (prev >= 0 && (text[prev] === ' ' || text[prev] === '\t')) prev--;
3707
+ if (prev >= 0 && text[prev] !== '\n') continue;
3708
+
3709
+ const end = _findMatchingBrace(text, i);
3710
+ if (end < 0) continue;
3711
+ const candidate = text.slice(i, end + 1);
3712
+ try {
3713
+ const parsed = JSON.parse(candidate.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']'));
3714
+ if (_looksLikeToolCall(parsed)) {
3715
+ calls.push(parsed);
3716
+ matchedRanges.push([i, end + 1]);
3717
+ i = end;
3718
+ }
3719
+ } catch {}
3720
+ }
3721
+
3722
+ return { calls, matchedRanges };
3723
+ }
3724
+
3725
+ // Walk forward from openIdx (which must be '{') to find the matching '}'.
3726
+ // Respects nested braces, string literals (single & double quote), and escapes.
3727
+ // Returns -1 if no match found (unclosed).
3728
+ function _findMatchingBrace(text, openIdx) {
3729
+ let depth = 0;
3730
+ let inStr = false;
3731
+ let strCh = '';
3732
+ for (let i = openIdx; i < text.length; i++) {
3733
+ const c = text[i];
3734
+ if (inStr) {
3735
+ if (c === '\\') { i++; continue; }
3736
+ if (c === strCh) inStr = false;
3737
+ continue;
3738
+ }
3739
+ if (c === '"' || c === "'") { inStr = true; strCh = c; continue; }
3740
+ if (c === '{') depth++;
3741
+ else if (c === '}') {
3742
+ depth--;
3743
+ if (depth === 0) return i;
3744
+ }
3745
+ }
3746
+ return -1;
3747
+ }
3748
+
3749
+ function _looksLikeToolCall(obj) {
3750
+ if (!obj || typeof obj !== 'object') return false;
3751
+ return (
3752
+ 'op' in obj ||
3753
+ 'tool' in obj ||
3754
+ 'name' in obj && ('input' in obj || 'arguments' in obj || 'args' in obj) ||
3755
+ 'function_call' in obj
3756
+ );
3757
+ }
3758
+
3759
+ /**
3760
+ * Normalize a parsed tool-call object to NHA's internal shape:
3761
+ * { op, path, old, new, content, newPath, query, glob, cmd }
3762
+ *
3763
+ * Accepts:
3764
+ * - {op, path, ...} — already normalized
3765
+ * - {tool, args: {file_path, old_text, ...}} — OpenAI-style
3766
+ * - {name, input: {...}} / {name, arguments} — Anthropic-style
3767
+ */
3768
+ export function _normalizeToolCall(raw) {
3769
+ if (!raw || typeof raw !== 'object') throw new Error('not an object');
3770
+
3771
+ // Already in NHA native shape
3772
+ if ('op' in raw && typeof raw.op === 'string') {
3773
+ return raw;
3774
+ }
3775
+
3776
+ // Extract tool name from various wire formats
3777
+ const toolName = raw.tool || raw.name || raw.function_call?.name;
3778
+ if (!toolName) throw new Error('no tool/op/name field');
3779
+
3780
+ const op = _TOOL_NAME_TO_OP.get(toolName);
3781
+ if (!op) throw new Error(`unknown tool name: ${toolName}`);
3782
+
3783
+ // Extract args payload (OpenAI uses "args", Anthropic uses "input" or "arguments")
3784
+ let args = raw.args || raw.input || raw.arguments || raw.parameters || {};
3785
+ if (typeof args === 'string') {
3786
+ try { args = JSON.parse(args); } catch { args = {}; }
3787
+ }
3788
+
3789
+ return {
3790
+ op,
3791
+ path: _pickParam(args, 'path'),
3792
+ old: _pickParam(args, 'old'),
3793
+ new: _pickParam(args, 'new'),
3794
+ content: _pickParam(args, 'content'),
3795
+ query: _pickParam(args, 'query'),
3796
+ glob: _pickParam(args, 'glob'),
3797
+ cmd: _pickParam(args, 'cmd'),
3798
+ newPath: args.newPath || args.new_path,
3799
+ };
3800
+ }
3801
+
3399
3802
  function _parseToolCallRobust(raw) {
3400
3803
  const result = {};
3401
3804
 
@@ -852,8 +852,8 @@ Errore: `+e.msg,L(!1))}catch{}}}}catch(e){e instanceof DOMException&&e.name===`A
852
852
 
853
853
  `);i=a.pop()??``;for(let e of a){let t=e.replace(/^data: /,``).trim();if(t)try{let e=JSON.parse(t);(e.type===`phase`||e.type===`status`||e.type===`log`||e.type===`warn`||e.type===`error`)&&_t(t=>[...t.slice(-49),{kind:e.type,msg:String(e.msg||``),ts:Date.now()}]),e.type===`ready`&&e.port?(lt(e.port),pt(!1)):e.type===`error`&&ht(e.msg)}catch{}}}}catch(e){ht(e.message||`Connection failed`)}pt(!1)}}async function Gt(){c&&await Wt(c)}(0,_.useEffect)(()=>{z.current=V,Ot.current=Gt});async function Kt(){if(!et||!c)return;let e=await E(`/api/studio/webcraft/grep`,{projectName:c,query:et});e?.matches&&rt(e.matches)}function qt(e){let t=g.findIndex(t=>t.name===e);t>=0&&(b(t),i(`files`))}function Jt(){window.open(`/api/studio/webcraft/download/${encodeURIComponent(c)}`,`_blank`)}async function Yt(e,t,n,r){t.endsWith(`.md`)||(t+=`.md`);let i={name:t,content:n,type:r},a;a=e.mode===`edit`&&e.idx!==null?We.map((t,n)=>n===e.idx?i:t):[...We,i],Ge(a),Ye(null);let o=c||`MyProject`;c||l(o),await E(`/api/studio/webcraft/skills/${encodeURIComponent(o)}`,{skills:a})}async function Xt(e){let t=We[e];!t||!confirm(`Eliminare "${t.name}"?`)||(await E(`/api/studio/webcraft/skills/${encodeURIComponent(c)}/delete`,{name:t.name}),Ge(We.filter((t,n)=>n!==e)))}async function Zt(e){let t=We[e];if(!t||!confirm(`Svuotare "${t.name}"? Il file rimane ma il contenuto viene cancellato.`))return;let n=We.map((t,n)=>n===e?{...t,content:``}:t);Ge(n),await E(`/api/studio/webcraft/skills/${encodeURIComponent(c)}`,{skills:n})}async function Qt(){let e=await T(`/api/studio/webcraft/projects`);e?.projects&&st(e.projects)}async function H(e){let t=await T(`/api/studio/webcraft/projects/load/${encodeURIComponent(e.name)}`);if(!t)return;let r=t.projectName??e.name;l(r),d(t.description??``),v(t.files??[]),b(0),n(`new`),i(`files`),Ie([]),Ge([]),qe(!1),pe([]),kt(r);let a=await T(`/api/studio/webcraft/projects/chat/load/${encodeURIComponent(r)}`);a?.chat&&Ie(a.chat);let o=await T(`/api/studio/webcraft/skills/${encodeURIComponent(r)}`);o?.skills&&(Ge(o.skills),qe(!0))}async function $t(e){confirm(`Eliminare: ${e.name} - ${e.dir}?`)&&(await E(`/api/studio/webcraft/projects/${encodeURIComponent(e.name)}`,{},`DELETE`),st(ot.filter(t=>t.name!==e.name)),c===e.name&&(l(``),v([]),Ie([]),d(``)))}async function en(){if(!He)return;let e=He.originalMessage;Ue(null),await Ft(e+`
854
854
  [Piano approvato — procedi con le modifiche]`,null,[])}function tn(e){e&&Array.from(e).forEach(e=>{let t=new FileReader;t.onload=t=>{let n=(t.target?.result).split(`,`)[1];Ve(t=>[...t,{name:e.name,mimeType:e.type,base64:n,size:e.size}])},t.readAsDataURL(e)})}let nn=c&&g.length>0,rn=g[y],U=ze||N;return(0,k.jsxs)(`div`,{className:Q.root,children:[(0,k.jsxs)(`div`,{className:Q.header,children:[(0,k.jsxs)(`div`,{children:[(0,k.jsxs)(`div`,{className:Q.title,children:[`⚙ WebCraft`,c?` — ${c}`:``]}),!c&&(0,k.jsx)(`div`,{className:Q.subtitle,children:`Genera progetti web completi con agenti AI`})]}),(0,k.jsxs)(`div`,{className:Q.headerTabs,children:[(0,k.jsx)(`button`,{className:`${Q.tabBtn} ${t===`new`?Q.tabActive:``}`,onClick:async()=>{if(!(ee.size>0&&!confirm(`${ee.size} unsaved file(s). Discard changes and create new project?`))){if(ct){try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}lt(null)}v([]),b(0),S(null),w(null),te(new Set),Ie([]),l(``),d(``),Re(``),Ge([]),qe(!1),Pe(null),Ce(!1),L(!1),oe(!1),ce(null),n(`new`)}},children:`+ Nuovo`}),(0,k.jsx)(`button`,{className:`${Q.tabBtn} ${t===`projects`?Q.tabActive:``}`,onClick:()=>{n(`projects`),Qt()},children:`📁 Progetti`})]})]}),(0,k.jsx)(`div`,{className:Q.body,children:t===`projects`?(0,k.jsx)(`div`,{className:Q.projectsList,children:ot.length===0?(0,k.jsxs)(`div`,{className:Q.emptyProjects,children:[(0,k.jsx)(`span`,{className:Q.emptyIcon,children:`📁`}),(0,k.jsx)(`span`,{children:e(`webcraft.noProjects`)}),(0,k.jsx)(`span`,{className:Q.emptyHint,children:`Crea un progetto nella tab Nuovo`})]}):ot.map(e=>(0,k.jsxs)(`div`,{className:Q.projectCard,children:[(0,k.jsxs)(`div`,{className:Q.projectInfo,children:[(0,k.jsx)(`div`,{className:Q.projectName,children:e.name}),(0,k.jsx)(`div`,{className:Q.projectDesc,children:e.description}),(0,k.jsxs)(`div`,{className:Q.projectMeta,children:[(0,k.jsxs)(`span`,{children:[`📄 `,e.fileCount,` file`]}),(0,k.jsxs)(`span`,{children:[`📅 `,e.createdAt?new Date(e.createdAt).toLocaleString():``]})]})]}),(0,k.jsx)(`button`,{className:Q.openBtn,onClick:()=>H(e),children:`↗ Apri`}),(0,k.jsx)(`button`,{className:Q.deleteBtn,onClick:()=>$t(e),children:`🗑`})]},e.name))}):(0,k.jsxs)(`div`,{className:Q.editor,"data-mobile-view":a?o:void 0,children:[(0,k.jsxs)(`div`,{className:Q.examples,children:[(0,k.jsx)(`div`,{className:Q.sectionLabel,children:`Esempi`}),(0,k.jsx)(`div`,{className:Q.examplePills,children:eE.map(e=>(0,k.jsx)(`button`,{className:Q.examplePill,onClick:()=>{l(e.name),d(e.desc),Re(e.desc)},children:e.name},e.name))})]}),(0,k.jsxs)(`div`,{className:Q.editorCols,children:[(0,k.jsxs)(`div`,{className:Q.leftSidebar,children:[(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`Blocchi`}),tE.map(e=>(0,k.jsxs)(`label`,{className:Q.blockLabel,children:[(0,k.jsx)(`input`,{type:`checkbox`,checked:f[e.key],onChange:t=>p(n=>({...n,[e.key]:t.target.checked})),className:Q.blockCheck}),(0,k.jsx)(`span`,{children:e.icon}),(0,k.jsx)(`span`,{children:e.label})]},e.key))]}),f.auth&&(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsxs)(`div`,{className:Q.panelHeader,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`Campi Auth`}),(0,k.jsx)(`button`,{className:Q.addBtn,onClick:()=>h(e=>[...e,{label:`New field`,type:`text`,required:!1}]),children:`+ Campo`})]}),m.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.authField,children:[(0,k.jsx)(`input`,{value:e.label,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,label:e.target.value}:n)),className:Q.authFieldInput}),(0,k.jsx)(`select`,{value:e.type,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,type:e.target.value}:n)),className:Q.authFieldSelect,children:[`text`,`email`,`password`,`tel`,`date`,`number`].map(e=>(0,k.jsx)(`option`,{value:e,children:e},e))}),(0,k.jsx)(`input`,{type:`checkbox`,checked:e.required,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,required:e.target.checked}:n)),title:`Required`,className:Q.authFieldReq}),(0,k.jsx)(`button`,{onClick:()=>h(e=>e.filter((e,n)=>n!==t)),className:Q.removeFieldBtn,children:`×`})]},t))]}),(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsxs)(`div`,{className:Q.panelHeader,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`🗂 Contesto AI`}),(0,k.jsx)(`button`,{className:Q.addBtn,onClick:()=>Ye({mode:`new`,idx:null,name:``,content:``,type:`skill`,generating:!1}),children:`+ Skill`})]}),We.length>0?(0,k.jsx)(`div`,{className:Q.skillsList,children:We.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.skillRow,children:[(0,k.jsx)(`span`,{className:Q.skillIcon,children:aE(e.type)}),(0,k.jsx)(`span`,{className:Q.skillName,title:e.name,children:e.name}),(0,k.jsx)(`span`,{className:`${Q.skillBadge} ${Q[`skillBadge_`+e.type]}`,children:e.type}),!e.content&&e.type!==`log`&&(0,k.jsx)(`span`,{className:Q.skillEmpty,children:`⚠`}),(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Ye({mode:e.type===`log`?`view`:`edit`,idx:t,name:e.name,content:e.content,type:e.type,generating:!1}),children:e.type===`log`?`👁`:`✏`}),e.type!==`memory`&&e.type!==`provider`&&e.type!==`log`&&(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Zt(t),children:`🗑`}),e.type===`log`&&(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Xt(t),children:`🗑`})]},t))}):(0,k.jsx)(`div`,{className:Q.skillsEmpty,children:Ke?`Nessun file di contesto. Clicca "+ Skill" per aggiungerne uno.`:`Crea o carica un progetto per i file di contesto.`})]}),Xe.length>0&&(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`💾 Snapshot`}),Xe.slice(0,5).map(e=>{let t=e.ts.replace(`T`,` `).slice(0,16);return(0,k.jsxs)(`div`,{className:Q.snapshotRow,children:[(0,k.jsx)(`span`,{className:Q.snapshotTs,children:t}),(0,k.jsxs)(`span`,{className:Q.snapshotCount,children:[e.fileCount,`f`]}),(0,k.jsx)(`button`,{className:Q.snapshotBtn,onClick:()=>Vt(e.ts),children:`↺`})]},e.ts)})]}),N&&(0,k.jsx)(`div`,{className:Q.genStatus,children:`⏳ Generazione...`}),we&&(0,k.jsxs)(`div`,{className:Q.repairStatus,children:[(0,k.jsx)(`div`,{className:Q.repairStatusTitle,children:`🔧 Correzione automatica...`}),(0,k.jsxs)(`div`,{className:Q.repairStatusProg,children:[Ee,` / `,De,` file`]}),(0,k.jsx)(`div`,{className:Q.repairStatusFile,children:ke})]}),g.length>0&&!N&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.actionRow,children:[(0,k.jsx)(`button`,{className:Q.actionBtn,onClick:Jt,children:`⬇ ZIP`}),(0,k.jsx)(`button`,{className:Q.actionBtnIcon,title:`Syntax check`,onClick:Ht,children:`✅`}),(0,k.jsx)(`button`,{className:`${Q.actionBtnIcon} ${Qe?Q.actionBtnActive:``}`,title:`Grep`,onClick:()=>$e(!Qe),children:`🔍`}),(0,k.jsx)(`button`,{className:Q.actionBtnIcon,title:`Snapshot`,onClick:zt,children:`💾`})]}),g.some(e=>e._error||e._syntaxError)&&!we&&(0,k.jsx)(`button`,{className:Q.repairBtn,onClick:V,children:`🔧 Correggi tutti i file rossi`}),(0,k.jsx)(`button`,{className:Q.sandboxBtn,onClick:()=>{c?Wt(c):i(`preview`)},children:ft?`⏳ Starting...`:ct?`🌐 Sandbox Live`:`▶ Sandbox`}),Ne&&(0,k.jsxs)(`div`,{className:Q.statsBar,children:[(0,k.jsxs)(`span`,{children:[`⏱ `,Ne.seconds>=60?`${Math.floor(Ne.seconds/60)}m ${Ne.seconds%60}s`:`${Ne.seconds}s`]}),(0,k.jsxs)(`span`,{children:[`↑ `,Ne.tokIn.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`↓ `,Ne.tokOut.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`📄 `,Ne.files,` file`]})]})]})]}),(0,k.jsxs)(`div`,{className:Q.rightPanel,children:[(0,k.jsxs)(`div`,{className:Q.rightTabBar,children:[(0,k.jsx)(`button`,{className:`${Q.rightTab} ${r===`preview`?``:Q.rightTabActive}`,onClick:()=>i(`files`),children:`📄 File`}),(0,k.jsx)(`button`,{className:`${Q.rightTab} ${r===`preview`?Q.rightTabActive:``}`,onClick:()=>i(`preview`),children:`🌐 Sandbox`})]}),we&&(0,k.jsxs)(`div`,{className:Q.repairBar,children:[(0,k.jsxs)(`div`,{className:Q.repairBarRow,children:[(0,k.jsx)(`span`,{className:Q.repairBarIcon,children:`🔧`}),(0,k.jsx)(`span`,{className:Q.repairBarLabel,children:`Auto-fix`}),(0,k.jsx)(`span`,{className:Q.repairBarFile,children:ke}),(0,k.jsxs)(`span`,{className:Q.repairBarCounter,children:[Ee,` / `,De]}),(0,k.jsx)(`span`,{className:Q.repairBarTime,children:R}),(0,k.jsx)(`button`,{className:Q.stopBtn,onClick:Lt,children:`⏹ Stop`})]}),(0,k.jsx)(`div`,{className:Q.progressTrack,children:(0,k.jsx)(`div`,{className:Q.repairProgress,style:{width:De>0?`${Math.round(Ee/De*100)}%`:`0%`}})})]}),N&&(0,k.jsxs)(`div`,{className:Q.genBar,children:[(0,k.jsxs)(`div`,{className:Q.genBarRow,children:[(0,k.jsx)(`span`,{className:Q.genBarRobot,children:`🤖`}),(0,k.jsx)(`span`,{className:Q.genBarLabel,children:I.total===0?`Pianificazione...`:`Generazione`}),(0,k.jsx)(`span`,{className:Q.genBarFile,children:(I.name||``).split(`,`)[0].trim()}),(0,k.jsx)(`span`,{className:Q.genBarCounter,children:I.total>0?`${I.fi} / ${I.total}`:``}),(0,k.jsx)(`span`,{className:Q.genBarCounter,children:je.tokIn+je.tokOut>0?`↑${jt(je.tokIn)} ↓${jt(je.tokOut)}`:``}),(0,k.jsx)(`span`,{className:Q.genBarTime,children:vt}),(0,k.jsxs)(`span`,{className:Q.genDots,children:[(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot1}`}),(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot2}`}),(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot3}`})]})]}),(0,k.jsx)(`div`,{className:Q.progressTrack,children:(0,k.jsx)(`div`,{className:Q.genProgress,style:{width:I.total>0?`${Math.round(I.fi/I.total*100)}%`:`0%`}})})]}),r===`preview`?(0,k.jsxs)(`div`,{className:Q.sandboxWrap,children:[(0,k.jsxs)(`div`,{className:Q.sandboxStatusBar,children:[(0,k.jsx)(`span`,{className:Q.sandboxStatusDot,style:{background:ct?`#4ade80`:ft?`#facc15`:`#64748b`}}),(0,k.jsx)(`span`,{className:Q.sandboxStatusText,children:ct?`Live :${ct}`:ft?`Starting...`:`Stopped`}),ct&&(0,k.jsx)(`button`,{className:Q.sandboxReloadBtn,onClick:()=>{let e=document.querySelector(`iframe[title="WebCraft Sandbox"]`);e&&(e.src=e.src)},children:`↻`}),ct&&(0,k.jsx)(`button`,{className:Q.sandboxStopBtn,onClick:async()=>{lt(null),M([]);try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}},children:`⏹`}),!ct&&!ft&&(0,k.jsx)(`button`,{className:Q.sandboxStartBtnSmall,onClick:()=>{c&&Wt(c)},children:`▶ Start`})]}),de.length>0&&(0,k.jsxs)(`div`,{className:Q.runtimeErrors,children:[(0,k.jsxs)(`div`,{className:Q.runtimeErrorsHeader,children:[(0,k.jsxs)(`span`,{children:[`❌ `,de.length,` runtime error`,de.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:Q.runtimeErrorsFix,onClick:()=>{let e=`Fix these runtime errors. Use edit_file (or create_file if the file doesn't exist) to actually modify the source — do NOT respond with text descriptions. After each fix, call check_syntax to verify. If edit_file fails because old_text doesn't match, read_file again and retry with the exact text.\n\n${de.map(e=>`${e.message} (${e.source||``}:${e.line||0})`).join(`
855
- `)}`;fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([]),i(`files`),Pt(e)},children:`🔧 Auto-fix`}),(0,k.jsx)(`button`,{className:Q.runtimeErrorsDismiss,onClick:()=>{fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([])},children:`✕`})]}),de.slice(0,3).map((e,t)=>(0,k.jsxs)(`div`,{className:Q.runtimeErrorLine,children:[e.message,e.source?` — ${e.source.split(`/`).pop()}:${e.line}`:``]},t))]}),ct?(0,k.jsx)(`iframe`,{src:`http://localhost:${ct}`,className:Q.sandboxFrame,title:`WebCraft Sandbox`,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups`}):(0,k.jsxs)(`div`,{className:Q.sandboxEmpty,children:[(0,k.jsx)(`span`,{style:{fontSize:48},children:ft?`⏳`:mt?`❌`:`🌐`}),(0,k.jsx)(`span`,{style:{fontWeight:700,fontSize:16},children:ft?`Starting sandbox...`:mt?`Sandbox Error`:`Preview`}),mt&&(0,k.jsx)(`pre`,{style:{fontSize:11,maxWidth:600,textAlign:`left`,color:`#f87171`,background:`rgba(248,113,113,0.08)`,border:`1px solid rgba(248,113,113,0.2)`,borderRadius:6,padding:`8px 12px`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,margin:`8px 0`,lineHeight:1.5,maxHeight:200,overflow:`auto`},children:mt}),gt.length>0&&(0,k.jsxs)(`div`,{style:{width:`100%`,maxWidth:720,margin:`8px 0`,textAlign:`left`},children:[(0,k.jsxs)(`div`,{style:{fontSize:10,color:`#94a3b8`,textTransform:`uppercase`,letterSpacing:`0.5px`,marginBottom:4},children:[`Sandbox log (`,gt.length,`)`]}),(0,k.jsx)(`pre`,{style:{fontSize:11,lineHeight:1.5,fontFamily:`SF Mono, Monaco, monospace`,background:`rgba(15, 23, 42, 0.6)`,border:`1px solid rgba(148, 163, 184, 0.18)`,borderRadius:6,padding:`8px 12px`,maxHeight:240,overflow:`auto`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,color:`#cbd5e1`},children:gt.slice(-40).map((e,t)=>(0,k.jsx)(`div`,{style:{color:e.kind===`error`?`#f87171`:e.kind===`warn`?`#fbbf24`:e.kind===`phase`?`#60a5fa`:e.kind===`status`?`#34d399`:`#94a3b8`},children:`[${e.kind}] ${e.msg}`},t))})]}),!ft&&(0,k.jsxs)(`button`,{className:Q.sandboxStartBtn,onClick:()=>{c&&Wt(c)},children:[`▶ `,mt?`Retry`:`Start Sandbox`]})]})]}):(0,k.jsx)(`div`,{className:Q.codeArea,children:g.length===0&&N?(0,k.jsx)(`div`,{className:Q.noFiles,children:(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`⏳`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`Pianificazione...`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:I.name||`Analisi della struttura del progetto in corso`})]})}):g.length===0?(0,k.jsxs)(`div`,{className:Q.noFiles,children:[(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`🔨`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`WebCraft`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:`Genera progetti web completi con AI`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesSteps,children:[(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`1`}),(0,k.jsx)(`span`,{children:`Scegli un esempio o scrivi una descrizione nel box in basso`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`2`}),(0,k.jsxs)(`span`,{children:[`Premi `,(0,k.jsx)(`strong`,{children:`▶ Genera`}),` — l'AI crea tutti i file del progetto`]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`3`}),(0,k.jsx)(`span`,{children:`Chiedi modifiche in chat, scarica lo ZIP o avvia il Sandbox`})]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesExamplesHint,children:[`💡 Prova: `,(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[0];l(e.name),d(e.desc),Re(e.desc)},children:`MySaaS`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[1];l(e.name),d(e.desc),Re(e.desc)},children:`MyShop`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[3];l(e.name),d(e.desc),Re(e.desc)},children:`MyPortfolio`})]})]}):(0,k.jsxs)(`div`,{className:Q.codeLayout,children:[(0,k.jsx)(`div`,{className:Q.ideTabBar,children:g.map((e,t)=>{let n=e._error||!!e._syntaxError,r=t===y;return(0,k.jsxs)(`button`,{className:`${Q.ideTab} ${r?Q.ideTabActive:``} ${n?Q.ideTabError:``} ${e._pending?Q.ideTabPending:``}`,onClick:()=>{b(t),w(null),S(null),N&&me.current!==null&&t!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))},title:e.name,children:[(0,k.jsx)(`span`,{className:Q.ideTabIcon,children:e._pending?`⌛`:n?`⚠`:rE(e.name)}),(0,k.jsx)(`span`,{className:Q.ideTabName,children:(e.name||``).split(`/`).pop()}),ee.has(e.name)&&(0,k.jsx)(`span`,{className:Q.ideTabUnsaved,children:`●`}),n&&(0,k.jsx)(`span`,{className:Q.ideTabDot})]},t)})}),xe&&(0,k.jsxs)(`div`,{className:Q.diffOverlay,children:[(0,k.jsxs)(`div`,{className:Q.diffOverlayHeader,children:[(0,k.jsxs)(`span`,{children:[`✏ Modifica proposta — `,(0,k.jsx)(`strong`,{children:xe.file})]}),(0,k.jsxs)(`div`,{className:Q.diffOverlayActions,children:[(0,k.jsx)(`button`,{className:Q.diffAcceptBtn,onClick:()=>{v(e=>e.map(e=>e.name===xe.file?{...e,content:xe.after}:e)),Se(null)},children:`✓ Accetta`}),(0,k.jsx)(`button`,{className:Q.diffRejectBtn,onClick:()=>Se(null),children:`✕ Rifiuta`})]})]}),(0,k.jsx)(`div`,{className:Q.diffOverlayBody,children:(0,k.jsx)(uE,{before:xe.before,after:xe.after})})]}),(0,k.jsxs)(`div`,{className:Q.codeRow,children:[(0,k.jsxs)(`div`,{className:Q.fileTreeWrap,children:[fe.length>0&&(0,k.jsxs)(`div`,{className:Q.scanBanner,children:[(0,k.jsx)(`span`,{className:Q.scanBannerIcon,children:`⚠`}),(0,k.jsxs)(`span`,{className:Q.scanBannerText,children:[fe.length,` issue`,fe.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:Q.scanBannerFix,onClick:()=>{let e=`Fix ALL these issues by editing the source files. Workflow per issue: read_file → edit_file (or create_file if missing) → check_syntax. Do NOT respond with a plan or description — call the tools and actually modify the code. If edit_file returns an error because old_text doesn't match, read_file again and retry with the exact text. Also check that any /js/*.js, /css/*.css referenced in HTML actually exist as files (create them if missing).\n\nISSUES TO FIX:\n${fe.map(e=>`[${e.severity}] ${e.file}: ${e.message}`).join(`
856
- `)}`;i(`files`),Pt(e)},children:`Fix`})]}),(0,k.jsx)(WT,{files:g,activeIndex:y,unsavedFiles:ee,errorFiles:new Set(fe.filter(e=>e.severity===`error`).map(e=>e.file)),onSelect:e=>{b(e),w(null),S(null),N&&me.current!==null&&e!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))}})]}),(0,k.jsx)(`div`,{className:Q.codeEditorWrap,children:rn&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(rn.name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:rn.name}),rn.content&&!rn._error&&(0,k.jsxs)(`span`,{className:Q.codeFileMeta,children:[(rn.content||``).split(`
855
+ `)}`;fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([]),i(`files`),Pt(e)},children:`🔧 Auto-fix`}),(0,k.jsx)(`button`,{className:Q.runtimeErrorsDismiss,onClick:()=>{fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([])},children:`✕`})]}),de.slice(0,3).map((e,t)=>(0,k.jsxs)(`div`,{className:Q.runtimeErrorLine,children:[e.message,e.source?` — ${e.source.split(`/`).pop()}:${e.line}`:``]},t))]}),ct?(0,k.jsx)(`iframe`,{src:`http://localhost:${ct}`,className:Q.sandboxFrame,title:`WebCraft Sandbox`,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups`}):(0,k.jsxs)(`div`,{className:Q.sandboxEmpty,children:[(0,k.jsx)(`span`,{style:{fontSize:48},children:ft?`⏳`:mt?`❌`:`🌐`}),(0,k.jsx)(`span`,{style:{fontWeight:700,fontSize:16},children:ft?`Starting sandbox...`:mt?`Sandbox Error`:`Preview`}),mt&&(0,k.jsx)(`pre`,{style:{fontSize:11,maxWidth:600,textAlign:`left`,color:`#f87171`,background:`rgba(248,113,113,0.08)`,border:`1px solid rgba(248,113,113,0.2)`,borderRadius:6,padding:`8px 12px`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,margin:`8px 0`,lineHeight:1.5,maxHeight:200,overflow:`auto`},children:mt}),gt.length>0&&(0,k.jsxs)(`div`,{style:{width:`100%`,maxWidth:720,margin:`8px 0`,textAlign:`left`},children:[(0,k.jsxs)(`div`,{style:{fontSize:10,color:`#94a3b8`,textTransform:`uppercase`,letterSpacing:`0.5px`,marginBottom:4},children:[`Sandbox log (`,gt.length,`)`]}),(0,k.jsx)(`pre`,{style:{fontSize:11,lineHeight:1.5,fontFamily:`SF Mono, Monaco, monospace`,background:`rgba(15, 23, 42, 0.6)`,border:`1px solid rgba(148, 163, 184, 0.18)`,borderRadius:6,padding:`8px 12px`,maxHeight:240,overflow:`auto`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,color:`#cbd5e1`},children:gt.slice(-40).map((e,t)=>(0,k.jsx)(`div`,{style:{color:e.kind===`error`?`#f87171`:e.kind===`warn`?`#fbbf24`:e.kind===`phase`?`#60a5fa`:e.kind===`status`?`#34d399`:`#94a3b8`},children:`[${e.kind}] ${e.msg}`},t))})]}),!ft&&(0,k.jsxs)(`button`,{className:Q.sandboxStartBtn,onClick:()=>{c&&Wt(c)},children:[`▶ `,mt?`Retry`:`Start Sandbox`]})]})]}):(0,k.jsx)(`div`,{className:Q.codeArea,children:g.length===0&&N?(0,k.jsx)(`div`,{className:Q.noFiles,children:(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`⏳`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`Pianificazione...`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:I.name||`Analisi della struttura del progetto in corso`})]})}):g.length===0?(0,k.jsxs)(`div`,{className:Q.noFiles,children:[(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`🔨`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`WebCraft`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:`Genera progetti web completi con AI`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesSteps,children:[(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`1`}),(0,k.jsx)(`span`,{children:`Scegli un esempio o scrivi una descrizione nel box in basso`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`2`}),(0,k.jsxs)(`span`,{children:[`Premi `,(0,k.jsx)(`strong`,{children:`▶ Genera`}),` — l'AI crea tutti i file del progetto`]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`3`}),(0,k.jsx)(`span`,{children:`Chiedi modifiche in chat, scarica lo ZIP o avvia il Sandbox`})]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesExamplesHint,children:[`💡 Prova: `,(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[0];l(e.name),d(e.desc),Re(e.desc)},children:`MySaaS`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[1];l(e.name),d(e.desc),Re(e.desc)},children:`MyShop`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[3];l(e.name),d(e.desc),Re(e.desc)},children:`MyPortfolio`})]})]}):(0,k.jsxs)(`div`,{className:Q.codeLayout,children:[(0,k.jsx)(`div`,{className:Q.ideTabBar,children:g.map((e,t)=>{let n=e._error||!!e._syntaxError,r=t===y;return(0,k.jsxs)(`button`,{className:`${Q.ideTab} ${r?Q.ideTabActive:``} ${n?Q.ideTabError:``} ${e._pending?Q.ideTabPending:``}`,onClick:()=>{b(t),w(null),S(null),N&&me.current!==null&&t!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))},title:e.name,children:[(0,k.jsx)(`span`,{className:Q.ideTabIcon,children:e._pending?`⌛`:n?`⚠`:rE(e.name)}),(0,k.jsx)(`span`,{className:Q.ideTabName,children:(e.name||``).split(`/`).pop()}),ee.has(e.name)&&(0,k.jsx)(`span`,{className:Q.ideTabUnsaved,children:`●`}),n&&(0,k.jsx)(`span`,{className:Q.ideTabDot})]},t)})}),xe&&(0,k.jsxs)(`div`,{className:Q.diffOverlay,children:[(0,k.jsxs)(`div`,{className:Q.diffOverlayHeader,children:[(0,k.jsxs)(`span`,{children:[`✏ Modifica proposta — `,(0,k.jsx)(`strong`,{children:xe.file})]}),(0,k.jsxs)(`div`,{className:Q.diffOverlayActions,children:[(0,k.jsx)(`button`,{className:Q.diffAcceptBtn,onClick:()=>{v(e=>e.map(e=>e.name===xe.file?{...e,content:xe.after}:e)),Se(null)},children:`✓ Accetta`}),(0,k.jsx)(`button`,{className:Q.diffRejectBtn,onClick:()=>Se(null),children:`✕ Rifiuta`})]})]}),(0,k.jsx)(`div`,{className:Q.diffOverlayBody,children:(0,k.jsx)(uE,{before:xe.before,after:xe.after})})]}),(0,k.jsxs)(`div`,{className:Q.codeRow,children:[(0,k.jsxs)(`div`,{className:Q.fileTreeWrap,children:[fe.length>0&&(0,k.jsxs)(`div`,{className:Q.scanBanner,children:[(0,k.jsx)(`span`,{className:Q.scanBannerIcon,children:`⚠`}),(0,k.jsxs)(`span`,{className:Q.scanBannerText,children:[fe.length,` issue`,fe.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:Q.scanBannerFix,onClick:async()=>{i(`files`);try{let e=await E(`/api/studio/webcraft/auto-repair`,{projectName:c});if(e?.summary){let t=(e.repairs||[]).slice(0,20).map(e=>`${e.file} (${e.kind}${e.source?` from ${e.source}`:``})`).join(`
856
+ `);Ie(n=>[...n,{role:`agent`,text:`🔧 Auto-repair: ${e.summary}\n${t}`,tools:[]}])}await It([...e?.filesRepaired||[],...e?.filesCreated||[]],{}),await kt(c)}catch(e){console.error(`[auto-repair] failed:`,e)}},children:`Fix`})]}),(0,k.jsx)(WT,{files:g,activeIndex:y,unsavedFiles:ee,errorFiles:new Set(fe.filter(e=>e.severity===`error`).map(e=>e.file)),onSelect:e=>{b(e),w(null),S(null),N&&me.current!==null&&e!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))}})]}),(0,k.jsx)(`div`,{className:Q.codeEditorWrap,children:rn&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(rn.name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:rn.name}),rn.content&&!rn._error&&(0,k.jsxs)(`span`,{className:Q.codeFileMeta,children:[(rn.content||``).split(`
857
857
  `).length,` righe · `,iE(rn.content||``)]}),!rn._pending&&!rn._error&&rn.content&&(0,k.jsx)(`button`,{className:`${Q.editToggleBtn} ${C===null?``:Q.editToggleBtnActive}`,onClick:()=>{C===null?w(rn.content):(v(e=>e.map((e,t)=>t===y?{...e,content:C}:e)),E(`/api/studio/webcraft/file/write`,{projectName:c,path:rn.name,content:C}),w(null))},children:C===null?`✏ Modifica`:`💾 Salva`}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,title:`Split view`,onClick:()=>ce(se===null?+(y===0&&g.length>1):null),children:`⫼`}),(0,k.jsx)(`button`,{className:`${Q.headerIconBtn} ${A?Q.headerIconBtnActive:``}`,title:`Terminal`,onClick:()=>oe(!A),children:`⌨`}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,title:`Development Guide`,onClick:()=>ue(!0),children:`📖`})]}),ne&&(0,k.jsxs)(`div`,{className:Q.findBar,children:[(0,k.jsx)(`input`,{className:Q.findInput,value:D,onChange:e=>ie(e.target.value),placeholder:`Find...`,autoFocus:!0}),(0,k.jsx)(`input`,{className:Q.findInput,value:O,onChange:e=>ae(e.target.value),placeholder:`Replace...`}),(0,k.jsx)(`span`,{className:Q.findCount,children:D?((rn.content||``).match(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`))?.length||0)+` found`:``}),(0,k.jsx)(`button`,{className:Q.findBtn,onClick:()=>{!D||C===null||w(C.replace(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`i`),O))},children:`Replace`}),(0,k.jsx)(`button`,{className:Q.findBtn,onClick:()=>{!D||C===null||w(C.replace(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`),O))},children:`All`}),(0,k.jsx)(`button`,{className:Q.findClose,onClick:()=>re(!1),children:`×`})]}),rn._error&&(0,k.jsx)(`div`,{className:Q.fileError,children:`⚠ Generazione fallita — chiedi al modello di rigenerare questo file`}),rn._syntaxError&&!rn._error&&(0,k.jsxs)(`div`,{className:Q.fileSyntaxError,children:[`⚠ Syntax error: `,rn._syntaxError]}),N&&x!==null?(0,k.jsx)(`pre`,{className:Q.streamingPre,ref:e=>{e&&ge.current&&(e.scrollTop=e.scrollHeight)},onScroll:e=>{let t=e.currentTarget;t.scrollHeight-t.scrollTop-t.clientHeight<50?ge.current=!0:(ge.current=!1,_e.current&&clearTimeout(_e.current),_e.current=setTimeout(()=>{ge.current=!0},15e3))},dangerouslySetInnerHTML:{__html:QT(x||``,(rn.name.split(`.`).pop()||`js`).toLowerCase())+`<span class="`+Q.streamingCursor+`">▋</span>`}}):(0,k.jsx)(RT,{value:C===null?rn.content||``:C,filename:rn.name,readOnly:C===null,projectName:c,onChange:e=>{w(e),rn&&te(e=>new Set(e).add(rn.name))},onSave:e=>{v(t=>t.map((t,n)=>n===y?{...t,content:e}:t)),E(`/api/studio/webcraft/file/write`,{projectName:c,path:rn.name,content:e}),w(null),te(e=>{let t=new Set(e);return t.delete(rn.name),t})}})]})}),se!==null&&g[se]&&(0,k.jsxs)(`div`,{className:Q.codeEditorWrap,children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(g[se].name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:g[se].name}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,onClick:()=>ce(null),children:`✕`})]}),(0,k.jsx)(RT,{value:g[se].content||``,filename:g[se].name,readOnly:!0})]})]}),A&&(0,k.jsxs)(`div`,{className:Q.terminalPanel,children:[(0,k.jsxs)(`div`,{className:Q.terminalHeader,children:[(0,k.jsx)(`span`,{className:Q.terminalTitle,children:`Terminal`}),(0,k.jsx)(`button`,{className:Q.terminalClose,onClick:()=>oe(!1),children:`✕`})]}),(0,k.jsx)(YT,{projectDir:c||void 0})]})]})})]})]})]})}),He&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.planBanner,children:[(0,k.jsx)(`div`,{className:Q.planTitle,children:`📌 Piano proposto — approva per eseguire`}),(0,k.jsx)(`pre`,{className:Q.planText,children:He.plan}),(0,k.jsxs)(`div`,{className:Q.planActions,children:[(0,k.jsx)(`button`,{className:Q.planApprove,onClick:en,children:`✓ Esegui`}),(0,k.jsx)(`button`,{className:Q.planReject,onClick:()=>Ue(null),children:`✕ Annulla`})]})]}),Qe&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.grepPanel,children:[(0,k.jsxs)(`div`,{className:Q.grepRow,children:[(0,k.jsx)(`input`,{className:Q.grepInput,value:et,onChange:e=>tt(e.target.value),onKeyDown:e=>e.key===`Enter`&&Kt(),placeholder:`Cerca nel codice...`}),(0,k.jsx)(`button`,{className:Q.grepBtn,onClick:Kt,children:`🔍`}),(0,k.jsx)(`button`,{className:Q.grepClose,onClick:()=>$e(!1),children:`×`})]}),nt.length>0&&(0,k.jsxs)(`div`,{className:Q.grepCount,children:[nt.length,` risultati`]}),(0,k.jsx)(`div`,{className:Q.grepResults,children:nt.length===0?(0,k.jsx)(`div`,{className:Q.grepEmpty,children:`Nessun risultato.`}):nt.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.grepMatch,onClick:()=>qt(e.file),children:[(0,k.jsxs)(`span`,{className:Q.grepMatchFile,children:[e.file,`:`,e.lineNum]}),(0,k.jsx)(`pre`,{className:Q.grepMatchLine,children:e.line})]},t))})]}),it.length>0&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.diffPanel,children:[(0,k.jsxs)(`div`,{className:Q.diffHeader,children:[(0,k.jsxs)(`span`,{children:[`🔌 Diff — `,it.length,` file modificati`]}),(0,k.jsx)(`button`,{className:Q.diffClose,onClick:()=>at([]),children:`✕ Chiudi`})]}),it.map((e,t)=>{let n=(e.after||``).split(`
858
858
  `).length-(e.before||``).split(`
859
859
  `).length;return(0,k.jsxs)(`details`,{open:!0,className:Q.diffFile,children:[(0,k.jsxs)(`summary`,{className:Q.diffSummary,children:[(0,k.jsx)(`span`,{className:Q.diffArrow,children:`▲`}),(0,k.jsx)(`span`,{className:Q.diffFileName,children:e.file}),(0,k.jsxs)(`span`,{className:n>=0?Q.diffAdded:Q.diffRemoved,children:[n>=0?`+`:``,n,` linee`]})]}),(0,k.jsx)(`div`,{className:Q.diffContent,children:(0,k.jsx)(uE,{before:e.before,after:e.after})})]},t)})]}),t!==`projects`&&(0,k.jsxs)(`div`,{className:`${Q.chatPanel} ${ut?Q.chatPanelCollapsed:``}`,children:[(0,k.jsxs)(`button`,{className:Q.chatCollapseBtn,onClick:()=>dt(e=>!e),children:[(0,k.jsx)(`span`,{children:ut?`▲`:`▼`}),(0,k.jsx)(`span`,{children:ut?`Show Chat`:`Hide Chat`}),Fe.length>0&&(0,k.jsxs)(`span`,{style:{opacity:.5},children:[`(`,Fe.length,`)`]})]}),(0,k.jsxs)(`div`,{className:Q.chatMessages,ref:Tt,children:[Fe.length===0&&nn&&(0,k.jsxs)(`div`,{className:Q.chatWelcome,children:[`🤖 `,e(`webcraft.doctrine.title`),` — `,(0,k.jsx)(`button`,{className:Q.doctrineOpenBtn,onClick:()=>ue(!0),children:`📖 Open Guide`})]}),Fe.map((e,t)=>(0,k.jsxs)(`div`,{className:e.role===`user`?Q.chatUser:e.role===`system`?Q.chatSystem:Q.chatAgent,children:[e.role===`user`&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`div`,{className:Q.chatUserBubble,children:e.text}),e.attachments&&e.attachments.length>0&&(0,k.jsx)(`div`,{className:Q.chatAttachPreviews,children:e.attachments.map((e,t)=>(0,k.jsxs)(`span`,{className:Q.chatAttachBadge,children:[`📎 `,e.name]},t))})]}),e.role===`system`&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`div`,{className:Q.chatSystemBubble,children:e.text}),e.syntaxErrors?.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.chatSyntaxErr,children:[`✕ `,e.file,`: `,e.error]},t))]}),e.role===`agent`&&(()=>{let t=e.text.replace(/<tool>[\s\S]*?<\/tool>/g,``).replace(/<done\s*\/?>/g,``).trim(),n=(e.tools||[]).filter(e=>(e.op===`edit`||e.op===`write`)&&(e.result===`ok`||e.result===`ok_fuzzy`||e.result===`ok_repaired`)),r=(e.tools||[]).filter(e=>e.result?.includes(`not_found`)||e.result?.includes(`error`)||e.result===`blocked_use_edit`),a=t.match(/^(.{10,120}?)[.\n]/),o=a?a[1]+`.`:t.slice(0,120),s=t.length>130;return(0,k.jsxs)(`div`,{className:Q.chatAgentCard,children:[n.map((e,t)=>(0,k.jsxs)(`div`,{style:{margin:`6px 0`,borderRadius:8,overflow:`hidden`,border:`1px solid rgba(255,255,255,0.08)`},children:[(0,k.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,padding:`6px 10px`,background:`rgba(99,102,241,0.1)`,fontSize:11},children:[(0,k.jsxs)(`span`,{style:{fontWeight:600,color:`#818cf8`,cursor:`pointer`},onClick:()=>{let t=g.findIndex(t=>t.name===e.path);t>=0&&(b(t),i(`files`))},children:[`✏ `,e.path]}),(0,k.jsx)(`span`,{style:{color:`#4ade80`,fontSize:10,fontWeight:600},children:e.result===`ok_fuzzy`?`applied (fuzzy)`:e.result===`ok_repaired`?`applied (repaired)`:`✓ applied`})]}),e.oldSnippet||e.newSnippet?(0,k.jsx)(uE,{before:e.oldSnippet||``,after:e.newSnippet||``,contextLines:3}):(0,k.jsx)(`div`,{style:{padding:`6px 10px`,fontSize:11,color:`#4ade80`,background:`rgba(74,222,128,0.05)`},children:`File modified successfully`})]},t)),r.length>0&&(0,k.jsx)(`div`,{style:{margin:`6px 0`,padding:`6px 10px`,background:`rgba(248,113,113,0.08)`,borderRadius:6,fontSize:11,color:`#f87171`},children:r.map((e,t)=>(0,k.jsxs)(`div`,{children:[`❌ `,e.op,` `,e.path,`: `,typeof e.result==`string`?e.result.slice(0,100):``]},t))}),t&&(s?(0,k.jsxs)(`details`,{style:{margin:`6px 0`,fontSize:11},children:[(0,k.jsx)(`summary`,{style:{cursor:`pointer`,color:`var(--dim)`,padding:`4px 0`,userSelect:`none`},children:o.slice(0,100)}),(0,k.jsx)(`div`,{className:Q.chatAgentText,style:{fontSize:11,opacity:.8,marginTop:4},dangerouslySetInnerHTML:{__html:$T(t)}})]}):(0,k.jsx)(`div`,{className:Q.chatAgentText,style:{fontSize:11,opacity:.8},dangerouslySetInnerHTML:{__html:$T(t)}}))]})})()]},t)),ze&&(()=>{let e=Fe[Fe.length-1]?.tools??[],t=e[e.length-1],n=t?t.op===`read`?`Reading ${t.path}`:t.op===`edit`?`Editing ${t.path}`:t.op===`search`?`Searching...`:t.op===`lint`?`Linting ${t.path}`:t.op===`check`?`Checking ${t.path}`:t.op===`run`?`Running command...`:t.op===`sandbox`?`Starting sandbox...`:t.op:`Thinking...`;return(0,k.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8,padding:`8px 12px`,fontSize:12,color:`#818cf8`},children:[(0,k.jsx)(`span`,{className:Q.chatAgentRobotAnim,style:{fontSize:14},children:`⟳`}),(0,k.jsx)(`span`,{style:{fontWeight:500},children:n})]})})()]}),Be.length>0&&(0,k.jsx)(`div`,{className:Q.attachPreviews,children:Be.map((e,t)=>(0,k.jsxs)(`span`,{className:Q.attachBadge,children:[`📎 `,e.name,(0,k.jsx)(`button`,{className:Q.removeAttachBtn,onClick:()=>Ve(e=>e.filter((e,n)=>n!==t)),children:`×`})]},t))}),nn?(0,k.jsxs)(`div`,{className:Q.projActiveRow,children:[`📄 `,(0,k.jsx)(`strong`,{className:Q.projActiveName,children:c}),` — scrivi per modificare o migliorare il progetto`]}):(0,k.jsxs)(`div`,{className:Q.projNameRow,children:[(0,k.jsx)(`span`,{className:Q.projNameLabel,children:`Nome progetto:`}),(0,k.jsx)(`input`,{className:Q.projNameInput,value:c,onChange:e=>l(e.target.value),placeholder:`MioProgetto`})]}),(0,k.jsxs)(`div`,{className:Q.chatInputRow,children:[(0,k.jsxs)(`label`,{className:Q.attachLabel,title:`Allega immagine o PDF`,children:[`📎`,(0,k.jsx)(`input`,{ref:Et,type:`file`,multiple:!0,accept:`image/*,.pdf`,style:{display:`none`},onChange:e=>tn(e.target.files)})]}),(0,k.jsx)(`textarea`,{className:Q.chatTextarea,value:Le,onChange:e=>Re(e.target.value),placeholder:nn?`Parla con il tuo agente: chiedi correzioni, migliorie, nuove funzionalità...`:`Descrivi il progetto da creare, poi premi Genera...`,disabled:U,onKeyDown:e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),Pt())},rows:4}),(0,k.jsxs)(`div`,{className:Q.chatSendCol,children:[(0,k.jsx)(`button`,{className:Q.chatSendBtn,onClick:()=>Pt(),disabled:U,children:N?`⏳`:nn?`▶`:`▶ Genera`}),U&&!we&&(0,k.jsx)(`button`,{className:Q.chatStopBtn,onClick:Lt,children:`⏹ Stop`})]})]})]}),le&&(0,k.jsx)(`div`,{className:Q.modalOverlay,onClick:()=>ue(!1),children:(0,k.jsxs)(`div`,{className:Q.modal,onClick:e=>e.stopPropagation(),style:{width:720,maxHeight:`90vh`},children:[(0,k.jsxs)(`div`,{className:Q.modalHeader,children:[(0,k.jsxs)(`span`,{className:Q.modalTitle,children:[`📖 `,e(`webcraft.doctrine.title`)]}),(0,k.jsx)(`span`,{className:Q.doctrineSubtitle,children:e(`webcraft.doctrine.subtitle`)}),(0,k.jsx)(`button`,{className:Q.modalClose,onClick:()=>ue(!1),children:`✕`})]}),(0,k.jsx)(`div`,{className:Q.modalBody,style:{gap:0},children:[`phase1`,`phase2`,`phase3`,`phase4`,`phase5`,`tools`,`golden`].map(t=>(0,k.jsxs)(`div`,{className:Q.doctrineSection,children:[(0,k.jsx)(`div`,{className:Q.doctrineSectionTitle,children:e(`webcraft.doctrine.${t}.title`)}),(0,k.jsx)(`div`,{className:Q.doctrineSectionBody,children:e(`webcraft.doctrine.${t}.desc`).split(`
@@ -8,7 +8,7 @@
8
8
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
10
  <title>NHA — NotHumanAllowed</title>
11
- <script type="module" crossorigin src="/assets/index-DfKlqTu1.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-Dy-GFsjK.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-nUBdqB1O.css">
13
13
  </head>
14
14
  <body>