From b7697723bcb825217d80e8bb8afc9042c1ca5f7d Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Wed, 27 Aug 2025 12:19:36 -0500 Subject: [PATCH] remove agent from ui, smarter viewer logic --- main.js | 2 +- settings.js | 2 +- src/agent/mindserver_proxy.js | 2 +- src/mindcraft/mindcraft.js | 14 ++- src/mindcraft/mindserver.js | 34 +++++-- src/mindcraft/public/index.html | 166 +++++++++++++++++++++++++------- 6 files changed, 167 insertions(+), 53 deletions(-) diff --git a/main.js b/main.js index cabf295..4402cb9 100644 --- a/main.js +++ b/main.js @@ -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')); diff --git a/settings.js b/settings.js index f5487e3..26c0347 100644 --- a/settings.js +++ b/settings.js @@ -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": [ diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index b1043b1..68024a1 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -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) { diff --git a/src/mindcraft/mindcraft.js b/src/mindcraft/mindcraft.js index 640576f..b797a67 100644 --- a/src/mindcraft/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -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) { diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index c06e0b5..64af008 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -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); } diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html index 6e81b32..a56f498 100644 --- a/src/mindcraft/public/index.html +++ b/src/mindcraft/public/index.html @@ -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 ? `
` : ''; - 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 ? `
` : ''; + const lastMessage = agentLastMessage[agent.name] || ''; + return `
@@ -773,6 +775,7 @@
equipped: -
Last Message: ${lastMessage}
@@ -783,30 +786,118 @@ oninput="onMsgInputChange('${agent.name}')" onkeydown="if(event.key === 'Enter') document.getElementById('sendBtn-${agent.name}').click()" ${!agent.in_game ? 'disabled' : ''}> - - - + + +
`; - }).join('') : - '
No agents connected
'; - - // 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 = '
No agents connected
'; + 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'); }