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",
"version": "1.0.0",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "re-commander",
"version": "1.0.0",
"version": "2.0.0",
"dependencies": {
"dotenv": "^17.4.2",
"express": "^4.18.2",
"golden-layout": "^2.6.0",
"rom-control": "^2.0.0",
"ws": "^8.14.2"
}
},
@@ -372,12 +372,6 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -636,6 +630,18 @@
"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": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",

View File

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

View File

@@ -81,7 +81,8 @@
<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;">
<input type="checkbox" id="llm-toggle" style="width:auto;">
<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 class="field">
<label>Completions endpoint</label>
@@ -281,6 +282,6 @@
<img id="photo-modal-img" src="" alt="Photo">
</div>
<script src="app.js"></script>
<script src="commander.js"></script>
</body>
</html>

View File

@@ -59,7 +59,8 @@ function handleJiboEvent(body, txId) {
switch (body.Event) {
case 'onHotWordHeard':
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;
case 'onStart':
@@ -257,7 +258,7 @@ document.getElementById('btn-say').addEventListener('click', async () => {
});
document.getElementById('btn-say-cancel').addEventListener('click', () => {
if (lastSayTx) post('/api/cancel', { txId: lastSayTx });
post('/api/say/cancel');
});
// ── Listen ────────────────────────────────────────────────────────────────────
@@ -288,20 +289,28 @@ document.getElementById('btn-listen').addEventListener('click', doListen);
document.getElementById('btn-listen-cancel').addEventListener('click', () => {
clearListenTimeout();
if (lastListenTx) post('/api/cancel', { txId: lastListenTx });
post('/api/listen/cancel');
document.getElementById('listen-result').textContent = '(cancelled)';
});
// ── 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) {
document.getElementById('llm-status').textContent = msg;
}
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…');
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 r = await post('/api/llm/chat', {
messages: llmHistory,
messages,
endpoint: endpoint || undefined,
model: model || undefined,
systemPrompt: systemPrompt || undefined,
});
if (!r || r.error) {
if (r?.error === 'cancelled') { llmStatus('Interrupted.'); if (!llmSessionMode) llmHistory.pop(); return; }
llmStatus('LLM error: ' + (r?.error || 'no response'));
llmHistory.pop(); // undo the user push so history stays consistent
if (!llmSessionMode) llmHistory.pop();
return;
}
const reply = r.reply;
llmHistory.push({ role: 'assistant', content: reply });
llmStatus(`[${llmHistory.length / 2} turns] Last: "${reply.slice(0, 60)}${reply.length > 60 ? '…' : ''}"`);
if (!llmSessionMode) llmHistory.push({ role: 'assistant', content: reply });
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
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', () => {
llmHistory = [];
llmStatus('Conversation cleared.');
llmHistory = [];
llmTurnCount = 0;
llmStatus(llmSessionMode ? 'Session turn counter reset (server session persists).' : 'Conversation cleared.');
});
// ── Attention ─────────────────────────────────────────────────────────────────
@@ -566,26 +590,24 @@ function flashHotword(utterance, score) {
hotwordTimer = setTimeout(() => el.classList.remove('active'), 3000);
}
// ── Menu: Robot & Mode Selection ────────────────────────────────────────────
// ── Menu: Robot & Mode Selection ────────────────────────────────────────────
let selectedRobot = null;
let selectedMode = 'commander';
let selectedMode = 'commander';
// Get the selected robot and mode from sessionStorage
function initializeMode() {
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', () => {
window.location.href = 'index.html';
});
// ── Initialization ───────────────────────────────────────────────────────────
// ── Init ──────────────────────────────────────────────────────────────────────
connectWS();
initializeMode();
connectWS();
// Populate LLM fields from server config (.env defaults)
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.llmModel) document.getElementById('llm-model').value = cfg.llmModel;
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-label">Commander</span>
</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-label">Animator</span>
</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-label">Telepresence</span>
</button>

1139
server.js

File diff suppressed because it is too large Load Diff