SysOps: voice-agy-webbuilder-backup — 2026-06-23 10:04 UTC
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
(function () {
|
||||
const buffers = {};
|
||||
let liveEs = null;
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s || '';
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function lineClass(type) {
|
||||
if (!type) return 'line-action';
|
||||
if (type === 'command') return 'line-command';
|
||||
if (type === 'output') return 'line-output';
|
||||
if (String(type).indexOf('handoff') >= 0) return 'line-handoff';
|
||||
if (type === 'error') return 'line-error';
|
||||
if (String(type).indexOf('monitor') >= 0) return 'line-handoff';
|
||||
if (String(type).indexOf('browse') >= 0) return 'line-action';
|
||||
return 'line-action';
|
||||
}
|
||||
|
||||
function formatLine(entry) {
|
||||
const t = (entry.at || '').substring(11, 19) || (entry.at || '').substring(11, 16) || '--:--';
|
||||
const msg = entry.message || entry.title || '';
|
||||
const detail = entry.detail ? ' — ' + String(entry.detail).slice(0, 200) : '';
|
||||
return (
|
||||
'<div class="term-line ' +
|
||||
lineClass(entry.type) +
|
||||
'"><span class="term-ts">' +
|
||||
esc(t) +
|
||||
'</span><span class="term-msg">' +
|
||||
esc(msg + detail) +
|
||||
'</span></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function render(key) {
|
||||
const els = document.querySelectorAll('[data-term-key="' + key + '"]');
|
||||
if (!els.length) return;
|
||||
const lines = buffers[key] || [];
|
||||
const html =
|
||||
!lines.length
|
||||
? '<div class="term-line term-idle"><span class="term-msg">Typ <code>help</code> voor commando\'s · Enter om uit te voeren</span></div>'
|
||||
: lines.slice(-80).map(formatLine).join('');
|
||||
els.forEach(function (el) {
|
||||
el.innerHTML = html;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
const win = el.closest('.agent-term-window');
|
||||
if (win && lines.length) win.classList.add('is-live');
|
||||
});
|
||||
}
|
||||
|
||||
function pushLine(key, entry, skipDup) {
|
||||
if (!key || !entry) return;
|
||||
key = String(key).toLowerCase();
|
||||
if (!buffers[key]) buffers[key] = [];
|
||||
if (skipDup && entry._id) {
|
||||
const seen = buffers[key].some(function (l) {
|
||||
return l._id === entry._id;
|
||||
});
|
||||
if (seen) return;
|
||||
}
|
||||
if (!entry.at) entry.at = nowIso();
|
||||
buffers[key].push(entry);
|
||||
if (buffers[key].length > 120) buffers[key].shift();
|
||||
render(key);
|
||||
}
|
||||
|
||||
function setBusy(key, busy) {
|
||||
document.querySelectorAll('[data-term-input="' + key + '"]').forEach(function (input) {
|
||||
input.disabled = !!busy;
|
||||
input.classList.toggle('is-busy', !!busy);
|
||||
});
|
||||
document.querySelectorAll('[data-term-key="' + key + '"]').forEach(function (body) {
|
||||
const win = body.closest('.agent-term-window');
|
||||
if (win) win.classList.toggle('is-running', !!busy);
|
||||
});
|
||||
}
|
||||
|
||||
async function runCommand(key, cmd) {
|
||||
key = String(key || '').toLowerCase();
|
||||
cmd = (cmd || '').trim();
|
||||
if (!cmd) return;
|
||||
if (cmd.toLowerCase() === 'clear') {
|
||||
buffers[key] = [];
|
||||
render(key);
|
||||
return;
|
||||
}
|
||||
pushLine(key, { type: 'command', message: '$ ' + cmd, at: nowIso() });
|
||||
setBusy(key, true);
|
||||
try {
|
||||
const r = await fetch('/api/agents/souls/' + encodeURIComponent(key) + '/terminal/command', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ command: cmd }),
|
||||
});
|
||||
const data = await r.json().catch(function () {
|
||||
return {};
|
||||
});
|
||||
if (!r.ok) {
|
||||
const err =
|
||||
(typeof data.detail === 'string' && data.detail) ||
|
||||
(Array.isArray(data.detail) && data.detail[0]?.msg) ||
|
||||
'Commando mislukt';
|
||||
pushLine(key, { type: 'error', message: err, at: nowIso() });
|
||||
return;
|
||||
}
|
||||
(data.lines || []).forEach(function (line) {
|
||||
pushLine(key, {
|
||||
type: line.type || 'output',
|
||||
message: line.message || '',
|
||||
detail: line.detail || '',
|
||||
at: nowIso(),
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
pushLine(key, { type: 'error', message: e.message || 'Netwerkfout', at: nowIso() });
|
||||
} finally {
|
||||
setBusy(key, false);
|
||||
}
|
||||
}
|
||||
|
||||
function bindInputs() {
|
||||
document.querySelectorAll('[data-term-input]').forEach(function (input) {
|
||||
if (input._termBound) return;
|
||||
input._termBound = true;
|
||||
const key = input.getAttribute('data-term-input');
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const val = input.value;
|
||||
input.value = '';
|
||||
runCommand(key, val);
|
||||
}
|
||||
});
|
||||
input.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadHistory(key) {
|
||||
try {
|
||||
const r = await fetch('/api/agents/souls/' + encodeURIComponent(key) + '/events?limit=30');
|
||||
if (!r.ok) return;
|
||||
const data = await r.json();
|
||||
buffers[key] = (data.items || []).map(function (ev) {
|
||||
const meta = ev.metadata || {};
|
||||
let type = 'action';
|
||||
const et = ev.event_type || '';
|
||||
if (et.indexOf('handoff') >= 0) type = 'handoff_out';
|
||||
if (meta.target_agent) type = 'handoff_out';
|
||||
if (meta.source_agent) type = 'handoff_in';
|
||||
if (et.indexOf('monitor') >= 0) type = 'monitor';
|
||||
if (et.indexOf('browse') >= 0) type = 'browse';
|
||||
if (et === 'terminal_in') type = 'command';
|
||||
if (et === 'terminal_out' || et === 'terminal_note') type = 'output';
|
||||
return {
|
||||
_id: ev.id,
|
||||
type: type,
|
||||
message: ev.title || ev.event_type,
|
||||
detail: ev.body,
|
||||
at: ev.created_at,
|
||||
};
|
||||
});
|
||||
render(key);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handleLivePayload(d) {
|
||||
if (!d || d.type === 'connected' || d.type === 'error') return;
|
||||
const key = (d.agent || '').toLowerCase();
|
||||
if (!key) return;
|
||||
pushLine(
|
||||
key,
|
||||
{
|
||||
_id: d.id,
|
||||
type: d.type,
|
||||
message: d.message,
|
||||
detail: d.detail,
|
||||
at: d.at,
|
||||
},
|
||||
true
|
||||
);
|
||||
document.querySelectorAll('[data-term-key="' + key + '"]').forEach(function (el) {
|
||||
const win = el.closest('.agent-term-window');
|
||||
if (win) win.classList.add('is-live');
|
||||
});
|
||||
}
|
||||
|
||||
function startLiveStream() {
|
||||
if (liveEs || typeof EventSource === 'undefined') return;
|
||||
liveEs = new EventSource('/api/agents/live/stream');
|
||||
liveEs.onmessage = function (ev) {
|
||||
try {
|
||||
handleLivePayload(JSON.parse(ev.data));
|
||||
} catch (e) {}
|
||||
};
|
||||
liveEs.onerror = function () {
|
||||
/* EventSource auto-reconnects */
|
||||
};
|
||||
}
|
||||
|
||||
function stopLiveStream() {
|
||||
if (liveEs) {
|
||||
try {
|
||||
liveEs.close();
|
||||
} catch (e) {}
|
||||
liveEs = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function mountAll() {
|
||||
const nodes = document.querySelectorAll('[data-term-key]');
|
||||
const keys = [];
|
||||
nodes.forEach(function (n) {
|
||||
const k = n.getAttribute('data-term-key');
|
||||
if (k && keys.indexOf(k) < 0) keys.push(k);
|
||||
});
|
||||
await Promise.all(keys.map(loadHistory));
|
||||
bindInputs();
|
||||
startLiveStream();
|
||||
}
|
||||
|
||||
function stopAll() {
|
||||
stopLiveStream();
|
||||
}
|
||||
|
||||
window.AgentsTerminals = {
|
||||
mountAll,
|
||||
stopAll,
|
||||
pushLine,
|
||||
render,
|
||||
runCommand,
|
||||
bindInputs,
|
||||
startLiveStream,
|
||||
stopLiveStream,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user