nothumanallowed 13.5.51 → 13.5.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/ui.mjs +186 -3
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +318 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.53",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -4249,11 +4249,15 @@ module.exports = { get, set, del, exists };
|
|
|
4249
4249
|
proc.stdout.on('data', d => { const l = d.toString().trim(); if (l) sendLog(' [server] ' + l); });
|
|
4250
4250
|
proc.stderr.on('data', d => {
|
|
4251
4251
|
const raw = d.toString();
|
|
4252
|
-
// Extract meaningful error: MODULE_NOT_FOUND
|
|
4252
|
+
// Extract meaningful error: MODULE_NOT_FOUND → trigger auto-fix
|
|
4253
4253
|
const modMatch = raw.match(/Cannot find module '([^']+)'/);
|
|
4254
4254
|
if (modMatch) {
|
|
4255
|
-
|
|
4256
|
-
sendLog('
|
|
4255
|
+
const missingMod = modMatch[1];
|
|
4256
|
+
sendLog(' ❌ Modulo mancante: ' + missingMod);
|
|
4257
|
+
// Store error for auto-fix agent — will be picked up by frontend
|
|
4258
|
+
if (!global._wcAutoFixQueue) global._wcAutoFixQueue = [];
|
|
4259
|
+
global._wcAutoFixQueue.push({ type: 'module_not_found', module: missingMod, dir: sandboxDir, ts: Date.now() });
|
|
4260
|
+
sendLog(' 🤖 Avvio auto-fix...');
|
|
4257
4261
|
return;
|
|
4258
4262
|
}
|
|
4259
4263
|
// Skip node internals noise
|
|
@@ -4307,6 +4311,185 @@ module.exports = { get, set, del, exists };
|
|
|
4307
4311
|
return;
|
|
4308
4312
|
}
|
|
4309
4313
|
|
|
4314
|
+
// ── WebCraft Agent — chat + tool-use + auto-fix ────────────────────────
|
|
4315
|
+
// POST /api/studio/webcraft/agent { projectName, message, attachments[]?, autofix? }
|
|
4316
|
+
// → SSE: { type:'text', token } | { type:'tool', tool, path, result } | { type:'done' } | { type:'error', msg }
|
|
4317
|
+
// The agent reads files, applies old_string→new_string edits, writes new files.
|
|
4318
|
+
// Rate-limit for Liara: 3 calls per 5 minutes (unlimited for own API key).
|
|
4319
|
+
if (pathname === '/api/studio/webcraft/agent' && method === 'POST') {
|
|
4320
|
+
const body = await parseBody(req, 32 * 1024 * 1024); // 32MB for attachments
|
|
4321
|
+
const { projectName, message, attachments, autofix } = body;
|
|
4322
|
+
if (!projectName || !message) {
|
|
4323
|
+
sendJSON(res, 400, { error: 'projectName and message required' });
|
|
4324
|
+
logRequest(method, pathname, 400, Date.now() - start);
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// Rate-limit Liara: 3 per 5 minutes
|
|
4329
|
+
const isLiara = !config.llm || !config.llm.apiKey || config.llm.provider === 'nha';
|
|
4330
|
+
if (isLiara) {
|
|
4331
|
+
if (!global._wcAgentCallLog) global._wcAgentCallLog = [];
|
|
4332
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
|
|
4333
|
+
global._wcAgentCallLog = global._wcAgentCallLog.filter(t => t > fiveMinAgo);
|
|
4334
|
+
if (global._wcAgentCallLog.length >= 3) {
|
|
4335
|
+
sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo 3 correzioni ogni 5 minuti con Liara. Usa una tua API key per correzioni illimitate.' });
|
|
4336
|
+
logRequest(method, pathname, 429, Date.now() - start);
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
global._wcAgentCallLog.push(Date.now());
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
const sandboxDir = path.join(os.homedir(), '.nha', 'webcraft', projectName.replace(/[^a-zA-Z0-9_-]/g, '-'));
|
|
4343
|
+
|
|
4344
|
+
res.writeHead(200, {
|
|
4345
|
+
'Content-Type': 'text/event-stream',
|
|
4346
|
+
'Cache-Control': 'no-cache',
|
|
4347
|
+
'Connection': 'keep-alive',
|
|
4348
|
+
'Access-Control-Allow-Origin': '*',
|
|
4349
|
+
});
|
|
4350
|
+
const sendEv = (data) => { try { res.write(`data: ${JSON.stringify(data)}\n\n`); } catch {} };
|
|
4351
|
+
|
|
4352
|
+
try {
|
|
4353
|
+
// Read all project files to give agent full context
|
|
4354
|
+
const allFiles = [];
|
|
4355
|
+
if (fs.existsSync(sandboxDir)) {
|
|
4356
|
+
const walkDir = (dir, base) => {
|
|
4357
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
4358
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
4359
|
+
const rel = base ? base + '/' + entry.name : entry.name;
|
|
4360
|
+
if (entry.isDirectory()) { walkDir(path.join(dir, entry.name), rel); }
|
|
4361
|
+
else {
|
|
4362
|
+
try {
|
|
4363
|
+
const content = fs.readFileSync(path.join(dir, entry.name), 'utf8');
|
|
4364
|
+
if (content.length < 64 * 1024) allFiles.push({ name: rel, content }); // skip >64KB
|
|
4365
|
+
} catch(_) {}
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
};
|
|
4369
|
+
walkDir(sandboxDir, '');
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
const fileContext = allFiles.map(f => `### FILE: ${f.name}\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n');
|
|
4373
|
+
const fileList = allFiles.map(f => f.name).join('\n');
|
|
4374
|
+
|
|
4375
|
+
// Build attachment context (images/PDF)
|
|
4376
|
+
const attachCtx = (attachments || []).map((a, i) => `[Allegato ${i+1}: ${a.name || 'file'}, type=${a.mimeType}]`).join('\n');
|
|
4377
|
+
|
|
4378
|
+
const systemPrompt = `Sei WebCraft Agent, un assistente AI esperto di sviluppo web fullstack embedded in NotHumanAllowed.
|
|
4379
|
+
Il tuo lavoro e di correggere, migliorare ed espandere il codice del progetto sandbox dell utente.
|
|
4380
|
+
|
|
4381
|
+
PROGETTO ATTIVO: ${projectName}
|
|
4382
|
+
PERCORSO: ${sandboxDir}
|
|
4383
|
+
|
|
4384
|
+
FILE DISPONIBILI:
|
|
4385
|
+
${fileList}
|
|
4386
|
+
|
|
4387
|
+
STRUMENTI A TUA DISPOSIZIONE (rispondi SOLO con JSON valido per le operazioni):
|
|
4388
|
+
Quando devi modificare o creare file, includi nel testo della risposta blocchi JSON tra i tag <tool> e </tool>:
|
|
4389
|
+
|
|
4390
|
+
Per modificare parte di un file (chirurgico, preferito):
|
|
4391
|
+
<tool>{"op":"edit","path":"server/routes/contact.js","old":"const email = require('../utils/email')","new":"const nodemailer = require('nodemailer')"}</tool>
|
|
4392
|
+
|
|
4393
|
+
Per creare/sovrascrivere un file intero:
|
|
4394
|
+
<tool>{"op":"write","path":"server/utils/mailer.js","content":"// codice completo..."}</tool>
|
|
4395
|
+
|
|
4396
|
+
Per leggere un file (se hai bisogno di piu contesto):
|
|
4397
|
+
<tool>{"op":"read","path":"server/index.js"}</tool>
|
|
4398
|
+
|
|
4399
|
+
REGOLE CRITICHE:
|
|
4400
|
+
- Spiega SEMPRE in linguaggio naturale cosa stai facendo PRIMA dei blocchi tool
|
|
4401
|
+
- Usa "edit" (old/new) quando possibile, "write" solo per file nuovi o riscritture complete
|
|
4402
|
+
- old_string deve essere ESATTO come appare nel file (copy-paste)
|
|
4403
|
+
- Non inventare moduli npm: usa solo quelli in package.json o standard Node.js
|
|
4404
|
+
- Dopo ogni fix spiega brevemente cosa hai cambiato e perche
|
|
4405
|
+
- Se usi immagini allegate, descrivile e usale come contesto per il fix`;
|
|
4406
|
+
|
|
4407
|
+
const userMsg = message + (attachCtx ? '\n\nAllegati:\n' + attachCtx : '') +
|
|
4408
|
+
'\n\n--- CONTENUTO FILE ---\n' + fileContext;
|
|
4409
|
+
|
|
4410
|
+
// Call LLM - stream tokens
|
|
4411
|
+
let fullResponse = '';
|
|
4412
|
+
const visionAttachments = (attachments || []).filter(a => a.base64 && (a.mimeType || '').startsWith('image/'));
|
|
4413
|
+
|
|
4414
|
+
if (visionAttachments.length > 0) {
|
|
4415
|
+
// Use vision call for first image attachment
|
|
4416
|
+
const { callLLMVision } = await import('../services/llm.mjs');
|
|
4417
|
+
const va = visionAttachments[0];
|
|
4418
|
+
fullResponse = await callLLMVision(config, systemPrompt, userMsg, { base64: va.base64, mimeType: va.mimeType });
|
|
4419
|
+
sendEv({ type: 'text', token: fullResponse });
|
|
4420
|
+
} else {
|
|
4421
|
+
await callLLMStream(config, systemPrompt, userMsg, (token) => {
|
|
4422
|
+
fullResponse += token;
|
|
4423
|
+
sendEv({ type: 'text', token });
|
|
4424
|
+
}, { max_tokens: 4096 });
|
|
4425
|
+
}
|
|
4426
|
+
|
|
4427
|
+
// Parse and execute tool calls from response
|
|
4428
|
+
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
4429
|
+
let toolMatch;
|
|
4430
|
+
const toolResults = [];
|
|
4431
|
+
while ((toolMatch = toolRegex.exec(fullResponse)) !== null) {
|
|
4432
|
+
let toolCall;
|
|
4433
|
+
try { toolCall = JSON.parse(toolMatch[1]); } catch(e) { continue; }
|
|
4434
|
+
|
|
4435
|
+
if (toolCall.op === 'read') {
|
|
4436
|
+
const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
|
|
4437
|
+
if (fs.existsSync(fp)) {
|
|
4438
|
+
const content = fs.readFileSync(fp, 'utf8');
|
|
4439
|
+
toolResults.push({ op: 'read', path: toolCall.path, ok: true });
|
|
4440
|
+
sendEv({ type: 'tool', op: 'read', path: toolCall.path, result: 'ok' });
|
|
4441
|
+
} else {
|
|
4442
|
+
sendEv({ type: 'tool', op: 'read', path: toolCall.path, result: 'file non trovato' });
|
|
4443
|
+
}
|
|
4444
|
+
} else if (toolCall.op === 'edit') {
|
|
4445
|
+
const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
|
|
4446
|
+
if (!fs.existsSync(fp)) {
|
|
4447
|
+
sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'file non trovato' });
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
let content = fs.readFileSync(fp, 'utf8');
|
|
4451
|
+
if (content.includes(toolCall.old)) {
|
|
4452
|
+
content = content.replace(toolCall.old, toolCall.new);
|
|
4453
|
+
fs.writeFileSync(fp, content, 'utf8');
|
|
4454
|
+
toolResults.push({ op: 'edit', path: toolCall.path, ok: true });
|
|
4455
|
+
sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'ok' });
|
|
4456
|
+
} else {
|
|
4457
|
+
sendEv({ type: 'tool', op: 'edit', path: toolCall.path, result: 'old_string non trovato — patch fallita' });
|
|
4458
|
+
}
|
|
4459
|
+
} else if (toolCall.op === 'write') {
|
|
4460
|
+
const fp = path.join(sandboxDir, toolCall.path.replace(/^\/+/, ''));
|
|
4461
|
+
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
|
4462
|
+
fs.writeFileSync(fp, toolCall.content || '', 'utf8');
|
|
4463
|
+
toolResults.push({ op: 'write', path: toolCall.path, ok: true });
|
|
4464
|
+
sendEv({ type: 'tool', op: 'write', path: toolCall.path, result: 'ok' });
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
const anyChange = toolResults.some(r => r.ok);
|
|
4469
|
+
sendEv({ type: 'done', changed: anyChange, toolCount: toolResults.length });
|
|
4470
|
+
|
|
4471
|
+
// If auto-fix and changes made → signal client to restart sandbox
|
|
4472
|
+
if (autofix && anyChange) {
|
|
4473
|
+
sendEv({ type: 'restart_sandbox' });
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
} catch(e) {
|
|
4477
|
+
sendEv({ type: 'error', msg: e.message });
|
|
4478
|
+
}
|
|
4479
|
+
res.end();
|
|
4480
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
// GET /api/studio/webcraft/agent/autofix-queue → { items[] } and clears queue
|
|
4485
|
+
if (pathname === '/api/studio/webcraft/agent/autofix-queue' && method === 'GET') {
|
|
4486
|
+
const items = global._wcAutoFixQueue || [];
|
|
4487
|
+
global._wcAutoFixQueue = [];
|
|
4488
|
+
sendJSON(res, 200, { items });
|
|
4489
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4310
4493
|
// ── Studio: Parliament deliberation (SSE streaming) ──────────────────
|
|
4311
4494
|
// Implements the Legion DeliberationEngine protocol adapted for Studio:
|
|
4312
4495
|
// Round 1 outputs already exist (from normal workflow steps).
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '13.5.
|
|
8
|
+
export const VERSION = '13.5.53';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -6246,6 +6246,12 @@ var wcRightTab = 'files';
|
|
|
6246
6246
|
var wcMainTab = 'new'; // 'new' | 'projects'
|
|
6247
6247
|
var wcProjectsList = []; // cached list from server
|
|
6248
6248
|
var wcSandboxExpanded = {}; // { phaseKey: true/false }
|
|
6249
|
+
// Agent chat state
|
|
6250
|
+
var wcChat = []; // [{role:'user'|'agent', text, tools:[]}]
|
|
6251
|
+
var wcChatRunning = false;
|
|
6252
|
+
var wcChatAttachments = []; // [{name, mimeType, base64, size}]
|
|
6253
|
+
var _wcAutoFixAttempts = 0;
|
|
6254
|
+
var _wcAutoFixTimer = null;
|
|
6249
6255
|
|
|
6250
6256
|
function wcEsc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'):''}
|
|
6251
6257
|
|
|
@@ -6307,14 +6313,10 @@ function renderWebCraft(el) {
|
|
|
6307
6313
|
'</div>';
|
|
6308
6314
|
|
|
6309
6315
|
var editorHtml =
|
|
6316
|
+
'<div style="display:flex;flex-direction:column;height:100%">' +
|
|
6310
6317
|
wcExHtml +
|
|
6311
6318
|
'<div style="display:flex;gap:14px;align-items:flex-start;flex:1;min-height:0">' +
|
|
6312
|
-
'<div style="width:
|
|
6313
|
-
'<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6314
|
-
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:8px">'+t('wc_project')+'</div>' +
|
|
6315
|
-
'<input id="wcProjectName" placeholder="'+t('wc_project_name')+'" value="'+wcEsc(wcState.projectName)+'" oninput="wcState.projectName=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);margin-bottom:8px;box-sizing:border-box">' +
|
|
6316
|
-
'<textarea id="wcDesc" placeholder="'+t('wc_desc')+'" rows="4" oninput="wcState.description=this.value" style="width:100%;padding:8px 10px;font-size:12px;border-radius:6px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:vertical;box-sizing:border-box;line-height:1.5">'+wcEsc(wcState.description)+'</textarea>' +
|
|
6317
|
-
'</div>' +
|
|
6319
|
+
'<div style="width:240px;flex-shrink:0;display:flex;flex-direction:column;gap:10px;overflow-y:auto;height:100%">' +
|
|
6318
6320
|
'<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
6319
6321
|
'<div style="font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.8px;margin-bottom:10px">'+t('wc_blocks')+'</div>' +
|
|
6320
6322
|
['auth','cookieBanner','securityMiddleware','emailVerification'].map(function(b){
|
|
@@ -6334,27 +6336,31 @@ function renderWebCraft(el) {
|
|
|
6334
6336
|
'<div id="wcFieldsList">'+authFieldsHtml+'</div>' +
|
|
6335
6337
|
'<div style="font-size:9px;color:var(--dim);margin-top:4px">'+t('wc_required_hint')+'</div>' +
|
|
6336
6338
|
'</div>' +
|
|
6337
|
-
|
|
6338
|
-
(
|
|
6339
|
-
'
|
|
6339
|
+
(wcState.running ?
|
|
6340
|
+
'<div style="width:100%;padding:11px;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--dim);font-size:12px;text-align:center">⏳ '+t('wc_generating')+'...</div>'
|
|
6341
|
+
: '') +
|
|
6340
6342
|
(wcState.generatedFiles.length > 0 && !wcState.running ?
|
|
6341
6343
|
'<button onclick="wcDownloadZip()" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--border2);border-radius:8px;color:var(--text);font-size:12px;font-weight:600;cursor:pointer">⇩ '+t('wc_download')+'</button>' +
|
|
6342
6344
|
'<button onclick="wcStartSandbox()" id="wcSandboxBtn" style="width:100%;padding:10px;background:var(--bg3);border:1px solid var(--green3);border-radius:8px;color:var(--green);font-size:12px;font-weight:600;cursor:pointer">▶ '+t('wc_sandbox_start')+'</button>'
|
|
6343
6345
|
: '') +
|
|
6344
6346
|
'</div>' +
|
|
6345
|
-
'<div style="flex:1;min-width:0;background:var(--bg2);border:1px solid var(--border);border-radius:10px;display:flex;flex-direction:column;height:
|
|
6347
|
+
'<div style="flex:1;min-width:0;background:var(--bg2);border:1px solid var(--border);border-radius:10px;display:flex;flex-direction:column;height:100%;overflow:hidden">' +
|
|
6346
6348
|
'<div style="display:flex;border-bottom:1px solid var(--border);flex-shrink:0">' +
|
|
6347
6349
|
'<button onclick="wcTabFiles()" style="padding:8px 16px;background:'+(wcRightTab==='preview'?'transparent':'var(--bg3)')+';border:none;border-right:1px solid var(--border);color:'+(wcRightTab==='preview'?'var(--dim)':'var(--text)')+';font-size:11px;font-weight:600;cursor:pointer">📄 File</button>' +
|
|
6348
6350
|
'<button onclick="wcTabPreview()" style="padding:8px 16px;background:'+(wcRightTab==='preview'?'var(--bg3)':'transparent')+';border:none;color:'+(wcRightTab==='preview'?'var(--text)':'var(--dim)')+';font-size:11px;font-weight:600;cursor:pointer">🌐 Sandbox</button>' +
|
|
6349
6351
|
'</div>' +
|
|
6350
6352
|
(wcRightTab === 'preview' ? wcSandboxPanelHtml() : (fileTabsHtml + codeHtml)) +
|
|
6351
6353
|
'</div>' +
|
|
6354
|
+
'</div>' +
|
|
6352
6355
|
'</div>';
|
|
6353
6356
|
|
|
6354
6357
|
el.innerHTML =
|
|
6355
6358
|
'<div style="display:flex;flex-direction:column;height:100%;min-height:0;padding:0 4px">' +
|
|
6356
6359
|
headerHtml +
|
|
6357
|
-
|
|
6360
|
+
'<div style="flex:1;min-height:0;overflow:hidden">' +
|
|
6361
|
+
(wcMainTab === 'projects' ? wcProjectsPanelHtml() : editorHtml) +
|
|
6362
|
+
'</div>' +
|
|
6363
|
+
wcChatPanelHtml() +
|
|
6358
6364
|
'</div>';
|
|
6359
6365
|
}
|
|
6360
6366
|
|
|
@@ -6376,6 +6382,301 @@ function wcPickExample(i) {
|
|
|
6376
6382
|
function wcTabFiles() { wcRightTab = 'files'; renderWebCraft(document.getElementById('content')); }
|
|
6377
6383
|
function wcTabPreview() { wcRightTab = 'preview'; renderWebCraft(document.getElementById('content')); }
|
|
6378
6384
|
function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0.1:' + wcState.sandbox.port, '_blank'); }
|
|
6385
|
+
|
|
6386
|
+
// ── WebCraft Agent Chat Panel ─────────────────────────────────────────────
|
|
6387
|
+
function wcChatPanelHtml() {
|
|
6388
|
+
var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
|
|
6389
|
+
var placeholder = hasProject
|
|
6390
|
+
? 'Parla con il tuo agente: chiedi correzioni, migliorie, nuove funzionalit\u00e0...'
|
|
6391
|
+
: 'Descrivi il progetto da creare, poi premi Genera...';
|
|
6392
|
+
|
|
6393
|
+
// Chat messages
|
|
6394
|
+
var messagesHtml = '';
|
|
6395
|
+
if (wcChat.length === 0 && hasProject) {
|
|
6396
|
+
messagesHtml = '<div style="font-size:11px;color:var(--dim);padding:8px 12px;font-style:italic">🤖 Pronto! Dimmi cosa vuoi modificare o migliorare nel progetto.</div>';
|
|
6397
|
+
}
|
|
6398
|
+
for (var mi = 0; mi < wcChat.length; mi++) {
|
|
6399
|
+
var msg = wcChat[mi];
|
|
6400
|
+
if (msg.role === 'user') {
|
|
6401
|
+
messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:4px 12px">' +
|
|
6402
|
+
'<div style="background:var(--green3);color:var(--bg);padding:6px 12px;border-radius:10px 10px 2px 10px;font-size:11px;max-width:70%;line-height:1.5">'+wcEsc(msg.text)+'</div>' +
|
|
6403
|
+
'</div>';
|
|
6404
|
+
if (msg.attachments && msg.attachments.length) {
|
|
6405
|
+
messagesHtml += '<div style="display:flex;justify-content:flex-end;margin:2px 12px;gap:4px">' +
|
|
6406
|
+
msg.attachments.map(function(a){ return '<span style="background:var(--bg3);border:1px solid var(--border2);border-radius:5px;padding:2px 7px;font-size:10px;color:var(--dim)">📎 '+wcEsc(a.name)+'</span>'; }).join('') +
|
|
6407
|
+
'</div>';
|
|
6408
|
+
}
|
|
6409
|
+
} else {
|
|
6410
|
+
var toolBadges = (msg.tools || []).map(function(tool){
|
|
6411
|
+
var icon = tool.op === 'edit' ? '✎' : (tool.op === 'write' ? '➕' : '👁');
|
|
6412
|
+
var color = tool.result === 'ok' ? 'var(--green)' : 'var(--red)';
|
|
6413
|
+
return '<span style="display:inline-flex;align-items:center;gap:3px;background:var(--bg3);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:9px;font-family:var(--mono);color:'+color+'">'+icon+' '+wcEsc(tool.path)+'</span>';
|
|
6414
|
+
}).join(' ');
|
|
6415
|
+
messagesHtml += '<div style="margin:4px 12px">' +
|
|
6416
|
+
'<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">' +
|
|
6417
|
+
'<span style="font-size:10px;font-weight:700;color:var(--green)">🤖 WebCraft Agent</span>' +
|
|
6418
|
+
'</div>' +
|
|
6419
|
+
'<div style="font-size:11px;color:var(--text);line-height:1.6;white-space:pre-wrap;max-width:85%">'+wcEsc(msg.text.replace(new RegExp('<tool>[\\s\\S]*?<\\/tool>', 'g'), '').trim())+'</div>' +
|
|
6420
|
+
(toolBadges ? '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:5px">'+toolBadges+'</div>' : '') +
|
|
6421
|
+
'</div>';
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
if (wcChatRunning) {
|
|
6425
|
+
messagesHtml += '<div id="wcChatTyping" style="margin:4px 12px;font-size:11px;color:var(--dim)">🤖 <span style="opacity:.6">…</span></div>';
|
|
6426
|
+
}
|
|
6427
|
+
|
|
6428
|
+
// Attachments preview
|
|
6429
|
+
var attachPreview = '';
|
|
6430
|
+
if (wcChatAttachments.length > 0) {
|
|
6431
|
+
attachPreview = '<div style="display:flex;gap:6px;flex-wrap:wrap;padding:4px 12px 0">' +
|
|
6432
|
+
wcChatAttachments.map(function(a, ai){
|
|
6433
|
+
return '<span style="display:inline-flex;align-items:center;gap:4px;background:var(--bg3);border:1px solid var(--border2);border-radius:5px;padding:3px 8px;font-size:10px;color:var(--text)">' +
|
|
6434
|
+
'📎 '+wcEsc(a.name)+' <button onclick="wcRemoveAttachment('+ai+')" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;line-height:1;padding:0">×</button>' +
|
|
6435
|
+
'</span>';
|
|
6436
|
+
}).join('') +
|
|
6437
|
+
'</div>';
|
|
6438
|
+
}
|
|
6439
|
+
|
|
6440
|
+
var sendBtnLabel = wcState.running ? '⏳' : (hasProject ? '▶' : '▶ Genera');
|
|
6441
|
+
var inputDisabled = wcChatRunning || wcState.running;
|
|
6442
|
+
|
|
6443
|
+
// Project name row — shown only when no project yet
|
|
6444
|
+
var projNameRow = !hasProject
|
|
6445
|
+
? '<div style="display:flex;align-items:center;gap:8px;padding:6px 12px 0">' +
|
|
6446
|
+
'<span style="font-size:10px;color:var(--dim);white-space:nowrap">Nome progetto:</span>' +
|
|
6447
|
+
'<input id="wcProjectName" placeholder="MioProgetto" value="'+wcEsc(wcState.projectName)+'" oninput="wcState.projectName=this.value" style="flex:1;padding:4px 8px;font-size:11px;border-radius:5px;border:1px solid var(--border2);background:var(--bg3);color:var(--text)">' +
|
|
6448
|
+
'</div>'
|
|
6449
|
+
: '<div style="padding:4px 12px 0;font-size:10px;color:var(--dim)">📄 <strong style="color:var(--green)">'+wcEsc(wcState.projectName)+'</strong> — scrivi per modificare o migliorare il progetto</div>';
|
|
6450
|
+
|
|
6451
|
+
return '<div style="border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;flex-direction:column">' +
|
|
6452
|
+
// Messages
|
|
6453
|
+
'<div id="wcChatMessages" style="max-height:160px;overflow-y:auto;padding:6px 0">' +
|
|
6454
|
+
messagesHtml +
|
|
6455
|
+
'</div>' +
|
|
6456
|
+
// Attachments
|
|
6457
|
+
attachPreview +
|
|
6458
|
+
// Project name (only pre-generation)
|
|
6459
|
+
projNameRow +
|
|
6460
|
+
// Input row
|
|
6461
|
+
'<div style="display:flex;align-items:flex-end;gap:8px;padding:8px 12px">' +
|
|
6462
|
+
'<label style="cursor:pointer;color:var(--dim);font-size:16px;flex-shrink:0;padding-bottom:2px" title="Allega immagine o PDF">' +
|
|
6463
|
+
'📎' +
|
|
6464
|
+
'<input type="file" id="wcFileInput" multiple accept="image/*,.pdf" style="display:none" onchange="wcHandleFileAttach(this)">' +
|
|
6465
|
+
'</label>' +
|
|
6466
|
+
'<textarea id="wcChatInput" rows="2" placeholder="'+placeholder+'" '+(inputDisabled?'disabled':'')+' style="flex:1;padding:8px 10px;font-size:12px;border-radius:8px;border:1px solid var(--border2);background:var(--bg3);color:var(--text);resize:none;line-height:1.5;font-family:inherit" onkeydown="wcChatKeydown(event)"></textarea>' +
|
|
6467
|
+
'<button onclick="wcChatSend()" '+(inputDisabled?'disabled':'')+' style="padding:8px 14px;background:var(--green3);border:none;border-radius:8px;color:var(--bg);font-size:13px;font-weight:700;cursor:pointer;flex-shrink:0;height:38px">'+sendBtnLabel+'</button>' +
|
|
6468
|
+
'</div>' +
|
|
6469
|
+
'</div>';
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
function wcChatKeydown(e) {
|
|
6473
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); wcChatSend(); }
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6476
|
+
function wcRemoveAttachment(ai) {
|
|
6477
|
+
wcChatAttachments.splice(ai, 1);
|
|
6478
|
+
renderWebCraft(document.getElementById('content'));
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
function wcHandleFileAttach(input) {
|
|
6482
|
+
var files = Array.from(input.files || []);
|
|
6483
|
+
files.forEach(function(file) {
|
|
6484
|
+
var reader = new FileReader();
|
|
6485
|
+
reader.onload = function(e) {
|
|
6486
|
+
var dataUrl = e.target.result;
|
|
6487
|
+
var base64 = dataUrl.split(',')[1];
|
|
6488
|
+
wcChatAttachments.push({ name: file.name, mimeType: file.type, base64: base64, size: file.size });
|
|
6489
|
+
renderWebCraft(document.getElementById('content'));
|
|
6490
|
+
};
|
|
6491
|
+
reader.readAsDataURL(file);
|
|
6492
|
+
});
|
|
6493
|
+
input.value = '';
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6496
|
+
async function wcChatSend() {
|
|
6497
|
+
var inputEl = document.getElementById('wcChatInput');
|
|
6498
|
+
var msg = (inputEl ? inputEl.value : '').trim();
|
|
6499
|
+
var hasProject = wcState.projectName && wcState.generatedFiles.length > 0;
|
|
6500
|
+
|
|
6501
|
+
// If no project yet, use as description and generate
|
|
6502
|
+
if (!hasProject) {
|
|
6503
|
+
if (!msg || msg.length < 5) { alert(t('wc_describe_first')); return; }
|
|
6504
|
+
var projNameEl = document.getElementById('wcProjectName');
|
|
6505
|
+
if (projNameEl && projNameEl.value.trim()) wcState.projectName = projNameEl.value.trim();
|
|
6506
|
+
if (!wcState.projectName) wcState.projectName = 'MyProject';
|
|
6507
|
+
wcState.description = msg;
|
|
6508
|
+
if (inputEl) inputEl.value = '';
|
|
6509
|
+
wcGenerate();
|
|
6510
|
+
return;
|
|
6511
|
+
}
|
|
6512
|
+
|
|
6513
|
+
if (!msg && wcChatAttachments.length === 0) return;
|
|
6514
|
+
if (wcChatRunning || wcState.running) return;
|
|
6515
|
+
|
|
6516
|
+
var attachCopy = wcChatAttachments.slice();
|
|
6517
|
+
wcChatAttachments = [];
|
|
6518
|
+
wcChatRunning = true;
|
|
6519
|
+
wcChat.push({ role: 'user', text: msg, attachments: attachCopy });
|
|
6520
|
+
if (inputEl) inputEl.value = '';
|
|
6521
|
+
renderWebCraft(document.getElementById('content'));
|
|
6522
|
+
wcScrollChatToBottom();
|
|
6523
|
+
|
|
6524
|
+
try {
|
|
6525
|
+
var r = await fetch(API + '/api/studio/webcraft/agent', {
|
|
6526
|
+
method: 'POST',
|
|
6527
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6528
|
+
body: JSON.stringify({
|
|
6529
|
+
projectName: wcState.projectName,
|
|
6530
|
+
message: msg,
|
|
6531
|
+
attachments: attachCopy.map(function(a){ return { name: a.name, mimeType: a.mimeType, base64: a.base64 }; })
|
|
6532
|
+
})
|
|
6533
|
+
});
|
|
6534
|
+
|
|
6535
|
+
if (!r.ok) {
|
|
6536
|
+
var errData = await r.json().catch(function(){ return {}; });
|
|
6537
|
+
wcChat.push({ role: 'agent', text: 'Errore: ' + (errData.error || r.status), tools: [] });
|
|
6538
|
+
wcChatRunning = false;
|
|
6539
|
+
renderWebCraft(document.getElementById('content'));
|
|
6540
|
+
return;
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
var agentMsg = { role: 'agent', text: '', tools: [] };
|
|
6544
|
+
wcChat.push(agentMsg);
|
|
6545
|
+
|
|
6546
|
+
var reader2 = r.body.getReader();
|
|
6547
|
+
var dec = new TextDecoder();
|
|
6548
|
+
var buf = '';
|
|
6549
|
+
while (true) {
|
|
6550
|
+
var res = await reader2.read();
|
|
6551
|
+
if (res.done) break;
|
|
6552
|
+
buf += dec.decode(res.value, { stream: true });
|
|
6553
|
+
var parts = buf.split(String.fromCharCode(10)+String.fromCharCode(10));
|
|
6554
|
+
buf = parts.pop();
|
|
6555
|
+
for (var pi2 = 0; pi2 < parts.length; pi2++) {
|
|
6556
|
+
var line = parts[pi2].replace(/^data: /, '').trim();
|
|
6557
|
+
if (!line) continue;
|
|
6558
|
+
try {
|
|
6559
|
+
var ev = JSON.parse(line);
|
|
6560
|
+
if (ev.type === 'text') {
|
|
6561
|
+
agentMsg.text += ev.token;
|
|
6562
|
+
var typingEl = document.getElementById('wcChatTyping');
|
|
6563
|
+
if (typingEl) typingEl.textContent = '...';
|
|
6564
|
+
wcScrollChatToBottom();
|
|
6565
|
+
} else if (ev.type === 'tool') {
|
|
6566
|
+
agentMsg.tools.push({ op: ev.op, path: ev.path, result: ev.result });
|
|
6567
|
+
// Update file in generatedFiles if edited/written
|
|
6568
|
+
if ((ev.op === 'edit' || ev.op === 'write') && ev.result === 'ok') {
|
|
6569
|
+
// Reload file from server via projects/load (simplified: mark for reload)
|
|
6570
|
+
wcChat[wcChat.length-1] = agentMsg;
|
|
6571
|
+
renderWebCraft(document.getElementById('content'));
|
|
6572
|
+
}
|
|
6573
|
+
} else if (ev.type === 'done') {
|
|
6574
|
+
wcChatRunning = false;
|
|
6575
|
+
if (ev.changed) {
|
|
6576
|
+
// Reload project files from disk
|
|
6577
|
+
wcReloadProjectFiles();
|
|
6578
|
+
}
|
|
6579
|
+
} else if (ev.type === 'restart_sandbox') {
|
|
6580
|
+
wcStartSandbox();
|
|
6581
|
+
} else if (ev.type === 'error') {
|
|
6582
|
+
agentMsg.text += String.fromCharCode(10) + 'Errore: ' + ev.msg;
|
|
6583
|
+
wcChatRunning = false;
|
|
6584
|
+
}
|
|
6585
|
+
} catch(_) {}
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
} catch(e) {
|
|
6589
|
+
wcChat.push({ role: 'agent', text: 'Errore di rete: ' + e.message, tools: [] });
|
|
6590
|
+
}
|
|
6591
|
+
|
|
6592
|
+
wcChatRunning = false;
|
|
6593
|
+
renderWebCraft(document.getElementById('content'));
|
|
6594
|
+
wcScrollChatToBottom();
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
function wcScrollChatToBottom() {
|
|
6598
|
+
var el2 = document.getElementById('wcChatMessages');
|
|
6599
|
+
if (el2) el2.scrollTop = el2.scrollHeight;
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6602
|
+
async function wcReloadProjectFiles() {
|
|
6603
|
+
if (!wcState.projectName) return;
|
|
6604
|
+
try {
|
|
6605
|
+
var r = await fetch(API + '/api/studio/webcraft/projects/load/' + encodeURIComponent(wcState.projectName));
|
|
6606
|
+
if (!r.ok) return;
|
|
6607
|
+
var d = await r.json();
|
|
6608
|
+
wcState.generatedFiles = d.files || [];
|
|
6609
|
+
renderWebCraft(document.getElementById('content'));
|
|
6610
|
+
} catch(_) {}
|
|
6611
|
+
}
|
|
6612
|
+
|
|
6613
|
+
// Auto-fix: poll autofix-queue every 3s while sandbox running
|
|
6614
|
+
function wcStartAutoFixPoller() {
|
|
6615
|
+
if (_wcAutoFixTimer) return;
|
|
6616
|
+
_wcAutoFixTimer = setInterval(function() {
|
|
6617
|
+
if (!wcState.sandbox.running && !wcState.sandbox.port) { wcStopAutoFixPoller(); return; }
|
|
6618
|
+
fetch(API + '/api/studio/webcraft/agent/autofix-queue').then(function(r){ return r.json(); }).then(function(d){
|
|
6619
|
+
var items = d.items || [];
|
|
6620
|
+
items.forEach(function(item) {
|
|
6621
|
+
if (item.type === 'module_not_found' && _wcAutoFixAttempts < 3) {
|
|
6622
|
+
_wcAutoFixAttempts++;
|
|
6623
|
+
wcTriggerAutoFix(item.module);
|
|
6624
|
+
}
|
|
6625
|
+
});
|
|
6626
|
+
}).catch(function(){});
|
|
6627
|
+
}, 3000);
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6630
|
+
function wcStopAutoFixPoller() {
|
|
6631
|
+
if (_wcAutoFixTimer) { clearInterval(_wcAutoFixTimer); _wcAutoFixTimer = null; }
|
|
6632
|
+
}
|
|
6633
|
+
|
|
6634
|
+
async function wcTriggerAutoFix(missingModule) {
|
|
6635
|
+
if (wcChatRunning) return;
|
|
6636
|
+
var fixMsg = 'AUTO-FIX: Cannot find module ' + missingModule + String.fromCharCode(10) + 'Analizza tutti i file del progetto e correggi il require/import per questo modulo. Se il modulo non esiste, rimuovi il require e implementa la funzionalita con moduli disponibili o Node.js built-in.';
|
|
6637
|
+
wcChat.push({ role: 'user', text: '🤖 Auto-fix modulo mancante: ' + missingModule });
|
|
6638
|
+
wcChatRunning = true;
|
|
6639
|
+
renderWebCraft(document.getElementById('content'));
|
|
6640
|
+
wcScrollChatToBottom();
|
|
6641
|
+
|
|
6642
|
+
try {
|
|
6643
|
+
var r = await fetch(API + '/api/studio/webcraft/agent', {
|
|
6644
|
+
method: 'POST',
|
|
6645
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6646
|
+
body: JSON.stringify({ projectName: wcState.projectName, message: fixMsg, autofix: true })
|
|
6647
|
+
});
|
|
6648
|
+
if (!r.ok) { wcChatRunning = false; renderWebCraft(document.getElementById('content')); return; }
|
|
6649
|
+
|
|
6650
|
+
var agentMsg = { role: 'agent', text: '', tools: [] };
|
|
6651
|
+
wcChat.push(agentMsg);
|
|
6652
|
+
var reader3 = r.body.getReader();
|
|
6653
|
+
var dec2 = new TextDecoder();
|
|
6654
|
+
var buf2 = '';
|
|
6655
|
+
while (true) {
|
|
6656
|
+
var res2 = await reader3.read();
|
|
6657
|
+
if (res2.done) break;
|
|
6658
|
+
buf2 += dec2.decode(res2.value, { stream: true });
|
|
6659
|
+
var parts2 = buf2.split(String.fromCharCode(10)+String.fromCharCode(10));
|
|
6660
|
+
buf2 = parts2.pop();
|
|
6661
|
+
for (var pi3 = 0; pi3 < parts2.length; pi3++) {
|
|
6662
|
+
var line2 = parts2[pi3].replace(/^data: /, '').trim();
|
|
6663
|
+
if (!line2) continue;
|
|
6664
|
+
try {
|
|
6665
|
+
var ev2 = JSON.parse(line2);
|
|
6666
|
+
if (ev2.type === 'text') { agentMsg.text += ev2.token; }
|
|
6667
|
+
else if (ev2.type === 'tool') { agentMsg.tools.push({ op: ev2.op, path: ev2.path, result: ev2.result }); }
|
|
6668
|
+
else if (ev2.type === 'done') { wcChatRunning = false; if (ev2.changed) { wcReloadProjectFiles(); } }
|
|
6669
|
+
else if (ev2.type === 'restart_sandbox') { wcStartSandbox(); }
|
|
6670
|
+
else if (ev2.type === 'error') { agentMsg.text += String.fromCharCode(10)+'Errore: '+ev2.msg; wcChatRunning = false; }
|
|
6671
|
+
} catch(_) {}
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
} catch(_) {}
|
|
6675
|
+
|
|
6676
|
+
wcChatRunning = false;
|
|
6677
|
+
renderWebCraft(document.getElementById('content'));
|
|
6678
|
+
wcScrollChatToBottom();
|
|
6679
|
+
}
|
|
6379
6680
|
var _wcPhaseKeys = ['files','shims','pkg','env','deps','install','start'];
|
|
6380
6681
|
function wcTogglePhase(idx) { var k = _wcPhaseKeys[idx]; if (k) { wcSandboxExpanded[k] = !wcSandboxExpanded[k]; renderWebCraft(document.getElementById('content')); } }
|
|
6381
6682
|
|
|
@@ -6453,9 +6754,9 @@ function wcSetFile(i) { wcState.activeFile = i; renderWebCraft(document.getEleme
|
|
|
6453
6754
|
|
|
6454
6755
|
async function wcGenerate() {
|
|
6455
6756
|
if (wcState.running) return;
|
|
6456
|
-
var desc =
|
|
6457
|
-
var projName =
|
|
6458
|
-
if (!desc || desc.length <
|
|
6757
|
+
var desc = wcState.description;
|
|
6758
|
+
var projName = wcState.projectName || 'myproject';
|
|
6759
|
+
if (!desc || desc.length < 5) { alert(t('wc_describe_first')); return; }
|
|
6459
6760
|
wcState.description = desc;
|
|
6460
6761
|
wcState.projectName = projName;
|
|
6461
6762
|
wcState.running = true;
|
|
@@ -6743,6 +7044,8 @@ async function wcStartSandbox() {
|
|
|
6743
7044
|
wcState.sandbox.running = false;
|
|
6744
7045
|
wcState.sandbox.port = evt.port;
|
|
6745
7046
|
wcState.sandbox.dir = evt.dir;
|
|
7047
|
+
_wcAutoFixAttempts = 0;
|
|
7048
|
+
wcStartAutoFixPoller();
|
|
6746
7049
|
renderWebCraft(document.getElementById('content'));
|
|
6747
7050
|
} else if (evt.type === 'error') {
|
|
6748
7051
|
wcState.sandbox.running = false;
|
|
@@ -6762,6 +7065,7 @@ async function wcStartSandbox() {
|
|
|
6762
7065
|
async function wcStopSandbox() {
|
|
6763
7066
|
await fetch(API + '/api/studio/webcraft/sandbox', {method:'DELETE'});
|
|
6764
7067
|
wcState.sandbox = { running: false, port: null, dir: null, logs: [], error: null };
|
|
7068
|
+
wcStopAutoFixPoller();
|
|
6765
7069
|
renderWebCraft(document.getElementById('content'));
|
|
6766
7070
|
}
|
|
6767
7071
|
|