Improve voice control html interface
This commit is contained in:
@@ -58,6 +58,30 @@ def create_api(
|
||||
except Exception as 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")
|
||||
def get_queue() -> list:
|
||||
"""Get the command queue."""
|
||||
|
||||
@@ -207,6 +207,60 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
font-size: 11px;
|
||||
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-list {
|
||||
@@ -424,6 +478,27 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
</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 -->
|
||||
<div class="card">
|
||||
<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() {
|
||||
const data = await fetchJson('/api/objects');
|
||||
const data = await fetchJson('/api/scene');
|
||||
if (data.error) return;
|
||||
|
||||
const list = $('objects-list');
|
||||
const detected = data.detected || [];
|
||||
const staticObjs = data.static || [];
|
||||
const heldId = data.held_object_id;
|
||||
|
||||
if (detected.length === 0 && staticObjs.length === 0) {
|
||||
list.innerHTML = '<div class="empty">No objects detected</div>';
|
||||
@@ -543,12 +658,14 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
const colorClass = obj.color || 'white';
|
||||
const conf = obj.confidence ? (obj.confidence * 100).toFixed(0) + '%' : '';
|
||||
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="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' +
|
||||
'<span style="color:#888;font-size:10px;">' + size + '</span>' +
|
||||
'<span style="color:#00ff88;font-size:10px;">' + conf + '</span>' +
|
||||
'<span class="pos">[' + pos + ']</span>' +
|
||||
'<span class="pos">[' + pos + ']' + stackInfo + '</span>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
}
|
||||
@@ -559,7 +676,8 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
html += staticObjs.map(obj => {
|
||||
const pos = obj.position_mm ? obj.position_mm.map(v => v.toFixed(0)).join(', ') : '--';
|
||||
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="color-badge ' + colorClass + '">' + (obj.color || '?') + '</span>' +
|
||||
'<span class="pos">[' + pos + ']</span>' +
|
||||
@@ -684,12 +802,14 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
|
||||
// Auto-refresh
|
||||
setInterval(updateStatus, 500);
|
||||
setInterval(updateScene, 500);
|
||||
setInterval(updateObjects, 1000);
|
||||
setInterval(updateQueue, 500);
|
||||
setInterval(updateCamera, 100);
|
||||
|
||||
// Initial load
|
||||
updateStatus();
|
||||
updateScene();
|
||||
updateObjects();
|
||||
updateQueue();
|
||||
updateCamera();
|
||||
|
||||
Reference in New Issue
Block a user