feat: initial commit — orchestratore presa-mano con AI assistant + dual-robot
Pipeline completa per calibrazione hand-eye e grasp planning su robot Unitree G1/H1 con camera depth Nuwa-HP60C. Esposta come FastAPI + GUI vanilla JS, con assistente AI (OpenRouter + tool calling curato) per pilotare il sistema in linguaggio naturale. Architettura: - FastAPI orchestrator (porta da .env, default 8890) che coordina 4 microservizi upstream: Nuwa-HP60C, marker-finder, YOLO, robot bridge. - Dual-robot: services.g1 e services.h1 in config.yaml; switchabili a runtime via PUT /api/home/robot. - Calibrazione hand-eye eye-to-hand split per-side (right/left). - Marker pose: marker-finder riceve gli intrinseci RGB della Nuwa al boot, PnP server-side con quei dati. Nessun re-PnP locale richiesto. - Depth-to-RGB: registrazione iterativa IR↔RGB per back-projection 3D corretta (Nuwa /api/measure usa coords IR; il nostro /api/perception/ measure_rgb prende pixel RGB e ritorna 3D nel frame RGB OpenCV). - Correzione affine sul depth bias (calibrabile via marker come ground-truth) — riduce errore Z da ~6% a ~1.5% nei test. - AI assistant: 16 tool curati (move_wrist_*, hand_action, get_marker[_3d], detect_object, calibration_*, set_active_robot…), system prompt dinamico che riflette robot attivo + marker config + addendum customizzabile da GUI. Temperature di default 0.1 per evitare invenzioni. GUI (7 tab): - Home: stato comunicazione device + selettore robot attivo G1/H1 - Assistente AI: chat con OpenRouter, history persistita lato browser, task riutilizzabili (prompt salvati), trace tool calls - Impostazioni AI: modello da combo curata, temperature, max iterations, prompt addendum, anteprima system prompt - Calibrazione, Auto-Calibrazione, TrovaUtensile, Servo Visuale, Vista 3D, Movimento EE — invariate dalle versioni precedenti Setup: - Variabili in presa-mano/.env (template in .env.example) - OPENROUTER_API_KEY richiesta solo per il tab Assistente AI - PRESA_MANO_PORT configurabile Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
ac573505b7
121 files changed
+67199
No files matched your search
+745
@@ -0,0 +1,745 @@
|
||||
:root {
|
||||
--bg: #111418;
|
||||
--panel: #191d23;
|
||||
--border: #2a2f37;
|
||||
--fg: #e6e6e6;
|
||||
--dim: #9aa0a6;
|
||||
--accent: #4da3ff;
|
||||
--ok: #3ecf8e;
|
||||
--warn: #f0ad4e;
|
||||
--danger: #e74c3c;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font: 14px/1.4 -apple-system, "Segoe UI", system-ui, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
header {
|
||||
padding: 12px 20px 0 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
}
|
||||
header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 18px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.tabs { display: flex; gap: 4px; }
|
||||
.tab {
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: 0;
|
||||
color: var(--dim);
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tab.active {
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
border-color: var(--border);
|
||||
}
|
||||
.status-strip { min-height: 22px; padding: 4px 0 8px 0; color: var(--dim); font-size: 12px; }
|
||||
main { padding: 20px; }
|
||||
.panel { display: none; }
|
||||
.panel.active { display: block; }
|
||||
|
||||
h2 { font-size: 15px; margin: 0 0 10px 0; }
|
||||
h3 { font-size: 13px; margin: 16px 0 6px 0; color: var(--dim); }
|
||||
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.grid-2 { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.canvas-wrap {
|
||||
background: #000;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
canvas { display: block; max-width: 100%; height: auto; }
|
||||
|
||||
.kv {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||
gap: 8px;
|
||||
margin: 8px 0 10px 0;
|
||||
}
|
||||
.kv label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
color: var(--dim);
|
||||
font-size: 12px;
|
||||
}
|
||||
.kv label.inline {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--fg);
|
||||
}
|
||||
.kv input, .kv select {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--fg);
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
background: var(--border);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--fg);
|
||||
padding: 7px 14px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
button:hover { background: #343a43; }
|
||||
button.primary { background: var(--accent); border-color: var(--accent); color: #000; }
|
||||
button.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
|
||||
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
|
||||
|
||||
.marker-info { margin-top: 6px; font-size: 12px; color: var(--dim); }
|
||||
|
||||
.samples {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
}
|
||||
.samples-head { display: flex; justify-content: space-between; margin-bottom: 6px; }
|
||||
.samples ul { list-style: none; padding: 0; margin: 0; font-size: 12px; }
|
||||
.samples li {
|
||||
padding: 4px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
font-family: ui-monospace, monospace;
|
||||
color: var(--dim);
|
||||
}
|
||||
.samples li.sample-dropped {
|
||||
color: var(--danger);
|
||||
background: rgba(231, 76, 60, 0.08);
|
||||
}
|
||||
|
||||
.result { margin-top: 16px; }
|
||||
pre.log, #calibMatrix, #graspOut {
|
||||
background: #0d1014;
|
||||
border: 1px solid var(--border);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
overflow: auto;
|
||||
max-height: 380px;
|
||||
white-space: pre;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
details.auto-grid {
|
||||
margin-top: 20px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
}
|
||||
details.auto-grid summary { cursor: pointer; font-weight: 600; }
|
||||
|
||||
.canvas-wrap.small { max-width: 340px; }
|
||||
.measure-strip {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
font-family: ui-monospace, monospace;
|
||||
color: var(--dim);
|
||||
}
|
||||
.scatter-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.scatter-row canvas {
|
||||
background: #0d1014;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.scatter-title { font-size: 12px; color: var(--dim); margin-bottom: 4px; }
|
||||
#graspCanvas { cursor: crosshair; }
|
||||
|
||||
table.stats {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
font-family: ui-monospace, monospace;
|
||||
margin: 6px 0 4px 0;
|
||||
}
|
||||
table.stats th, table.stats td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 4px 8px;
|
||||
text-align: right;
|
||||
}
|
||||
table.stats th { background: var(--panel); color: var(--dim); font-weight: 600; }
|
||||
table.stats td:first-child, table.stats th:first-child { text-align: center; color: var(--dim); }
|
||||
|
||||
button.running { background: var(--danger); border-color: var(--danger); color: #fff; }
|
||||
|
||||
.legend {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--dim);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.legend-row { display: flex; align-items: center; gap: 6px; }
|
||||
.legend .dot {
|
||||
display: inline-block;
|
||||
width: 10px; height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#view3dCanvas { cursor: grab; background: #0d1014; }
|
||||
#view3dCanvas:active { cursor: grabbing; }
|
||||
|
||||
.grasp-legend {
|
||||
display: flex; flex-wrap: wrap; gap: 10px 14px;
|
||||
margin-top: 6px; font-size: 12px; color: var(--dim);
|
||||
}
|
||||
.grasp-legend .item { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.grasp-legend .sw {
|
||||
display: inline-block; width: 12px; height: 12px;
|
||||
border-radius: 2px; border: 1px solid rgba(255,255,255,.15);
|
||||
}
|
||||
.grasp-legend .item.selected { color: var(--fg); font-weight: 600; }
|
||||
.grasp-legend .item.missing { opacity: 0.4; }
|
||||
|
||||
/* ---- Posture & Stati ---- */
|
||||
.posture-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.post-health {
|
||||
margin-left: auto;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 12px;
|
||||
color: var(--dim);
|
||||
}
|
||||
.pose-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 12px ui-monospace, monospace;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.pose-table th {
|
||||
text-align: left;
|
||||
color: var(--dim);
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
width: 90px;
|
||||
}
|
||||
.pose-table td {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
text-align: right;
|
||||
background: #0d1014;
|
||||
}
|
||||
.link-btn {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--accent);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 2px 0;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
.finger-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.finger-row {
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr 40px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--dim);
|
||||
}
|
||||
.finger-row input[type="range"] { width: 100%; }
|
||||
.finger-row .v {
|
||||
font-family: ui-monospace, monospace;
|
||||
text-align: right;
|
||||
color: var(--fg);
|
||||
}
|
||||
.ctrl-block {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.kv.compact {
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
.info-box {
|
||||
background: #1e2530;
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--warn);
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
color: var(--dim);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info-box code {
|
||||
background: #0d1014;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
color: var(--fg);
|
||||
}
|
||||
pre.log.small { max-height: 180px; font-size: 11px; }
|
||||
.preset-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.preset-table th, .preset-table td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
}
|
||||
.preset-table th { background: var(--panel); color: var(--dim); font-weight: 600; }
|
||||
.preset-table td.actions-cell { text-align: right; white-space: nowrap; }
|
||||
.preset-table td.dim { color: var(--dim); text-align: center; }
|
||||
.preset-table .pose-cell {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 11px;
|
||||
color: var(--dim);
|
||||
}
|
||||
|
||||
/* ---- Accensione e Stati ---- */
|
||||
.power-state-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 12px ui-monospace, monospace;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.power-state-table th {
|
||||
text-align: left;
|
||||
color: var(--dim);
|
||||
font-weight: 600;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
width: 110px;
|
||||
}
|
||||
.power-state-table td {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: #0d1014;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: var(--border);
|
||||
color: var(--fg);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.badge.ok { background: var(--ok); color: #000; }
|
||||
.badge.warn { background: var(--warn); color: #000; }
|
||||
.badge.danger { background: var(--danger); color: #fff; }
|
||||
.state-desc { color: var(--dim); font-size: 11px; }
|
||||
.wiz-step { position: relative; }
|
||||
.wiz-step.done { border-left: 3px solid var(--ok); }
|
||||
.wiz-step.current { border-left: 3px solid var(--accent); }
|
||||
.wiz-step.blocked { opacity: 0.55; }
|
||||
.wiz-hint { font-size: 11px; color: var(--dim); margin-left: 8px; }
|
||||
.wiz-hint.warn { color: var(--warn); }
|
||||
.cmd-desc { font-size: 12px; color: var(--dim); margin-bottom: 8px; }
|
||||
.cmd-desc.warn { color: var(--warn); }
|
||||
.cmd-desc code, .info-box code {
|
||||
background: #0d1014;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
color: var(--fg);
|
||||
}
|
||||
.cmd-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.cmd-row:first-child { border-top: 0; }
|
||||
.cmd-row > div:first-child { flex: 1; min-width: 0; }
|
||||
.cmd-row strong { display: block; margin-bottom: 2px; }
|
||||
.cmd-row-right { display: flex; gap: 6px; align-items: center; }
|
||||
.cmd-row-right input {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--fg);
|
||||
padding: 5px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.fsm-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.fsm-cell {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
.fsm-cell .fsm-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.fsm-cell .fsm-head strong { font-size: 13px; }
|
||||
.fsm-cell .fsm-id { font-family: ui-monospace, monospace; color: var(--dim); font-size: 11px; }
|
||||
.fsm-cell .cmd-desc { margin: 4px 0 6px 0; }
|
||||
.fsm-cell button { width: 100%; }
|
||||
.fsm-cell.current { border-color: var(--ok); }
|
||||
.fsm-cell.current .fsm-id::before { content: "● "; color: var(--ok); }
|
||||
.gated.disabled-group {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
}
|
||||
.gated.disabled-group::after {
|
||||
content: "🔒 Disponibile solo con FSM = start";
|
||||
position: absolute;
|
||||
top: 4px; right: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--warn);
|
||||
pointer-events: none;
|
||||
}
|
||||
pre.log.err {
|
||||
border-color: var(--danger);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* Diagnostic 3D tab */
|
||||
.diag3d-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#diag3dStatus { margin-left: auto; font: 12px ui-monospace, monospace; color: var(--dim); }
|
||||
.diag3d-layout .canvas-wrap { position: relative; }
|
||||
#diag3dCanvas { cursor: grab; background: #0d1014; width: 100%; height: auto; }
|
||||
#diag3dCanvas:active { cursor: grabbing; }
|
||||
.diag3d-tooltip {
|
||||
position: absolute;
|
||||
background: rgba(13,16,20,.95);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--fg);
|
||||
padding: 6px 8px;
|
||||
font: 11px ui-monospace, monospace;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
}
|
||||
.diag3d-tooltip.hidden { display: none; }
|
||||
.diag3d-tooltip .tt-title { font-weight: 700; margin-bottom: 4px; color: var(--accent); }
|
||||
.diag3d-tooltip .tt-row { display: flex; justify-content: space-between; gap: 10px; }
|
||||
.diag3d-tooltip .tt-row .k { color: var(--dim); }
|
||||
.diag3d-tooltip.warn .tt-title { color: var(--warn); }
|
||||
.diag3d-tooltip.err .tt-title { color: var(--danger); }
|
||||
|
||||
.diag3d-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 18px;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--dim);
|
||||
}
|
||||
.diag3d-legend .legend-sw {
|
||||
display: inline-block;
|
||||
width: 14px; height: 14px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#diag3dPinned table, #diag3dSummary table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: 12px ui-monospace, monospace;
|
||||
}
|
||||
#diag3dPinned td, #diag3dSummary td {
|
||||
padding: 3px 8px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
#diag3dPinned td:first-child, #diag3dSummary td:first-child { color: var(--dim); width: 130px; }
|
||||
#diag3dPinned tr.warn td { color: var(--warn); }
|
||||
#diag3dPinned tr.err td { color: var(--danger); }
|
||||
|
||||
.autocal-chart {
|
||||
width: 100%;
|
||||
background: #0d1014;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Button feedback flash */
|
||||
button.flash-ok {
|
||||
animation: flashOk 0.8s ease-out;
|
||||
}
|
||||
button.flash-err {
|
||||
animation: flashErr 1.2s ease-out;
|
||||
}
|
||||
@keyframes flashOk {
|
||||
0% { background: var(--ok); color: #000; box-shadow: 0 0 0 3px rgba(62,207,142,.6); }
|
||||
100% {}
|
||||
}
|
||||
@keyframes flashErr {
|
||||
0%, 30% { background: var(--danger); color: #fff; box-shadow: 0 0 0 3px rgba(231,76,60,.6); }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Global alert banner (visible across all tabs) */
|
||||
.global-alert {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: var(--panel);
|
||||
color: var(--dim);
|
||||
border: 0;
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 6px 20px;
|
||||
font: 600 12px ui-monospace, monospace;
|
||||
cursor: pointer;
|
||||
margin: 0 -20px;
|
||||
}
|
||||
.global-alert.hidden { display: none; }
|
||||
.global-alert.warn { background: rgba(240,173,78,.18); color: var(--warn); border-color: var(--warn); }
|
||||
.global-alert.err { background: rgba(231,76,60,.22); color: var(--danger); border-color: var(--danger); animation: alertPulse 1.4s infinite; }
|
||||
@keyframes alertPulse { 50% { background: rgba(231,76,60,.40); } }
|
||||
|
||||
/* Diagnostica panel on Accensione tab */
|
||||
.diag-banner {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--dim);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.diag-banner.hidden { display: none; }
|
||||
.diag-banner.warn { border-left-color: var(--warn); color: var(--warn); }
|
||||
.diag-banner.err { border-left-color: var(--danger); color: var(--danger); }
|
||||
.diag-banner.ok { border-left-color: var(--ok); color: var(--ok); }
|
||||
.diag-table {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 170px 1fr auto;
|
||||
gap: 4px 10px;
|
||||
align-items: center;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.diag-row {
|
||||
display: contents;
|
||||
}
|
||||
.diag-row > * {
|
||||
padding: 3px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.diag-row:first-child > * { border-top: 0; }
|
||||
.diag-pill {
|
||||
display: inline-block;
|
||||
width: 18px; height: 18px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
font-size: 11px;
|
||||
background: var(--border);
|
||||
color: var(--dim);
|
||||
font-weight: 700;
|
||||
}
|
||||
.diag-row.ok .diag-pill { background: var(--ok); color: #000; }
|
||||
.diag-row.warn .diag-pill { background: var(--warn); color: #000; }
|
||||
.diag-row.err .diag-pill { background: var(--danger); color: #fff; }
|
||||
.diag-row.na .diag-pill { background: transparent; color: var(--dim); border: 1px dashed var(--border); }
|
||||
.diag-label { color: var(--fg); font-weight: 600; }
|
||||
.diag-value { font-family: ui-monospace, monospace; color: var(--dim); overflow: hidden; text-overflow: ellipsis; }
|
||||
.diag-row.ok .diag-value { color: var(--ok); }
|
||||
.diag-row.warn .diag-value { color: var(--warn); }
|
||||
.diag-row.err .diag-value { color: var(--danger); }
|
||||
.diag-hint { color: var(--dim); font-size: 11px; text-align: right; }
|
||||
|
||||
/* Per-joint temperature grid */
|
||||
details.joint-temps {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
details.joint-temps summary { cursor: pointer; font-weight: 600; font-size: 12px; color: var(--fg); }
|
||||
.joint-temps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
||||
gap: 4px 14px;
|
||||
margin-top: 10px;
|
||||
font: 11px ui-monospace, monospace;
|
||||
}
|
||||
.joint-row {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr 44px 90px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.joint-row .j-idx { color: var(--dim); text-align: right; }
|
||||
.joint-row .j-name { color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.joint-row .j-temp { color: var(--dim); text-align: right; }
|
||||
.joint-row .j-bar {
|
||||
background: #0d1014;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
height: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.joint-row .j-fill {
|
||||
position: absolute; top: 0; left: 0; bottom: 0;
|
||||
background: var(--ok);
|
||||
transition: width .2s ease, background .2s ease;
|
||||
}
|
||||
.joint-row .j-mark {
|
||||
position: absolute; top: -1px; bottom: -1px;
|
||||
width: 2px;
|
||||
margin-left: -1px;
|
||||
background: var(--fg);
|
||||
opacity: 0.85;
|
||||
pointer-events: none;
|
||||
transition: left .2s ease;
|
||||
}
|
||||
.joint-row.warn .j-fill { background: var(--warn); }
|
||||
.joint-row.err .j-fill { background: var(--danger); }
|
||||
.joint-row.warn .j-temp { color: var(--warn); }
|
||||
.joint-row.err .j-temp { color: var(--danger); }
|
||||
.joint-row.faulted { outline: 1px solid var(--danger); border-radius: 3px; }
|
||||
.joint-row.disabled .j-name { color: var(--dim); }
|
||||
|
||||
/* Toast shown globally on each command outcome */
|
||||
.toast-host {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast {
|
||||
background: var(--panel);
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 4px solid var(--dim);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
min-width: 260px;
|
||||
max-width: 520px;
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,.4);
|
||||
animation: toastIn 0.2s ease-out;
|
||||
}
|
||||
.toast.ok { border-left-color: var(--ok); }
|
||||
.toast.err { border-left-color: var(--danger); color: var(--danger); }
|
||||
.toast.fade { transition: opacity 0.4s ease-in; opacity: 0; }
|
||||
.toast .t-title { font-weight: 700; margin-bottom: 2px; }
|
||||
.toast .t-body { font-family: ui-monospace, monospace; color: var(--dim); white-space: pre-wrap; }
|
||||
@keyframes toastIn { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
|
||||
|
||||
/* ---------- Home tab ---------- */
|
||||
.device-status-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; }
|
||||
.device-status-table th, .device-status-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; }
|
||||
.device-status-table th { font-weight: 600; color: var(--dim); background: rgba(255,255,255,0.02); }
|
||||
.device-status-table code { font-size: 12px; color: var(--dim); }
|
||||
.device-pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 600; }
|
||||
.device-pill.ok { background: rgba(62,207,142,0.15); color: var(--ok); border: 1px solid var(--ok); }
|
||||
.device-pill.err { background: rgba(231,76,60,0.15); color: var(--danger); border: 1px solid var(--danger); }
|
||||
.active-robot-pill { font-size: 12px; padding: 4px 10px; border-radius: 999px; background: rgba(77,163,255,0.15); color: var(--accent); border: 1px solid var(--accent); margin-left: 12px; }
|
||||
|
||||
/* ---------- Assistente AI tab ---------- */
|
||||
.agent-layout > div { display: flex; flex-direction: column; }
|
||||
.agent-chat {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
height: 420px;
|
||||
overflow-y: auto;
|
||||
background: rgba(0,0,0,0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-msg { padding: 8px 12px; border-radius: 6px; max-width: 90%; line-height: 1.4; font-size: 13px; white-space: pre-wrap; word-wrap: break-word; }
|
||||
.agent-msg.user { align-self: flex-end; background: rgba(77,163,255,0.15); border: 1px solid var(--accent); }
|
||||
.agent-msg.assistant { align-self: flex-start; background: rgba(255,255,255,0.04); border: 1px solid var(--border); }
|
||||
.agent-msg.system { align-self: stretch; background: rgba(240,173,78,0.08); border: 1px dashed var(--warn); color: var(--dim); font-style: italic; }
|
||||
.agent-msg.error { align-self: stretch; background: rgba(231,76,60,0.10); border: 1px solid var(--danger); color: var(--danger); }
|
||||
.agent-msg .meta { font-size: 10px; color: var(--dim); margin-top: 4px; }
|
||||
.agent-tool-pill { display: inline-block; padding: 1px 8px; margin: 2px 2px 0 0; font-size: 11px; border-radius: 999px; background: rgba(62,207,142,0.12); border: 1px solid var(--ok); color: var(--ok); }
|
||||
.agent-tool-pill.err { background: rgba(231,76,60,0.12); border-color: var(--danger); color: var(--danger); }
|
||||
.agent-input-row { display: flex; gap: 8px; align-items: stretch; margin-top: 8px; }
|
||||
.agent-input-row textarea { flex: 1; resize: vertical; font-family: ui-monospace, monospace; font-size: 13px; padding: 8px; background: #0e1116; color: inherit; border: 1px solid var(--border); border-radius: 4px; }
|
||||
.agent-input-row button { white-space: nowrap; }
|
||||
.agent-task-list { list-style: none; padding: 0; margin: 8px 0; max-height: 260px; overflow-y: auto; }
|
||||
.agent-task-list li { display: flex; gap: 6px; align-items: center; padding: 6px 8px; border-bottom: 1px solid var(--border); font-size: 12px; }
|
||||
.agent-task-list li:hover { background: rgba(255,255,255,0.03); }
|
||||
.agent-task-list .task-name { font-weight: 600; flex: 0 0 auto; }
|
||||
.agent-task-list .task-desc { color: var(--dim); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.agent-task-list button.link-btn { font-size: 11px; padding: 2px 6px; }
|
||||
Reference in new issue
Block a user