Improve voice control html interface

This commit is contained in:
cristhian aguilera
2026-02-03 10:40:42 -03:00
parent 45bbdcb9f5
commit 2e3c5eab6a
2 changed files with 148 additions and 4 deletions

View File

@@ -58,6 +58,30 @@ def create_api(
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/scene")
def get_scene() -> dict:
"""Get complete scene state including held object."""
try:
detected = [obj.to_dict() for obj in scene.query(source="detected")]
config_objs = [obj.to_dict() for obj in scene.query(source="config")]
held_id = state.get_held_object_id()
held_obj = None
if held_id:
obj = scene.get(held_id)
if obj:
held_obj = obj.to_dict()
return {
"detected": detected,
"static": config_objs,
"held_object_id": held_id,
"held_object": held_obj,
"update_mode": scene._update_mode if hasattr(scene, "_update_mode") else "unknown",
"is_captured": scene.is_captured() if hasattr(scene, "is_captured") else False,
"total_count": scene.count(),
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/queue") @app.get("/api/queue")
def get_queue() -> list: def get_queue() -> list:
"""Get the command queue.""" """Get the command queue."""

View File

@@ -207,6 +207,60 @@ HTML_TEMPLATE = """<!DOCTYPE html>
font-size: 11px; font-size: 11px;
color: #888; color: #888;
} }
.object-item.held {
border: 2px solid #00ff88;
background: #1a3a2e;
}
.object-item.held::before {
content: '\\1F91A';
margin-right: 6px;
}
/* Scene State */
.scene-state {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 12px;
}
.held-object {
grid-column: span 2;
background: #1a3a2e;
border: 1px solid #00ff88;
padding: 12px;
border-radius: 6px;
}
.held-object.empty {
background: #1a1a2e;
border-color: #333;
}
.held-object .label {
font-size: 11px;
color: #00ff88;
text-transform: uppercase;
margin-bottom: 6px;
}
.held-object.empty .label {
color: #666;
}
.held-object .info {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.held-object .field {
display: flex;
flex-direction: column;
}
.held-object .field .key {
font-size: 10px;
color: #888;
}
.held-object .field .val {
font-size: 14px;
color: #fff;
font-weight: 500;
}
/* Queue Display */ /* Queue Display */
.queue-list { .queue-list {
@@ -424,6 +478,27 @@ HTML_TEMPLATE = """<!DOCTYPE html>
</div> </div>
</div> </div>
<!-- Scene State -->
<div class="card">
<h2>Scene State</h2>
<div class="scene-state">
<div class="status-item">
<div class="label">Update Mode</div>
<div class="value" id="scene-mode">--</div>
</div>
<div class="status-item">
<div class="label">Captured</div>
<div class="value" id="scene-captured">--</div>
</div>
</div>
<div class="held-object empty" id="held-object-card">
<div class="label">Held Object</div>
<div class="info" id="held-object-info">
<span style="color:#666;font-style:italic;">Not holding any object</span>
</div>
</div>
</div>
<!-- Detected Objects --> <!-- Detected Objects -->
<div class="card"> <div class="card">
<h2>Detected Objects</h2> <h2>Detected Objects</h2>
@@ -520,13 +595,53 @@ HTML_TEMPLATE = """<!DOCTYPE html>
} }
} }
let currentHeldId = null;
async function updateScene() {
const data = await fetchJson('/api/scene');
if (data.error) return;
// Update scene mode
$('scene-mode').textContent = data.update_mode || '--';
$('scene-mode').className = 'value ' + (data.update_mode === 'static' ? 'warn' : 'ok');
// Update captured status
$('scene-captured').textContent = data.is_captured ? 'Yes' : 'No';
$('scene-captured').className = 'value ' + (data.is_captured ? 'ok' : 'warn');
// Update held object
currentHeldId = data.held_object_id;
const heldCard = $('held-object-card');
const heldInfo = $('held-object-info');
if (data.held_object) {
const obj = data.held_object;
heldCard.className = 'held-object';
const pos = obj.position_mm ? obj.position_mm.map(v => v.toFixed(0)).join(', ') : '--';
heldInfo.innerHTML =
'<div class="field"><span class="key">ID</span><span class="val">' + obj.id + '</span></div>' +
'<div class="field"><span class="key">Type</span><span class="val">' + (obj.object_type || '?') + '</span></div>' +
'<div class="field"><span class="key">Color</span><span class="val">' + (obj.color || '?') + '</span></div>' +
'<div class="field"><span class="key">Size</span><span class="val">' + (obj.size || 'normal') + '</span></div>' +
'<div class="field"><span class="key">Height</span><span class="val">' + (obj.height_mm || 0).toFixed(1) + 'mm</span></div>' +
'<div class="field"><span class="key">Position</span><span class="val">[' + pos + ']</span></div>';
} else if (data.held_object_id) {
heldCard.className = 'held-object';
heldInfo.innerHTML = '<span style="color:#ffaa00;">Holding ID: ' + data.held_object_id + ' (not in scene)</span>';
} else {
heldCard.className = 'held-object empty';
heldInfo.innerHTML = '<span style="color:#666;font-style:italic;">Not holding any object</span>';
}
}
async function updateObjects() { async function updateObjects() {
const data = await fetchJson('/api/objects'); const data = await fetchJson('/api/scene');
if (data.error) return; if (data.error) return;
const list = $('objects-list'); const list = $('objects-list');
const detected = data.detected || []; const detected = data.detected || [];
const staticObjs = data.static || []; const staticObjs = data.static || [];
const heldId = data.held_object_id;
if (detected.length === 0 && staticObjs.length === 0) { if (detected.length === 0 && staticObjs.length === 0) {
list.innerHTML = '<div class="empty">No objects detected</div>'; list.innerHTML = '<div class="empty">No objects detected</div>';
@@ -543,12 +658,14 @@ HTML_TEMPLATE = """<!DOCTYPE html>
const colorClass = obj.color || 'white'; const colorClass = obj.color || 'white';
const conf = obj.confidence ? (obj.confidence * 100).toFixed(0) + '%' : ''; const conf = obj.confidence ? (obj.confidence * 100).toFixed(0) + '%' : '';
const size = obj.size || ''; const size = obj.size || '';
return '<div class="object-item">' + const isHeld = obj.id === heldId;
const stackInfo = obj.on_top_of ? ' on:' + obj.on_top_of : '';
return '<div class="object-item' + (isHeld ? ' held' : '') + '">' +
'<span class="type">' + (obj.object_type || '?') + '</span>' + '<span class="type">' + (obj.object_type || '?') + '</span>' +
'<span class="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' + '<span class="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' +
'<span style="color:#888;font-size:10px;">' + size + '</span>' + '<span style="color:#888;font-size:10px;">' + size + '</span>' +
'<span style="color:#00ff88;font-size:10px;">' + conf + '</span>' + '<span style="color:#00ff88;font-size:10px;">' + conf + '</span>' +
'<span class="pos">[' + pos + ']</span>' + '<span class="pos">[' + pos + ']' + stackInfo + '</span>' +
'</div>'; '</div>';
}).join(''); }).join('');
} }
@@ -559,7 +676,8 @@ HTML_TEMPLATE = """<!DOCTYPE html>
html += staticObjs.map(obj => { html += staticObjs.map(obj => {
const pos = obj.position_mm ? obj.position_mm.map(v => v.toFixed(0)).join(', ') : '--'; const pos = obj.position_mm ? obj.position_mm.map(v => v.toFixed(0)).join(', ') : '--';
const colorClass = obj.color || 'white'; const colorClass = obj.color || 'white';
return '<div class="object-item" style="opacity:0.7;">' + const isHeld = obj.id === heldId;
return '<div class="object-item' + (isHeld ? ' held' : '') + '" style="opacity:0.7;">' +
'<span class="type">' + (obj.object_type || '?') + '</span>' + '<span class="type">' + (obj.object_type || '?') + '</span>' +
'<span class="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' + '<span class="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' +
'<span class="pos">[' + pos + ']</span>' + '<span class="pos">[' + pos + ']</span>' +
@@ -684,12 +802,14 @@ HTML_TEMPLATE = """<!DOCTYPE html>
// Auto-refresh // Auto-refresh
setInterval(updateStatus, 500); setInterval(updateStatus, 500);
setInterval(updateScene, 500);
setInterval(updateObjects, 1000); setInterval(updateObjects, 1000);
setInterval(updateQueue, 500); setInterval(updateQueue, 500);
setInterval(updateCamera, 100); setInterval(updateCamera, 100);
// Initial load // Initial load
updateStatus(); updateStatus();
updateScene();
updateObjects(); updateObjects();
updateQueue(); updateQueue();
updateCamera(); updateCamera();