mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-07-13 03:25:17 +02:00
improve ui and default settings
This commit is contained in:
parent
0eb16cc3ec
commit
00127506b1
7 changed files with 341 additions and 161 deletions
|
@ -28,7 +28,7 @@ const settings = {
|
|||
"only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly
|
||||
"speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak`
|
||||
"language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages
|
||||
"render_bot_views": false, // show bot's view in browser at localhost:3000, 3001...
|
||||
"render_bot_view": false, // show bot's view in browser at localhost:3000, 3001...
|
||||
|
||||
"allow_insecure_coding": false, // allows newAction command and model can write/run code on your computer. enable at own risk
|
||||
"allow_vision": false, // allows vision model to interpret screenshots as inputs
|
||||
|
|
|
@ -23,10 +23,10 @@ export class Agent {
|
|||
this.count_id = count_id;
|
||||
|
||||
// Initialize components with more detailed error handling
|
||||
console.log(`Initializing agent ${this.name}...`);
|
||||
this.actions = new ActionManager(this);
|
||||
this.prompter = new Prompter(this, settings.profile);
|
||||
this.name = this.prompter.getName();
|
||||
console.log(`Initializing agent ${this.name}...`);
|
||||
this.history = new History(this);
|
||||
this.coder = new Coder(this);
|
||||
this.npc = new NPCContoller(this);
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"minecraft_version": "1.21.1",
|
||||
"host": "127.0.0.1",
|
||||
"port": 55916,
|
||||
"auth": "offline",
|
||||
"base_profile": "survival",
|
||||
"load_memory": false,
|
||||
"init_message": "Respond with hello world and your name",
|
||||
"only_chat_with": [],
|
||||
"speak": false,
|
||||
"language": "en",
|
||||
"allow_vision": false,
|
||||
"blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] ,
|
||||
"relevant_docs_count": 5,
|
||||
"max_messages": 15,
|
||||
"num_examples": 2,
|
||||
"max_commands": -1,
|
||||
"narrate_behavior": true,
|
||||
"log_all_prompts": false,
|
||||
"verbose_commands": true,
|
||||
"chat_bot_messages": true,
|
||||
"render_bot_view": false,
|
||||
"allow_insecure_coding": false,
|
||||
"code_timeout_mins": -1
|
||||
}
|
|
@ -16,7 +16,7 @@ let io;
|
|||
let server;
|
||||
const agent_connections = {};
|
||||
|
||||
const default_settings = JSON.parse(readFileSync(path.join(__dirname, 'default_settings.json'), 'utf8'));
|
||||
const settings_spec = JSON.parse(readFileSync(path.join(__dirname, 'public/settings_spec.json'), 'utf8'));
|
||||
|
||||
class AgentConnection {
|
||||
constructor(settings) {
|
||||
|
@ -58,7 +58,22 @@ export function createMindServer(host_public = false, port = 8080) {
|
|||
|
||||
socket.on('create-agent', (settings, callback) => {
|
||||
console.log('API create agent...');
|
||||
settings = { ...default_settings, ...settings };
|
||||
for (let key in settings_spec) {
|
||||
if (!(key in settings)) {
|
||||
if (settings_spec[key].required) {
|
||||
callback({ success: false, error: `Setting ${key} is required` });
|
||||
return;
|
||||
}
|
||||
else {
|
||||
settings[key] = settings_spec[key].default;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let key in settings) {
|
||||
if (!(key in settings_spec)) {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
if (settings.profile?.name) {
|
||||
if (settings.profile.name in agent_connections) {
|
||||
callback({ success: false, error: 'Agent already exists' });
|
||||
|
|
|
@ -58,97 +58,229 @@
|
|||
.status-icon.offline {
|
||||
color: #f44336;
|
||||
}
|
||||
#settingsForm {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.setting-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #3a3a3a;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
.setting-wrapper label {
|
||||
flex: 0 0 50%;
|
||||
font-size: 0.9em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.setting-wrapper input[type="text"],
|
||||
.setting-wrapper input[type="number"] {
|
||||
flex: 1 1 0;
|
||||
background: #262626;
|
||||
border: 1px solid #555;
|
||||
color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.setting-wrapper input[type="checkbox"] {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
.agent-viewer {
|
||||
width: 200px;
|
||||
height: 150px;
|
||||
border: none;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.start-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.agent-view-container {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Mindcraft</h1>
|
||||
<div id="agents"></div>
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button id="createAgentBtn" class="start-btn">Create Agent</button>
|
||||
<input type="file" id="agentConfigFile" accept=".json,application/json" style="display: none">
|
||||
<div id="createAgentSection" style="margin-top:20px;background:#2d2d2d;padding:20px;border-radius:8px;">
|
||||
<h2>Create Agent</h2>
|
||||
<div id="settingsForm"></div>
|
||||
<div id="profileStatus" style="margin-top:6px;font-style:italic;color:#cccccc;">Profile: Not uploaded</div>
|
||||
<div style="margin-top:10px;">
|
||||
<button id="uploadProfileBtn" class="start-btn">Upload Profile</button>
|
||||
<input type="file" id="profileFileInput" accept=".json,application/json" style="display:none">
|
||||
<button id="submitCreateAgentBtn" class="start-btn" disabled>Create Agent</button>
|
||||
</div>
|
||||
<div id="createError" style="color:#f44336;margin-top:10px;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const socket = io();
|
||||
const agentsDiv = document.getElementById('agents');
|
||||
let settingsSpec = {};
|
||||
let profileData = null;
|
||||
const agentSettings = {};
|
||||
|
||||
document.getElementById('createAgentBtn').addEventListener('click', () => {
|
||||
document.getElementById('agentConfigFile').click();
|
||||
fetch('/settings_spec.json')
|
||||
.then(r => r.json())
|
||||
.then(spec => {
|
||||
settingsSpec = spec;
|
||||
const form = document.getElementById('settingsForm');
|
||||
Object.keys(spec).forEach(key => {
|
||||
if (key === 'profile') return; // profile handled via upload
|
||||
const cfg = spec[key];
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'setting-wrapper';
|
||||
const label = document.createElement('label');
|
||||
label.textContent = key;
|
||||
label.title = cfg.description || '';
|
||||
let input;
|
||||
switch (cfg.type) {
|
||||
case 'boolean':
|
||||
input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.checked = cfg.default === true;
|
||||
break;
|
||||
case 'number':
|
||||
input = document.createElement('input');
|
||||
input.type = 'number';
|
||||
input.value = cfg.default;
|
||||
break;
|
||||
default:
|
||||
input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.value = typeof cfg.default === 'object' ? JSON.stringify(cfg.default) : cfg.default;
|
||||
}
|
||||
input.title = cfg.description || '';
|
||||
input.id = `setting-${key}`;
|
||||
wrapper.appendChild(label);
|
||||
wrapper.appendChild(input);
|
||||
form.appendChild(wrapper);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('uploadProfileBtn').addEventListener('click', () => {
|
||||
document.getElementById('profileFileInput').click();
|
||||
});
|
||||
|
||||
document.getElementById('agentConfigFile').addEventListener('change', (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('profileFileInput').addEventListener('change', e => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
reader.onload = ev => {
|
||||
try {
|
||||
const profile = JSON.parse(e.target.result);
|
||||
socket.emit('create-agent', {profile}, (response) => {
|
||||
if (response.success) {
|
||||
alert('Agent created successfully!');
|
||||
} else {
|
||||
alert(`Error creating agent: ${response.error}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Error parsing JSON file: ' + error.message);
|
||||
profileData = JSON.parse(ev.target.result);
|
||||
document.getElementById('submitCreateAgentBtn').disabled = false;
|
||||
document.getElementById('profileStatus').textContent = `Profile: ${profileData.name || 'Uploaded'}`;
|
||||
document.getElementById('createError').textContent = '';
|
||||
} catch (err) {
|
||||
document.getElementById('createError').textContent = 'Invalid profile JSON: ' + err.message;
|
||||
profileData = null;
|
||||
document.getElementById('submitCreateAgentBtn').disabled = true;
|
||||
document.getElementById('profileStatus').textContent = 'Profile: Not uploaded';
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
// Reset file input to allow re-uploading the same file
|
||||
event.target.value = '';
|
||||
e.target.value = '';
|
||||
});
|
||||
|
||||
socket.on('agents-update', (agents) => {
|
||||
agentsDiv.innerHTML = agents.length ?
|
||||
agents.map(agent => `
|
||||
document.getElementById('submitCreateAgentBtn').addEventListener('click', () => {
|
||||
if (!profileData) return;
|
||||
const settings = { profile: profileData };
|
||||
Object.keys(settingsSpec).forEach(key => {
|
||||
if (key === 'profile') return;
|
||||
const input = document.getElementById(`setting-${key}`);
|
||||
if (!input) return;
|
||||
const type = settingsSpec[key].type;
|
||||
let val;
|
||||
if (type === 'boolean') val = input.checked;
|
||||
else if (type === 'number') val = Number(input.value);
|
||||
else if (type === 'array' || type === 'object') {
|
||||
try { val = JSON.parse(input.value); }
|
||||
catch { val = input.value; }
|
||||
} else val = input.value;
|
||||
settings[key] = val;
|
||||
});
|
||||
socket.emit('create-agent', settings, res => {
|
||||
if (!res.success) {
|
||||
document.getElementById('createError').textContent = res.error || 'Unknown error';
|
||||
} else {
|
||||
// reset on success
|
||||
profileData = null;
|
||||
document.getElementById('submitCreateAgentBtn').disabled = true;
|
||||
document.getElementById('profileStatus').textContent = 'Profile: Not uploaded';
|
||||
document.getElementById('createError').textContent = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function fetchAgentSettings(name) {
|
||||
return new Promise((resolve) => {
|
||||
if (agentSettings[name]) { resolve(agentSettings[name]); return; }
|
||||
socket.emit('get-settings', name, res => {
|
||||
if (res.settings) {
|
||||
agentSettings[name] = res.settings;
|
||||
resolve(res.settings);
|
||||
} else resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function renderAgents(agents) {
|
||||
// fetch settings for any new agents
|
||||
await Promise.all(agents.map(a => fetchAgentSettings(a.name)));
|
||||
|
||||
agentsDiv.innerHTML = agents.length ?
|
||||
agents.map((agent, idx) => {
|
||||
const cfg = agentSettings[agent.name] || {};
|
||||
const showViewer = cfg.render_bot_view === true;
|
||||
const viewerHTML = showViewer ? `<div class="agent-view-container"><iframe class="agent-viewer" src="http://localhost:${3000 + idx}"></iframe></div>` : '';
|
||||
return `
|
||||
<div class="agent">
|
||||
<span>
|
||||
<span class="status-icon ${agent.in_game ? 'online' : 'offline'}">●</span>
|
||||
${agent.name}
|
||||
</span>
|
||||
<div>
|
||||
${agent.in_game ? `
|
||||
<button class="stop-btn" onclick="stopAgent('${agent.name}')">Stop</button>
|
||||
<button class="restart-btn" onclick="restartAgent('${agent.name}')">Restart</button>
|
||||
<input type="text" id="messageInput" placeholder="Enter a message or command..."></input><button class="start-btn" onclick="sendMessage('${agent.name}', document.getElementById('messageInput').value)">Send</button>
|
||||
` : `
|
||||
<button class="start-btn" onclick="startAgent('${agent.name}')">Start</button>
|
||||
`}
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<span><span class="status-icon ${agent.in_game ? 'online' : 'offline'}">●</span>${agent.name}</span>
|
||||
<div style="display:flex;align-items:center;">
|
||||
${agent.in_game ? `
|
||||
<button class="stop-btn" onclick="stopAgent('${agent.name}')">Stop</button>
|
||||
<button class="restart-btn" onclick="restartAgent('${agent.name}')">Restart</button>
|
||||
<input type="text" id="messageInput-${agent.name}" placeholder="Enter message..." style="margin-left:4px;">
|
||||
<button class="start-btn" onclick="sendMessage('${agent.name}', document.getElementById('messageInput-${agent.name}').value)">Send</button>
|
||||
` : `
|
||||
<button class="start-btn" onclick="startAgent('${agent.name}')">Start</button>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') +
|
||||
${viewerHTML}
|
||||
</div>`;
|
||||
}).join('') +
|
||||
`<button class="stop-btn" onclick="killAllAgents()">Stop All</button>
|
||||
<button class="stop-btn" onclick="shutdown()">Shutdown</button>` :
|
||||
<button class="stop-btn" onclick="shutdown()">Shutdown</button>` :
|
||||
'<div class="agent">No agents connected</div>';
|
||||
});
|
||||
|
||||
function restartAgent(agentName) {
|
||||
socket.emit('restart-agent', agentName);
|
||||
}
|
||||
|
||||
function startAgent(agentName) {
|
||||
socket.emit('start-agent', agentName);
|
||||
}
|
||||
socket.on('agents-update', agents => { renderAgents(agents); });
|
||||
|
||||
function stopAgent(agentName) {
|
||||
socket.emit('stop-agent', agentName);
|
||||
}
|
||||
|
||||
function killAllAgents() {
|
||||
socket.emit('stop-all-agents');
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
socket.emit('shutdown');
|
||||
}
|
||||
|
||||
function sendMessage(agentName, message) {
|
||||
socket.emit('send-message', agentName, message)
|
||||
}
|
||||
function restartAgent(n) { socket.emit('restart-agent', n); }
|
||||
function startAgent(n) { 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); }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
127
src/mindcraft/public/settings_spec.json
Normal file
127
src/mindcraft/public/settings_spec.json
Normal file
|
@ -0,0 +1,127 @@
|
|||
{
|
||||
"profile": {
|
||||
"type": "object",
|
||||
"required": true,
|
||||
"description": "The profile object to use, including name, prompts, and examples"
|
||||
},
|
||||
"minecraft_version": {
|
||||
"type": "string",
|
||||
"description": "The version of Minecraft to use",
|
||||
"default": "1.21.1"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The minecraft server host address to connect to",
|
||||
"default": "127.0.0.1"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"description": "The minecraft server port to connect to",
|
||||
"default": 55916
|
||||
},
|
||||
"auth": {
|
||||
"type": "string",
|
||||
"description": "The authentication method to use",
|
||||
"default": "offline"
|
||||
},
|
||||
"base_profile": {
|
||||
"type": "string",
|
||||
"description": "Allowed values: survival, creative, god_mode. Each has fine tuned settings for different game modes.",
|
||||
"default": "survival"
|
||||
},
|
||||
"load_memory": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to load bot's previous memory",
|
||||
"default": false
|
||||
},
|
||||
"init_message": {
|
||||
"type": "string",
|
||||
"description": "The initial message to send to the bot",
|
||||
"default": "Respond with hello world and your name"
|
||||
},
|
||||
"only_chat_with": {
|
||||
"type": "array",
|
||||
"description": "List of agents to only chat with. If empty, the bot will chat publicly",
|
||||
"default": []
|
||||
},
|
||||
"speak": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to enable text-to-speech reading on the host machine",
|
||||
"default": false
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "The language to automatically translate to and from using google translate",
|
||||
"default": "en"
|
||||
},
|
||||
"allow_vision": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to allow vision capabilities",
|
||||
"default": false
|
||||
},
|
||||
"blocked_actions": {
|
||||
"type": "array",
|
||||
"description": "List of actions that are blocked",
|
||||
"default": ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"]
|
||||
},
|
||||
"relevant_docs_count": {
|
||||
"type": "number",
|
||||
"description": "Number of relevant function documents to include in the prompt for LLM code writing",
|
||||
"default": 5
|
||||
},
|
||||
"max_messages": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of recent messages to keep in context for LLM",
|
||||
"default": 15
|
||||
},
|
||||
"num_examples": {
|
||||
"type": "number",
|
||||
"description": "Number of examples to select to help prompt better LLM responses",
|
||||
"default": 2
|
||||
},
|
||||
"max_commands": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of commands allowed in consecutive responses. -1 for no limit",
|
||||
"default": -1
|
||||
},
|
||||
"narrate_behavior": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to openly chat automatic behavior like 'Picking up item!'",
|
||||
"default": true
|
||||
},
|
||||
"log_all_prompts": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to log all prompts to file. Can be very verbose.",
|
||||
"default": false
|
||||
},
|
||||
"verbose_commands": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to show full command syntax in bot responses. If false will use a shortened syntax.",
|
||||
"default": true
|
||||
},
|
||||
"chat_bot_messages": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to publicly chat messages to and from other bots",
|
||||
"default": true
|
||||
},
|
||||
"render_bot_view": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to render bot view for user observation. Does not give bot vision.",
|
||||
"default": false
|
||||
},
|
||||
"allow_insecure_coding": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to allow newAction command that let's LLM write/run code on host computer. Despite sandboxxing, it is potentially insecure.",
|
||||
"default": false
|
||||
},
|
||||
"code_timeout_mins": {
|
||||
"type": "number",
|
||||
"description": "Number of minutes to allow code execution. -1 for no timeout",
|
||||
"default": -1
|
||||
},
|
||||
"task": {
|
||||
"type": "object",
|
||||
"description": "The task object to give the agent on start. If null, the agent will not have a task.",
|
||||
"default": null
|
||||
}
|
||||
}
|
69
viewer.html
69
viewer.html
|
@ -1,69 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Viewer</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
.iframe-wrapper {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<iframe data-port="3000" src="http://localhost:3000" class="iframe-wrapper"></iframe>
|
||||
<iframe data-port="3001" src="http://localhost:3001" class="iframe-wrapper"></iframe>
|
||||
<iframe data-port="3002" src="http://localhost:3002" class="iframe-wrapper"></iframe>
|
||||
<iframe data-port="3003" src="http://localhost:3003" class="iframe-wrapper"></iframe>
|
||||
</div>
|
||||
<script>
|
||||
function updateLayout() {
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
let iframes = document.querySelectorAll('.iframe-wrapper');
|
||||
if (width > height) {
|
||||
iframes.forEach(function(iframe) {
|
||||
iframe.style.width = '50%';
|
||||
iframe.style.height = '50%';
|
||||
});
|
||||
} else {
|
||||
iframes.forEach(function(iframe) {
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '25%';
|
||||
});
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', updateLayout);
|
||||
window.addEventListener('load', updateLayout);
|
||||
let iframes = document.querySelectorAll('.iframe-wrapper');
|
||||
iframes.forEach(function(iframe) {
|
||||
let port = iframe.getAttribute('data-port');
|
||||
let loaded = false;
|
||||
function checkServer() {
|
||||
fetch('http://localhost:' + port, { method: 'HEAD' })
|
||||
.then(function(response) {
|
||||
if (response.ok && !loaded) {
|
||||
iframe.src = 'http://localhost:' + port;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {});
|
||||
}
|
||||
iframe.onload = function() {
|
||||
loaded = true;
|
||||
};
|
||||
iframe.onerror = function() {
|
||||
loaded = false;
|
||||
};
|
||||
setInterval(checkServer, 3000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue