remove agent from ui, smarter viewer logic

This commit is contained in:
MaxRobinsonTheGreat 2025-08-27 12:19:36 -05:00
parent 69642d15fd
commit b7697723bc
6 changed files with 167 additions and 53 deletions

View file

@ -63,7 +63,7 @@ if (process.env.LOG_ALL) {
settings.log_all_prompts = process.env.LOG_ALL;
}
Mindcraft.init(false, settings.mindserver_port, settings.auto_open_browser);
Mindcraft.init(false, settings.mindserver_port, settings.auto_open_ui);
for (let profile of settings.profiles) {
const profile_json = JSON.parse(readFileSync(profile, 'utf8'));

View file

@ -6,7 +6,7 @@ const settings = {
// the mindserver manages all agents and hosts the UI
"mindserver_port": 8080,
"auto_open_browser": true,
"auto_open_ui": true,
"base_profile": "assistant", // survival, assistant, creative, or god_mode
"profiles": [

View file

@ -44,7 +44,7 @@ class MindServerProxy {
convoManager.receiveFromBot(agentName, json);
});
this.socket.on('agents-update', (agents) => {
this.socket.on('agents-status', (agents) => {
this.agents = agents;
convoManager.updateAgents(agents);
if (this.agent?.task) {

View file

@ -9,7 +9,7 @@ let agent_processes = {};
let agent_count = 0;
let port = 8080;
export async function init(host_public=false, port=8080, auto_open_browser=true) {
export async function init(host_public=false, port=8080, auto_open_ui=true) {
if (connected) {
console.error('Already initiliazed!');
return;
@ -17,7 +17,7 @@ export async function init(host_public=false, port=8080, auto_open_browser=true)
mindserver = createMindServer(host_public, port);
port = port;
connected = true;
if (auto_open_browser) {
if (auto_open_ui) {
setTimeout(() => {
// check if browser listener is already open
if (numStateListeners() === 0) {
@ -34,7 +34,8 @@ export async function createAgent(settings) {
}
settings = JSON.parse(JSON.stringify(settings));
let agent_name = settings.profile.name;
registerAgent(settings);
const viewer_port = 3000 + agent_count;
registerAgent(settings, viewer_port);
let load_memory = settings.load_memory || false;
let init_message = settings.init_message || null;
@ -68,6 +69,13 @@ export function stopAgent(agentName) {
}
}
export function destroyAgent(agentName) {
if (agent_processes[agentName]) {
agent_processes[agentName].stop();
delete agent_processes[agentName];
}
}
export function shutdown() {
console.log('Shutting down');
for (let agentName in agent_processes) {

View file

@ -20,26 +20,27 @@ const agent_listeners = [];
const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8'));
class AgentConnection {
constructor(settings) {
constructor(settings, viewer_port) {
this.socket = null;
this.settings = settings;
this.in_game = false;
this.full_state = null;
this.viewer_port = viewer_port;
}
setSettings(settings) {
this.settings = settings;
}
}
export function registerAgent(settings) {
let agentConnection = new AgentConnection(settings);
export function registerAgent(settings, viewer_port) {
let agentConnection = new AgentConnection(settings, viewer_port);
agent_connections[settings.profile.name] = agentConnection;
}
export function logoutAgent(agentName) {
if (agent_connections[agentName]) {
agent_connections[agentName].in_game = false;
agentsUpdate();
agentsStatusUpdate();
}
}
@ -58,7 +59,7 @@ export function createMindServer(host_public = false, port = 8080) {
let curAgentName = null;
console.log('Client connected');
agentsUpdate(socket);
agentsStatusUpdate(socket);
socket.on('create-agent', (settings, callback) => {
console.log('API create agent...');
@ -105,7 +106,7 @@ export function createMindServer(host_public = false, port = 8080) {
agent_connections[agentName].socket = socket;
agent_connections[agentName].in_game = true;
curAgentName = agentName;
agentsUpdate();
agentsStatusUpdate();
}
else {
console.warn(`Unregistered agent ${agentName} tried to login`);
@ -116,7 +117,7 @@ export function createMindServer(host_public = false, port = 8080) {
if (agent_connections[curAgentName]) {
console.log(`Agent ${curAgentName} disconnected`);
agent_connections[curAgentName].in_game = false;
agentsUpdate();
agentsStatusUpdate();
}
if (agent_listeners.includes(socket)) {
removeListener(socket);
@ -153,6 +154,14 @@ export function createMindServer(host_public = false, port = 8080) {
mindcraft.startAgent(agentName);
});
socket.on('destroy-agent', (agentName) => {
if (agent_connections[agentName]) {
mindcraft.destroyAgent(agentName);
delete agent_connections[agentName];
}
agentsStatusUpdate();
});
socket.on('stop-all-agents', () => {
console.log('Killing all agents');
for (let agentName in agent_connections) {
@ -202,15 +211,20 @@ export function createMindServer(host_public = false, port = 8080) {
return server;
}
function agentsUpdate(socket) {
function agentsStatusUpdate(socket) {
if (!socket) {
socket = io;
}
let agents = [];
for (let agentName in agent_connections) {
agents.push({name: agentName, in_game: agent_connections[agentName].in_game});
const conn = agent_connections[agentName];
agents.push({
name: agentName,
in_game: conn.in_game,
viewerPort: conn.viewer_port
});
};
socket.emit('agents-update', agents);
socket.emit('agents-status', agents);
}

View file

@ -174,7 +174,7 @@
.controls-row {
margin-top: 8px;
display: grid;
grid-template-columns: auto 1fr auto auto auto auto;
grid-template-columns: auto minmax(100px, 1fr) repeat(5, auto);
gap: 8px;
align-items: center;
}
@ -383,6 +383,7 @@
let profileData = null;
const agentSettings = {};
const agentLastMessage = {};
let currentAgents = [];
const statusEl = document.getElementById('msStatus');
function updateStatus(connected) {
@ -405,6 +406,8 @@
socket.on('connect', () => {
updateStatus(true);
subscribeToState();
// Clear all cached settings on reconnect
Object.keys(agentSettings).forEach(name => delete agentSettings[name]);
});
socket.on('disconnect', () => {
updateStatus(false);
@ -574,6 +577,16 @@
const e = st.inventory.equipment;
equippedEl.textContent = `equipped: ${e.mainHand || 'none'}`;
}
const armorEl = document.getElementById(`armor-${name}`);
if (armorEl && st.inventory?.equipment) {
const e = st.inventory.equipment;
const armor = [];
if (e.helmet) armor.push(`head: ${e.helmet}`);
if (e.chestplate) armor.push(`chest: ${e.chestplate}`);
if (e.leggings) armor.push(`legs: ${e.leggings}`);
if (e.boots) armor.push(`feet: ${e.boots}`);
armorEl.textContent = `armor: ${armor.length ? armor.join(', ') : 'none'}`;
}
if (actionEl && st.action) {
actionEl.textContent = `${st.action.current || 'Idle'}`;
}
@ -713,7 +726,7 @@
const viewerContainer = agentEl.querySelector('.agent-view-container');
if (!viewerContainer) return;
const agentState = agents.find(a => a.name === name);
const agentState = currentAgents.find(a => a.name === name);
const shouldShow = agentState?.in_game && settings?.render_bot_view === true;
viewerContainer.parentElement.style.display = shouldShow ? '' : 'none';
}
@ -735,25 +748,14 @@
closeAgentSettingsBtn.addEventListener('click', closeAgentSettings);
async function renderAgents(agents) {
// fetch settings for any new agents
await Promise.all(agents.map(a => fetchAgentSettings(a.name)));
// Update all agent viewers after render
const updateViewers = () => {
agents.forEach(a => {
if (a.in_game) updateAgentViewer(a.name);
});
};
agentsDiv.innerHTML = agents.length ?
// Set timeout to run after DOM update
agents.map((agent, idx) => {
const cfg = agentSettings[agent.name] || {};
const showViewer = agent.in_game && cfg.render_bot_view === true;
const viewerHTML = showViewer ? `<div class="agent-view-container"><iframe class="agent-viewer" src="http://localhost:${3000 + idx}"></iframe></div>` : '';
const lastMessage = agentLastMessage[agent.name] || '';
return `
function renderAgentCard(agent) {
const cfg = agentSettings[agent.name] || {};
const showViewer = agent.in_game && cfg.render_bot_view === true;
const viewerPort = agent.viewerPort;
const viewerHTML = showViewer ? `<div class="agent-view-container"><iframe class="agent-viewer" id="viewer-${agent.name}" src="http://localhost:${viewerPort}"></iframe></div>` : '';
const lastMessage = agentLastMessage[agent.name] || '';
return `
<div class="agent" id="agent-${agent.name}">
<div class="agent-grid">
<div class="cell title" style="grid-column: 1 / -1; display:flex; align-items:center; justify-content:space-between;">
@ -773,6 +775,7 @@
<div class="cell" id="equipped-${agent.name}">equipped: -</div>
<div class="agent-inventory" id="inventorySection-${agent.name}" style="display:none; grid-column: 1 / -1;">
<h3>Inventory</h3>
<div class="cell" id="armor-${agent.name}" style="margin-bottom: 8px;">armor: -</div>
<div class="inventory-grid" id="inventory-${agent.name}"></div>
</div>
<div id="lastMessage-${agent.name}" class="last-message" style="grid-column: 1 / -1;"><strong>Last Message:</strong> ${lastMessage}</div>
@ -783,30 +786,118 @@
oninput="onMsgInputChange('${agent.name}')"
onkeydown="if(event.key === 'Enter') document.getElementById('sendBtn-${agent.name}').click()"
${!agent.in_game ? 'disabled' : ''}>
<button class="neutral-btn" onclick="startAgent('${agent.name}')" style="display: ${agent.in_game ? 'none' : ''}">Connect</button>
<button class="neutral-btn" onclick="disconnectAgent('${agent.name}')" style="display: ${agent.in_game ? '' : 'none'}">Disconnect</button>
<button class="neutral-btn" onclick="restartAgent('${agent.name}')" ${!agent.in_game ? 'disabled' : ''}>Restart</button>
<button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stop')" ${!agent.in_game ? 'disabled' : ''}>Stop Action</button>
<button class="neutral-btn" onclick="sendMessage('${agent.name}', '!stay(-1)')" ${!agent.in_game ? 'disabled' : ''}>Stay Still</button>
<button class="neutral-btn" onclick="restartAgent('${agent.name}')" ${!agent.in_game ? 'disabled' : ''}>Restart</button>
<button class="neutral-btn" onclick="${agent.in_game ? 'disconnectAgent' : 'startAgent'}('${agent.name}')">${agent.in_game ? 'Disconnect' : 'Connect'}</button>
<button class="stop-btn" onclick="destroyAgent('${agent.name}')">Remove</button>
</div>
</div>`;
}).join('') :
'<div class="agent">No agents connected</div>';
// Update viewers after DOM has updated
setTimeout(updateViewers, 0);
}
socket.on('agents-update', async (agents) => {
// Fetch settings for any newly connected agents
const newlyConnected = agents.filter(a => a.in_game && (!agentSettings[a.name] || !agentSettings[a.name].fetched));
await Promise.all(newlyConnected.map(async (a) => {
const settings = await fetchAgentSettings(a.name);
if (settings) {
agentSettings[a.name] = { ...settings, fetched: true };
async function renderAgents(agents) {
if (!agents.length) {
agentsDiv.innerHTML = '<div class="agent">No agents connected</div>';
return;
}
// If agentsDiv is empty, do a full render
if (!agentsDiv.children.length) {
agentsDiv.innerHTML = agents.map(agent => renderAgentCard(agent)).join('');
// Update all viewers after initial render
setTimeout(() => {
agents.forEach(a => {
if (a.in_game) updateAgentViewer(a.name);
});
}, 0);
return;
}
// Compare with current agents to find changes
const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {});
const changedAgents = agents.filter(a => {
const prev = prevAgents[a.name];
return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort;
});
// Update only changed agents
changedAgents.forEach(agent => {
const el = document.getElementById(`agent-${agent.name}`);
if (el) {
// Update existing card
el.outerHTML = renderAgentCard(agent);
if (agent.in_game) updateAgentViewer(agent.name);
} else {
// Add new card
agentsDiv.insertAdjacentHTML('beforeend', renderAgentCard(agent));
if (agent.in_game) updateAgentViewer(agent.name);
}
}));
renderAgents(agents);
});
// Remove cards for agents that no longer exist
Array.from(agentsDiv.children).forEach(el => {
const name = el.id.replace('agent-', '');
if (!agents.find(a => a.name === name)) {
el.remove();
}
});
}
socket.on('agents-status', async (agents) => {
// Fetch settings for all agents that don't have current settings
const needSettings = agents.filter(a => !agentSettings[a.name]);
if (needSettings.length > 0) {
await Promise.all(needSettings.map(async (a) => {
const settings = await fetchAgentSettings(a.name);
if (settings) {
agentSettings[a.name] = settings;
}
}));
}
// Compare with current agents to find changes
const prevAgents = currentAgents.reduce((acc, a) => ({ ...acc, [a.name]: a }), {});
const changedAgents = agents.filter(a => {
const prev = prevAgents[a.name];
return !prev || prev.in_game !== a.in_game || prev.viewerPort !== a.viewerPort;
});
// Update current agents list
currentAgents = agents;
// If agentsDiv is empty, do a full render
if (!agentsDiv.children.length) {
agentsDiv.innerHTML = agents.map(agent => renderAgentCard(agent)).join('');
// Update all viewers after initial render
setTimeout(() => {
agents.forEach(a => {
if (a.in_game) updateAgentViewer(a.name);
});
}, 0);
return;
}
// Update only changed agents
changedAgents.forEach(agent => {
const el = document.getElementById(`agent-${agent.name}`);
if (el) {
// Update existing card
el.outerHTML = renderAgentCard(agent);
if (agent.in_game) updateAgentViewer(agent.name);
} else {
// Add new card
agentsDiv.insertAdjacentHTML('beforeend', renderAgentCard(agent));
if (agent.in_game) updateAgentViewer(agent.name);
}
});
// Remove cards for agents that no longer exist
Array.from(agentsDiv.children).forEach(el => {
const name = el.id.replace('agent-', '');
if (!agents.find(a => a.name === name)) {
el.remove();
}
});
});
function restartAgent(n) { socket.emit('restart-agent', n); }
@ -820,6 +911,7 @@
socket.emit('start-agent', n);
}
function stopAgent(n) { socket.emit('stop-agent', n); }
function destroyAgent(n) { socket.emit('destroy-agent', n); }
function disconnectAllAgents() {
socket.emit('stop-all-agents');
}