nothumanallowed 15.0.19 → 15.1.1
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": "15.
|
|
3
|
+
"version": "15.1.1",
|
|
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": {
|
|
@@ -19,7 +19,7 @@ import { createServer } from 'net';
|
|
|
19
19
|
import { promisify } from 'util';
|
|
20
20
|
import { sendJSON, sendError, parseBody, sendSSE } from '../index.mjs';
|
|
21
21
|
import { loadConfig } from '../../config.mjs';
|
|
22
|
-
import { callLLM, callLLMStream, fixQwen3BPE } from '../../services/llm.mjs';
|
|
22
|
+
import { callLLM, callLLMStream, callLLMWithTools, getApiKey, fixQwen3BPE } from '../../services/llm.mjs';
|
|
23
23
|
import { NHA_DIR } from '../../constants.mjs';
|
|
24
24
|
import * as acorn from 'acorn';
|
|
25
25
|
import acornJsx from 'acorn-jsx';
|
|
@@ -616,41 +616,31 @@ async function runWebCraftAgent(config, projectName, message, attachments, emit,
|
|
|
616
616
|
const LANG_MAP = { en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese' };
|
|
617
617
|
const language = LANG_MAP[(config?.language||'it').slice(0,2)] || 'Italian';
|
|
618
618
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
⚠ CRITICAL RULES:
|
|
646
|
-
- To modify an existing file: ALWAYS use edit. NEVER use write on existing files — it will be REJECTED.
|
|
647
|
-
- edit workflow: read the file first → copy EXACT lines from the read output → use edit with old/new
|
|
648
|
-
- To APPEND content to a truncated file: use edit where "old" is the last few lines of the file, and "new" is those same lines PLUS the new content after them.
|
|
649
|
-
- Keep each edit SMALL — max 30-40 lines of old/new. If you need to add more, do MULTIPLE edits.
|
|
650
|
-
- If edit returns "old_not_found": your old text didn't match. Read the file again and retry with the EXACT text.
|
|
651
|
-
- NEVER say you fixed something without verifying — use check or read after edit to confirm.
|
|
652
|
-
- write is ONLY for creating brand new files
|
|
653
|
-
- Output <done/> when finished
|
|
619
|
+
// Native tool definitions for function calling (Anthropic/OpenAI)
|
|
620
|
+
const nativeTools = [
|
|
621
|
+
{ name: 'read_file', description: 'Read a file from the project', input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path to the file' } }, required: ['path'] } },
|
|
622
|
+
{ name: 'edit_file', description: 'Make a surgical edit to an existing file. The old_text must be an EXACT match of the current file content. Copy-paste from read_file output.', input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path' }, old_text: { type: 'string', description: 'Exact text to replace (copy from read_file output)' }, new_text: { type: 'string', description: 'Replacement text' } }, required: ['path', 'old_text', 'new_text'] } },
|
|
623
|
+
{ name: 'create_file', description: 'Create a new file. ONLY for files that do not exist yet. Cannot overwrite existing files.', input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path' }, content: { type: 'string', description: 'Full file content' } }, required: ['path', 'content'] } },
|
|
624
|
+
{ name: 'delete_file', description: 'Delete a file', input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path' } }, required: ['path'] } },
|
|
625
|
+
{ name: 'list_files', description: 'List all project files', input_schema: { type: 'object', properties: {} } },
|
|
626
|
+
{ name: 'search_files', description: 'Search for text in project files', input_schema: { type: 'object', properties: { query: { type: 'string', description: 'Search pattern' }, glob: { type: 'string', description: 'File glob pattern (e.g. *.js)' } }, required: ['query'] } },
|
|
627
|
+
{ name: 'run_command', description: 'Run a shell command in the project directory', input_schema: { type: 'object', properties: { command: { type: 'string', description: 'Shell command' } }, required: ['command'] } },
|
|
628
|
+
{ name: 'check_syntax', description: 'Check file for syntax errors', input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path' } }, required: ['path'] } },
|
|
629
|
+
{ name: 'restart_sandbox', description: 'Restart the sandbox server to test changes', input_schema: { type: 'object', properties: {} } },
|
|
630
|
+
];
|
|
631
|
+
|
|
632
|
+
// System prompt for native tool calling (no <tool> tags needed)
|
|
633
|
+
const toolInstructions = `
|
|
634
|
+
WORKFLOW: read_file → edit_file (surgical changes) → check_syntax → restart_sandbox
|
|
635
|
+
|
|
636
|
+
RULES:
|
|
637
|
+
- To modify existing files: ALWAYS use edit_file. NEVER use create_file on existing files — it will fail.
|
|
638
|
+
- edit_file: read the file first, then copy EXACT text from the read output as old_text.
|
|
639
|
+
- To APPEND to a truncated file: old_text = last few lines, new_text = those lines + new content.
|
|
640
|
+
- Keep each edit SMALL — max 30-40 lines. Do MULTIPLE edits for larger changes.
|
|
641
|
+
- If edit_file fails (old_text not found): read the file again, copy the EXACT text, retry.
|
|
642
|
+
- NEVER say you fixed something without verifying with check_syntax.
|
|
643
|
+
- create_file is ONLY for brand new files.
|
|
654
644
|
`;
|
|
655
645
|
|
|
656
646
|
// Build system prompt with fresh file list each step
|
|
@@ -685,16 +675,14 @@ WORKFLOW:
|
|
|
685
675
|
return [
|
|
686
676
|
`You are WebCraft Agent — an elite AI coding assistant. Today: ${today}. Language: ${language}.`,
|
|
687
677
|
`\nYou control the project IDE. You MUST use tools to implement changes — never just explain.`,
|
|
688
|
-
`\nYour workflow: read → plan → edit/write → check → verify → done.`,
|
|
689
678
|
`\nAfter EVERY tool use, you will receive the result. Based on the result, decide what to do next.`,
|
|
690
|
-
`\nWhen finished, output <done/> to signal completion.`,
|
|
691
679
|
`\n\n## PROJECT: ${projectName}`,
|
|
692
680
|
`\n## FILE TREE:\n${fileIndex}`,
|
|
693
681
|
skillContext,
|
|
694
682
|
skillCtx ? `\n\n## PROJECT KNOWLEDGE:\n${skillCtx}` : '',
|
|
695
683
|
attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
|
|
696
684
|
fileContents ? `\n\n## LOADED FILES:\n${fileContents}` : '',
|
|
697
|
-
`\n\n${
|
|
685
|
+
`\n\n${toolInstructions}`,
|
|
698
686
|
].join('');
|
|
699
687
|
}
|
|
700
688
|
|
|
@@ -703,17 +691,164 @@ WORKFLOW:
|
|
|
703
691
|
? _buildMultimodalContent(message, attachments)
|
|
704
692
|
: message;
|
|
705
693
|
|
|
706
|
-
// ── Agentic loop
|
|
694
|
+
// ── Agentic loop — native tool calling ────────────────────────────────────
|
|
707
695
|
let hasChanges = false;
|
|
708
696
|
const modifiedFiles = new Set();
|
|
709
|
-
let conversationHistory = [
|
|
710
|
-
{ role: 'user', content: userContent },
|
|
711
|
-
];
|
|
712
697
|
|
|
713
|
-
|
|
714
|
-
|
|
698
|
+
// Tool execution handler — called by callLLMWithTools for each tool_use
|
|
699
|
+
async function handleToolCall(toolName, input) {
|
|
700
|
+
const relPath = input.path;
|
|
701
|
+
if (relPath && !_isSafePath(relPath)) return 'Error: unsafe path';
|
|
702
|
+
|
|
703
|
+
if (toolName === 'read_file') {
|
|
704
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
705
|
+
emit({ type: 'tool', op: 'read', path: relPath, result: src ? 'ok' : 'not_found' });
|
|
706
|
+
return src !== null ? src.slice(0, 16000) : 'Error: file not found';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (toolName === 'edit_file') {
|
|
710
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
711
|
+
if (!src) { emit({ type: 'tool', op: 'edit', path: relPath, result: 'file_not_found' }); return 'Error: file not found'; }
|
|
712
|
+
const oldStr = input.old_text;
|
|
713
|
+
const newStr = input.new_text;
|
|
714
|
+
if (src.includes(oldStr)) {
|
|
715
|
+
const newSrc = src.replace(oldStr, newStr ?? '');
|
|
716
|
+
ProjectStore.writeFile(projectName, relPath, newSrc);
|
|
717
|
+
hasChanges = true;
|
|
718
|
+
modifiedFiles.add(relPath);
|
|
719
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 2000), newSnippet: (newStr ?? '').slice(0, 2000) });
|
|
720
|
+
return 'OK — edit applied successfully';
|
|
721
|
+
}
|
|
722
|
+
// Fuzzy match
|
|
723
|
+
const oldLines = oldStr.split('\n').map(l => l.trim());
|
|
724
|
+
const srcLines = src.split('\n');
|
|
725
|
+
let matchStart = -1;
|
|
726
|
+
for (let i = 0; i <= srcLines.length - oldLines.length; i++) {
|
|
727
|
+
let ok = true;
|
|
728
|
+
for (let j = 0; j < oldLines.length; j++) {
|
|
729
|
+
if (srcLines[i + j].trim() !== oldLines[j]) { ok = false; break; }
|
|
730
|
+
}
|
|
731
|
+
if (ok) { matchStart = i; break; }
|
|
732
|
+
}
|
|
733
|
+
if (matchStart >= 0) {
|
|
734
|
+
const before = srcLines.slice(0, matchStart).join('\n');
|
|
735
|
+
const after = srcLines.slice(matchStart + oldLines.length).join('\n');
|
|
736
|
+
const result = (before ? before + '\n' : '') + (newStr ?? '') + (after ? '\n' + after : '');
|
|
737
|
+
ProjectStore.writeFile(projectName, relPath, result);
|
|
738
|
+
hasChanges = true;
|
|
739
|
+
modifiedFiles.add(relPath);
|
|
740
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 2000), newSnippet: (newStr ?? '').slice(0, 2000) });
|
|
741
|
+
return 'OK — edit applied (fuzzy match)';
|
|
742
|
+
}
|
|
743
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found' });
|
|
744
|
+
return 'Error: old_text not found in file. Use read_file to see the EXACT current content, copy the exact lines, and retry.';
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (toolName === 'create_file') {
|
|
748
|
+
const existing = ProjectStore.readFile(projectName, relPath);
|
|
749
|
+
if (existing !== null) {
|
|
750
|
+
emit({ type: 'tool', op: 'write', path: relPath, result: 'blocked_use_edit' });
|
|
751
|
+
return 'Error: file already exists. Use edit_file to modify it.';
|
|
752
|
+
}
|
|
753
|
+
ProjectStore.writeFile(projectName, relPath, input.content ?? '');
|
|
754
|
+
hasChanges = true;
|
|
755
|
+
modifiedFiles.add(relPath);
|
|
756
|
+
emit({ type: 'tool', op: 'write', path: relPath, result: 'ok', newSnippet: (input.content ?? '').slice(0, 500) });
|
|
757
|
+
return 'OK — file created';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (toolName === 'delete_file') {
|
|
761
|
+
const abs = path.join(dir, relPath);
|
|
762
|
+
if (fs.existsSync(abs)) { fs.unlinkSync(abs); hasChanges = true; modifiedFiles.add(relPath); }
|
|
763
|
+
emit({ type: 'tool', op: 'delete', path: relPath, result: 'ok' });
|
|
764
|
+
return 'OK — file deleted';
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (toolName === 'list_files') {
|
|
768
|
+
const files = _listProjectFiles(dir);
|
|
769
|
+
emit({ type: 'tool', op: 'list', path: '', result: `${files.length} files` });
|
|
770
|
+
return files.map(f => `- ${f}`).join('\n');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (toolName === 'search_files') {
|
|
774
|
+
const matches = ProjectStore.grep(projectName, input.query);
|
|
775
|
+
emit({ type: 'tool', op: 'search', path: '', result: `${matches.length} matches` });
|
|
776
|
+
return matches.length === 0 ? 'No matches found' : matches.slice(0, 20).map(m => `${m.file}:${m.lineNum}: ${m.line}`).join('\n');
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (toolName === 'run_command') {
|
|
780
|
+
const blocked = /rm\s+-rf|rmdir|format|mkfs|dd\s+if|shutdown|reboot|kill\s+-9\s+1\b/i;
|
|
781
|
+
if (blocked.test(input.command)) { emit({ type: 'tool', op: 'run', path: '', result: 'blocked' }); return 'Error: dangerous command blocked'; }
|
|
782
|
+
try {
|
|
783
|
+
const { stdout, stderr } = await execAsync(input.command, { cwd: dir, timeout: 30000, env: { ...process.env, NODE_ENV: 'development' } });
|
|
784
|
+
emit({ type: 'tool', op: 'run', path: '', result: 'ok' });
|
|
785
|
+
return (stdout + (stderr ? '\n[stderr] ' + stderr : '')).slice(0, 4000) || 'OK — no output';
|
|
786
|
+
} catch (e) {
|
|
787
|
+
emit({ type: 'tool', op: 'run', path: '', result: 'error' });
|
|
788
|
+
return `Error: ${(e.stderr || e.message || '').slice(0, 2000)}`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (toolName === 'check_syntax') {
|
|
793
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
794
|
+
if (!src) { emit({ type: 'tool', op: 'check', path: relPath, result: 'not_found' }); return 'File not found'; }
|
|
795
|
+
const ext = relPath.split('.').pop()?.toLowerCase();
|
|
796
|
+
let result = 'ok';
|
|
797
|
+
if (ext === 'js' || ext === 'mjs') { try { new Function(src); } catch (e) { result = `syntax_error: ${e.message.replace(/\n.*/s, '')}`; } }
|
|
798
|
+
else if (ext === 'json') { try { JSON.parse(src); } catch (e) { result = `json_error: ${e.message}`; } }
|
|
799
|
+
else if (ext === 'html') { result = src.includes('</html>') ? 'ok' : 'missing </html> tag'; }
|
|
800
|
+
emit({ type: 'tool', op: 'check', path: relPath, result });
|
|
801
|
+
return result === 'ok' ? 'OK — no syntax errors' : result;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (toolName === 'restart_sandbox') {
|
|
805
|
+
if (sandbox.isRunning()) await sandbox.stop();
|
|
806
|
+
try {
|
|
807
|
+
const port = await _findFreePort(4000, 4999);
|
|
808
|
+
if (port) {
|
|
809
|
+
const shimDir = path.join(dir, '.nha-shims');
|
|
810
|
+
ensureDir(shimDir);
|
|
811
|
+
_writeShims(shimDir);
|
|
812
|
+
const entryFile = _detectEntry(dir);
|
|
813
|
+
if (entryFile) {
|
|
814
|
+
const patchedEntry = _patchEntry(dir, entryFile, shimDir, port);
|
|
815
|
+
const proc = spawn('node', [patchedEntry], { cwd: dir, env: { ...process.env, PORT: String(port), NODE_ENV: 'development', NHA_SANDBOX: '1' }, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
816
|
+
sandbox._sandbox = { proc, port, projectName, startedAt: new Date(), healthy: false };
|
|
817
|
+
const healthy = await _waitForPort(port, 10000);
|
|
818
|
+
if (healthy) { sandbox._sandbox.healthy = true; emit({ type: 'sandbox_ready', port }); return `OK — sandbox running on port ${port}`; }
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
} catch {}
|
|
822
|
+
emit({ type: 'tool', op: 'sandbox', path: '', result: 'error' });
|
|
823
|
+
return 'Error: sandbox failed to start';
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return `Error: unknown tool ${toolName}`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Try native tool calling first (Anthropic, OpenAI)
|
|
830
|
+
const provider = config.llm?.provider || 'anthropic';
|
|
831
|
+
const useNativeTools = provider === 'anthropic' || provider === 'openai';
|
|
832
|
+
|
|
833
|
+
if (useNativeTools) {
|
|
715
834
|
const systemPrompt = buildSystemPrompt();
|
|
716
|
-
|
|
835
|
+
const messages = [{ role: 'user', content: userContent }];
|
|
836
|
+
|
|
837
|
+
await callLLMWithTools(config, systemPrompt, messages, nativeTools,
|
|
838
|
+
(text) => emit({ type: 'text', token: text }),
|
|
839
|
+
handleToolCall,
|
|
840
|
+
{ max_tokens: 16384, isAborted, maxTurns: MAX_STEPS }
|
|
841
|
+
);
|
|
842
|
+
} else {
|
|
843
|
+
// Fallback for other providers — use old text-based <tool> system
|
|
844
|
+
// (kept for backward compatibility with Gemini, DeepSeek, etc.)
|
|
845
|
+
let conversationHistory = [{ role: 'user', content: userContent }];
|
|
846
|
+
emit({ type: 'text', token: 'Note: Using text-based tools (native tool calling not available for this provider).\n' });
|
|
847
|
+
// Minimal old-style loop with <tool> tags — simplified
|
|
848
|
+
for (let step = 0; step < MAX_STEPS; step++) {
|
|
849
|
+
if (isAborted()) break;
|
|
850
|
+
const systemPrompt = buildSystemPrompt();
|
|
851
|
+
emit({ type: 'step', step: step + 1, max: MAX_STEPS });
|
|
717
852
|
|
|
718
853
|
// Build user message from conversation history
|
|
719
854
|
// callLLMStream takes a single string, so we concatenate the conversation
|
|
@@ -867,18 +1002,9 @@ WORKFLOW:
|
|
|
867
1002
|
toolResults.push({ op: 'edit', path: relPath, result: 'ok' });
|
|
868
1003
|
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 2000), newSnippet: newStr?.slice(0, 2000) });
|
|
869
1004
|
} else {
|
|
870
|
-
// No
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
ProjectStore.writeFile(projectName, relPath, repaired);
|
|
874
|
-
hasChanges = true;
|
|
875
|
-
modifiedFiles.add(relPath);
|
|
876
|
-
toolResults.push({ op: 'edit', path: relPath, result: 'ok_repaired' });
|
|
877
|
-
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok_repaired', oldSnippet: oldStr.slice(0, 2000), newSnippet: newStr?.slice(0, 2000) });
|
|
878
|
-
} else {
|
|
879
|
-
toolResults.push({ op: 'edit', path: relPath, result: 'old_not_found — read the file first, copy EXACT text to replace, then retry' });
|
|
880
|
-
emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found', oldSnippet: oldStr.slice(0, 200) });
|
|
881
|
-
}
|
|
1005
|
+
// No match — return error, let the agent retry with correct text
|
|
1006
|
+
toolResults.push({ op: 'edit', path: relPath, result: 'old_not_found — your old text does not match the file. Use read tool to see the EXACT current content, then copy-paste the exact lines and retry.' });
|
|
1007
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found', oldSnippet: oldStr.slice(0, 200) });
|
|
882
1008
|
}
|
|
883
1009
|
}
|
|
884
1010
|
|
|
@@ -1156,6 +1282,7 @@ WORKFLOW:
|
|
|
1156
1282
|
conversationHistory = [conversationHistory[0], ...conversationHistory.slice(-6)];
|
|
1157
1283
|
}
|
|
1158
1284
|
}
|
|
1285
|
+
} // end of fallback else block
|
|
1159
1286
|
|
|
1160
1287
|
// ── Post-edit: syntax check all modified JS files ──────────────────────────
|
|
1161
1288
|
const syntaxErrors = [];
|
|
@@ -2856,6 +2983,7 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
|
|
|
2856
2983
|
const launcher = [
|
|
2857
2984
|
`// NHA WebCraft Sandbox Launcher — auto-generated`,
|
|
2858
2985
|
`process.env.PORT = '${port}';`,
|
|
2986
|
+
`process.env.HOST = '0.0.0.0';`,
|
|
2859
2987
|
`process.env.NODE_ENV = 'development';`,
|
|
2860
2988
|
`require('${shimAbs}');`,
|
|
2861
2989
|
`// Inject error reporter into HTML responses`,
|
|
@@ -3023,17 +3151,14 @@ function _waitForPort(port, timeoutMs) {
|
|
|
3023
3151
|
return new Promise((resolve) => {
|
|
3024
3152
|
const deadline = Date.now() + timeoutMs;
|
|
3025
3153
|
const check = () => {
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
});
|
|
3031
|
-
sock.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
if (Date.now() > deadline) { resolve(false); return; }
|
|
3035
|
-
setTimeout(check, 300);
|
|
3036
|
-
});
|
|
3154
|
+
// Try to connect to the port — if connection succeeds, the server is listening
|
|
3155
|
+
const { Socket } = require('net');
|
|
3156
|
+
const sock = new Socket();
|
|
3157
|
+
sock.setTimeout(500);
|
|
3158
|
+
sock.once('connect', () => { sock.destroy(); resolve(true); });
|
|
3159
|
+
sock.once('error', () => { sock.destroy(); if (Date.now() > deadline) { resolve(false); } else { setTimeout(check, 300); } });
|
|
3160
|
+
sock.once('timeout', () => { sock.destroy(); if (Date.now() > deadline) { resolve(false); } else { setTimeout(check, 300); } });
|
|
3161
|
+
sock.connect(port, 'localhost');
|
|
3037
3162
|
};
|
|
3038
3163
|
check();
|
|
3039
3164
|
});
|
package/src/services/llm.mjs
CHANGED
|
@@ -713,6 +713,288 @@ export function getProviderCall(provider) {
|
|
|
713
713
|
return PROVIDERS[provider] || null;
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
/**
|
|
717
|
+
* Call LLM with native tool_use (function calling) — supports Anthropic and OpenAI.
|
|
718
|
+
* Handles the full agentic loop: call → tool_use → tool_result → call → ... → end_turn.
|
|
719
|
+
*
|
|
720
|
+
* @param {object} config — NHA config with llm.provider, llm.apiKey, etc.
|
|
721
|
+
* @param {string} systemPrompt
|
|
722
|
+
* @param {Array} messages — conversation history [{role, content}]
|
|
723
|
+
* @param {Array} tools — tool definitions [{name, description, input_schema}]
|
|
724
|
+
* @param {Function} onText — callback for text tokens (text: string) => void
|
|
725
|
+
* @param {Function} onToolCall — callback when tool is called (name: string, input: object) => Promise<string> — must return the tool result
|
|
726
|
+
* @param {object} opts — { max_tokens, isAborted, maxTurns }
|
|
727
|
+
* @returns {Promise<{messages: Array, stopReason: string}>} — updated messages array
|
|
728
|
+
*/
|
|
729
|
+
export async function callLLMWithTools(config, systemPrompt, messages, tools, onText, onToolCall, opts = {}) {
|
|
730
|
+
const provider = config.llm?.provider || 'anthropic';
|
|
731
|
+
const model = config.llm?.model || null;
|
|
732
|
+
const apiKey = getApiKey(config, provider);
|
|
733
|
+
if (!apiKey) throw new Error(`No API key for ${provider}`);
|
|
734
|
+
|
|
735
|
+
// Anthropic native tool calling
|
|
736
|
+
if (provider === 'anthropic') {
|
|
737
|
+
return _callAnthropicWithTools(apiKey, model, systemPrompt, messages, tools, onText, onToolCall, opts);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// OpenAI native function calling
|
|
741
|
+
if (provider === 'openai') {
|
|
742
|
+
return _callOpenAIWithTools(apiKey, model, systemPrompt, messages, tools, onText, onToolCall, opts);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Other providers: fallback to text-based tool calling (old system)
|
|
746
|
+
// The caller should handle this by checking the return value
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function _callAnthropicWithTools(apiKey, model, systemPrompt, messages, tools, onText, onToolCall, opts = {}) {
|
|
751
|
+
const maxTokens = opts.max_tokens || 16384;
|
|
752
|
+
const isAborted = opts.isAborted || (() => false);
|
|
753
|
+
const MAX_TURNS = opts.maxTurns || 8;
|
|
754
|
+
|
|
755
|
+
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
756
|
+
if (isAborted()) break;
|
|
757
|
+
|
|
758
|
+
const body = {
|
|
759
|
+
model: model || 'claude-sonnet-4-20250514',
|
|
760
|
+
max_tokens: maxTokens,
|
|
761
|
+
system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
|
|
762
|
+
messages,
|
|
763
|
+
tools,
|
|
764
|
+
stream: true,
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
768
|
+
method: 'POST',
|
|
769
|
+
headers: {
|
|
770
|
+
'Content-Type': 'application/json',
|
|
771
|
+
'x-api-key': apiKey,
|
|
772
|
+
'anthropic-version': '2023-06-01',
|
|
773
|
+
'anthropic-beta': 'prompt-caching-2024-07-31',
|
|
774
|
+
},
|
|
775
|
+
body: JSON.stringify(body),
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
if (!res.ok) {
|
|
779
|
+
const err = await res.text();
|
|
780
|
+
throw new Error(`Anthropic ${res.status}: ${err}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Parse streaming response
|
|
784
|
+
const reader = res.body.getReader();
|
|
785
|
+
const dec = new TextDecoder();
|
|
786
|
+
let buf = '';
|
|
787
|
+
let assistantContent = [];
|
|
788
|
+
let currentText = '';
|
|
789
|
+
let currentToolUse = null;
|
|
790
|
+
let currentToolInput = '';
|
|
791
|
+
let stopReason = null;
|
|
792
|
+
|
|
793
|
+
while (true) {
|
|
794
|
+
if (isAborted()) { try { reader.cancel(); } catch {} break; }
|
|
795
|
+
const { done, value } = await reader.read();
|
|
796
|
+
if (done) break;
|
|
797
|
+
buf += dec.decode(value, { stream: true });
|
|
798
|
+
const lines = buf.split('\n');
|
|
799
|
+
buf = lines.pop() || '';
|
|
800
|
+
|
|
801
|
+
for (const line of lines) {
|
|
802
|
+
if (!line.startsWith('data: ')) continue;
|
|
803
|
+
const data = line.slice(6).trim();
|
|
804
|
+
if (data === '[DONE]') continue;
|
|
805
|
+
try {
|
|
806
|
+
const ev = JSON.parse(data);
|
|
807
|
+
if (ev.type === 'content_block_start') {
|
|
808
|
+
if (ev.content_block?.type === 'text') {
|
|
809
|
+
currentText = '';
|
|
810
|
+
} else if (ev.content_block?.type === 'tool_use') {
|
|
811
|
+
currentToolUse = { id: ev.content_block.id, name: ev.content_block.name };
|
|
812
|
+
currentToolInput = '';
|
|
813
|
+
}
|
|
814
|
+
} else if (ev.type === 'content_block_delta') {
|
|
815
|
+
if (ev.delta?.type === 'text_delta') {
|
|
816
|
+
currentText += ev.delta.text;
|
|
817
|
+
if (onText) onText(ev.delta.text);
|
|
818
|
+
} else if (ev.delta?.type === 'input_json_delta') {
|
|
819
|
+
currentToolInput += ev.delta.partial_json;
|
|
820
|
+
}
|
|
821
|
+
} else if (ev.type === 'content_block_stop') {
|
|
822
|
+
if (currentText) {
|
|
823
|
+
assistantContent.push({ type: 'text', text: currentText });
|
|
824
|
+
currentText = '';
|
|
825
|
+
}
|
|
826
|
+
if (currentToolUse) {
|
|
827
|
+
let input = {};
|
|
828
|
+
try { input = JSON.parse(currentToolInput); } catch {}
|
|
829
|
+
assistantContent.push({
|
|
830
|
+
type: 'tool_use',
|
|
831
|
+
id: currentToolUse.id,
|
|
832
|
+
name: currentToolUse.name,
|
|
833
|
+
input,
|
|
834
|
+
});
|
|
835
|
+
currentToolUse = null;
|
|
836
|
+
currentToolInput = '';
|
|
837
|
+
}
|
|
838
|
+
} else if (ev.type === 'message_delta') {
|
|
839
|
+
if (ev.delta?.stop_reason) stopReason = ev.delta.stop_reason;
|
|
840
|
+
}
|
|
841
|
+
} catch {}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Add assistant message to conversation
|
|
846
|
+
messages.push({ role: 'assistant', content: assistantContent });
|
|
847
|
+
|
|
848
|
+
// If stop reason is end_turn (no more tool calls), we're done
|
|
849
|
+
if (stopReason === 'end_turn' || stopReason !== 'tool_use') break;
|
|
850
|
+
|
|
851
|
+
// Execute tool calls
|
|
852
|
+
const toolUseBlocks = assistantContent.filter(b => b.type === 'tool_use');
|
|
853
|
+
if (toolUseBlocks.length === 0) break;
|
|
854
|
+
|
|
855
|
+
const toolResults = [];
|
|
856
|
+
for (const toolBlock of toolUseBlocks) {
|
|
857
|
+
if (isAborted()) break;
|
|
858
|
+
const result = await onToolCall(toolBlock.name, toolBlock.input);
|
|
859
|
+
toolResults.push({
|
|
860
|
+
type: 'tool_result',
|
|
861
|
+
tool_use_id: toolBlock.id,
|
|
862
|
+
content: typeof result === 'string' ? result : JSON.stringify(result),
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Add tool results to conversation
|
|
867
|
+
messages.push({ role: 'user', content: toolResults });
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return { messages, stopReason: 'end' };
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async function _callOpenAIWithTools(apiKey, model, systemPrompt, messages, tools, onText, onToolCall, opts = {}) {
|
|
874
|
+
const maxTokens = opts.max_tokens || 16384;
|
|
875
|
+
const isAborted = opts.isAborted || (() => false);
|
|
876
|
+
const MAX_TURNS = opts.maxTurns || 8;
|
|
877
|
+
|
|
878
|
+
// Convert Anthropic tool format to OpenAI function format
|
|
879
|
+
const openaiTools = tools.map(t => ({
|
|
880
|
+
type: 'function',
|
|
881
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema },
|
|
882
|
+
}));
|
|
883
|
+
|
|
884
|
+
// Convert messages: Anthropic uses content arrays, OpenAI uses strings
|
|
885
|
+
const toOpenAI = (msgs) => {
|
|
886
|
+
const result = [{ role: 'system', content: systemPrompt }];
|
|
887
|
+
for (const m of msgs) {
|
|
888
|
+
if (m.role === 'user') {
|
|
889
|
+
if (Array.isArray(m.content)) {
|
|
890
|
+
// tool_result blocks
|
|
891
|
+
for (const block of m.content) {
|
|
892
|
+
if (block.type === 'tool_result') {
|
|
893
|
+
result.push({ role: 'tool', tool_call_id: block.tool_use_id, content: block.content || '' });
|
|
894
|
+
} else {
|
|
895
|
+
result.push({ role: 'user', content: typeof block === 'string' ? block : block.text || JSON.stringify(block) });
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
result.push({ role: 'user', content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) });
|
|
900
|
+
}
|
|
901
|
+
} else if (m.role === 'assistant') {
|
|
902
|
+
if (Array.isArray(m.content)) {
|
|
903
|
+
const textParts = m.content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
904
|
+
const toolCalls = m.content.filter(b => b.type === 'tool_use').map(b => ({
|
|
905
|
+
id: b.id, type: 'function', function: { name: b.name, arguments: JSON.stringify(b.input) },
|
|
906
|
+
}));
|
|
907
|
+
const msg = { role: 'assistant', content: textParts || null };
|
|
908
|
+
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
909
|
+
result.push(msg);
|
|
910
|
+
} else {
|
|
911
|
+
result.push({ role: 'assistant', content: typeof m.content === 'string' ? m.content : '' });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return result;
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
919
|
+
if (isAborted()) break;
|
|
920
|
+
|
|
921
|
+
const body = {
|
|
922
|
+
model: model || 'gpt-4o',
|
|
923
|
+
max_tokens: maxTokens,
|
|
924
|
+
messages: toOpenAI(messages),
|
|
925
|
+
tools: openaiTools,
|
|
926
|
+
stream: true,
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
930
|
+
method: 'POST',
|
|
931
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
|
|
932
|
+
body: JSON.stringify(body),
|
|
933
|
+
});
|
|
934
|
+
if (!res.ok) { const err = await res.text(); throw new Error(`OpenAI ${res.status}: ${err}`); }
|
|
935
|
+
|
|
936
|
+
const reader = res.body.getReader();
|
|
937
|
+
const dec = new TextDecoder();
|
|
938
|
+
let buf = '';
|
|
939
|
+
let assistantContent = [];
|
|
940
|
+
let currentText = '';
|
|
941
|
+
let toolCalls = {};
|
|
942
|
+
let finishReason = null;
|
|
943
|
+
|
|
944
|
+
while (true) {
|
|
945
|
+
if (isAborted()) { try { reader.cancel(); } catch {} break; }
|
|
946
|
+
const { done, value } = await reader.read();
|
|
947
|
+
if (done) break;
|
|
948
|
+
buf += dec.decode(value, { stream: true });
|
|
949
|
+
const lines = buf.split('\n');
|
|
950
|
+
buf = lines.pop() || '';
|
|
951
|
+
|
|
952
|
+
for (const line of lines) {
|
|
953
|
+
if (!line.startsWith('data: ')) continue;
|
|
954
|
+
const data = line.slice(6).trim();
|
|
955
|
+
if (data === '[DONE]') continue;
|
|
956
|
+
try {
|
|
957
|
+
const ev = JSON.parse(data);
|
|
958
|
+
const delta = ev.choices?.[0]?.delta;
|
|
959
|
+
if (delta?.content) { currentText += delta.content; if (onText) onText(delta.content); }
|
|
960
|
+
if (delta?.tool_calls) {
|
|
961
|
+
for (const tc of delta.tool_calls) {
|
|
962
|
+
if (!toolCalls[tc.index]) toolCalls[tc.index] = { id: tc.id || '', name: '', args: '' };
|
|
963
|
+
if (tc.id) toolCalls[tc.index].id = tc.id;
|
|
964
|
+
if (tc.function?.name) toolCalls[tc.index].name = tc.function.name;
|
|
965
|
+
if (tc.function?.arguments) toolCalls[tc.index].args += tc.function.arguments;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (ev.choices?.[0]?.finish_reason) finishReason = ev.choices[0].finish_reason;
|
|
969
|
+
} catch {}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (currentText) assistantContent.push({ type: 'text', text: currentText });
|
|
974
|
+
for (const tc of Object.values(toolCalls)) {
|
|
975
|
+
let input = {};
|
|
976
|
+
try { input = JSON.parse(tc.args); } catch {}
|
|
977
|
+
assistantContent.push({ type: 'tool_use', id: tc.id, name: tc.name, input });
|
|
978
|
+
}
|
|
979
|
+
messages.push({ role: 'assistant', content: assistantContent });
|
|
980
|
+
|
|
981
|
+
if (finishReason !== 'tool_calls' && Object.keys(toolCalls).length === 0) break;
|
|
982
|
+
|
|
983
|
+
const toolUseBlocks = assistantContent.filter(b => b.type === 'tool_use');
|
|
984
|
+
if (toolUseBlocks.length === 0) break;
|
|
985
|
+
|
|
986
|
+
const toolResults = [];
|
|
987
|
+
for (const toolBlock of toolUseBlocks) {
|
|
988
|
+
if (isAborted()) break;
|
|
989
|
+
const result = await onToolCall(toolBlock.name, toolBlock.input);
|
|
990
|
+
toolResults.push({ type: 'tool_result', tool_use_id: toolBlock.id, content: typeof result === 'string' ? result : JSON.stringify(result) });
|
|
991
|
+
}
|
|
992
|
+
messages.push({ role: 'user', content: toolResults });
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return { messages, stopReason: 'end' };
|
|
996
|
+
}
|
|
997
|
+
|
|
716
998
|
export function getApiKey(config, provider) {
|
|
717
999
|
// NHA Free (Liara) doesn't need an API key
|
|
718
1000
|
if (provider === 'nha') return 'nha-free-tier';
|
|
@@ -777,7 +777,7 @@ Errore: `+e.msg,Le(!1))}catch{}}}}catch(e){e instanceof DOMException&&e.name===`
|
|
|
777
777
|
|
|
778
778
|
`);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===`ready`&&e.port?(ot(e.port),ut(!1)):e.type===`status`||e.type===`log`||e.type===`warn`||(e.type===`error`?ft(e.msg):e.type))}catch{}}}}catch(e){ft(e.message||`Connection failed`)}ut(!1)}}async function zt(){a&&await Rt(a)}(0,_.useEffect)(()=>{xt.current=Lt,Ct.current=zt});async function Bt(){if(!Ze||!a)return;let e=await D(`/api/studio/webcraft/grep`,{projectName:a,query:Ze});e?.matches&&et(e.matches)}function H(e){let t=p.findIndex(t=>t.name===e);t>=0&&(g(t),i(`files`))}function Vt(){window.open(`/api/studio/webcraft/download/${encodeURIComponent(a)}`,`_blank`)}async function Ht(e,t,n,r){t.endsWith(`.md`)||(t+=`.md`);let i={name:t,content:n,type:r},s;s=e.mode===`edit`&&e.idx!==null?Ve.map((t,n)=>n===e.idx?i:t):[...Ve,i],He(s),Ke(null);let c=a||`MyProject`;a||o(c),await D(`/api/studio/webcraft/skills/${encodeURIComponent(c)}`,{skills:s})}async function Ut(e){let t=Ve[e];!t||!confirm(`Eliminare "${t.name}"?`)||(await D(`/api/studio/webcraft/skills/${encodeURIComponent(a)}/delete`,{name:t.name}),He(Ve.filter((t,n)=>n!==e)))}async function Wt(e){let t=Ve[e];if(!t||!confirm(`Svuotare "${t.name}"? Il file rimane ma il contenuto viene cancellato.`))return;let n=Ve.map((t,n)=>n===e?{...t,content:``}:t);He(n),await D(`/api/studio/webcraft/skills/${encodeURIComponent(a)}`,{skills:n})}async function Gt(){let e=await E(`/api/studio/webcraft/projects`);e?.projects&&it(e.projects)}async function U(e){let t=await E(`/api/studio/webcraft/projects/load/${encodeURIComponent(e.name)}`);if(!t)return;let r=t.projectName??e.name;o(r),c(t.description??``),m(t.files??[]),g(0),n(`new`),i(`files`),Ne([]),He([]),We(!1),de([]),wt(r);let a=await E(`/api/studio/webcraft/projects/chat/load/${encodeURIComponent(r)}`);a?.chat&&Ne(a.chat);let s=await E(`/api/studio/webcraft/skills/${encodeURIComponent(r)}`);s?.skills&&(He(s.skills),We(!0))}async function Kt(e){confirm(`Eliminare: ${e.name} - ${e.dir}?`)&&(await D(`/api/studio/webcraft/projects/${encodeURIComponent(e.name)}`,{},`DELETE`),it(rt.filter(t=>t.name!==e.name)),a===e.name&&(o(``),m([]),Ne([]),c(``)))}async function qt(){if(!L)return;let e=L.originalMessage;Be(null),await At(e+`
|
|
779
779
|
[Piano approvato — procedi con le modifiche]`,null,[])}function Jt(e){e&&Array.from(e).forEach(e=>{let t=new FileReader;t.onload=t=>{let n=(t.target?.result).split(`,`)[1];ze(t=>[...t,{name:e.name,mimeType:e.type,base64:n,size:e.size}])},t.readAsDataURL(e)})}let Yt=a&&p.length>0,W=p[h],Xt=Ie||be;return(0,k.jsxs)(`div`,{className:$.root,children:[(0,k.jsxs)(`div`,{className:$.header,children:[(0,k.jsxs)(`div`,{children:[(0,k.jsxs)(`div`,{className:$.title,children:[`⚙ WebCraft`,a?` — ${a}`:``]}),!a&&(0,k.jsx)(`div`,{className:$.subtitle,children:`Genera progetti web completi con agenti AI`})]}),(0,k.jsxs)(`div`,{className:$.headerTabs,children:[(0,k.jsx)(`button`,{className:`${$.tabBtn} ${t===`new`?$.tabActive:``}`,onClick:async()=>{if(!(S.size>0&&!confirm(`${S.size} unsaved file(s). Discard changes and create new project?`))){if(at){try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}ot(null)}m([]),g(0),y(null),x(null),C(new Set),Ne([]),o(``),c(``),Fe(``),He([]),We(!1),je(null),xe(!1),Le(!1),O(!1),A(null),n(`new`)}},children:`+ Nuovo`}),(0,k.jsx)(`button`,{className:`${$.tabBtn} ${t===`projects`?$.tabActive:``}`,onClick:()=>{n(`projects`),Gt()},children:`📁 Progetti`})]})]}),(0,k.jsx)(`div`,{className:$.body,children:t===`projects`?(0,k.jsx)(`div`,{className:$.projectsList,children:rt.length===0?(0,k.jsxs)(`div`,{className:$.emptyProjects,children:[(0,k.jsx)(`span`,{className:$.emptyIcon,children:`📁`}),(0,k.jsx)(`span`,{children:e(`webcraft.noProjects`)}),(0,k.jsx)(`span`,{className:$.emptyHint,children:`Crea un progetto nella tab Nuovo`})]}):rt.map(e=>(0,k.jsxs)(`div`,{className:$.projectCard,children:[(0,k.jsxs)(`div`,{className:$.projectInfo,children:[(0,k.jsx)(`div`,{className:$.projectName,children:e.name}),(0,k.jsx)(`div`,{className:$.projectDesc,children:e.description}),(0,k.jsxs)(`div`,{className:$.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:$.openBtn,onClick:()=>U(e),children:`↗ Apri`}),(0,k.jsx)(`button`,{className:$.deleteBtn,onClick:()=>Kt(e),children:`🗑`})]},e.name))}):(0,k.jsxs)(`div`,{className:$.editor,children:[(0,k.jsxs)(`div`,{className:$.examples,children:[(0,k.jsx)(`div`,{className:$.sectionLabel,children:`Esempi`}),(0,k.jsx)(`div`,{className:$.examplePills,children:zT.map(e=>(0,k.jsx)(`button`,{className:$.examplePill,onClick:()=>{o(e.name),c(e.desc),Fe(e.desc)},children:e.name},e.name))})]}),(0,k.jsxs)(`div`,{className:$.editorCols,children:[(0,k.jsxs)(`div`,{className:$.leftSidebar,children:[(0,k.jsxs)(`div`,{className:$.panel,children:[(0,k.jsx)(`div`,{className:$.panelTitle,children:`Blocchi`}),BT.map(e=>(0,k.jsxs)(`label`,{className:$.blockLabel,children:[(0,k.jsx)(`input`,{type:`checkbox`,checked:l[e.key],onChange:t=>u(n=>({...n,[e.key]:t.target.checked})),className:$.blockCheck}),(0,k.jsx)(`span`,{children:e.icon}),(0,k.jsx)(`span`,{children:e.label})]},e.key))]}),l.auth&&(0,k.jsxs)(`div`,{className:$.panel,children:[(0,k.jsxs)(`div`,{className:$.panelHeader,children:[(0,k.jsx)(`div`,{className:$.panelTitle,children:`Campi Auth`}),(0,k.jsx)(`button`,{className:$.addBtn,onClick:()=>f(e=>[...e,{label:`New field`,type:`text`,required:!1}]),children:`+ Campo`})]}),d.map((e,t)=>(0,k.jsxs)(`div`,{className:$.authField,children:[(0,k.jsx)(`input`,{value:e.label,onChange:e=>f(n=>n.map((n,r)=>r===t?{...n,label:e.target.value}:n)),className:$.authFieldInput}),(0,k.jsx)(`select`,{value:e.type,onChange:e=>f(n=>n.map((n,r)=>r===t?{...n,type:e.target.value}:n)),className:$.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=>f(n=>n.map((n,r)=>r===t?{...n,required:e.target.checked}:n)),title:`Required`,className:$.authFieldReq}),(0,k.jsx)(`button`,{onClick:()=>f(e=>e.filter((e,n)=>n!==t)),className:$.removeFieldBtn,children:`×`})]},t))]}),(0,k.jsxs)(`div`,{className:$.panel,children:[(0,k.jsxs)(`div`,{className:$.panelHeader,children:[(0,k.jsx)(`div`,{className:$.panelTitle,children:`🗂 Contesto AI`}),(0,k.jsx)(`button`,{className:$.addBtn,onClick:()=>Ke({mode:`new`,idx:null,name:``,content:``,type:`skill`,generating:!1}),children:`+ Skill`})]}),Ve.length>0?(0,k.jsx)(`div`,{className:$.skillsList,children:Ve.map((e,t)=>(0,k.jsxs)(`div`,{className:$.skillRow,children:[(0,k.jsx)(`span`,{className:$.skillIcon,children:WT(e.type)}),(0,k.jsx)(`span`,{className:$.skillName,title:e.name,children:e.name}),(0,k.jsx)(`span`,{className:`${$.skillBadge} ${$[`skillBadge_`+e.type]}`,children:e.type}),!e.content&&e.type!==`log`&&(0,k.jsx)(`span`,{className:$.skillEmpty,children:`⚠`}),(0,k.jsx)(`button`,{className:$.skillBtn,onClick:()=>Ke({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:$.skillBtn,onClick:()=>Wt(t),children:`🗑`}),e.type===`log`&&(0,k.jsx)(`button`,{className:$.skillBtn,onClick:()=>Ut(t),children:`🗑`})]},t))}):(0,k.jsx)(`div`,{className:$.skillsEmpty,children:Ue?`Nessun file di contesto. Clicca "+ Skill" per aggiungerne uno.`:`Crea o carica un progetto per i file di contesto.`})]}),qe.length>0&&(0,k.jsxs)(`div`,{className:$.panel,children:[(0,k.jsx)(`div`,{className:$.panelTitle,children:`💾 Snapshot`}),qe.slice(0,5).map(e=>{let t=e.ts.replace(`T`,` `).slice(0,16);return(0,k.jsxs)(`div`,{className:$.snapshotRow,children:[(0,k.jsx)(`span`,{className:$.snapshotTs,children:t}),(0,k.jsxs)(`span`,{className:$.snapshotCount,children:[e.fileCount,`f`]}),(0,k.jsx)(`button`,{className:$.snapshotBtn,onClick:()=>Ft(e.ts),children:`↺`})]},e.ts)})]}),be&&(0,k.jsx)(`div`,{className:$.genStatus,children:`⏳ Generazione...`}),Se&&(0,k.jsxs)(`div`,{className:$.repairStatus,children:[(0,k.jsx)(`div`,{className:$.repairStatusTitle,children:`🔧 Correzione automatica...`}),(0,k.jsxs)(`div`,{className:$.repairStatusProg,children:[Ce,` / `,Te,` file`]}),(0,k.jsx)(`div`,{className:$.repairStatusFile,children:P})]}),p.length>0&&!be&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:$.actionRow,children:[(0,k.jsx)(`button`,{className:$.actionBtn,onClick:Vt,children:`⬇ ZIP`}),(0,k.jsx)(`button`,{className:$.actionBtnIcon,title:`Syntax check`,onClick:It,children:`✅`}),(0,k.jsx)(`button`,{className:`${$.actionBtnIcon} ${Ye?$.actionBtnActive:``}`,title:`Grep`,onClick:()=>Xe(!Ye),children:`🔍`}),(0,k.jsx)(`button`,{className:$.actionBtnIcon,title:`Snapshot`,onClick:V,children:`💾`})]}),p.some(e=>e._error||e._syntaxError)&&!Se&&(0,k.jsx)(`button`,{className:$.repairBtn,onClick:Lt,children:`🔧 Correggi tutti i file rossi`}),(0,k.jsx)(`button`,{className:$.sandboxBtn,onClick:()=>{a?Rt(a):i(`preview`)},children:lt?`⏳ Starting...`:at?`🌐 Sandbox Live`:`▶ Sandbox`}),Ae&&(0,k.jsxs)(`div`,{className:$.statsBar,children:[(0,k.jsxs)(`span`,{children:[`⏱ `,Ae.seconds>=60?`${Math.floor(Ae.seconds/60)}m ${Ae.seconds%60}s`:`${Ae.seconds}s`]}),(0,k.jsxs)(`span`,{children:[`↑ `,Ae.tokIn.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`↓ `,Ae.tokOut.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`📄 `,Ae.files,` file`]})]})]})]}),(0,k.jsxs)(`div`,{className:$.rightPanel,children:[(0,k.jsxs)(`div`,{className:$.rightTabBar,children:[(0,k.jsx)(`button`,{className:`${$.rightTab} ${r===`preview`?``:$.rightTabActive}`,onClick:()=>i(`files`),children:`📄 File`}),(0,k.jsx)(`button`,{className:`${$.rightTab} ${r===`preview`?$.rightTabActive:``}`,onClick:()=>i(`preview`),children:`🌐 Sandbox`})]}),Se&&(0,k.jsxs)(`div`,{className:$.repairBar,children:[(0,k.jsxs)(`div`,{className:$.repairBarRow,children:[(0,k.jsx)(`span`,{className:$.repairBarIcon,children:`🔧`}),(0,k.jsx)(`span`,{className:$.repairBarLabel,children:`Auto-fix`}),(0,k.jsx)(`span`,{className:$.repairBarFile,children:P}),(0,k.jsxs)(`span`,{className:$.repairBarCounter,children:[Ce,` / `,Te]}),(0,k.jsx)(`span`,{className:$.repairBarTime,children:mt}),(0,k.jsx)(`button`,{className:$.stopBtn,onClick:Mt,children:`⏹ Stop`})]}),(0,k.jsx)(`div`,{className:$.progressTrack,children:(0,k.jsx)(`div`,{className:$.repairProgress,style:{width:Te>0?`${Math.round(Ce/Te*100)}%`:`0%`}})})]}),be&&(0,k.jsxs)(`div`,{className:$.genBar,children:[(0,k.jsxs)(`div`,{className:$.genBarRow,children:[(0,k.jsx)(`span`,{className:$.genBarRobot,children:`🤖`}),(0,k.jsx)(`span`,{className:$.genBarLabel,children:Oe.total===0?`Pianificazione...`:`Generazione`}),(0,k.jsx)(`span`,{className:$.genBarFile,children:(Oe.name||``).split(`,`)[0].trim()}),(0,k.jsx)(`span`,{className:$.genBarCounter,children:Oe.total>0?`${Oe.fi} / ${Oe.total}`:``}),(0,k.jsx)(`span`,{className:$.genBarCounter,children:F.tokIn+F.tokOut>0?`↑${Tt(F.tokIn)} ↓${Tt(F.tokOut)}`:``}),(0,k.jsx)(`span`,{className:$.genBarTime,children:R}),(0,k.jsxs)(`span`,{className:$.genDots,children:[(0,k.jsx)(`span`,{className:`${$.dot} ${$.dot1}`}),(0,k.jsx)(`span`,{className:`${$.dot} ${$.dot2}`}),(0,k.jsx)(`span`,{className:`${$.dot} ${$.dot3}`})]})]}),(0,k.jsx)(`div`,{className:$.progressTrack,children:(0,k.jsx)(`div`,{className:$.genProgress,style:{width:Oe.total>0?`${Math.round(Oe.fi/Oe.total*100)}%`:`0%`}})})]}),r===`preview`?(0,k.jsxs)(`div`,{className:$.sandboxWrap,children:[(0,k.jsxs)(`div`,{className:$.sandboxStatusBar,children:[(0,k.jsx)(`span`,{className:$.sandboxStatusDot,style:{background:at?`#4ade80`:lt?`#facc15`:`#64748b`}}),(0,k.jsx)(`span`,{className:$.sandboxStatusText,children:at?`Live :${at}`:lt?`Starting...`:`Stopped`}),at&&(0,k.jsx)(`button`,{className:$.sandboxReloadBtn,onClick:()=>{let e=document.querySelector(`iframe[title="WebCraft Sandbox"]`);e&&(e.src=e.src)},children:`↻`}),at&&(0,k.jsx)(`button`,{className:$.sandboxStopBtn,onClick:async()=>{ot(null),le([]);try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}},children:`⏹`}),!at&&!lt&&(0,k.jsx)(`button`,{className:$.sandboxStartBtnSmall,onClick:()=>{a&&Rt(a)},children:`▶ Start`})]}),ce.length>0&&(0,k.jsxs)(`div`,{className:$.runtimeErrors,children:[(0,k.jsxs)(`div`,{className:$.runtimeErrorsHeader,children:[(0,k.jsxs)(`span`,{children:[`❌ `,ce.length,` runtime error`,ce.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:$.runtimeErrorsFix,onClick:()=>{Fe(`Fix these runtime errors:\n${ce.map(e=>`${e.message} (${e.source||``}:${e.line||0})`).join(`
|
|
780
|
-
`)}`),fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),le([]),i(`files`)},children:`🔧 Auto-fix`}),(0,k.jsx)(`button`,{className:$.runtimeErrorsDismiss,onClick:()=>{fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),le([])},children:`✕`})]}),ce.slice(0,3).map((e,t)=>(0,k.jsxs)(`div`,{className:$.runtimeErrorLine,children:[e.message,e.source?` — ${e.source.split(`/`).pop()}:${e.line}`:``]},t))]}),at?(0,k.jsx)(`iframe`,{src:`http://
|
|
780
|
+
`)}`),fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),le([]),i(`files`)},children:`🔧 Auto-fix`}),(0,k.jsx)(`button`,{className:$.runtimeErrorsDismiss,onClick:()=>{fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),le([])},children:`✕`})]}),ce.slice(0,3).map((e,t)=>(0,k.jsxs)(`div`,{className:$.runtimeErrorLine,children:[e.message,e.source?` — ${e.source.split(`/`).pop()}:${e.line}`:``]},t))]}),at?(0,k.jsx)(`iframe`,{src:`http://localhost:${at}`,className:$.sandboxFrame,title:`WebCraft Sandbox`,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups`}):(0,k.jsxs)(`div`,{className:$.sandboxEmpty,children:[(0,k.jsx)(`span`,{style:{fontSize:48},children:lt?`⏳`:dt?`❌`:`🌐`}),(0,k.jsx)(`span`,{style:{fontWeight:700,fontSize:16},children:lt?`Starting sandbox...`:dt?`Sandbox Error`:`Preview`}),dt&&(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:dt}),!lt&&(0,k.jsxs)(`button`,{className:$.sandboxStartBtn,onClick:()=>{a&&Rt(a)},children:[`▶ `,dt?`Retry`:`Start Sandbox`]})]})]}):(0,k.jsx)(`div`,{className:$.codeArea,children:p.length===0&&be?(0,k.jsx)(`div`,{className:$.noFiles,children:(0,k.jsxs)(`div`,{className:$.noFilesHero,children:[(0,k.jsx)(`span`,{className:$.noFilesIcon,children:`⏳`}),(0,k.jsx)(`span`,{className:$.noFilesTitle,children:`Pianificazione...`}),(0,k.jsx)(`span`,{className:$.noFilesTagline,children:Oe.name||`Analisi della struttura del progetto in corso`})]})}):p.length===0?(0,k.jsxs)(`div`,{className:$.noFiles,children:[(0,k.jsxs)(`div`,{className:$.noFilesHero,children:[(0,k.jsx)(`span`,{className:$.noFilesIcon,children:`🔨`}),(0,k.jsx)(`span`,{className:$.noFilesTitle,children:`WebCraft`}),(0,k.jsx)(`span`,{className:$.noFilesTagline,children:`Genera progetti web completi con AI`})]}),(0,k.jsxs)(`div`,{className:$.noFilesSteps,children:[(0,k.jsxs)(`div`,{className:$.noFilesStep,children:[(0,k.jsx)(`span`,{className:$.noFilesStepNum,children:`1`}),(0,k.jsx)(`span`,{children:`Scegli un esempio o scrivi una descrizione nel box in basso`})]}),(0,k.jsxs)(`div`,{className:$.noFilesStep,children:[(0,k.jsx)(`span`,{className:$.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:$.noFilesStep,children:[(0,k.jsx)(`span`,{className:$.noFilesStepNum,children:`3`}),(0,k.jsx)(`span`,{children:`Chiedi modifiche in chat, scarica lo ZIP o avvia il Sandbox`})]})]}),(0,k.jsxs)(`div`,{className:$.noFilesExamplesHint,children:[`💡 Prova: `,(0,k.jsx)(`button`,{className:$.noFilesExampleBtn,onClick:()=>{let e=zT[0];o(e.name),c(e.desc),Fe(e.desc)},children:`MySaaS`}),(0,k.jsx)(`button`,{className:$.noFilesExampleBtn,onClick:()=>{let e=zT[1];o(e.name),c(e.desc),Fe(e.desc)},children:`MyShop`}),(0,k.jsx)(`button`,{className:$.noFilesExampleBtn,onClick:()=>{let e=zT[3];o(e.name),c(e.desc),Fe(e.desc)},children:`MyPortfolio`})]})]}):(0,k.jsxs)(`div`,{className:$.codeLayout,children:[(0,k.jsx)(`div`,{className:$.ideTabBar,children:p.map((e,t)=>{let n=e._error||!!e._syntaxError,r=t===h;return(0,k.jsxs)(`button`,{className:`${$.ideTab} ${r?$.ideTabActive:``} ${n?$.ideTabError:``} ${e._pending?$.ideTabPending:``}`,onClick:()=>{g(t),x(null),y(null),be&&M.current!==null&&t!==M.current&&(fe.current&&clearTimeout(fe.current),fe.current=setTimeout(()=>{M.current!==null&&(g(M.current),y(null))},1e4))},title:e.name,children:[(0,k.jsx)(`span`,{className:$.ideTabIcon,children:e._pending?`⌛`:n?`⚠`:HT(e.name)}),(0,k.jsx)(`span`,{className:$.ideTabName,children:(e.name||``).split(`/`).pop()}),S.has(e.name)&&(0,k.jsx)(`span`,{className:$.ideTabUnsaved,children:`●`}),n&&(0,k.jsx)(`span`,{className:$.ideTabDot})]},t)})}),ve&&(0,k.jsxs)(`div`,{className:$.diffOverlay,children:[(0,k.jsxs)(`div`,{className:$.diffOverlayHeader,children:[(0,k.jsxs)(`span`,{children:[`✏ Modifica proposta — `,(0,k.jsx)(`strong`,{children:ve.file})]}),(0,k.jsxs)(`div`,{className:$.diffOverlayActions,children:[(0,k.jsx)(`button`,{className:$.diffAcceptBtn,onClick:()=>{m(e=>e.map(e=>e.name===ve.file?{...e,content:ve.after}:e)),ye(null)},children:`✓ Accetta`}),(0,k.jsx)(`button`,{className:$.diffRejectBtn,onClick:()=>ye(null),children:`✕ Rifiuta`})]})]}),(0,k.jsx)(`div`,{className:$.diffOverlayBody,children:(0,k.jsx)(YT,{before:ve.before,after:ve.after})})]}),(0,k.jsxs)(`div`,{className:$.codeRow,children:[(0,k.jsxs)(`div`,{className:$.fileTreeWrap,children:[ue.length>0&&(0,k.jsxs)(`div`,{className:$.scanBanner,children:[(0,k.jsx)(`span`,{className:$.scanBannerIcon,children:`⚠`}),(0,k.jsxs)(`span`,{className:$.scanBannerText,children:[ue.length,` issue`,ue.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:$.scanBannerFix,onClick:()=>{Fe(`Fix all these issues:\n${ue.map(e=>`[${e.severity}] ${e.file}: ${e.message}`).join(`
|
|
781
781
|
`)}`),i(`files`)},children:`Fix`})]}),(0,k.jsx)(jT,{files:p,activeIndex:h,unsavedFiles:S,errorFiles:new Set(ue.filter(e=>e.severity===`error`).map(e=>e.file)),onSelect:e=>{g(e),x(null),y(null),be&&M.current!==null&&e!==M.current&&(fe.current&&clearTimeout(fe.current),fe.current=setTimeout(()=>{M.current!==null&&(g(M.current),y(null))},1e4))}})]}),(0,k.jsx)(`div`,{className:$.codeEditorWrap,children:W&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:$.codeHeader,children:[(0,k.jsx)(`span`,{className:$.codeFileIcon,children:HT(W.name)}),(0,k.jsx)(`span`,{className:$.codeFileName,children:W.name}),W.content&&!W._error&&(0,k.jsxs)(`span`,{className:$.codeFileMeta,children:[(W.content||``).split(`
|
|
782
782
|
`).length,` righe · `,UT(W.content||``)]}),!W._pending&&!W._error&&W.content&&(0,k.jsx)(`button`,{className:`${$.editToggleBtn} ${b===null?``:$.editToggleBtnActive}`,onClick:()=>{b===null?x(W.content):(m(e=>e.map((e,t)=>t===h?{...e,content:b}:e)),D(`/api/studio/webcraft/file/write`,{projectName:a,path:W.name,content:b}),x(null))},children:b===null?`✏ Modifica`:`💾 Salva`}),(0,k.jsx)(`button`,{className:$.headerIconBtn,title:`Split view`,onClick:()=>A(ae===null?+(h===0&&p.length>1):null),children:`⫼`}),(0,k.jsx)(`button`,{className:`${$.headerIconBtn} ${ie?$.headerIconBtnActive:``}`,title:`Terminal`,onClick:()=>O(!ie),children:`⌨`}),(0,k.jsx)(`button`,{className:$.headerIconBtn,title:`Development Guide`,onClick:()=>se(!0),children:`📖`})]}),w&&(0,k.jsxs)(`div`,{className:$.findBar,children:[(0,k.jsx)(`input`,{className:$.findInput,value:te,onChange:e=>T(e.target.value),placeholder:`Find...`,autoFocus:!0}),(0,k.jsx)(`input`,{className:$.findInput,value:ne,onChange:e=>re(e.target.value),placeholder:`Replace...`}),(0,k.jsx)(`span`,{className:$.findCount,children:te?((W.content||``).match(new RegExp(te.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`))?.length||0)+` found`:``}),(0,k.jsx)(`button`,{className:$.findBtn,onClick:()=>{!te||b===null||x(b.replace(new RegExp(te.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`i`),ne))},children:`Replace`}),(0,k.jsx)(`button`,{className:$.findBtn,onClick:()=>{!te||b===null||x(b.replace(new RegExp(te.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`),ne))},children:`All`}),(0,k.jsx)(`button`,{className:$.findClose,onClick:()=>ee(!1),children:`×`})]}),W._error&&(0,k.jsx)(`div`,{className:$.fileError,children:`⚠ Generazione fallita — chiedi al modello di rigenerare questo file`}),W._syntaxError&&!W._error&&(0,k.jsxs)(`div`,{className:$.fileSyntaxError,children:[`⚠ Syntax error: `,W._syntaxError]}),be&&v!==null?(0,k.jsx)(`pre`,{className:$.streamingPre,ref:e=>{e&&pe.current&&(e.scrollTop=e.scrollHeight)},onScroll:e=>{let t=e.currentTarget;t.scrollHeight-t.scrollTop-t.clientHeight<50?pe.current=!0:(pe.current=!1,me.current&&clearTimeout(me.current),me.current=setTimeout(()=>{pe.current=!0},15e3))},dangerouslySetInnerHTML:{__html:LT(v||``,(W.name.split(`.`).pop()||`js`).toLowerCase())+`<span class="`+$.streamingCursor+`">▋</span>`}}):(0,k.jsx)(TT,{value:b===null?W.content||``:b,filename:W.name,readOnly:b===null,projectName:a,onChange:e=>{x(e),W&&C(e=>new Set(e).add(W.name))},onSave:e=>{m(t=>t.map((t,n)=>n===h?{...t,content:e}:t)),D(`/api/studio/webcraft/file/write`,{projectName:a,path:W.name,content:e}),x(null),C(e=>{let t=new Set(e);return t.delete(W.name),t})}})]})}),ae!==null&&p[ae]&&(0,k.jsxs)(`div`,{className:$.codeEditorWrap,children:[(0,k.jsxs)(`div`,{className:$.codeHeader,children:[(0,k.jsx)(`span`,{className:$.codeFileIcon,children:HT(p[ae].name)}),(0,k.jsx)(`span`,{className:$.codeFileName,children:p[ae].name}),(0,k.jsx)(`button`,{className:$.headerIconBtn,onClick:()=>A(null),children:`✕`})]}),(0,k.jsx)(TT,{value:p[ae].content||``,filename:p[ae].name,readOnly:!0})]})]}),ie&&(0,k.jsxs)(`div`,{className:$.terminalPanel,children:[(0,k.jsxs)(`div`,{className:$.terminalHeader,children:[(0,k.jsx)(`span`,{className:$.terminalTitle,children:`Terminal`}),(0,k.jsx)(`button`,{className:$.terminalClose,onClick:()=>O(!1),children:`✕`})]}),(0,k.jsx)(IT,{projectDir:a||void 0})]})]})})]})]})]})}),L&&t!==`projects`&&(0,k.jsxs)(`div`,{className:$.planBanner,children:[(0,k.jsx)(`div`,{className:$.planTitle,children:`📌 Piano proposto — approva per eseguire`}),(0,k.jsx)(`pre`,{className:$.planText,children:L.plan}),(0,k.jsxs)(`div`,{className:$.planActions,children:[(0,k.jsx)(`button`,{className:$.planApprove,onClick:qt,children:`✓ Esegui`}),(0,k.jsx)(`button`,{className:$.planReject,onClick:()=>Be(null),children:`✕ Annulla`})]})]}),Ye&&t!==`projects`&&(0,k.jsxs)(`div`,{className:$.grepPanel,children:[(0,k.jsxs)(`div`,{className:$.grepRow,children:[(0,k.jsx)(`input`,{className:$.grepInput,value:Ze,onChange:e=>Qe(e.target.value),onKeyDown:e=>e.key===`Enter`&&Bt(),placeholder:`Cerca nel codice...`}),(0,k.jsx)(`button`,{className:$.grepBtn,onClick:Bt,children:`🔍`}),(0,k.jsx)(`button`,{className:$.grepClose,onClick:()=>Xe(!1),children:`×`})]}),$e.length>0&&(0,k.jsxs)(`div`,{className:$.grepCount,children:[$e.length,` risultati`]}),(0,k.jsx)(`div`,{className:$.grepResults,children:$e.length===0?(0,k.jsx)(`div`,{className:$.grepEmpty,children:`Nessun risultato.`}):$e.map((e,t)=>(0,k.jsxs)(`div`,{className:$.grepMatch,onClick:()=>H(e.file),children:[(0,k.jsxs)(`span`,{className:$.grepMatchFile,children:[e.file,`:`,e.lineNum]}),(0,k.jsx)(`pre`,{className:$.grepMatchLine,children:e.line})]},t))})]}),tt.length>0&&t!==`projects`&&(0,k.jsxs)(`div`,{className:$.diffPanel,children:[(0,k.jsxs)(`div`,{className:$.diffHeader,children:[(0,k.jsxs)(`span`,{children:[`🔌 Diff — `,tt.length,` file modificati`]}),(0,k.jsx)(`button`,{className:$.diffClose,onClick:()=>nt([]),children:`✕ Chiudi`})]}),tt.map((e,t)=>{let n=(e.after||``).split(`
|
|
783
783
|
`).length-(e.before||``).split(`
|
package/src/ui-dist/index.html
CHANGED
|
@@ -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-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-B9Bd4Mrx.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-DnJMrYkq.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|