diff --git a/src/agent/library/full_state.js b/src/agent/library/full_state.js index c5db85f..7323457 100644 --- a/src/agent/library/full_state.js +++ b/src/agent/library/full_state.js @@ -66,6 +66,8 @@ export function getFullState(agent) { }, inventory: { counts: getInventoryCounts(bot), + stacksUsed: bot.inventory.items().length, + totalSlots: bot.inventory.slots.length, equipment: { helmet: helmet ? helmet.name : null, chestplate: chestplate ? chestplate.name : null, @@ -78,7 +80,6 @@ export function getFullState(agent) { humanPlayers: players, botPlayers: bots, entityTypes: getNearbyEntityTypes(bot).filter(t => t !== 'player' && t !== 'item'), - blockTypes: getNearbyBlockTypes(bot) }, modes: { summary: bot.modes.getMiniDocs() diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index 2db78e3..b1043b1 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -58,9 +58,9 @@ class MindServerProxy { this.agent.cleanKill(); }); - this.socket.on('send-message', (agentName, message) => { + this.socket.on('send-message', (data) => { try { - this.agent.respondFunc("NO USERNAME", message); + this.agent.respondFunc(data.from, data.message); } catch (error) { console.error('Error: ', JSON.stringify(error, Object.getOwnPropertyNames(error))); } diff --git a/src/mindcraft/mindserver.js b/src/mindcraft/mindserver.js index 0112660..c06e0b5 100644 --- a/src/mindcraft/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -173,14 +173,13 @@ export function createMindServer(host_public = false, port = 8080) { }); - socket.on('send-message', (agentName, message) => { + socket.on('send-message', (agentName, data) => { if (!agent_connections[agentName]) { console.warn(`Agent ${agentName} not in game, cannot send message via MindServer.`); return } try { - console.log(`Sending message to agent ${agentName}: ${message}`); - agent_connections[agentName].socket.emit('send-message', agentName, message) + agent_connections[agentName].socket.emit('send-message', data) } catch (error) { console.error('Error: ', error); } diff --git a/src/mindcraft/public/index.html b/src/mindcraft/public/index.html index 78f1fe5..6e81b32 100644 --- a/src/mindcraft/public/index.html +++ b/src/mindcraft/public/index.html @@ -26,7 +26,7 @@ border-radius: 4px; display: flex; flex-direction: column; - align-items: flex-start; + align-items: stretch; } .restart-btn, .start-btn, .stop-btn { color: white; @@ -107,18 +107,26 @@ .setting-wrapper input[type="checkbox"] { transform: scale(1.2); } + .agent-view-container { + width: 100%; + height: 100%; + aspect-ratio: 4/3; + overflow: hidden; + } .agent-viewer { - width: 200px; - height: 150px; + width: 100%; + height: 100%; border: none; - margin-left: 10px; + display: block; } .last-message { font-style: italic; color: #aaa; margin-top: 5px; - white-space: pre-wrap; - word-break: break-word; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; } .start-btn:disabled { opacity: 0.4; @@ -129,6 +137,90 @@ display: flex; justify-content: flex-start; } + .agent-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 8px; + width: 100%; + align-items: start; + } + .agent-grid .cell { + background: #3a3a3a; + padding: 6px 8px; + border-radius: 4px; + } + .agent-grid .cell.title { + background: transparent; + padding: 0; + } + .agent-inventory { + margin-top: 8px; + background: #2f2f2f; + border-radius: 6px; + padding: 8px; + } + .agent-inventory h3 { margin: 0 0 6px 0; font-size: 1em; } + .inventory-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 6px; + } + .agent-details-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 6px; + width: 100%; + } + .controls-row { + margin-top: 8px; + display: grid; + grid-template-columns: auto 1fr auto auto auto auto; + gap: 8px; + align-items: center; + } + .msg-input { + width: calc(100% - 8px); + background: #262626; + border: 1px solid #555; + color: #e0e0e0; + border-radius: 4px; + padding: 4px 6px; + } + .neutral-btn { + color: white; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + margin-left: 5px; + background: #505050; + } + .neutral-btn:hover { background: #5a5a5a; } + .neutral-btn:disabled { + background: #383838; + color: #666; + cursor: not-allowed; + } + .msg-input:disabled { + background: #1a1a1a; + color: #666; + cursor: not-allowed; + } + .page-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #2d2d2d; + padding: 16px; + display: flex; + gap: 12px; + justify-content: space-between; + box-shadow: 0 -2px 10px rgba(0,0,0,0.2); + } + body { + padding-bottom: 80px; /* Make room for footer */ + } .agent-stats-row { display: grid; grid-template-columns: repeat(5, 1fr); @@ -226,8 +318,14 @@
-
- + @@ -439,6 +537,7 @@ // Subscribe to aggregated state updates (re-sent on each connect) socket.on('state-update', (states) => { + window.lastStates = states; Object.keys(states || {}).forEach(name => { const st = states[name]; const healthEl = document.getElementById(`health-${name}`); @@ -452,9 +551,13 @@ const hunEl = document.getElementById(`hunger-${name}`); const bioEl = document.getElementById(`biome-${name}`); const modeEl = document.getElementById(`mode-${name}`); + const itemsEl = document.getElementById(`items-${name}`); + const equippedEl = document.getElementById(`equipped-${name}`); + const invGrid = document.getElementById(`inventory-${name}`); + const actionEl = document.getElementById(`action-${name}`); if (posEl && gp.position) { const p = gp.position; - posEl.textContent = `pos: x ${p.x}, y ${p.y}, z ${p.z}`; + posEl.textContent = `x ${p.x}, y ${p.y}, z ${p.z}`; } if (hunEl && typeof gp.hunger === 'number') { const fMax = typeof gp.hungerMax === 'number' ? gp.hungerMax : 20; @@ -462,6 +565,24 @@ } if (bioEl && gp.biome) bioEl.textContent = `biome: ${gp.biome}`; if (modeEl && gp.gamemode) modeEl.textContent = `gamemode: ${gp.gamemode}`; + if (itemsEl && st.inventory) { + const used = st.inventory.stacksUsed ?? 0; + const total = st.inventory.totalSlots ?? 0; + itemsEl.textContent = `inventory slots: ${used}/${total}`; + } + if (equippedEl && st.inventory?.equipment) { + const e = st.inventory.equipment; + equippedEl.textContent = `equipped: ${e.mainHand || 'none'}`; + } + if (actionEl && st.action) { + actionEl.textContent = `${st.action.current || 'Idle'}`; + } + if (invGrid && st.inventory?.counts) { + const counts = st.inventory.counts; + invGrid.innerHTML = Object.keys(counts).length ? + Object.entries(counts).map(([k, v]) => `
${k}: ${v}
`).join('') : + '
(empty)
'; + } } }); }); @@ -584,6 +705,19 @@ originalAgentSettings = null; } + function updateAgentViewer(name) { + const agentEl = document.getElementById(`agent-${name}`); + if (!agentEl) return; + + const settings = agentSettings[name]; + const viewerContainer = agentEl.querySelector('.agent-view-container'); + if (!viewerContainer) return; + + const agentState = agents.find(a => a.name === name); + const shouldShow = agentState?.in_game && settings?.render_bot_view === true; + viewerContainer.parentElement.style.display = shouldShow ? '' : 'none'; + } + discardBtn.addEventListener('click', () => { if (!currentAgentName || !originalAgentSettings) return; buildAgentSettingsForm(originalAgentSettings); @@ -593,6 +727,9 @@ if (!currentAgentName) return; const edited = getEditedAgentSettings(); socket.emit('set-agent-settings', currentAgentName, edited); + // Update local settings immediately + agentSettings[currentAgentName] = { ...edited, fetched: true }; + updateAgentViewer(currentAgentName); closeAgentSettings(); }); @@ -602,53 +739,118 @@ // 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 = cfg.render_bot_view === true; + const showViewer = agent.in_game && cfg.render_bot_view === true; const viewerHTML = showViewer ? `
` : ''; const lastMessage = agentLastMessage[agent.name] || ''; return ` -
-
- ${agent.name} - - -
- ${agent.in_game ? ` - - - - - ` : ` - - `} +
+
+
+ ${agent.name} + + +
+ ${showViewer ? `
${viewerHTML}
` : ''} +
action: -
+
gamemode: -
+
health: -
+
hunger: -
+
pos: -
+
biome: -
+
inventory slots: -
+
equipped: -
+ +
Last Message: ${lastMessage}
-
-
gamemode: -
-
health: -
-
hunger: -
-
pos: -
-
biome: -
+
+ + + + + + +
-
${lastMessage}
- ${viewerHTML}
`; - }).join('') + - ` - ` : + }).join('') : '
No agents connected
'; + + // Update viewers after DOM has updated + setTimeout(updateViewers, 0); } - socket.on('agents-update', agents => { renderAgents(agents); }); + 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 }; + } + })); + renderAgents(agents); + }); function restartAgent(n) { socket.emit('restart-agent', n); } - function startAgent(n) { socket.emit('start-agent', n); } + function disconnectAgent(n) { socket.emit('stop-agent', n); } + function startAgent(n) { + const btn = document.querySelector(`button[onclick="startAgent('${n}')"]`); + if (btn) { + btn.textContent = 'Connecting...'; + btn.disabled = true; + } + socket.emit('start-agent', n); + } function stopAgent(n) { socket.emit('stop-agent', n); } - function killAllAgents() { socket.emit('stop-all-agents'); } - function shutdown() { socket.emit('shutdown'); } - function sendMessage(n, m) { socket.emit('send-message', n, m); } + function disconnectAllAgents() { + socket.emit('stop-all-agents'); + } + function confirmShutdown() { + if (confirm('Are you sure you want to perform a full shutdown?\nThis will stop all agents and close the server.')) { + socket.emit('shutdown'); + } + } + function sendMessage(n, m) { + if (!m || !m.trim()) return; + socket.emit('send-message', n, { from: 'ADMIN', message: m }); + const input = document.getElementById(`messageInput-${n}`); + const btn = document.getElementById(`sendBtn-${n}`); + if (input) input.value = ''; + if (btn) btn.disabled = true; + } + function onMsgInputChange(name) { + const input = document.getElementById(`messageInput-${name}`); + const btn = document.getElementById(`sendBtn-${name}`); + if (btn && input) { + btn.disabled = !(input.value && input.value.trim().length > 0); + } + } + + function toggleDetails(name) { + const invSection = document.getElementById(`inventorySection-${name}`); + if (!invSection) return; + const visible = invSection.style.display !== 'none'; + invSection.style.display = visible ? 'none' : ''; + } + window.toggleDetails = toggleDetails;