Upgrade to rom-control v2 Client API; rename app.js → commander.js; hide incomplete telepresence/animator pages

This commit is contained in:
pasketti
2026-04-23 02:13:03 -04:00
parent 56578e59f9
commit ae624da7c2
6 changed files with 446 additions and 806 deletions

24
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{ {
"name": "re-commander", "name": "re-commander",
"version": "1.0.0", "version": "2.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "re-commander", "name": "re-commander",
"version": "1.0.0", "version": "2.0.0",
"dependencies": { "dependencies": {
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"express": "^4.18.2", "express": "^4.18.2",
"golden-layout": "^2.6.0", "rom-control": "^2.0.0",
"ws": "^8.14.2" "ws": "^8.14.2"
} }
}, },
@@ -372,12 +372,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/golden-layout": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz",
"integrity": "sha512-sIVQCiRWOymHbVD1Aw/T9/ijbPYAVGBlgGYd1N9MRKfcyBNSpjr87Vg9nSHm+RCT8ELrvK8IJYJV0QRJuVUkCQ==",
"license": "MIT"
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -636,6 +630,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/rom-control": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/rom-control/-/rom-control-2.0.0.tgz",
"integrity": "sha512-mENZI9Cf8fUzB02X1tTNGn4HUlCEpASds9YZQvbp/T5LJoCYCrgryLAE7OCIkLa+4Ob+NlO1jBK84n8/zR7/tg==",
"license": "MIT",
"dependencies": {
"ws": "^8.14.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "re-commander", "name": "re-commander",
"version": "1.0.0", "version": "2.0.0",
"description": "Jibo ROM Commander — local Node.js recreation using port 8160", "description": "Jibo ROM Commander — built on the rom-control module",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"start": "node server.js" "start": "node server.js"
@@ -9,7 +9,7 @@
"dependencies": { "dependencies": {
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"express": "^4.18.2", "express": "^4.18.2",
"golden-layout": "^2.6.0", "rom-control": "^2.0.0",
"ws": "^8.14.2" "ws": "^8.14.2"
} }
} }

View File

@@ -81,7 +81,8 @@
<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;"> <div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;">
<input type="checkbox" id="llm-toggle" style="width:auto;"> <input type="checkbox" id="llm-toggle" style="width:auto;">
<label for="llm-toggle" style="margin:0;cursor:pointer;">LLM voice loop</label> <label for="llm-toggle" style="margin:0;cursor:pointer;">LLM voice loop</label>
<button id="btn-llm-clear" style="margin-left:auto;font-size:11px;padding:2px 8px;" title="Clear conversation history">↺ Clear</button> <button id="btn-llm-cancel" class="danger" style="margin-left:auto;font-size:11px;padding:2px 8px;" title="Cancel active LLM request">✕ Stop</button>
<button id="btn-llm-clear" style="font-size:11px;padding:2px 8px;" title="Clear conversation history">↺ Clear</button>
</div> </div>
<div class="field"> <div class="field">
<label>Completions endpoint</label> <label>Completions endpoint</label>
@@ -281,6 +282,6 @@
<img id="photo-modal-img" src="" alt="Photo"> <img id="photo-modal-img" src="" alt="Photo">
</div> </div>
<script src="app.js"></script> <script src="commander.js"></script>
</body> </body>
</html> </html>

View File

@@ -59,7 +59,8 @@ function handleJiboEvent(body, txId) {
switch (body.Event) { switch (body.Event) {
case 'onHotWordHeard': case 'onHotWordHeard':
flashHotword(body.utterance || 'hey jibo', body.score); flashHotword(body.utterance || 'hey jibo', body.score);
if (document.getElementById('auto-listen-toggle').checked) doListen(); if (document.getElementById('auto-listen-toggle').checked)
post('/api/interrupt').then(() => doListen());
break; break;
case 'onStart': case 'onStart':
@@ -257,7 +258,7 @@ document.getElementById('btn-say').addEventListener('click', async () => {
}); });
document.getElementById('btn-say-cancel').addEventListener('click', () => { document.getElementById('btn-say-cancel').addEventListener('click', () => {
if (lastSayTx) post('/api/cancel', { txId: lastSayTx }); post('/api/say/cancel');
}); });
// ── Listen ──────────────────────────────────────────────────────────────────── // ── Listen ────────────────────────────────────────────────────────────────────
@@ -288,20 +289,28 @@ document.getElementById('btn-listen').addEventListener('click', doListen);
document.getElementById('btn-listen-cancel').addEventListener('click', () => { document.getElementById('btn-listen-cancel').addEventListener('click', () => {
clearListenTimeout(); clearListenTimeout();
if (lastListenTx) post('/api/cancel', { txId: lastListenTx }); post('/api/listen/cancel');
document.getElementById('listen-result').textContent = '(cancelled)'; document.getElementById('listen-result').textContent = '(cancelled)';
}); });
// ── Auto-listen + Voice AI ──────────────────────────────────────────────────── // ── Auto-listen + Voice AI ────────────────────────────────────────────────────
let llmHistory = []; // [{role:'user'|'assistant', content:string}] let llmHistory = []; // [{role:'user'|'assistant', content:string}]
let llmSessionMode = false; // true when server uses LLM_SESSION_KEY (OpenClaw session)
let llmTurnCount = 0;
function llmStatus(msg) { function llmStatus(msg) {
document.getElementById('llm-status').textContent = msg; document.getElementById('llm-status').textContent = msg;
} }
async function runLLMLoop(speechText) { async function runLLMLoop(speechText) {
llmHistory.push({ role: 'user', content: speechText }); // In session mode send only the latest message — history lives on the server.
// In history mode accumulate the full thread and send it each time.
const messages = llmSessionMode
? [{ role: 'user', content: speechText }]
: [...llmHistory, { role: 'user', content: speechText }];
if (!llmSessionMode) llmHistory.push({ role: 'user', content: speechText });
llmStatus('Thinking…'); llmStatus('Thinking…');
const endpoint = document.getElementById('llm-endpoint').value.trim(); const endpoint = document.getElementById('llm-endpoint').value.trim();
@@ -309,30 +318,45 @@ async function runLLMLoop(speechText) {
const systemPrompt = document.getElementById('llm-system-prompt').value.trim(); const systemPrompt = document.getElementById('llm-system-prompt').value.trim();
const r = await post('/api/llm/chat', { const r = await post('/api/llm/chat', {
messages: llmHistory, messages,
endpoint: endpoint || undefined, endpoint: endpoint || undefined,
model: model || undefined, model: model || undefined,
systemPrompt: systemPrompt || undefined, systemPrompt: systemPrompt || undefined,
}); });
if (!r || r.error) { if (!r || r.error) {
if (r?.error === 'cancelled') { llmStatus('Interrupted.'); if (!llmSessionMode) llmHistory.pop(); return; }
llmStatus('LLM error: ' + (r?.error || 'no response')); llmStatus('LLM error: ' + (r?.error || 'no response'));
llmHistory.pop(); // undo the user push so history stays consistent if (!llmSessionMode) llmHistory.pop();
return; return;
} }
const reply = r.reply; const reply = r.reply;
llmHistory.push({ role: 'assistant', content: reply }); if (!llmSessionMode) llmHistory.push({ role: 'assistant', content: reply });
llmStatus(`[${llmHistory.length / 2} turns] Last: "${reply.slice(0, 60)}${reply.length > 60 ? '…' : ''}"`); llmTurnCount++;
const modeTag = llmSessionMode ? 'session' : 'local';
llmStatus(`[${modeTag} · ${llmTurnCount} turns] Last: "${reply.slice(0, 50)}${reply.length > 50 ? '…' : ''}"`);
// Fill say box so user can see what Jibo is about to say // Fill say box so user can see what Jibo is about to say
document.getElementById('say-text').value = reply; document.getElementById('say-text').value = reply;
await post('/api/say', { text: reply }); const sayResult = await post('/api/say', { text: reply });
// If the reply ends with a question and wasn't interrupted, listen for the user's answer
const endsWithQuestion = /\?[^a-zA-Z0-9]*$/.test(reply.trim());
if (endsWithQuestion && !sayResult?.aborted && document.getElementById('llm-toggle').checked) {
doListen();
}
} }
document.getElementById('btn-llm-cancel').addEventListener('click', () => {
post('/api/llm/cancel');
llmStatus('Cancelled.');
});
document.getElementById('btn-llm-clear').addEventListener('click', () => { document.getElementById('btn-llm-clear').addEventListener('click', () => {
llmHistory = []; llmHistory = [];
llmStatus('Conversation cleared.'); llmTurnCount = 0;
llmStatus(llmSessionMode ? 'Session turn counter reset (server session persists).' : 'Conversation cleared.');
}); });
// ── Attention ───────────────────────────────────────────────────────────────── // ── Attention ─────────────────────────────────────────────────────────────────
@@ -566,26 +590,24 @@ function flashHotword(utterance, score) {
hotwordTimer = setTimeout(() => el.classList.remove('active'), 3000); hotwordTimer = setTimeout(() => el.classList.remove('active'), 3000);
} }
// ── Menu: Robot & Mode Selection ──────────────────────────────────────────── // ── Menu: Robot & Mode Selection ────────────────────────────────────────────
let selectedRobot = null; let selectedRobot = null;
let selectedMode = 'commander'; let selectedMode = 'commander';
// Get the selected robot and mode from sessionStorage
function initializeMode() { function initializeMode() {
selectedRobot = sessionStorage.getItem('selectedRobot') || 'jibo-001'; selectedRobot = sessionStorage.getItem('selectedRobot') || 'jibo-001';
selectedMode = sessionStorage.getItem('selectedMode') || 'commander'; selectedMode = sessionStorage.getItem('selectedMode') || 'commander';
} }
// Back to home button
document.getElementById('btn-back-home')?.addEventListener('click', () => { document.getElementById('btn-back-home')?.addEventListener('click', () => {
window.location.href = 'index.html'; window.location.href = 'index.html';
}); });
// ── Initialization ─────────────────────────────────────────────────────────── // ── Init ──────────────────────────────────────────────────────────────────────
connectWS();
initializeMode(); initializeMode();
connectWS();
// Populate LLM fields from server config (.env defaults) // Populate LLM fields from server config (.env defaults)
get('/api/config').then(cfg => { get('/api/config').then(cfg => {
@@ -593,4 +615,8 @@ get('/api/config').then(cfg => {
if (cfg.llmEndpoint) document.getElementById('llm-endpoint').value = cfg.llmEndpoint; if (cfg.llmEndpoint) document.getElementById('llm-endpoint').value = cfg.llmEndpoint;
if (cfg.llmModel) document.getElementById('llm-model').value = cfg.llmModel; if (cfg.llmModel) document.getElementById('llm-model').value = cfg.llmModel;
if (cfg.llmSystemPrompt) document.getElementById('llm-system-prompt').value = cfg.llmSystemPrompt; if (cfg.llmSystemPrompt) document.getElementById('llm-system-prompt').value = cfg.llmSystemPrompt;
if (cfg.sessionMode) {
llmSessionMode = true;
llmStatus('Session mode (OpenClaw) — history managed server-side.');
}
}); });

View File

@@ -253,11 +253,11 @@
<span class="mode-icon">🎮</span> <span class="mode-icon">🎮</span>
<span class="mode-label">Commander</span> <span class="mode-label">Commander</span>
</button> </button>
<button class="mode-btn" data-mode="animator" title="Animator Mode"> <button class="mode-btn" data-mode="animator" title="Animator Mode" style="display:none;">
<span class="mode-icon">🎬</span> <span class="mode-icon">🎬</span>
<span class="mode-label">Animator</span> <span class="mode-label">Animator</span>
</button> </button>
<button class="mode-btn" data-mode="telepresence" title="Telepresence Mode"> <button class="mode-btn" data-mode="telepresence" title="Telepresence Mode" style="display:none;">
<span class="mode-icon">👁</span> <span class="mode-icon">👁</span>
<span class="mode-label">Telepresence</span> <span class="mode-label">Telepresence</span>
</button> </button>

1115
server.js

File diff suppressed because it is too large Load Diff