Improve voice control html interface
This commit is contained in:
@@ -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."""
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user