nothumanallowed 13.5.152 → 13.5.154
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 +33 -0
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +188 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.154",
|
|
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
|
@@ -4652,6 +4652,39 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
4652
4652
|
return;
|
|
4653
4653
|
}
|
|
4654
4654
|
|
|
4655
|
+
// POST /api/studio/webcraft/stream { system, user, max_tokens } → SSE token stream
|
|
4656
|
+
// Used by WebCraft generation for live typewriter effect per file
|
|
4657
|
+
if (pathname === '/api/studio/webcraft/stream' && method === 'POST') {
|
|
4658
|
+
const body = await parseBody(req, 131072);
|
|
4659
|
+
if (!body.system || !body.user) {
|
|
4660
|
+
sendJSON(res, 400, { error: 'system and user required' });
|
|
4661
|
+
logRequest(method, pathname, 400, Date.now() - start);
|
|
4662
|
+
return;
|
|
4663
|
+
}
|
|
4664
|
+
res.writeHead(200, {
|
|
4665
|
+
'Content-Type': 'text/event-stream',
|
|
4666
|
+
'Cache-Control': 'no-cache',
|
|
4667
|
+
'Connection': 'keep-alive',
|
|
4668
|
+
'Access-Control-Allow-Origin': '*',
|
|
4669
|
+
});
|
|
4670
|
+
let fullText = '';
|
|
4671
|
+
let tokIn = 0, tokOut = 0;
|
|
4672
|
+
try {
|
|
4673
|
+
await callLLMStream(config, body.system, body.user, (token) => {
|
|
4674
|
+
fullText += token;
|
|
4675
|
+
tokOut++;
|
|
4676
|
+
res.write('data: ' + JSON.stringify({ type: 'token', token }) + '\n\n');
|
|
4677
|
+
}, { max_tokens: body.max_tokens || 16384, temperature: 0.3 });
|
|
4678
|
+
tokIn = Math.round((body.system.length + body.user.length) / 4);
|
|
4679
|
+
res.write('data: ' + JSON.stringify({ type: 'done', text: fullText, usage: { prompt_tokens: tokIn, completion_tokens: tokOut } }) + '\n\n');
|
|
4680
|
+
} catch (e) {
|
|
4681
|
+
res.write('data: ' + JSON.stringify({ type: 'error', message: e.message }) + '\n\n');
|
|
4682
|
+
}
|
|
4683
|
+
res.end();
|
|
4684
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4655
4688
|
// ── WebCraft Projects — list and delete saved projects ──────────────────
|
|
4656
4689
|
// GET /api/studio/webcraft/projects → { projects: [{name, description, fileCount, createdAt, dir}] }
|
|
4657
4690
|
// DELETE /api/studio/webcraft/projects/:name → { ok }
|
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.154';
|
|
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
|
@@ -7384,38 +7384,32 @@ function wcGenElapsed() {
|
|
|
7384
7384
|
function wcStartGenTimer() {
|
|
7385
7385
|
if (_wcTimerInterval) clearInterval(_wcTimerInterval);
|
|
7386
7386
|
_wcTimerInterval = setInterval(function() {
|
|
7387
|
-
if (!wcState.running) { clearInterval(_wcTimerInterval); _wcTimerInterval = null; return; }
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
if (ov) { var spans = ov.querySelectorAll('span'); if (spans[1]) spans[1].textContent = wcGenElapsed(); }
|
|
7393
|
-
} else {
|
|
7394
|
-
wcUpdateGenOverlay(_wcGenOverlayState.fi, _wcGenOverlayState.total, _wcGenOverlayState.name);
|
|
7395
|
-
}
|
|
7387
|
+
if (!wcState.running && !wcState.repairing) { clearInterval(_wcTimerInterval); _wcTimerInterval = null; return; }
|
|
7388
|
+
// Always update time in-place — never re-render the whole component
|
|
7389
|
+
wcUpdateGenOverlay(_wcGenOverlayState.fi, _wcGenOverlayState.total, _wcGenOverlayState.name);
|
|
7390
|
+
var repairTime = document.getElementById('wcRepairTime');
|
|
7391
|
+
if (repairTime) repairTime.textContent = wcGenElapsed();
|
|
7396
7392
|
}, 1000);
|
|
7397
7393
|
}
|
|
7398
7394
|
|
|
7399
7395
|
function wcUpdateGenOverlay(fi2, total, name) {
|
|
7400
7396
|
_wcGenOverlayState = { fi: fi2, total: total, name: name };
|
|
7401
|
-
|
|
7402
|
-
var
|
|
7403
|
-
|
|
7404
|
-
var
|
|
7405
|
-
var
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
tokLabel +
|
|
7418
|
-
'<div style="display:flex;gap:4px;margin-top:10px">'+[0,1,2,3,4].map(function(_,idx){ return '<div style="width:6px;height:6px;border-radius:50%;background:var(--green);animation:wcDot 1.1s ease-in-out infinite '+(idx*0.14)+'s"></div>'; }).join('')+'</div>';
|
|
7397
|
+
// Update only the specific DOM nodes — no innerHTML rewrite, no flicker
|
|
7398
|
+
var pct = total > 0 ? Math.round((fi2 / total) * 100) : 0;
|
|
7399
|
+
var counterEl = document.getElementById('wcGenCounter');
|
|
7400
|
+
var barEl = document.getElementById('wcGenBar');
|
|
7401
|
+
var nameEl = document.getElementById('wcGenFileName');
|
|
7402
|
+
var timeEl = document.getElementById('wcGenTime');
|
|
7403
|
+
var pillLabel = document.getElementById('wcPillLabel');
|
|
7404
|
+
var pillCount = document.getElementById('wcPillCount');
|
|
7405
|
+
var pillTime = document.getElementById('wcPillTime');
|
|
7406
|
+
if (counterEl) counterEl.textContent = fi2 + ' / ' + total;
|
|
7407
|
+
if (barEl) barEl.style.width = pct + '%';
|
|
7408
|
+
if (nameEl) nameEl.textContent = name || '';
|
|
7409
|
+
if (timeEl) timeEl.textContent = wcGenElapsed();
|
|
7410
|
+
if (pillLabel) pillLabel.textContent = name ? name.split(',')[0].trim() : 'Generando...';
|
|
7411
|
+
if (pillCount) pillCount.textContent = fi2 + '/' + total;
|
|
7412
|
+
if (pillTime) pillTime.textContent = wcGenElapsed();
|
|
7419
7413
|
}
|
|
7420
7414
|
|
|
7421
7415
|
// Skills state
|
|
@@ -7479,8 +7473,8 @@ function renderWebCraft(el) {
|
|
|
7479
7473
|
'</div>' +
|
|
7480
7474
|
(_activeFile._error ? '<div style="padding:8px 14px;background:rgba(239,68,68,0.12);border-bottom:1px solid rgba(239,68,68,0.3);font-size:11px;color:#f87171;display:flex;align-items:center;gap:6px">⚠ Generazione fallita — chiedi al modello di rigenerare questo file</div>' :
|
|
7481
7475
|
_activeFile._syntaxError ? '<div style="padding:8px 14px;background:rgba(234,179,8,0.1);border-bottom:1px solid rgba(234,179,8,0.3);font-size:11px;color:#facc15;display:flex;align-items:center;gap:6px">⚠ Syntax error: '+wcEsc(_activeFile._syntaxError)+'</div>' : '') +
|
|
7482
|
-
(_activeFile._pending ? '<div style="display:flex;align-items:center;justify-content:center;height:120px;color:var(--dim);font-size:12px;gap:8px">⌛ In generazione...</div>' :
|
|
7483
|
-
'<pre style="margin:0;padding:14px 16px;font-size:11px;line-height:1.6;color:'+(_activeFile._error?'#f87171':_activeFile._syntaxError?'#fde68a':'var(--text)')+';font-family:var(--mono);white-space:pre-wrap;word-break:break-all">'+wcEsc(_activeFile.content)+'</pre>') +
|
|
7476
|
+
(_activeFile._pending ? '<div id="wcLivePending" style="display:flex;align-items:center;justify-content:center;height:120px;color:var(--dim);font-size:12px;gap:8px">⌛ In generazione...</div>' :
|
|
7477
|
+
'<pre id="wcLiveCode" style="margin:0;padding:14px 16px;font-size:11px;line-height:1.6;color:'+(_activeFile._error?'#f87171':_activeFile._syntaxError?'#fde68a':'var(--text)')+';font-family:var(--mono);white-space:pre-wrap;word-break:break-all">'+wcEsc(_activeFile.content)+'</pre>') +
|
|
7484
7478
|
'</div>' +
|
|
7485
7479
|
fileSidebarHtml +
|
|
7486
7480
|
'</div>'
|
|
@@ -7591,36 +7585,47 @@ function renderWebCraft(el) {
|
|
|
7591
7585
|
'<div data-wc-files style="position:relative;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">' +
|
|
7592
7586
|
(wcState.repairing ?
|
|
7593
7587
|
(_wcOverlayMinimized
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
+'<span style="font-size:
|
|
7597
|
-
+'<span style="font-size:
|
|
7588
|
+
// Minimized repair: pill bottom-right
|
|
7589
|
+
? '<div id="wcRepairOverlay" onclick="wcOverlayRestore()" style="position:absolute;bottom:12px;right:12px;z-index:50;background:rgba(0,0,0,0.85);border:1px solid rgba(234,179,8,0.6);border-radius:20px;padding:5px 12px;display:flex;align-items:center;gap:7px;cursor:pointer;backdrop-filter:blur(4px)">'
|
|
7590
|
+
+'<span style="font-size:14px;animation:wcRobotBob .9s ease-in-out infinite">🔧</span>'
|
|
7591
|
+
+'<span style="font-size:10px;color:#facc15;font-weight:700;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" id="wcRepairFile">'+wcEsc(wcState.repairCurrent || 'Correzione...')+'</span>'
|
|
7592
|
+
+'<span style="font-size:9px;color:var(--dim)" id="wcRepairCounter">'+wcState.repairDone+'/'+wcState.repairTotal+'</span>'
|
|
7598
7593
|
+'<span style="display:flex;gap:3px">'+[0,1,2].map(function(_,idx){ return '<span style="width:4px;height:4px;border-radius:50%;background:#facc15;animation:wcDot 1.1s ease-in-out infinite '+(idx*0.18)+'s"></span>'; }).join('')+'</span>'
|
|
7599
7594
|
+'</div>'
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
+'<
|
|
7603
|
-
+'<
|
|
7604
|
-
+'<
|
|
7605
|
-
+'<
|
|
7606
|
-
+'<
|
|
7595
|
+
// Full repair: sticky header bar — code stays visible below
|
|
7596
|
+
: '<div id="wcRepairOverlay" style="position:absolute;top:0;left:0;right:0;z-index:50;background:rgba(20,16,0,0.92);backdrop-filter:blur(6px);border-bottom:1px solid rgba(234,179,8,0.5);padding:8px 16px;display:flex;align-items:center;gap:10px">'
|
|
7597
|
+
+'<span style="font-size:16px;animation:wcRobotBob .9s ease-in-out infinite;flex-shrink:0">🔧</span>'
|
|
7598
|
+
+'<span style="font-size:11px;font-weight:700;color:#facc15;flex-shrink:0">Correzione automatica</span>'
|
|
7599
|
+
+'<span style="font-size:10px;color:#fde68a;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1" id="wcRepairFile">'+wcEsc(wcState.repairCurrent || '')+'</span>'
|
|
7600
|
+
+'<span style="font-size:10px;color:var(--dim);flex-shrink:0" id="wcRepairCounter">'+wcState.repairDone+' / '+wcState.repairTotal+' file</span>'
|
|
7601
|
+
+'<span onclick="wcOverlayMinimize()" style="font-size:10px;color:var(--dim);cursor:pointer;flex-shrink:0;padding:2px 6px;border:1px solid var(--border2);border-radius:4px" title="Minimizza">–</span>'
|
|
7602
|
+
+'<span style="display:flex;gap:3px">'+[0,1,2].map(function(_,idx){ return '<span style="width:4px;height:4px;border-radius:50%;background:#facc15;animation:wcDot 1.1s ease-in-out infinite '+(idx*0.18)+'s"></span>'; }).join('')+'</span>'
|
|
7607
7603
|
+'</div>'
|
|
7608
7604
|
)
|
|
7609
7605
|
: wcState.running ? (
|
|
7610
7606
|
_wcOverlayMinimized
|
|
7611
|
-
// Minimized:
|
|
7612
|
-
? '<div id="wcGenOverlay" onclick="wcOverlayRestore()" style="position:absolute;bottom:12px;right:12px;z-index:50;background:rgba(0,0,0,0.85);border:1px solid var(--green3);border-radius:20px;padding:5px 12px;display:flex;align-items:center;gap:7px;cursor:pointer;
|
|
7613
|
-
+'<span style="font-size:
|
|
7614
|
-
+'<span id="wcPillLabel" style="font-size:10px;color:var(--green);font-weight:700;max-width:
|
|
7615
|
-
+'<span style="font-size:9px;color:var(--dim)">'+
|
|
7607
|
+
// Minimized gen: pill bottom-right
|
|
7608
|
+
? '<div id="wcGenOverlay" onclick="wcOverlayRestore()" style="position:absolute;bottom:12px;right:12px;z-index:50;background:rgba(0,0,0,0.85);border:1px solid var(--green3);border-radius:20px;padding:5px 12px;display:flex;align-items:center;gap:7px;cursor:pointer;backdrop-filter:blur(4px)">'
|
|
7609
|
+
+'<span style="font-size:14px;animation:wcRobotBob .9s ease-in-out infinite">🤖</span>'
|
|
7610
|
+
+'<span id="wcPillLabel" style="font-size:10px;color:var(--green);font-weight:700;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+wcEsc((_wcGenOverlayState.name||'').split(',')[0].trim() || 'Generando...')+'</span>'
|
|
7611
|
+
+'<span id="wcPillCount" style="font-size:9px;color:var(--dim)">'+_wcGenOverlayState.fi+'/'+_wcGenOverlayState.total+'</span>'
|
|
7612
|
+
+'<span id="wcPillTime" style="font-size:9px;color:var(--dim)">'+wcGenElapsed()+'</span>'
|
|
7616
7613
|
+'<span style="display:flex;gap:3px">'+[0,1,2].map(function(_,idx){ return '<span style="width:4px;height:4px;border-radius:50%;background:var(--green);animation:wcDot 1.1s ease-in-out infinite '+(idx*0.18)+'s"></span>'; }).join('')+'</span>'
|
|
7617
7614
|
+'</div>'
|
|
7618
|
-
// Full
|
|
7619
|
-
: '<div id="wcGenOverlay"
|
|
7620
|
-
+'<div style="
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7615
|
+
// Full gen: sticky header bar — code streams visibly below
|
|
7616
|
+
: '<div id="wcGenOverlay" style="position:absolute;top:0;left:0;right:0;z-index:50;background:rgba(0,14,0,0.90);backdrop-filter:blur(6px);border-bottom:1px solid var(--green3);padding:8px 16px;display:flex;flex-direction:column;gap:4px">'
|
|
7617
|
+
+'<div style="display:flex;align-items:center;gap:10px">'
|
|
7618
|
+
+'<span style="font-size:16px;animation:wcRobotBob .9s ease-in-out infinite;flex-shrink:0">🤖</span>'
|
|
7619
|
+
+'<span style="font-size:11px;font-weight:700;color:var(--green);flex-shrink:0">Generazione in corso</span>'
|
|
7620
|
+
+'<span id="wcGenFileName" style="font-size:10px;color:var(--dim);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">'+wcEsc((_wcGenOverlayState.name||'').split(',')[0].trim())+'</span>'
|
|
7621
|
+
+'<span id="wcGenCounter" style="font-size:10px;color:var(--dim);flex-shrink:0">'+_wcGenOverlayState.fi+' / '+_wcGenOverlayState.total+'</span>'
|
|
7622
|
+
+'<span id="wcGenTime" style="font-size:10px;color:var(--dim);flex-shrink:0">'+wcGenElapsed()+'</span>'
|
|
7623
|
+
+'<span onclick="wcOverlayMinimize()" style="font-size:10px;color:var(--dim);cursor:pointer;flex-shrink:0;padding:2px 6px;border:1px solid var(--border2);border-radius:4px" title="Minimizza">–</span>'
|
|
7624
|
+
+'<span style="display:flex;gap:3px">'+[0,1,2].map(function(_,idx){ return '<span style="width:4px;height:4px;border-radius:50%;background:var(--green);animation:wcDot 1.1s ease-in-out infinite '+(idx*0.18)+'s"></span>'; }).join('')+'</span>'
|
|
7625
|
+
+'</div>'
|
|
7626
|
+
+'<div style="height:2px;background:rgba(255,255,255,0.07);border-radius:1px;overflow:hidden">'
|
|
7627
|
+
+'<div id="wcGenBar" style="height:100%;width:'+Math.round((_wcGenOverlayState.fi/_wcGenOverlayState.total||0)*100)+'%;background:var(--green);border-radius:1px;transition:width .3s ease"></div>'
|
|
7628
|
+
+'</div>'
|
|
7624
7629
|
+'</div>'
|
|
7625
7630
|
) : '') +
|
|
7626
7631
|
'<div style="display:flex;border-bottom:1px solid var(--border);flex-shrink:0">' +
|
|
@@ -8116,12 +8121,17 @@ function wcStopAll() {
|
|
|
8116
8121
|
}
|
|
8117
8122
|
|
|
8118
8123
|
function wcOverlayMinimize() {
|
|
8124
|
+
if (_wcOverlayTimer) clearTimeout(_wcOverlayTimer);
|
|
8119
8125
|
_wcOverlayMinimized = true;
|
|
8120
8126
|
renderWebCraft(document.getElementById('content'));
|
|
8121
|
-
|
|
8127
|
+
// Auto-restore after 30s of inactivity so user doesn't miss progress
|
|
8122
8128
|
_wcOverlayTimer = setTimeout(function() {
|
|
8123
|
-
if (wcState.running || wcState.repairing) {
|
|
8124
|
-
|
|
8129
|
+
if (wcState.running || wcState.repairing) {
|
|
8130
|
+
_wcOverlayMinimized = false;
|
|
8131
|
+
renderWebCraft(document.getElementById('content'));
|
|
8132
|
+
}
|
|
8133
|
+
_wcOverlayTimer = null;
|
|
8134
|
+
}, 30000);
|
|
8125
8135
|
}
|
|
8126
8136
|
|
|
8127
8137
|
function wcOverlayRestore() {
|
|
@@ -8864,19 +8874,20 @@ async function wcGenerate() {
|
|
|
8864
8874
|
}
|
|
8865
8875
|
|
|
8866
8876
|
// Helper: generate one file (with two-pass split for large CSS files)
|
|
8867
|
-
|
|
8877
|
+
// onLiveUpdate(partialContent) is called on each token for live display
|
|
8878
|
+
async function wcGenOneFile(fp, signal, onLiveUpdate) {
|
|
8868
8879
|
var _nl2 = String.fromCharCode(10);
|
|
8869
8880
|
var splitPrompts = WC_CSS_SPLIT[fp.name];
|
|
8870
8881
|
if (splitPrompts) {
|
|
8871
|
-
// Two-pass generation:
|
|
8872
|
-
var part1 = await wcCallLLM(sysPreamble, splitPrompts[0] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192);
|
|
8882
|
+
// Two-pass generation: streaming on first pass only
|
|
8883
|
+
var part1 = await wcCallLLM(sysPreamble, splitPrompts[0] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192, onLiveUpdate);
|
|
8873
8884
|
part1 = wcStripFences(part1);
|
|
8874
8885
|
if (signal && signal.aborted) return part1;
|
|
8875
|
-
var part2 = await wcCallLLM(sysPreamble, splitPrompts[1] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192);
|
|
8886
|
+
var part2 = await wcCallLLM(sysPreamble, splitPrompts[1] + _nl2 + _nl2 + 'File: ' + fp.name, signal, fp.lang, 8192, function(p2) { if (onLiveUpdate) onLiveUpdate(part1 + _nl2 + _nl2 + p2); });
|
|
8876
8887
|
part2 = wcStripFences(part2);
|
|
8877
8888
|
return part1 + _nl2 + _nl2 + part2;
|
|
8878
8889
|
}
|
|
8879
|
-
var content = await wcCallLLM(sysPreamble, fp.prompt + _nl2 + _nl2 + 'File to generate: ' + fp.name, signal, fp.lang);
|
|
8890
|
+
var content = await wcCallLLM(sysPreamble, fp.prompt + _nl2 + _nl2 + 'File to generate: ' + fp.name, signal, fp.lang, undefined, onLiveUpdate);
|
|
8880
8891
|
return wcStripFences(content);
|
|
8881
8892
|
}
|
|
8882
8893
|
|
|
@@ -8889,17 +8900,67 @@ async function wcGenerate() {
|
|
|
8889
8900
|
wcState.activeFile = 0;
|
|
8890
8901
|
renderWebCraft(document.getElementById('content'));
|
|
8891
8902
|
|
|
8903
|
+
// Live update: write token directly into DOM — zero re-render, zero flicker
|
|
8904
|
+
var _wcLiveDomTimers = {};
|
|
8905
|
+
function wcLiveUpdateFile(fpName, fpLang, partialContent) {
|
|
8906
|
+
// Always update state
|
|
8907
|
+
var fileIdx = -1;
|
|
8908
|
+
for (var li = 0; li < wcState.generatedFiles.length; li++) {
|
|
8909
|
+
if (wcState.generatedFiles[li].name === fpName) {
|
|
8910
|
+
wcState.generatedFiles[li].content = partialContent;
|
|
8911
|
+
wcState.generatedFiles[li]._pending = false;
|
|
8912
|
+
fileIdx = li;
|
|
8913
|
+
break;
|
|
8914
|
+
}
|
|
8915
|
+
}
|
|
8916
|
+
// If this file is the active one, update the <pre> directly (throttled 80ms)
|
|
8917
|
+
if (fileIdx !== wcState.activeFile) return;
|
|
8918
|
+
if (_wcLiveDomTimers[fpName]) return;
|
|
8919
|
+
_wcLiveDomTimers[fpName] = setTimeout(function() {
|
|
8920
|
+
delete _wcLiveDomTimers[fpName];
|
|
8921
|
+
// Replace pending placeholder with live <pre> if needed
|
|
8922
|
+
var pending = document.getElementById('wcLivePending');
|
|
8923
|
+
var pre = document.getElementById('wcLiveCode');
|
|
8924
|
+
var wrap = document.getElementById('wcCodeWrap');
|
|
8925
|
+
if (pending && wrap) {
|
|
8926
|
+
// First token: swap placeholder for <pre>
|
|
8927
|
+
var newPre = document.createElement('pre');
|
|
8928
|
+
newPre.id = 'wcLiveCode';
|
|
8929
|
+
newPre.style.cssText = 'margin:0;padding:14px 16px;font-size:11px;line-height:1.6;color:var(--text);font-family:var(--mono);white-space:pre-wrap;word-break:break-all';
|
|
8930
|
+
newPre.textContent = partialContent;
|
|
8931
|
+
pending.parentNode.replaceChild(newPre, pending);
|
|
8932
|
+
return;
|
|
8933
|
+
}
|
|
8934
|
+
if (pre) {
|
|
8935
|
+
pre.textContent = partialContent;
|
|
8936
|
+
// Auto-scroll to bottom so user sees latest tokens
|
|
8937
|
+
var codeWrap = document.getElementById('wcCodeWrap');
|
|
8938
|
+
if (codeWrap) codeWrap.scrollTop = codeWrap.scrollHeight;
|
|
8939
|
+
}
|
|
8940
|
+
}, 80);
|
|
8941
|
+
}
|
|
8942
|
+
|
|
8892
8943
|
// Generate in parallel batches of 4 — each call is independent/fresh to Liara
|
|
8944
|
+
// Counter increments 1 per file as it completes, not per batch
|
|
8893
8945
|
var BATCH = 4;
|
|
8894
8946
|
var doneCount = 0;
|
|
8947
|
+
wcUpdateGenOverlay(0, filePlan.length, '');
|
|
8895
8948
|
for (var bi = 0; bi < filePlan.length; bi += BATCH) {
|
|
8896
8949
|
if (_wcGenAbortCtrl && _wcGenAbortCtrl.signal.aborted) break;
|
|
8897
8950
|
var batch = filePlan.slice(bi, bi + BATCH);
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8951
|
+
var results = await Promise.allSettled(batch.map(function(fp, bii) {
|
|
8952
|
+
// Point activeFile at the first file of this batch so tokens appear immediately
|
|
8953
|
+
if (bii === 0) {
|
|
8954
|
+
var firstIdx = wcState.generatedFiles.findIndex(function(f){ return f.name === fp.name; });
|
|
8955
|
+
if (firstIdx >= 0) wcState.activeFile = firstIdx;
|
|
8956
|
+
}
|
|
8957
|
+
// Show file name in overlay as soon as it starts
|
|
8958
|
+
wcUpdateGenOverlay(doneCount, filePlan.length, fp.name);
|
|
8959
|
+
var liveCallback = function(partial) { wcLiveUpdateFile(fp.name, fp.lang, partial); };
|
|
8960
|
+
return wcGenOneFile(fp, _wcGenAbortCtrl ? _wcGenAbortCtrl.signal : null, liveCallback).then(function(c){ return { fp: fp, content: c }; });
|
|
8901
8961
|
}));
|
|
8902
|
-
results.forEach(function(r) {
|
|
8962
|
+
results.forEach(function(r, ri) {
|
|
8963
|
+
var batchIdx = ri;
|
|
8903
8964
|
if (r.status === 'fulfilled') {
|
|
8904
8965
|
var fp = r.value.fp;
|
|
8905
8966
|
for (var gi = 0; gi < wcState.generatedFiles.length; gi++) {
|
|
@@ -8909,20 +8970,20 @@ async function wcGenerate() {
|
|
|
8909
8970
|
}
|
|
8910
8971
|
}
|
|
8911
8972
|
} else if (r.reason && r.reason.name !== 'AbortError') {
|
|
8912
|
-
var
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
break;
|
|
8973
|
+
var fpErr = batch[batchIdx];
|
|
8974
|
+
if (fpErr) {
|
|
8975
|
+
for (var gi2 = 0; gi2 < wcState.generatedFiles.length; gi2++) {
|
|
8976
|
+
if (wcState.generatedFiles[gi2].name === fpErr.name) {
|
|
8977
|
+
wcState.generatedFiles[gi2] = { name: fpErr.name, content: '// Error generating this file: ' + (r.reason.message || 'unknown error'), lang: fpErr.lang || '', _error: true };
|
|
8978
|
+
break;
|
|
8979
|
+
}
|
|
8920
8980
|
}
|
|
8921
8981
|
}
|
|
8922
8982
|
}
|
|
8923
8983
|
doneCount++;
|
|
8984
|
+
// Update counter immediately on each file completion — no full re-render
|
|
8985
|
+
wcUpdateGenOverlay(doneCount, filePlan.length, doneCount < filePlan.length ? (batch[Math.min(batchIdx+1, batch.length-1)] || batch[batchIdx] || {}).name || '' : '');
|
|
8924
8986
|
});
|
|
8925
|
-
renderWebCraft(document.getElementById('content'));
|
|
8926
8987
|
}
|
|
8927
8988
|
|
|
8928
8989
|
if (_wcTimerInterval) { clearInterval(_wcTimerInterval); _wcTimerInterval = null; }
|
|
@@ -9120,7 +9181,57 @@ function wcIsTruncated(content, lang) {
|
|
|
9120
9181
|
return false;
|
|
9121
9182
|
}
|
|
9122
9183
|
|
|
9123
|
-
async function wcCallLLMRaw(sys, user, signal, maxTok) {
|
|
9184
|
+
async function wcCallLLMRaw(sys, user, signal, maxTok, onToken) {
|
|
9185
|
+
// Streaming path: use SSE endpoint so tokens appear live in the file editor
|
|
9186
|
+
if (onToken) {
|
|
9187
|
+
var streamOpts = {
|
|
9188
|
+
method: 'POST',
|
|
9189
|
+
headers: {'Content-Type':'application/json'},
|
|
9190
|
+
body: JSON.stringify({system: sys, user: user, max_tokens: maxTok || 16384})
|
|
9191
|
+
};
|
|
9192
|
+
if (signal) streamOpts.signal = signal;
|
|
9193
|
+
for (var sa = 0; sa < 3; sa++) {
|
|
9194
|
+
if (signal && signal.aborted) throw new DOMException('Aborted', 'AbortError');
|
|
9195
|
+
var sr = await fetch(API + '/api/studio/webcraft/stream', streamOpts);
|
|
9196
|
+
if (!sr.ok) {
|
|
9197
|
+
if (sr.status < 500 || sa === 2) throw new Error('LLM stream error ' + sr.status);
|
|
9198
|
+
await new Promise(function(resolve) { setTimeout(resolve, 2000); });
|
|
9199
|
+
continue;
|
|
9200
|
+
}
|
|
9201
|
+
var sreader = sr.body.getReader();
|
|
9202
|
+
var sdec = new TextDecoder();
|
|
9203
|
+
var sbuf = '';
|
|
9204
|
+
var fullText = '';
|
|
9205
|
+
while (true) {
|
|
9206
|
+
var sres = await sreader.read();
|
|
9207
|
+
if (sres.done) break;
|
|
9208
|
+
sbuf += sdec.decode(sres.value, {stream: true});
|
|
9209
|
+
var sparts = sbuf.split(String.fromCharCode(10) + String.fromCharCode(10));
|
|
9210
|
+
sbuf = sparts.pop();
|
|
9211
|
+
for (var si = 0; si < sparts.length; si++) {
|
|
9212
|
+
var sline = sparts[si].replace(/^data: /, '').trim();
|
|
9213
|
+
if (!sline) continue;
|
|
9214
|
+
try {
|
|
9215
|
+
var sev = JSON.parse(sline);
|
|
9216
|
+
if (sev.type === 'token') {
|
|
9217
|
+
fullText += sev.token;
|
|
9218
|
+
onToken(fullText);
|
|
9219
|
+
} else if (sev.type === 'done') {
|
|
9220
|
+
if (sev.usage) {
|
|
9221
|
+
_wcTokIn += (sev.usage.prompt_tokens || 0);
|
|
9222
|
+
_wcTokOut += (sev.usage.completion_tokens || 0);
|
|
9223
|
+
}
|
|
9224
|
+
} else if (sev.type === 'error') {
|
|
9225
|
+
throw new Error(sev.message || 'Stream error');
|
|
9226
|
+
}
|
|
9227
|
+
} catch(_) {}
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
return fullText;
|
|
9231
|
+
}
|
|
9232
|
+
}
|
|
9233
|
+
|
|
9234
|
+
// Non-streaming fallback (used by repair and continuation passes)
|
|
9124
9235
|
var fetchOpts = {
|
|
9125
9236
|
method: 'POST',
|
|
9126
9237
|
headers: {'Content-Type':'application/json'},
|
|
@@ -9132,12 +9243,10 @@ async function wcCallLLMRaw(sys, user, signal, maxTok) {
|
|
|
9132
9243
|
var r = await fetch(API + '/api/studio/webcraft', fetchOpts);
|
|
9133
9244
|
if (r.ok) {
|
|
9134
9245
|
var d = await r.json();
|
|
9135
|
-
// Accumulate token counts if the server returns usage data
|
|
9136
9246
|
if (d && d.usage) {
|
|
9137
9247
|
_wcTokIn += (d.usage.prompt_tokens || d.usage.input_tokens || 0);
|
|
9138
9248
|
_wcTokOut += (d.usage.completion_tokens || d.usage.output_tokens || 0);
|
|
9139
9249
|
} else if (d && d.text) {
|
|
9140
|
-
// Estimate from char count (4 chars ≈ 1 token)
|
|
9141
9250
|
_wcTokIn += Math.round((sys.length + user.length) / 4);
|
|
9142
9251
|
_wcTokOut += Math.round((d.text || '').length / 4);
|
|
9143
9252
|
}
|
|
@@ -9153,9 +9262,9 @@ async function wcCallLLMRaw(sys, user, signal, maxTok) {
|
|
|
9153
9262
|
}
|
|
9154
9263
|
}
|
|
9155
9264
|
|
|
9156
|
-
async function wcCallLLM(sys, user, signal, lang, maxTok) {
|
|
9157
|
-
var content = await wcCallLLMRaw(sys, user, signal, maxTok);
|
|
9158
|
-
// Continuation loop: if response is truncated, ask model to continue
|
|
9265
|
+
async function wcCallLLM(sys, user, signal, lang, maxTok, onToken) {
|
|
9266
|
+
var content = await wcCallLLMRaw(sys, user, signal, maxTok, onToken);
|
|
9267
|
+
// Continuation loop: if response is truncated, ask model to continue (no streaming for continuations)
|
|
9159
9268
|
var maxContinuations = 2;
|
|
9160
9269
|
for (var ci = 0; ci < maxContinuations; ci++) {
|
|
9161
9270
|
if (!wcIsTruncated(content, lang || 'text')) break;
|
|
@@ -9166,6 +9275,7 @@ async function wcCallLLM(sys, user, signal, lang, maxTok) {
|
|
|
9166
9275
|
var continuation = await wcCallLLMRaw(sys, continuePrompt, signal, maxTok);
|
|
9167
9276
|
if (!continuation || continuation.trim().length < 5) break;
|
|
9168
9277
|
content = content + String.fromCharCode(10) + continuation;
|
|
9278
|
+
if (onToken) onToken(content);
|
|
9169
9279
|
}
|
|
9170
9280
|
return content;
|
|
9171
9281
|
}
|