infinicode 2.8.108 → 2.8.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/cli.js +23 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +15 -4
- package/dist/commands/mesh-install.d.ts +1 -0
- package/dist/commands/mesh-install.js +28 -11
- package/dist/commands/mesh-link.d.ts +10 -0
- package/dist/commands/mesh-link.js +44 -0
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +404 -155
- package/dist/kernel/federation/index.d.ts +2 -0
- package/dist/kernel/federation/index.js +1 -0
- package/dist/kernel/federation/mesh-link.d.ts +16 -0
- package/dist/kernel/federation/mesh-link.js +52 -0
- package/dist/kernel/federation/transport-http.js +24 -0
- package/dist/kernel/mcp/mcp-server.js +61 -0
- package/dist/robopark/python-env.d.ts +1 -0
- package/dist/robopark/python-env.js +3 -0
- package/dist/robopark/robot-runtime.d.ts +1 -0
- package/dist/robopark/robot-runtime.js +52 -10
- package/dist/robopark/setup.d.ts +1 -0
- package/dist/robopark/setup.js +2 -0
- package/dist/robopark-cli.js +4 -0
- package/docs/ROBOPARK_ROBOVOICE_SIMPLIFICATION_PLAN.html +139 -0
- package/package.json +5 -2
- package/packages/robopark/pi-client/motor_bridge.py +10 -7
- package/packages/robopark/scheduler/main.py +144 -19
- package/packages/robopark/scheduler/robot_supervisor.py +46 -15
- package/packages/robopark/vision/motor_server.py +44 -18
- package/scripts/integrate-robovoice-control-center.mjs +423 -0
|
@@ -230,14 +230,42 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
230
230
|
.offline{background:var(--bad-bg);color:var(--bad);text-align:center;font-family:var(--mono);font-size:0.72rem;padding:0.4rem;display:none;
|
|
231
231
|
border-bottom:1px solid var(--glass-border);}
|
|
232
232
|
|
|
233
|
-
/*
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
.
|
|
240
|
-
.
|
|
233
|
+
/* global gradient-glass navigation */
|
|
234
|
+
:root{--global-nav-width:264px}
|
|
235
|
+
body{padding-left:var(--global-nav-width);transition:padding-left .24s cubic-bezier(.2,.8,.2,1)}
|
|
236
|
+
body.global-nav-collapsed{--global-nav-width:78px}
|
|
237
|
+
.global-sidebar{position:fixed;inset:0 auto 0 0;z-index:48;width:var(--global-nav-width);height:100vh;max-height:100vh;display:flex;flex-direction:column;align-self:auto;overflow:hidden;
|
|
238
|
+
background:radial-gradient(240px 260px at 22% 0,rgba(255,211,112,.18),transparent 72%),radial-gradient(260px 340px at 100% 88%,rgba(126,79,8,.2),transparent 70%),linear-gradient(165deg,rgba(24,20,13,.93),rgba(7,7,6,.96));
|
|
239
|
+
-webkit-backdrop-filter:blur(28px) saturate(1.35);backdrop-filter:blur(28px) saturate(1.35);border-right:1px solid rgba(255,222,143,.18);box-shadow:22px 0 70px rgba(0,0,0,.38),inset -1px 0 rgba(255,240,200,.035);transition:width .24s cubic-bezier(.2,.8,.2,1),transform .24s cubic-bezier(.2,.8,.2,1)}
|
|
240
|
+
.global-sidebar:before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.22;background-image:linear-gradient(rgba(255,224,151,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(255,224,151,.03) 1px,transparent 1px);background-size:28px 28px;mask-image:linear-gradient(to bottom,black,transparent 88%)}
|
|
241
|
+
.global-side-head{position:relative;display:flex;align-items:center;gap:.72rem;min-height:76px;padding:.78rem;border-bottom:1px solid var(--glass-border)}
|
|
242
|
+
.global-side-mark{display:grid;place-items:center;flex:0 0 46px;height:46px;border-radius:15px;font-family:var(--display);font-size:1rem;font-weight:800;color:#171006;background:linear-gradient(145deg,#fff0b5,var(--gold-bright) 48%,#a66f15);box-shadow:0 10px 28px rgba(224,168,55,.25),inset 0 1px rgba(255,255,255,.7)}
|
|
243
|
+
.global-side-brand{min-width:0;overflow:hidden;white-space:nowrap}.global-side-brand b{display:block;font-family:var(--display);font-size:.92rem;color:var(--gold-pale)}.global-side-brand span{display:block;margin-top:.1rem;font-family:var(--mono);font-size:.52rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
|
244
|
+
.global-nav-collapse{position:absolute;right:-1px;bottom:-13px;display:grid;place-items:center;width:28px;height:28px;border-radius:9px 0 0 9px;border:1px solid var(--border-strong);border-right:0;background:#171209;color:var(--gold-bright);font-family:var(--mono);font-size:.58rem;cursor:pointer;z-index:2}
|
|
245
|
+
.global-nav{position:relative;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:1rem .65rem 6.2rem;scrollbar-width:thin;scrollbar-color:rgba(232,190,92,.3) transparent}
|
|
246
|
+
.global-nav-group{margin:.2rem .45rem .48rem;font-family:var(--mono);font-size:.49rem;letter-spacing:.14em;text-transform:uppercase;color:#7f735e;white-space:nowrap;overflow:hidden}.global-nav-group:not(:first-child){margin-top:1.1rem}
|
|
247
|
+
.global-nav-item,.global-nav-sub{position:relative;width:100%;display:flex;align-items:center;gap:.7rem;min-height:46px;margin:.16rem 0;padding:.48rem .58rem;border:1px solid transparent;border-radius:13px;background:transparent;color:var(--muted);cursor:pointer;text-align:left;transition:background .15s,border-color .15s,color .15s,transform .15s;overflow:hidden}
|
|
248
|
+
.global-nav-sub{min-height:39px;padding:.38rem .58rem .38rem 1rem}
|
|
249
|
+
.global-nav-item:hover,.global-nav-sub:hover{color:var(--ink);background:rgba(255,236,186,.055);border-color:rgba(255,224,151,.09);transform:translateX(2px)}
|
|
250
|
+
.global-nav-item.active,.global-nav-sub.active{color:var(--gold-pale);border-color:rgba(246,215,123,.28);background:linear-gradient(100deg,rgba(232,190,92,.17),rgba(232,190,92,.035));box-shadow:inset 3px 0 0 var(--gold-bright),0 8px 28px rgba(0,0,0,.16)}
|
|
251
|
+
.global-nav-item.active:after,.global-nav-sub.active:after{content:"";position:absolute;right:10px;width:5px;height:5px;border-radius:50%;background:var(--gold-bright);box-shadow:0 0 10px var(--gold-bright)}
|
|
252
|
+
.global-sidebar:not([data-active-tab="robopark"]) .global-nav-sub.active{color:var(--muted);border-color:transparent;background:transparent;box-shadow:none}.global-sidebar:not([data-active-tab="robopark"]) .global-nav-sub.active:after{display:none}
|
|
253
|
+
.global-nav-icon{display:grid;place-items:center;flex:0 0 34px;height:34px;border-radius:10px;border:1px solid rgba(255,224,151,.12);background:linear-gradient(145deg,rgba(255,239,195,.09),rgba(0,0,0,.12));font-family:var(--mono);font-size:.56rem;font-weight:800;letter-spacing:.03em;color:var(--gold-bright);box-shadow:inset 0 1px rgba(255,255,255,.04)}
|
|
254
|
+
.global-nav-sub .global-nav-icon{flex-basis:28px;height:28px;border-radius:8px;font-size:.49rem}.global-nav-copy{min-width:0;display:block;white-space:nowrap}.global-nav-copy b{display:block;font-size:.69rem;font-weight:700;color:inherit}.global-nav-copy small{display:block;margin-top:.05rem;font-size:.52rem;color:#8f846f}
|
|
255
|
+
.global-side-foot{position:absolute;left:.65rem;right:.65rem;bottom:.7rem;display:flex;align-items:center;gap:.55rem;min-height:48px;padding:.58rem;border:1px solid rgba(255,224,151,.1);border-radius:13px;background:rgba(3,3,2,.48);box-shadow:inset 0 1px rgba(255,255,255,.025)}
|
|
256
|
+
.global-side-foot i{flex:0 0 8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 12px var(--ok)}.global-side-foot span{white-space:nowrap;overflow:hidden;font-family:var(--mono);font-size:.54rem;color:var(--muted)}
|
|
257
|
+
body.global-nav-collapsed .global-side-brand,body.global-nav-collapsed .global-nav-group,body.global-nav-collapsed .global-nav-copy,body.global-nav-collapsed .global-side-foot span{opacity:0;width:0;pointer-events:none}
|
|
258
|
+
body.global-nav-collapsed .global-side-head{padding-left:1rem}body.global-nav-collapsed .global-nav{padding-left:.65rem;padding-right:.65rem}body.global-nav-collapsed .global-nav-item,body.global-nav-collapsed .global-nav-sub{justify-content:center;padding:.42rem}body.global-nav-collapsed .global-nav-item.active:after,body.global-nav-collapsed .global-nav-sub.active:after{right:5px}body.global-nav-collapsed .global-side-foot{justify-content:center}
|
|
259
|
+
.global-nav-mobile{display:none;width:38px;height:38px;border-radius:11px;border:1px solid var(--glass-border);background:var(--panel-2);color:var(--gold-bright);font-family:var(--mono);font-size:.72rem;cursor:pointer}.global-nav-backdrop{display:none;position:fixed;inset:0;z-index:47;background:rgba(0,0,0,.62);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}
|
|
260
|
+
footer.cmd{left:var(--global-nav-width);transition:left .24s cubic-bezier(.2,.8,.2,1)}
|
|
261
|
+
#out{max-width:min(560px,calc(100vw - var(--global-nav-width) - 2rem))}
|
|
262
|
+
/* The sidebar owns Control Center navigation; legacy buttons remain for routing and accessibility tests. */
|
|
263
|
+
.rp-subtabs{display:none!important}
|
|
264
|
+
.pane{display:none;} .pane.active{display:block;animation:globalPaneIn .22s ease-out}
|
|
265
|
+
@keyframes globalPaneIn{from{opacity:.35;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
266
|
+
@media(max-width:760px){
|
|
267
|
+
:root,body.global-nav-collapsed{--global-nav-width:0px}body{padding-left:0}.global-sidebar{width:min(300px,86vw);transform:translateX(-105%);box-shadow:30px 0 90px rgba(0,0,0,.62)}body.global-nav-open .global-sidebar{transform:translateX(0)}body.global-nav-open .global-nav-backdrop{display:block}.global-nav-collapse{display:none}.global-nav-mobile{display:grid;place-items:center}body.global-nav-collapsed .global-side-brand,body.global-nav-collapsed .global-nav-group,body.global-nav-collapsed .global-nav-copy,body.global-nav-collapsed .global-side-foot span{opacity:1;width:auto;pointer-events:auto}body.global-nav-collapsed .global-nav-item,body.global-nav-collapsed .global-nav-sub{justify-content:flex-start;padding:.48rem .58rem}body.global-nav-collapsed .global-nav-sub{padding-left:1rem}footer.cmd{left:0}#out{max-width:90vw}
|
|
268
|
+
}
|
|
241
269
|
.rp-head{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.6rem;padding:1rem 1.1rem 0.2rem;}
|
|
242
270
|
.rp-totals{display:flex;gap:0.5rem;flex-wrap:wrap;}
|
|
243
271
|
.rp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:0.75rem;padding:0.8rem 1.1rem;}
|
|
@@ -706,12 +734,18 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
706
734
|
|
|
707
735
|
/* command sheet */
|
|
708
736
|
.rp-command-shell{display:grid;gap:1rem}.rp-command-env{display:grid;grid-template-columns:repeat(3,minmax(170px,1fr));gap:.65rem;padding:.85rem;border:1px solid var(--glass-border);border-radius:16px;background:var(--panel-2)}
|
|
737
|
+
.rp-minute{display:grid;gap:1rem;padding:1rem;border:1px solid rgba(232,190,92,.34);border-radius:20px;background:radial-gradient(circle at 90% 0,rgba(232,190,92,.13),transparent 38%),linear-gradient(145deg,rgba(29,24,15,.88),rgba(7,7,6,.94));box-shadow:0 22px 65px rgba(0,0,0,.34),inset 0 1px 0 rgba(255,239,190,.11)}
|
|
738
|
+
.rp-minute-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.rp-minute-head h4{font-family:var(--display);font-size:1.15rem;color:var(--gold-pale);margin:.15rem 0}.rp-minute-head p{font-size:.68rem;color:var(--muted);line-height:1.55;max-width:680px}.rp-minute-badge{font-family:var(--mono);font-size:.58rem;color:#0a0905;background:linear-gradient(135deg,var(--gold-bright),#fff0ad);border-radius:100px;padding:.35rem .65rem;white-space:nowrap;font-weight:800}
|
|
739
|
+
.rp-minute-fields{display:grid;grid-template-columns:repeat(4,minmax(150px,1fr));gap:.65rem}.rp-minute-field{position:relative;padding:.72rem;border:1px solid var(--glass-border);border-radius:14px;background:rgba(4,4,3,.42)}.rp-minute-field .step{display:block;font-family:var(--mono);font-size:.53rem;color:var(--gold-bright);text-transform:uppercase;letter-spacing:.08em;margin-bottom:.35rem}.rp-minute-field label{font-size:.64rem;color:var(--text);display:block;margin-bottom:.35rem}.rp-minute-field small{display:block;color:var(--muted);font-size:.54rem;line-height:1.4;margin-top:.35rem}
|
|
740
|
+
.rp-minute-command{display:grid;grid-template-columns:1fr auto;gap:.7rem;align-items:stretch}.rp-minute-command .rp-command-code{min-height:112px}.rp-minute-copy{min-width:180px;font-size:.76rem!important;font-weight:800}.rp-minute-status{font-family:var(--mono);font-size:.61rem;color:var(--muted)}.rp-minute-status.ok{color:var(--ok)}.rp-minute-status.bad{color:var(--bad)}
|
|
741
|
+
.rp-minute-run{display:grid;grid-template-columns:repeat(3,1fr);gap:.55rem}.rp-minute-run div{display:flex;gap:.5rem;align-items:flex-start;padding:.62rem;border-radius:12px;background:rgba(255,255,255,.025);font-size:.61rem;color:var(--muted);line-height:1.45}.rp-minute-run b{display:grid;place-items:center;flex:0 0 22px;height:22px;border-radius:50%;background:rgba(232,190,92,.15);color:var(--gold-bright);font-family:var(--mono)}
|
|
742
|
+
.rp-command-advanced{border:1px solid var(--glass-border);border-radius:16px;background:rgba(7,7,6,.46);overflow:hidden}.rp-command-advanced>summary{cursor:pointer;padding:.85rem 1rem;color:var(--gold-pale);font-family:var(--mono);font-size:.69rem;list-style:none}.rp-command-advanced>summary:after{content:' +';float:right;color:var(--gold-bright)}.rp-command-advanced[open]>summary:after{content:' -'}.rp-command-advanced-body{display:grid;gap:.85rem;padding:0 1rem 1rem}
|
|
709
743
|
.rp-command-toolbar{display:flex;gap:.55rem;align-items:center;flex-wrap:wrap}.rp-command-toolbar input{flex:1;min-width:220px}.rp-command-toolbar select{width:auto;min-width:170px}
|
|
710
744
|
.rp-command-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:.75rem}.rp-command-card{display:flex;flex-direction:column;gap:.55rem;min-width:0;padding:.85rem;border:1px solid var(--glass-border);border-radius:16px;background:linear-gradient(145deg,rgba(31,26,18,.66),rgba(9,8,6,.76));box-shadow:inset 0 1px 0 var(--glass-hi),0 12px 32px rgba(0,0,0,.22)}
|
|
711
745
|
.rp-command-head{display:flex;align-items:flex-start;justify-content:space-between;gap:.6rem}.rp-command-head b{font-family:var(--mono);font-size:.74rem;color:var(--gold-pale)}.rp-command-tag{font-family:var(--mono);font-size:.52rem;text-transform:uppercase;letter-spacing:.06em;color:var(--gold-bright);border:1px solid var(--border-strong);border-radius:100px;padding:.15rem .42rem;white-space:nowrap}.rp-command-card p{font-size:.64rem;line-height:1.5;color:var(--muted);min-height:2.9em}
|
|
712
746
|
.rp-command-code{position:relative;min-height:86px;padding:.68rem .72rem;border:1px solid rgba(255,224,151,.1);border-radius:11px;background:#050504;overflow:auto}.rp-command-code code{display:block;font-family:var(--mono);font-size:.59rem;line-height:1.55;color:#f2dfab;white-space:pre-wrap;overflow-wrap:anywhere}.rp-command-actions{display:flex;gap:.4rem;align-items:center;margin-top:auto}.rp-command-actions .rp-btn:first-child{flex:1}.rp-command-empty{grid-column:1/-1;padding:2rem;text-align:center;color:var(--muted);border:1px dashed var(--glass-border);border-radius:16px}
|
|
713
747
|
.rp-command-custom{display:grid;grid-template-columns:1fr 1fr;gap:.65rem;padding:.9rem;border:1px solid var(--glass-border);border-radius:16px;background:var(--panel-2)}.rp-command-custom h4,.rp-command-custom .wide{grid-column:1/-1}.rp-command-custom h4{font-family:var(--mono);font-size:.72rem;color:var(--gold-bright)}.rp-command-custom textarea{min-height:92px;resize:vertical}.rp-command-note{font-size:.61rem;color:var(--muted)}
|
|
714
|
-
@media(max-width:
|
|
748
|
+
@media(max-width:900px){.rp-minute-fields{grid-template-columns:1fr 1fr}.rp-minute-run{grid-template-columns:1fr}.rp-command-env{grid-template-columns:1fr 1fr}.rp-command-grid{grid-template-columns:1fr}}@media(max-width:540px){.rp-minute-head{display:grid}.rp-minute-fields,.rp-command-env,.rp-command-custom{grid-template-columns:1fr}.rp-minute-command{grid-template-columns:1fr}.rp-minute-copy{min-height:52px}.rp-command-custom h4,.rp-command-custom .wide{grid-column:auto}}
|
|
715
749
|
|
|
716
750
|
.rp-fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:0.7rem;padding:0;}
|
|
717
751
|
.rp-fleet-card{position:relative;}
|
|
@@ -732,9 +766,42 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
732
766
|
.rp-input-grow{flex:1;min-width:140px;}
|
|
733
767
|
</style>
|
|
734
768
|
</head>
|
|
735
|
-
<body>
|
|
769
|
+
<body>
|
|
770
|
+
<aside class="global-sidebar" id="global-sidebar" aria-label="Global navigation">
|
|
771
|
+
<div class="global-side-head">
|
|
772
|
+
<div class="global-side-mark">RP</div>
|
|
773
|
+
<div class="global-side-brand"><b>RoboPark</b><span>Control center</span></div>
|
|
774
|
+
<button class="global-nav-collapse" id="global-nav-toggle" type="button" aria-label="Collapse navigation" aria-expanded="true"><<</button>
|
|
775
|
+
</div>
|
|
776
|
+
<nav class="global-nav">
|
|
777
|
+
<div class="global-nav-group">Fleet operations</div>
|
|
778
|
+
<button class="tab global-nav-item active" data-tab="fleet" id="tabbtn-fleet" type="button" title="Fleet intelligence">
|
|
779
|
+
<span class="global-nav-icon">FL</span><span class="global-nav-copy"><b>Fleet</b><small>Readiness and workload</small></span>
|
|
780
|
+
</button>
|
|
781
|
+
<button class="tab global-nav-item" data-tab="park" id="tabbtn-park" type="button" title="Park map">
|
|
782
|
+
<span class="global-nav-icon">PK</span><span class="global-nav-copy"><b>Park</b><small>Live map and camera ops</small></span>
|
|
783
|
+
</button>
|
|
784
|
+
<div class="global-nav-group">Control center</div>
|
|
785
|
+
<button class="tab global-nav-item" data-tab="robopark" id="tabbtn-robopark" type="button" title="Sessions and telemetry">
|
|
786
|
+
<span class="global-nav-icon">CC</span><span class="global-nav-copy"><b>Control center</b><small>Production oversight</small></span>
|
|
787
|
+
</button>
|
|
788
|
+
<button class="global-nav-sub" data-global-sub="overview" type="button" title="Overview"><span class="global-nav-icon">OV</span><span class="global-nav-copy"><b>Overview</b><small>Pipeline health</small></span></button>
|
|
789
|
+
<button class="global-nav-sub" data-global-sub="incidents" type="button" title="Incidents"><span class="global-nav-icon">IN</span><span class="global-nav-copy"><b>Incidents</b><small>Failures and blockers</small></span></button>
|
|
790
|
+
<button class="global-nav-sub" data-global-sub="live" type="button" title="Live sessions"><span class="global-nav-icon">LS</span><span class="global-nav-copy"><b>Live sessions</b><small>Active conversations</small></span></button>
|
|
791
|
+
<button class="global-nav-sub" data-global-sub="voice" type="button" title="Voice calls"><span class="global-nav-icon">VC</span><span class="global-nav-copy"><b>Voice calls</b><small>Operator voice studio</small></span></button>
|
|
792
|
+
<button class="global-nav-sub" data-global-sub="history" type="button" title="History"><span class="global-nav-icon">HI</span><span class="global-nav-copy"><b>History</b><small>Sessions and transcripts</small></span></button>
|
|
793
|
+
<button class="global-nav-sub" data-global-sub="audit" type="button" title="Audit log"><span class="global-nav-icon">AU</span><span class="global-nav-copy"><b>Audit log</b><small>Operator activity</small></span></button>
|
|
794
|
+
<button class="global-nav-sub" data-global-sub="fleet" type="button" title="Fleet analytics"><span class="global-nav-icon">FA</span><span class="global-nav-copy"><b>Fleet analytics</b><small>Robot telemetry</small></span></button>
|
|
795
|
+
<button class="global-nav-sub" data-global-sub="config" type="button" title="Characters and voice"><span class="global-nav-icon">CH</span><span class="global-nav-copy"><b>Characters</b><small>Voice stacks and prompts</small></span></button>
|
|
796
|
+
<button class="global-nav-sub" data-global-sub="commands" type="button" title="Command sheet"><span class="global-nav-icon">CM</span><span class="global-nav-copy"><b>Command sheet</b><small>Setup and recovery</small></span></button>
|
|
797
|
+
<button class="global-nav-sub" data-global-sub="setup" type="button" title="Setup"><span class="global-nav-icon">ST</span><span class="global-nav-copy"><b>Setup</b><small>Robots and servers</small></span></button>
|
|
798
|
+
</nav>
|
|
799
|
+
<div class="global-side-foot"><i></i><span>Live fleet navigation</span></div>
|
|
800
|
+
</aside>
|
|
801
|
+
<div class="global-nav-backdrop" id="global-nav-backdrop"></div>
|
|
736
802
|
<div class="offline" id="offline">connection lost — retrying</div>
|
|
737
|
-
<header class="bar">
|
|
803
|
+
<header class="bar">
|
|
804
|
+
<button class="global-nav-mobile" id="global-nav-mobile" type="button" aria-label="Open navigation" aria-expanded="false">NAV</button>
|
|
738
805
|
<div class="brand">
|
|
739
806
|
<span class="livedot" id="livedot"></span>
|
|
740
807
|
<div>
|
|
@@ -747,12 +814,6 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
747
814
|
<span class="rp-operator-badge set" id="rp-operator-badge" role="button" tabindex="0">set operator</span>
|
|
748
815
|
</header>
|
|
749
816
|
|
|
750
|
-
<div class="tabs">
|
|
751
|
-
<button class="tab active" data-tab="fleet" id="tabbtn-fleet">Fleet</button>
|
|
752
|
-
<button class="tab" data-tab="park" id="tabbtn-park">Park</button>
|
|
753
|
-
<button class="tab" data-tab="robopark" id="tabbtn-robopark">Sessions & Telemetry</button>
|
|
754
|
-
</div>
|
|
755
|
-
|
|
756
817
|
<div class="pane active" id="pane-fleet">
|
|
757
818
|
<div class="wrap">
|
|
758
819
|
<section>
|
|
@@ -825,7 +886,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
825
886
|
<div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
|
|
826
887
|
<div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
|
|
827
888
|
<div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
|
|
828
|
-
<div class="section"><h3>Motor operations</h3><div class="rp-field"><label>Saved sequence</label><select id="pk-camera-ops-motor-sequence"></select></div><div class="pk-ctl"><button class="rp-btn" id="pk-camera-ops-motor-discover">Discover relays</button><button class="rp-btn primary" id="pk-camera-ops-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-camera-ops-motor-test">Test registered GPIO</button></div><div class="pk-motor-status" id="pk-camera-ops-motor-status">Loading robot motor profile...</div></div>
|
|
889
|
+
<div class="section"><h3>Motor operations</h3><div class="rp-field"><label>Saved sequence</label><select id="pk-camera-ops-motor-sequence"></select></div><div class="pk-ctl"><button class="rp-btn" id="pk-camera-ops-motor-discover">Discover relays</button><button class="rp-btn primary" id="pk-camera-ops-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-camera-ops-motor-stop">STOP ALL</button><button class="rp-btn danger" id="pk-camera-ops-motor-test">Test registered GPIO</button></div><div class="pk-motor-status" id="pk-camera-ops-motor-status">Loading robot motor profile...</div></div>
|
|
829
890
|
<div class="section"><h3>Operator note</h3><div class="pk-note">Video is relayed directly from the robot's RoboVision MJPEG service through the authenticated mesh. Voice remains on LiveKit, avoiding camera ownership conflicts.</div></div>
|
|
830
891
|
</aside>
|
|
831
892
|
</div>
|
|
@@ -1172,31 +1233,49 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1172
1233
|
<div class="rp-sub" id="rp-sub-commands">
|
|
1173
1234
|
<div class="rp-command-shell">
|
|
1174
1235
|
<div class="rp-config-hero">
|
|
1175
|
-
<div><h3>RoboPark
|
|
1176
|
-
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset
|
|
1236
|
+
<div><h3>RoboPark Easy Setup</h3><p>Set up or repair a complete robot in about one minute. Choose three things, copy one command, and paste it on the robot.</p></div>
|
|
1237
|
+
<div class="rp-config-hero-actions"><button class="rp-btn" id="rp-command-reset">Reset setup</button></div>
|
|
1177
1238
|
</div>
|
|
1178
|
-
<div class="rp-
|
|
1179
|
-
<div class="rp-
|
|
1180
|
-
<div class="rp-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1239
|
+
<div class="rp-minute">
|
|
1240
|
+
<div class="rp-minute-head"><div><div class="eyebrow">Guided robot setup</div><h4>One command. Complete production runtime.</h4><p>The command installs RoboPark, removes the old supervised process safely, and starts camera, microphone, speaker, motion detection, motors, RoboVoice, and remote management.</p></div><span class="rp-minute-badge">ABOUT 1 MINUTE</span></div>
|
|
1241
|
+
<div class="rp-minute-fields">
|
|
1242
|
+
<div class="rp-minute-field"><span class="step">Step 1</span><label>Robot name</label><input id="rp-command-robot" value="bmw" spellcheck="false" placeholder="bmw"><small>Use the permanent physical robot name.</small></div>
|
|
1243
|
+
<div class="rp-minute-field"><span class="step">Step 2</span><label>Character</label><select id="rp-command-character"><option value="vixen">VIXEN</option></select><small>Only real character presets appear here.</small></div>
|
|
1244
|
+
<div class="rp-minute-field"><span class="step">Step 3</span><label>Connection</label><select id="rp-command-network"><option value="lan">Same LAN as livekit-1</option><option value="tailscale">Tailscale remote</option></select><small>Choose LAN when the robot is onsite.</small></div>
|
|
1245
|
+
<div class="rp-minute-field"><span class="step">Optional</span><label>Site</label><input id="rp-command-site" value="Tel Aviv"><small>Shown in fleet and session history.</small></div>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div class="rp-minute-command"><pre class="rp-command-code"><code id="rp-minute-command"></code></pre><button class="rp-btn primary rp-minute-copy" id="rp-minute-copy">Copy setup command</button></div>
|
|
1248
|
+
<div class="rp-minute-status" id="rp-minute-status">Checking setup...</div>
|
|
1249
|
+
<div class="rp-minute-run"><div><b>1</b><span>Open Terminal on the Raspberry Pi.</span></div><div><b>2</b><span>Paste the copied command and press Enter.</span></div><div><b>3</b><span>Wait for <strong>enabled and started systemd unit</strong>. The robot then appears in Fleet.</span></div></div>
|
|
1185
1250
|
</div>
|
|
1186
|
-
<
|
|
1187
|
-
<
|
|
1188
|
-
<
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1251
|
+
<details class="rp-command-advanced">
|
|
1252
|
+
<summary>Advanced tools, addresses, diagnostics, and recovery</summary>
|
|
1253
|
+
<div class="rp-command-advanced-body">
|
|
1254
|
+
<div class="rp-command-env">
|
|
1255
|
+
<div class="rp-field"><label>livekit-1 LAN IP</label><input id="rp-command-lan" value="192.168.0.9" spellcheck="false"></div>
|
|
1256
|
+
<div class="rp-field"><label>livekit-1 Tailscale IP / DNS</label><input id="rp-command-tail" value="100.84.147.24" spellcheck="false"></div>
|
|
1257
|
+
<div class="rp-field"><label>Canonical device ID</label><input id="rp-command-device" placeholder="optional: dev_0422e545" spellcheck="false"></div>
|
|
1258
|
+
<div class="rp-field"><label>Enrollment token</label><input id="rp-command-enrollment" placeholder="optional" spellcheck="false"></div>
|
|
1259
|
+
<div class="rp-field"><label>Control token</label><input id="rp-command-token" type="password" spellcheck="false"></div>
|
|
1260
|
+
<div class="rp-field"><label>RoboVoice folder on livekit-1</label><input id="rp-command-robovoice" value="C:\ROBOVOICE-main" spellcheck="false"></div>
|
|
1261
|
+
<div class="rp-field"><label>Release</label><input id="rp-command-release" value="2.8.114" spellcheck="false"></div>
|
|
1262
|
+
</div>
|
|
1263
|
+
<div class="rp-command-toolbar">
|
|
1264
|
+
<input type="search" id="rp-command-search" placeholder="Search logs, camera, Docker, motors, npm...">
|
|
1265
|
+
<select id="rp-command-category"><option value="">All categories</option><option>Provision</option><option>Production</option><option>Voice</option><option>Health</option><option>Recovery</option><option>Media</option><option>Motors</option><option>Packages</option><option>Custom</option></select>
|
|
1266
|
+
<button class="rp-btn" id="rp-command-copy-all">Copy visible commands</button><span class="rp-command-note" id="rp-command-count"></span>
|
|
1267
|
+
</div>
|
|
1268
|
+
<div class="rp-command-grid" id="rp-command-grid"></div>
|
|
1269
|
+
<form class="rp-command-custom" id="rp-command-custom-form">
|
|
1270
|
+
<h4>Add a persistent command</h4>
|
|
1271
|
+
<div class="rp-field"><label>Title</label><input id="rp-command-custom-title" required placeholder="Restart camera service"></div>
|
|
1272
|
+
<div class="rp-field"><label>Category</label><input id="rp-command-custom-category" value="Custom" placeholder="Custom"></div>
|
|
1273
|
+
<div class="rp-field wide"><label>Simple explanation</label><input id="rp-command-custom-description" placeholder="When to use this and what success looks like"></div>
|
|
1274
|
+
<div class="rp-field wide"><label>Command</label><textarea id="rp-command-custom-command" required spellcheck="false" placeholder="Paste a safe command or one-liner"></textarea></div>
|
|
1275
|
+
<div class="wide"><button class="rp-btn primary" type="submit">Save command to this browser</button> <span class="rp-command-note">Saved only in this operator browser.</span></div>
|
|
1276
|
+
</form>
|
|
1277
|
+
</div>
|
|
1278
|
+
</details>
|
|
1200
1279
|
</div>
|
|
1201
1280
|
</div>
|
|
1202
1281
|
|
|
@@ -1264,9 +1343,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1264
1343
|
var lastSchedulerPollAt = 0; // updated by pollRobopark() on every successful /api/telemetry fetch
|
|
1265
1344
|
var lastFedStatusAt = 0; // updated by poll() on every successful /fed/status fetch
|
|
1266
1345
|
var lastStatus = null; // most recent /fed/status snapshot (shared with Park)
|
|
1267
|
-
var roboparkByName = {}; // robot_id -> latest telemetry row (shared with Park drawer)
|
|
1346
|
+
var roboparkByName = {}; // robot_id -> latest telemetry row (shared with Park drawer)
|
|
1268
1347
|
var schedulerDevicesById = {}; // enrolled device rows + camera/audio inventory
|
|
1269
1348
|
var schedulerDevicesByName = {}; // same rows, keyed by device name (id != name, e.g. dev_8984a80e vs jaguar2)
|
|
1349
|
+
function indexRoboparkTelemetry(rows){
|
|
1350
|
+
var indexed={};
|
|
1351
|
+
(Array.isArray(rows)?rows:[]).forEach(function(row){
|
|
1352
|
+
var id=String(row.robot_id||row.id||'').trim().toLowerCase();
|
|
1353
|
+
var name=String(row.name||'').trim().toLowerCase();
|
|
1354
|
+
if(id)indexed[id]=row;
|
|
1355
|
+
if(name)indexed[name]=row;
|
|
1356
|
+
});
|
|
1357
|
+
roboparkByName=indexed;
|
|
1358
|
+
return indexed;
|
|
1359
|
+
}
|
|
1360
|
+
function schedulerDeviceByReference(reference){
|
|
1361
|
+
var key=String(reference||'').trim().toLowerCase();
|
|
1362
|
+
if(!key)return null;
|
|
1363
|
+
return schedulerDevicesById[reference]||schedulerDevicesById[key]||schedulerDevicesByName[key]||null;
|
|
1364
|
+
}
|
|
1270
1365
|
function schedulerDeviceScore(device){
|
|
1271
1366
|
if(!device) return -1;
|
|
1272
1367
|
var status=String(device.status||'').trim().toLowerCase();
|
|
@@ -1773,6 +1868,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1773
1868
|
if(!id) return;
|
|
1774
1869
|
var name = row.name || id;
|
|
1775
1870
|
var device = deviceById[id] || deviceByName[norm(name)] || {};
|
|
1871
|
+
var characterId = device.character_id || row.character_id || row.character_preset_id || '';
|
|
1776
1872
|
var status = String(device.status || row.status || '').toLowerCase();
|
|
1777
1873
|
var heartbeat = device.last_heartbeat || row.last_heartbeat;
|
|
1778
1874
|
var heartbeatAt = heartbeat ? Date.parse(heartbeat) : NaN;
|
|
@@ -1787,6 +1883,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1787
1883
|
if(existing){
|
|
1788
1884
|
existing.schedulerRobotId = id;
|
|
1789
1885
|
existing.schedulerDeviceId = device.id || id;
|
|
1886
|
+
existing.parkSlotId = characterId || existing.parkSlotId || '';
|
|
1790
1887
|
existing.displayName = existing.displayName || name;
|
|
1791
1888
|
existing.connected = !!existing.connected || connected;
|
|
1792
1889
|
existing.lastSeenAt = heartbeatAt && Number.isFinite(heartbeatAt) ? heartbeatAt : existing.lastSeenAt;
|
|
@@ -1797,6 +1894,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1797
1894
|
nodeId: 'scheduler-robot-'+id,
|
|
1798
1895
|
schedulerRobotId: id,
|
|
1799
1896
|
schedulerDeviceId: device.id || id,
|
|
1897
|
+
parkSlotId: characterId,
|
|
1800
1898
|
displayName: name,
|
|
1801
1899
|
role: 'robot',
|
|
1802
1900
|
platform: 'RoboPark robot',
|
|
@@ -1875,16 +1973,56 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1875
1973
|
});
|
|
1876
1974
|
|
|
1877
1975
|
// ── tabs: Fleet + Park + RoboPark sessions/telemetry ──
|
|
1878
|
-
var tabs = document.querySelectorAll('.tab');
|
|
1879
|
-
var activeTab = 'fleet';
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1976
|
+
var tabs = document.querySelectorAll('.tab');
|
|
1977
|
+
var activeTab = 'fleet';
|
|
1978
|
+
var globalSidebar=$('global-sidebar');
|
|
1979
|
+
var globalNavToggle=$('global-nav-toggle');
|
|
1980
|
+
var globalNavMobile=$('global-nav-mobile');
|
|
1981
|
+
var globalNavBackdrop=$('global-nav-backdrop');
|
|
1982
|
+
var globalNavStorageKey='rp-global-nav-collapsed-v1';
|
|
1983
|
+
function globalNavIsMobile(){ return !!(window.matchMedia && window.matchMedia('(max-width:760px)').matches); }
|
|
1984
|
+
function setGlobalNavCollapsed(collapsed){
|
|
1985
|
+
if(globalNavIsMobile()) collapsed=false;
|
|
1986
|
+
document.body.classList.toggle('global-nav-collapsed',!!collapsed);
|
|
1987
|
+
if(globalNavToggle){
|
|
1988
|
+
globalNavToggle.innerHTML=collapsed?'>>':'<<';
|
|
1989
|
+
globalNavToggle.setAttribute('aria-expanded',collapsed?'false':'true');
|
|
1990
|
+
globalNavToggle.setAttribute('aria-label',collapsed?'Expand navigation':'Collapse navigation');
|
|
1991
|
+
}
|
|
1992
|
+
if(!globalNavIsMobile()){ try{ localStorage.setItem(globalNavStorageKey,collapsed?'1':'0'); }catch(e){} }
|
|
1993
|
+
}
|
|
1994
|
+
function closeGlobalNavMobile(){
|
|
1995
|
+
document.body.classList.remove('global-nav-open');
|
|
1996
|
+
if(globalNavMobile) globalNavMobile.setAttribute('aria-expanded','false');
|
|
1997
|
+
}
|
|
1998
|
+
function openGlobalNavMobile(){
|
|
1999
|
+
document.body.classList.add('global-nav-open');
|
|
2000
|
+
if(globalNavMobile) globalNavMobile.setAttribute('aria-expanded','true');
|
|
2001
|
+
}
|
|
2002
|
+
var initialGlobalNavCollapsed=false;
|
|
2003
|
+
try{ initialGlobalNavCollapsed=localStorage.getItem(globalNavStorageKey)==='1'; }catch(e){}
|
|
2004
|
+
setGlobalNavCollapsed(initialGlobalNavCollapsed);
|
|
2005
|
+
if(globalNavToggle) globalNavToggle.addEventListener('click',function(){ setGlobalNavCollapsed(!document.body.classList.contains('global-nav-collapsed')); });
|
|
2006
|
+
if(globalNavMobile) globalNavMobile.addEventListener('click',function(){ if(document.body.classList.contains('global-nav-open')) closeGlobalNavMobile(); else openGlobalNavMobile(); });
|
|
2007
|
+
if(globalNavBackdrop) globalNavBackdrop.addEventListener('click',closeGlobalNavMobile);
|
|
2008
|
+
document.addEventListener('keydown',function(e){ if(e.key==='Escape') closeGlobalNavMobile(); });
|
|
2009
|
+
var globalSubNav=document.querySelectorAll('[data-global-sub]');
|
|
2010
|
+
for(var gsi=0;gsi<globalSubNav.length;gsi++){
|
|
2011
|
+
(function(btn){ btn.addEventListener('click',function(){ selectTab('robopark'); setRpSubTab(btn.getAttribute('data-global-sub')); closeGlobalNavMobile(); }); })(globalSubNav[gsi]);
|
|
2012
|
+
}
|
|
2013
|
+
for (var ti=0; ti<tabs.length; ti++){
|
|
2014
|
+
(function(btn){
|
|
2015
|
+
btn.addEventListener('click', function(){ selectTab(btn.getAttribute('data-tab')); closeGlobalNavMobile(); });
|
|
2016
|
+
})(tabs[ti]);
|
|
2017
|
+
}
|
|
1885
2018
|
function selectTab(name){
|
|
1886
|
-
activeTab = name;
|
|
1887
|
-
|
|
2019
|
+
activeTab = name;
|
|
2020
|
+
if(globalSidebar) globalSidebar.setAttribute('data-active-tab',name);
|
|
2021
|
+
for (var i=0;i<tabs.length;i++){
|
|
2022
|
+
var tabActive=tabs[i].getAttribute('data-tab')===name;
|
|
2023
|
+
tabs[i].classList.toggle('active',tabActive);
|
|
2024
|
+
if(tabActive) tabs[i].setAttribute('aria-current','page'); else tabs[i].removeAttribute('aria-current');
|
|
2025
|
+
}
|
|
1888
2026
|
$('pane-fleet').classList.toggle('active', name==='fleet');
|
|
1889
2027
|
$('pane-park').classList.toggle('active', name==='park');
|
|
1890
2028
|
$('pane-robopark').classList.toggle('active', name==='robopark');
|
|
@@ -1896,6 +2034,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1896
2034
|
initRpSubTabs();
|
|
1897
2035
|
}
|
|
1898
2036
|
if (name==='park') Park.activate(); else Park.deactivate();
|
|
2037
|
+
closeGlobalNavMobile();
|
|
1899
2038
|
}
|
|
1900
2039
|
var fleetParkBtn=$('fleet-open-park'); if(fleetParkBtn) fleetParkBtn.addEventListener('click',function(){selectTab('park');});
|
|
1901
2040
|
var fleetSessionsBtn=$('fleet-open-sessions'); if(fleetSessionsBtn) fleetSessionsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('live');});
|
|
@@ -1906,6 +2045,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1906
2045
|
function initTab(){
|
|
1907
2046
|
if (initHash==='robopark'||initHash.indexOf('robopark/')===0) selectTab('robopark');
|
|
1908
2047
|
else if(initHash==='park') selectTab('park');
|
|
2048
|
+
else selectTab('fleet');
|
|
1909
2049
|
}
|
|
1910
2050
|
// initTab() is called at the very bottom of this IIFE, after Park is assigned.
|
|
1911
2051
|
|
|
@@ -2168,15 +2308,26 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2168
2308
|
if(Park && Park.updateSlots) Park.updateSlots(parkSlots);
|
|
2169
2309
|
});
|
|
2170
2310
|
}
|
|
2171
|
-
function fillCharacterDropdowns(){
|
|
2311
|
+
function fillCharacterDropdowns(){
|
|
2172
2312
|
var sels=document.querySelectorAll('.rp-char');
|
|
2173
2313
|
for(var i=0;i<sels.length;i++){ var sel=sels[i]; var cur=sel.dataset.current||'';
|
|
2174
2314
|
var opts=sel.querySelectorAll('option'); for(var k=opts.length-1;k>=0;k--){ if(opts[k].value) opts[k].parentNode.removeChild(opts[k]); }
|
|
2175
2315
|
for(var j=0;j<characterPresets.length;j++){ var p=characterPresets[j];
|
|
2176
2316
|
var o=document.createElement('option'); o.value=p.id||p.preset_id||p.character_id||''; o.textContent=(p.name||p.id||p.preset_id||'preset')+(p.description?(' — '+p.description):''); if((o.value)===cur) o.selected=true; sel.appendChild(o);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
var commandCharacter=$('rp-command-character');
|
|
2320
|
+
if(commandCharacter){
|
|
2321
|
+
var commandCurrent=commandCharacter.dataset.current||commandCharacter.value||'vixen';
|
|
2322
|
+
commandCharacter.innerHTML=characterPresets.map(function(p){
|
|
2323
|
+
var id=p.id||p.preset_id||p.character_id||'',name=p.name||id||'character';
|
|
2324
|
+
return '<option value="'+esc(id)+'"'+(id===commandCurrent?' selected':'')+'>'+esc(name)+'</option>';
|
|
2325
|
+
}).join('');
|
|
2326
|
+
if(!commandCharacter.value&&characterPresets.length)commandCharacter.value=characterPresets[0].id||characterPresets[0].preset_id||characterPresets[0].character_id||'';
|
|
2327
|
+
commandCharacter.dataset.current=commandCharacter.value;
|
|
2328
|
+
renderOneMinuteSetup();
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2180
2331
|
function fillVoiceStackDropdowns(){
|
|
2181
2332
|
var sels=document.querySelectorAll('.rp-voice');
|
|
2182
2333
|
for(var i=0;i<sels.length;i++){ var sel=sels[i]; var cur=sel.dataset.current||'';
|
|
@@ -2269,38 +2420,33 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2269
2420
|
}
|
|
2270
2421
|
function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; $('srv-autonote').textContent=''; $('srv-autonote').className='rp-autonote'; }
|
|
2271
2422
|
|
|
2272
|
-
//
|
|
2273
|
-
|
|
2423
|
+
// Robust RoboVoice-first one-liners update live as the operator edits the form.
|
|
2424
|
+
function rpControlUrl(network){
|
|
2425
|
+
var env=rpCommandEnv(),host=network==='tailscale'?env.tail:env.lan;
|
|
2426
|
+
host=String(host||'').replace(/^https?:\\/\\//i,'').replace(/\\/+$/,'').replace(/:47913$/,'');
|
|
2427
|
+
return 'http://'+host+':47913';
|
|
2428
|
+
}
|
|
2274
2429
|
function buildAddRobotCommand(name, characterId){
|
|
2275
2430
|
if(!name) return '';
|
|
2276
2431
|
var network=$('rb-network')&&$('rb-network').value||'lan';
|
|
2277
|
-
var
|
|
2278
|
-
var
|
|
2279
|
-
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\/+$/,''):'http://'+hubHost.replace(/\\/+$/,'')+':47913';
|
|
2280
|
-
var schedulerUrl=hubUrl+'/robopark';
|
|
2281
|
-
var argv=['robopark','add-robot',name];
|
|
2282
|
-
if(characterId) argv.push('--character', characterId);
|
|
2432
|
+
var hubUrl=rpControlUrl(network);
|
|
2433
|
+
var argv=['sudo','robopark','setup','--robot','--name',name];
|
|
2283
2434
|
var site=$('rb-site')&&$('rb-site').value.trim();if(site)argv.push('--site',site);
|
|
2284
2435
|
if(hubUrl) argv.push('--hub-url', hubUrl);
|
|
2285
2436
|
var meshToken=$('rb-token')&&$('rb-token').value.trim()||dashboardMeshToken()||'<mesh-token>';
|
|
2286
2437
|
argv.push('--token', meshToken);
|
|
2287
|
-
if(
|
|
2438
|
+
if(characterId)argv.push('--character',characterId);
|
|
2288
2439
|
argv.push(network==='tailscale'?'--tailscale':'--lan','--start','--auto-start','--yes');
|
|
2289
|
-
return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
|
|
2440
|
+
return rpCompleteRobotCommand(argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' '));
|
|
2290
2441
|
}
|
|
2291
2442
|
function buildPadRobotCommand(name,characterId,enrollmentToken){
|
|
2292
|
-
var env=rpCommandEnv(),
|
|
2293
|
-
var hubUrl=/^https?:\/\//i.test(hubHost)?hubHost.replace(/\\\/+$/,''):'http://'+hubHost.replace(/\\\/+$/,'')+':47913';
|
|
2443
|
+
var env=rpCommandEnv(),network=env.network||'lan',hubUrl=rpControlUrl(network);
|
|
2294
2444
|
var meshToken=env.token||dashboardMeshToken()||'<mesh-token>',site=env.site||'Tel Aviv';
|
|
2295
|
-
var argv;
|
|
2296
|
-
if(enrollmentToken)
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
if(characterId)argv.push('--character',characterId);
|
|
2301
|
-
argv.push('--site',site,'--hub-url',hubUrl,'--token',meshToken,'--scheduler-url',hubUrl+'/robopark','--lan','--start','--auto-start','--yes');
|
|
2302
|
-
}
|
|
2303
|
-
return argv.map(function(a){return /[^a-zA-Z0-9_./:=,-]/.test(a)?'"'+String(a).replace(/"/g,'\\"')+'"':String(a);}).join(' ');
|
|
2445
|
+
var argv=['sudo','robopark','setup','--robot','--name',name,'--site',site,'--hub-url',hubUrl,'--token',meshToken,'--port','47913',network==='tailscale'?'--tailscale':'--lan'];
|
|
2446
|
+
if(enrollmentToken)argv.push('--enrollment-token',enrollmentToken);
|
|
2447
|
+
if(characterId)argv.push('--character',characterId);
|
|
2448
|
+
argv.push('--start','--auto-start','--yes');
|
|
2449
|
+
return rpCompleteRobotCommand(argv.map(function(a){return /[^a-zA-Z0-9_./:=,-]/.test(a)?'"'+String(a).replace(/"/g,'\\"')+'"':String(a);}).join(' '));
|
|
2304
2450
|
}
|
|
2305
2451
|
function refreshRobotCliCommand(){
|
|
2306
2452
|
var name=$('rb-name').value.trim(); var cid=$('rb-char').value||'';
|
|
@@ -2395,7 +2541,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2395
2541
|
+'<div class="rp-config-editor-intro"><span class="mark">AI</span><div><b>'+(editing?'Edit character preset':'Create character preset')+'</b><span>The persona prompt and default voice stack are frozen into each session record for reliable history and replay.</span></div></div>'
|
|
2396
2542
|
+'<div class="rp-config-section"><div class="rp-config-section-head">Identity <span>fleet-facing appearance</span></div><div class="rp-config-fields"><div class="rp-field"><label>Preset ID</label><input id="cfg-cp-id" value="'+esc(p.id||'')+'" placeholder="jaguar"'+(editing?' disabled':'')+'></div><div class="rp-field"><label>Display name</label><input id="cfg-cp-name" value="'+esc(p.name||'')+'" placeholder="JAGUAR"></div><div class="rp-field wide"><label>Description</label><input id="cfg-cp-description" value="'+esc(p.description||'')+'" placeholder="Short operator-facing description"></div><div class="rp-field wide"><label>Avatar image path</label><input id="cfg-cp-img" value="'+esc(p.img||'')+'" placeholder="/static/robopark/jaguar.png"></div></div></div>'
|
|
2397
2543
|
+'<div class="rp-config-section"><div class="rp-config-section-head">Persona intelligence <span>ROBOVOICE system prompt</span></div><div class="rp-config-fields"><div class="rp-field wide"><label>System prompt</label><textarea id="cfg-cp-prompt" placeholder="Define personality, constraints, speaking style, and robot behavior…">'+esc(p.system_prompt||'')+'</textarea></div></div></div>'
|
|
2398
|
-
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice & embodiment <span>pipeline and physical
|
|
2544
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice & embodiment <span>pipeline and physical permissions</span></div><div class="rp-config-fields"><div class="rp-field"><label>Default voice stack</label><select id="cfg-cp-stack">'+stackOptions+'</select></div><div class="rp-field"><label>Agent-allowed motor sequences</label><input id="cfg-cp-motors" value="'+esc((p.motors||[]).join(', '))+'" placeholder="greeting-motion, wave"><small>Exact saved sequence IDs. Empty means this character cannot invoke motors through agent tools.</small></div><div class="rp-field"><label>Park pad X (0–1)</label><input id="cfg-cp-nx" type="number" min="0" max="1" step="0.01" value="'+esc(p.nx!=null?p.nx:0.5)+'"></div><div class="rp-field"><label>Park pad Y (0–1)</label><input id="cfg-cp-ny" type="number" min="0" max="1" step="0.01" value="'+esc(p.ny!=null?p.ny:0.5)+'"></div></div></div>'
|
|
2399
2545
|
+'<div class="rp-config-editor-foot"><button class="rp-btn" id="cfg-cp-cancel">Cancel</button><button class="rp-btn primary" id="cfg-cp-save">'+(editing?'Save changes':'Create character')+'</button></div></div>';
|
|
2400
2546
|
showModal(editing?'Edit character · '+(p.name||p.id):'New character preset',html); $('rp-modal').classList.add('config-modal'); $('cfg-cp-cancel').addEventListener('click',closeModal);
|
|
2401
2547
|
$('cfg-cp-save').addEventListener('click',function(){ var body=characterDraft(); if(!body.name){ showOut('Character name is required'); return; } if(!editing&&!body.id) body.id=body.name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,48); var btn=$('cfg-cp-save');btn.disabled=true;btn.textContent='Saving…';rpAction(editing?'PUT':'POST',editing?'/robopark/api/character-presets/'+encodeURIComponent(id):'/robopark/api/character-presets',body).then(function(){closeModal();showOut((editing?'Updated ':'Created ')+body.name);return Promise.all([loadCharacterPresets(),loadParkSlots()]);}).then(function(){pollRobopark();if(Park&&Park.refreshDrawer)Park.refreshDrawer();}).catch(function(e){btn.disabled=false;btn.textContent=editing?'Save changes':'Create character';showOut('character preset: '+e.message);}); });
|
|
@@ -2572,8 +2718,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2572
2718
|
lastSchedulerPollAt = Date.now();
|
|
2573
2719
|
var grid = $('rp-grid'), totals = $('rp-totals');
|
|
2574
2720
|
// stash per-robot rows so the Park drawer can show live session stats
|
|
2575
|
-
|
|
2576
|
-
robotsFrom(d).forEach(function(x){ roboparkByName[String(robotName(x)).toLowerCase()] = x; if(x.name) roboparkByName[String(x.name).toLowerCase()] = x; });
|
|
2721
|
+
indexRoboparkTelemetry(robotsFrom(d));
|
|
2577
2722
|
// C4: also feed lastRobots for the Incidents sub-tab
|
|
2578
2723
|
lastRobots = robotsFrom(d) || [];
|
|
2579
2724
|
// push the same map into the Park so the canvas can color robots
|
|
@@ -2695,43 +2840,57 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2695
2840
|
|
|
2696
2841
|
// ── Sub-tab routing ──
|
|
2697
2842
|
// Command sheet: generate production commands from one saved environment.
|
|
2698
|
-
var RP_COMMAND_ENV_KEY='rp-command-env-
|
|
2843
|
+
var RP_COMMAND_ENV_KEY='rp-command-env-v2';
|
|
2699
2844
|
var RP_CUSTOM_COMMANDS_KEY='rp-custom-commands-v1';
|
|
2700
2845
|
var rpVisibleCommands=[];
|
|
2701
|
-
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',enrollment:'',token:dashboardMeshToken()};}
|
|
2846
|
+
function rpCommandDefaults(){return {lan:'192.168.0.9',tail:'100.84.147.24',site:'Tel Aviv',robot:'bmw',character:'vixen',device:'',enrollment:'',token:dashboardMeshToken(),robovoice:'C:\\ROBOVOICE-main',release:'2.8.114',network:'lan'};}
|
|
2702
2847
|
function rpLoadStoredJson(key,fallback){try{var value=JSON.parse(localStorage.getItem(key)||'null');return value&&typeof value==='object'?value:fallback;}catch(e){return fallback;}}
|
|
2703
2848
|
function rpCommandEnv(){
|
|
2704
2849
|
var d=rpCommandDefaults(),s=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2705
2850
|
function value(name){var input=$('rp-command-'+name),raw=input?input.value:(s[name]!=null?s[name]:d[name]);return String(raw==null?'':raw).trim();}
|
|
2706
|
-
return {lan:value('lan'),tail:value('tail'),site:value('site'),robot:value('robot'),enrollment:value('enrollment'),token:value('token')};
|
|
2851
|
+
return {lan:value('lan'),tail:value('tail'),site:value('site'),robot:value('robot'),character:value('character'),device:value('device'),enrollment:value('enrollment'),token:value('token'),robovoice:value('robovoice'),release:value('release'),network:value('network')||'lan'};
|
|
2707
2852
|
}
|
|
2708
2853
|
function rpSaveCommandEnv(){try{localStorage.setItem(RP_COMMAND_ENV_KEY,JSON.stringify(rpCommandEnv()));}catch(e){}}
|
|
2709
2854
|
function rpShellArg(value){var text=String(value||'');return /^[A-Za-z0-9._:\/-]+$/.test(text)?text:'"'+text.replace(/"/g,'\\"')+'"';}
|
|
2855
|
+
function rpCompleteRobotCommand(setupCommand){var release=rpCommandEnv().release||'2.8.114';return 'sudo npm install -g infinicode@'+rpShellArg(release)+' --force && hash -r && (sudo robopark stop || true) && '+setupCommand;}
|
|
2856
|
+
function rpRobotSetupCommand(){
|
|
2857
|
+
var e=rpCommandEnv(),control=rpControlUrl(e.network),argv=['sudo','robopark','setup','--robot','--name',e.robot,'--site',e.site,'--hub-url',control,'--token',e.token,'--port','47913',e.network==='tailscale'?'--tailscale':'--lan'];
|
|
2858
|
+
if(e.enrollment)argv.push('--enrollment-token',e.enrollment);if(e.character)argv.push('--character',e.character);argv.push('--start','--auto-start','--yes');
|
|
2859
|
+
return rpCompleteRobotCommand(argv.map(function(a){return rpShellArg(a);}).join(' '));
|
|
2860
|
+
}
|
|
2710
2861
|
function rpCommandCatalog(){
|
|
2711
|
-
var e=rpCommandEnv(),
|
|
2862
|
+
var e=rpCommandEnv(),control=rpControlUrl(e.network),device=e.device||e.robot,enroll=e.enrollment?' --enrollment-token '+rpShellArg(e.enrollment):'',auth='?token='+encodeURIComponent(e.token);
|
|
2863
|
+
var api=control+'/robopark/api',release=e.release||'2.8.114';
|
|
2712
2864
|
return [
|
|
2713
|
-
{id:'hub-up',category:'Provision',title:'
|
|
2714
|
-
{id:'robot-
|
|
2715
|
-
{id:'
|
|
2716
|
-
{id:'
|
|
2717
|
-
{id:'
|
|
2718
|
-
{id:'
|
|
2865
|
+
{id:'hub-up',category:'Provision',title:'Install and start RoboVoice-first control host',description:'Run in elevated PowerShell on livekit-1. Starts the private compatibility data service, proven RoboVoice stack, mesh relay, and full Control Center.',command:'npm install -g infinicode@'+rpShellArg(release)+' --force; robopark serve --port 8080 --no-open; robopark agent-up --path '+rpShellArg(e.robovoice)+' --scheduler-url http://127.0.0.1:8080 --token '+rpShellArg(e.token)+'; infinicode serve --hub --name livekit-1 --port 47913 --token '+rpShellArg(e.token)+' --lan --dashboard --scheduler-url http://127.0.0.1:8080 --auto-update --supervised'},
|
|
2866
|
+
{id:'robot-install',category:'Provision',title:'Install or repair selected robot',description:'Run on the Pi. One control URL, one token, one supervised runtime; camera, audio, motion, motor server, and mesh start together.',command:rpRobotSetupCommand()},
|
|
2867
|
+
{id:'operator-tail',category:'Provision',title:'Direct agent mesh link',description:'Copy only this URL and give it to the agent on the other Tailscale device. It contains the exact control endpoint and mesh credential; the agent can run infinicode mesh join <link>.',command:rpControlUrl('tailscale')+'/fed/join?token='+encodeURIComponent(e.token)},
|
|
2868
|
+
{id:'production-on',category:'Production',title:'Arm always-on motion conversations',description:'Enables local motion-triggered RoboVoice calls for the canonical device.',command:'Invoke-RestMethod -Method Patch -ContentType "application/json" -Body (@{production_mode=$true} | ConvertTo-Json) "'+api+'/devices/'+encodeURIComponent(device)+auth+'"'},
|
|
2869
|
+
{id:'production-off',category:'Production',title:'Disarm production conversations',description:'Stops new motion-triggered calls without taking camera monitoring or remote control offline.',command:'Invoke-RestMethod -Method Patch -ContentType "application/json" -Body (@{production_mode=$false} | ConvertTo-Json) "'+api+'/devices/'+encodeURIComponent(device)+auth+'"'},
|
|
2870
|
+
{id:'session-start',category:'Voice',title:'Start one production conversation',description:'Runs the same robot motion-to-RoboVoice path used onsite.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/pipeline-test/start'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2871
|
+
{id:'session-stop',category:'Voice',title:'Stop current conversation',description:'Ends the selected robot session while leaving production mode armed.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/pipeline-test/stop'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2872
|
+
{id:'voice-health',category:'Voice',title:'Verify RoboVoice configuration API',description:'Confirms characters and voice stacks are visible through the single control URL.',command:'Invoke-RestMethod "'+api+'/character-presets'+auth+'"; Invoke-RestMethod "'+api+'/voice-stacks'+auth+'"'},
|
|
2873
|
+
{id:'hub-ui',category:'Health',title:'Open full production control center',description:'Opens Park through the selected LAN or Tailscale control URL.',command:'Start-Process "'+control+'/?token='+encodeURIComponent(e.token)+'#park"'},
|
|
2874
|
+
{id:'hub-build',category:'Health',title:'Verify deployed Control Center release',description:'Compares installed CLI, UI build, and expected pinned release.',command:'infinicode --version; Write-Host "expected '+release+'"; Invoke-RestMethod "'+control+'/fed/ui-build'+auth+'"'},
|
|
2719
2875
|
{id:'hub-ports',category:'Health',title:'Inspect hub ports on Windows',description:'Lists processes owning the control center, scheduler, and LiveKit ports without changing them.',command:'Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Where-Object { $_.LocalPort -in 47913,8080,7880,7881,7882,7883 } | Sort-Object LocalPort | Format-Table LocalAddress,LocalPort,OwningProcess -AutoSize'},
|
|
2720
|
-
{id:'
|
|
2721
|
-
{id:'mesh-status',category:'Health',title:'Check mesh nodes',description:'Lists connected hub, robot, and operator nodes.',command:'Invoke-RestMethod "'+
|
|
2876
|
+
{id:'control-health',category:'Health',title:'Check robots through control URL',description:'Confirms the management API returns canonical robot and device inventory.',command:'Invoke-RestMethod "'+api+'/devices'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2877
|
+
{id:'mesh-status',category:'Health',title:'Check mesh nodes',description:'Lists connected hub, robot, and operator nodes.',command:'Invoke-RestMethod "'+control+'/fed/status'+auth+'" | ConvertTo-Json -Depth 6'},
|
|
2722
2878
|
{id:'pi-runtime',category:'Health',title:'Follow robot runtime logs',description:'Live systemd logs for the selected robot unified runtime.',command:'sudo journalctl -u robopark-robot-runtime-'+e.robot+'.service -f'},
|
|
2723
2879
|
{id:'pi-ports',category:'Health',title:'Inspect robot service ports',description:'Checks mesh, camera, motion webhook, and audio listeners on a Linux robot.',command:"sudo ss -ltnp | grep -E ':(47913|5000|5057|8000)'"},
|
|
2880
|
+
{id:'camera-relay',category:'Media',title:'Open always-on robot camera relay',description:'Opens RoboVision video through authenticated livekit-1 mesh relay; no voice session is required.',command:'Start-Process "'+control+'/fed/media/robots/'+encodeURIComponent(e.robot)+'/video_feed'+auth+'"'},
|
|
2724
2881
|
{id:'camera-inventory',category:'Media',title:'Read RoboVision media inventory',description:'Returns the real cameras, microphones, and speakers detected by RoboVision.',command:'curl -s http://127.0.0.1:5000/api/media/inventory'},
|
|
2725
2882
|
{id:'audio-inventory',category:'Media',title:'Read RoboVision audio devices',description:'Returns the microphone and speaker inventory used by the robot audio server.',command:'curl -s http://127.0.0.1:8000/devices'},
|
|
2726
2883
|
{id:'camera-devices',category:'Media',title:'List valid Linux capture devices',description:'Shows V4L2 devices and capabilities so metadata-only nodes are not selected as cameras.',command:'v4l2-ctl --list-devices; for d in /dev/video*; do echo "--- $d"; v4l2-ctl -d "$d" --all 2>/dev/null | grep -E "Card type|Video Capture|Device Caps"; done'},
|
|
2727
2884
|
{id:'camera-preview',category:'Media',title:'Open direct RoboVision camera feed',description:'Opens the robot MJPEG feed locally for camera-only diagnosis.',command:'xdg-open http://127.0.0.1:5000/video_feed'},
|
|
2728
|
-
{id:'
|
|
2885
|
+
{id:'motor-discover',category:'Motors',title:'Discover registered robot relays',description:'Queues safe registry discovery through the canonical robot supervisor without pulsing GPIO.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/motors/discover'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2886
|
+
{id:'motor-stop',category:'Motors',title:'Emergency all motors off',description:'Immediately queues all registered relays off for the selected robot.',command:'Invoke-RestMethod -Method Post "'+api+'/robots/'+encodeURIComponent(device)+'/motors/stop'+auth+'" | ConvertTo-Json -Depth 8'},
|
|
2887
|
+
{id:'robot-restart',category:'Recovery',title:'Clean repair selected robot',description:'Stops the supervised runtime, installs the pinned release, and recreates the complete runtime from one control URL.',command:rpRobotSetupCommand()},
|
|
2729
2888
|
{id:'robot-status',category:'Recovery',title:'Inspect robot service failure',description:'Shows service state and the latest 150 log lines after a failed start.',command:'sudo systemctl status robopark-robot-runtime-'+e.robot+'.service --no-pager; sudo journalctl -u robopark-robot-runtime-'+e.robot+'.service -n 150 --no-pager'},
|
|
2730
2889
|
{id:'docker-health',category:'Health',title:'Inspect Docker and LiveKit containers',description:'Shows container state, health, ports, and names on the hub.',command:'docker ps -a --format "table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\\t{{.Image}}"'},
|
|
2731
2890
|
{id:'docker-restart',category:'Recovery',title:'Restart Docker Desktop service',description:'Run from elevated PowerShell only when Docker Desktop is stuck starting.',command:'Restart-Service com.docker.service; Start-Process -FilePath "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"'},
|
|
2732
2891
|
{id:'tailscale-status',category:'Health',title:'Check Tailscale routes and Serve',description:'Confirms the node is connected and shows any active Serve proxy.',command:'tailscale status; tailscale serve status'},
|
|
2733
|
-
{id:'npm-version',category:'Packages',title:'Check installed and published versions',description:'
|
|
2734
|
-
{id:'npm-update',category:'Packages',title:'
|
|
2892
|
+
{id:'npm-version',category:'Packages',title:'Check installed, pinned, and published versions',description:'Prevents accidental downgrade to an older latest tag.',command:'infinicode --version; Write-Host "pinned '+release+'"; npm view infinicode version'},
|
|
2893
|
+
{id:'npm-update',category:'Packages',title:'Install pinned RoboPark release',description:'Installs the exact release selected above instead of an ambiguous latest tag.',command:'npm install -g infinicode@'+rpShellArg(release)+' --force'},
|
|
2735
2894
|
{id:'npm-cache',category:'Packages',title:'Repair npm metadata cache',description:'Use when a new version returns ETARGET. Verify metadata before clearing anything.',command:'npm cache verify; npm view infinicode dist-tags --json'}
|
|
2736
2895
|
];
|
|
2737
2896
|
}
|
|
@@ -2739,6 +2898,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2739
2898
|
function rpSaveCustomCommands(list){try{localStorage.setItem(RP_CUSTOM_COMMANDS_KEY,JSON.stringify(list));}catch(e){showOut('Could not save custom command: '+e.message);}}
|
|
2740
2899
|
function rpLegacyCopy(value){var area=document.createElement('textarea');area.value=value;area.style.position='fixed';area.style.opacity='0';document.body.appendChild(area);area.select();try{document.execCommand('copy');showOut('command copied');}catch(e){showOut('Copy failed: select the command manually');}area.remove();}
|
|
2741
2900
|
function rpCopyCommand(text){var value=String(text||'');if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(value).then(function(){showOut('command copied');}).catch(function(){rpLegacyCopy(value);});}else rpLegacyCopy(value);}
|
|
2901
|
+
function renderOneMinuteSetup(){
|
|
2902
|
+
var code=$('rp-minute-command'),button=$('rp-minute-copy'),status=$('rp-minute-status');if(!code||!button||!status)return;
|
|
2903
|
+
var e=rpCommandEnv(),host=e.network==='tailscale'?e.tail:e.lan,issues=[];
|
|
2904
|
+
if(!e.robot)issues.push('Enter the robot name');else if(!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(e.robot))issues.push('Robot name may use letters, numbers, dash, and underscore only');
|
|
2905
|
+
if(!e.character)issues.push('Choose a character');
|
|
2906
|
+
if(!host)issues.push('Set the livekit-1 address under Advanced tools');
|
|
2907
|
+
if(!e.token)issues.push('Open this dashboard with its token, or enter the control token under Advanced tools');
|
|
2908
|
+
var command=rpRobotSetupCommand();code.textContent=command;button.disabled=issues.length>0;button.dataset.command=command;
|
|
2909
|
+
status.className='rp-minute-status '+(issues.length?'bad':'ok');status.textContent=issues.length?issues[0]:'Ready: copy this command and run it once on '+e.robot+'.';
|
|
2910
|
+
}
|
|
2742
2911
|
function renderCommandSheet(){
|
|
2743
2912
|
var grid=$('rp-command-grid');if(!grid)return;
|
|
2744
2913
|
var search=String(($('rp-command-search')||{}).value||'').toLowerCase().trim(),category=String(($('rp-command-category')||{}).value||'').toLowerCase();
|
|
@@ -2749,25 +2918,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2749
2918
|
grid.innerHTML=rpVisibleCommands.map(function(x){return '<article class="rp-command-card"><div class="rp-command-head"><b>'+esc(x.title)+'</b><span class="rp-command-tag">'+esc(x.category)+'</span></div><p>'+esc(x.description)+'</p><pre class="rp-command-code"><code>'+esc(x.command)+'</code></pre><div class="rp-command-actions"><button class="rp-btn primary" data-command-copy="'+esc(x.id)+'">Copy command</button>'+(x.custom?'<button class="rp-btn danger" data-command-delete="'+esc(x.id)+'">Delete</button>':'')+'</div></article>';}).join('');
|
|
2750
2919
|
}
|
|
2751
2920
|
function initCommandSheet(){
|
|
2752
|
-
var fields=['lan','tail','site','robot','enrollment','token'],defaults=rpCommandDefaults(),saved=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2753
|
-
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;
|
|
2754
|
-
var search=$('rp-command-search'),category=$('rp-command-category'),grid=$('rp-command-grid'),reset=$('rp-command-reset'),copyAll=$('rp-command-copy-all'),form=$('rp-command-custom-form');
|
|
2921
|
+
var fields=['lan','tail','site','robot','character','device','enrollment','token','robovoice','release','network'],defaults=rpCommandDefaults(),saved=rpLoadStoredJson(RP_COMMAND_ENV_KEY,{});
|
|
2922
|
+
fields.forEach(function(name){var input=$('rp-command-'+name);if(!input)return;var initial=name==='token'&&TOKEN?TOKEN:(saved[name]!=null?saved[name]:defaults[name]);if(name==='character')input.dataset.current=initial;input.value=initial;var refresh=function(){if(name==='character')input.dataset.current=input.value;rpSaveCommandEnv();renderOneMinuteSetup();renderCommandSheet();};input.addEventListener('input',refresh);input.addEventListener('change',refresh);});
|
|
2923
|
+
var search=$('rp-command-search'),category=$('rp-command-category'),grid=$('rp-command-grid'),reset=$('rp-command-reset'),copyAll=$('rp-command-copy-all'),form=$('rp-command-custom-form'),minuteCopy=$('rp-minute-copy');
|
|
2755
2924
|
if(search)search.addEventListener('input',renderCommandSheet);if(category)category.addEventListener('change',renderCommandSheet);
|
|
2756
|
-
if(reset)reset.addEventListener('click',function(){try{localStorage.removeItem(RP_COMMAND_ENV_KEY);}catch(e){}fields.forEach(function(name){var input=$('rp-command-'+name);if(input)input.value=defaults[name];});renderCommandSheet();showOut('
|
|
2925
|
+
if(reset)reset.addEventListener('click',function(){try{localStorage.removeItem(RP_COMMAND_ENV_KEY);}catch(e){}fields.forEach(function(name){var input=$('rp-command-'+name);if(input){input.value=defaults[name];if(name==='character')input.dataset.current=defaults[name];}});renderOneMinuteSetup();renderCommandSheet();showOut('setup reset');});
|
|
2926
|
+
if(minuteCopy)minuteCopy.addEventListener('click',function(){if(minuteCopy.disabled)return;rpCopyCommand(minuteCopy.dataset.command||'');minuteCopy.textContent='Copied - paste on robot';setTimeout(function(){minuteCopy.textContent='Copy setup command';},2200);});
|
|
2757
2927
|
if(copyAll)copyAll.addEventListener('click',function(){rpCopyCommand(rpVisibleCommands.map(function(x){return '# '+x.title+'\\n'+x.command;}).join('\\n\\n'));});
|
|
2758
2928
|
if(grid)grid.addEventListener('click',function(ev){var copy=ev.target.closest('[data-command-copy]'),del=ev.target.closest('[data-command-delete]');if(copy){var id=copy.getAttribute('data-command-copy'),item=rpVisibleCommands.find(function(x){return x.id===id;});if(item)rpCopyCommand(item.command);}if(del){var removeId=del.getAttribute('data-command-delete');rpSaveCustomCommands(rpCustomCommands().filter(function(x){return String(x.id)!==removeId;}));renderCommandSheet();showOut('custom command deleted');}});
|
|
2759
2929
|
if(form)form.addEventListener('submit',function(ev){ev.preventDefault();var title=$('rp-command-custom-title').value.trim(),command=$('rp-command-custom-command').value.trim();if(!title||!command)return;var list=rpCustomCommands();list.push({id:'custom-'+Date.now(),title:title,category:$('rp-command-custom-category').value.trim()||'Custom',description:$('rp-command-custom-description').value.trim(),command:command});rpSaveCustomCommands(list);$('rp-command-custom-title').value='';$('rp-command-custom-description').value='';$('rp-command-custom-command').value='';renderCommandSheet();showOut('custom command saved in this browser');});
|
|
2760
|
-
renderCommandSheet();
|
|
2930
|
+
renderOneMinuteSetup();renderCommandSheet();
|
|
2761
2931
|
}
|
|
2762
2932
|
initCommandSheet();
|
|
2763
2933
|
|
|
2764
2934
|
var rpSubTab = 'overview';
|
|
2765
2935
|
var rpIntervals = {}; // sub-tab name -> setInterval handle
|
|
2766
2936
|
var rpHistoryAll = []; // full recent-session list (we paginate client-side)
|
|
2767
|
-
function setRpSubTab(name){
|
|
2768
|
-
if(!name) return;
|
|
2769
|
-
rpSubTab = name;
|
|
2770
|
-
var
|
|
2937
|
+
function setRpSubTab(name){
|
|
2938
|
+
if(!name) return;
|
|
2939
|
+
rpSubTab = name;
|
|
2940
|
+
var globalBtns=document.querySelectorAll('[data-global-sub]');
|
|
2941
|
+
for(var gi=0;gi<globalBtns.length;gi++){
|
|
2942
|
+
var globalActive=globalBtns[gi].getAttribute('data-global-sub')===name;
|
|
2943
|
+
globalBtns[gi].classList.toggle('active',globalActive);
|
|
2944
|
+
if(globalActive) globalBtns[gi].setAttribute('aria-current','page'); else globalBtns[gi].removeAttribute('aria-current');
|
|
2945
|
+
}
|
|
2946
|
+
var btns = document.querySelectorAll('.rp-subtab');
|
|
2771
2947
|
for(var i=0;i<btns.length;i++){
|
|
2772
2948
|
var b=btns[i], sub=b.getAttribute('data-sub');
|
|
2773
2949
|
var isActive = sub===name;
|
|
@@ -2792,7 +2968,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2792
2968
|
if(name==='history'){ rpHistoryPage=0; pollHistory(); }
|
|
2793
2969
|
if(name==='audit'){ pollAuditLog(); rpIntervals.audit = setInterval(pollAuditLog, 6000); }
|
|
2794
2970
|
if(name==='fleet'){ pollFleet(); rpIntervals.fleet = setInterval(pollFleet, 5000); }
|
|
2795
|
-
if(name==='commands'){ renderCommandSheet(); }
|
|
2971
|
+
if(name==='commands'){ renderOneMinuteSetup(); renderCommandSheet(); }
|
|
2796
2972
|
if(name==='setup'){ pollSettings(); loadCharacterPresets(); loadVoiceStacks(); pollRuntimeHealth(); }
|
|
2797
2973
|
// remember last sub-tab in URL hash
|
|
2798
2974
|
try{ history.replaceState(null,'','#robopark/'+name); }catch(e){}
|
|
@@ -3056,14 +3232,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
3056
3232
|
if(!filtered.length){ tbody.innerHTML=''; wrap.style.display='none'; empty.style.display='block'; }
|
|
3057
3233
|
else { empty.style.display='none'; wrap.style.display='block'; }
|
|
3058
3234
|
var html='';
|
|
3059
|
-
for(var i=0;i<filtered.length;i++){
|
|
3060
|
-
var s = filtered[i];
|
|
3061
|
-
var rid = s.robot_id || '';
|
|
3062
|
-
var
|
|
3235
|
+
for(var i=0;i<filtered.length;i++){
|
|
3236
|
+
var s = filtered[i];
|
|
3237
|
+
var rid = s.robot_id || '';
|
|
3238
|
+
var sessionDevice=schedulerDeviceByReference(rid);
|
|
3239
|
+
var robotLabel=(sessionDevice&&sessionDevice.name)||s.robot_name||s.name||rid;
|
|
3240
|
+
var characterLabel=(sessionDevice&&sessionDevice.character_id)||s.character_name||s.character_id||'';
|
|
3241
|
+
var sid = s.session_id || '';
|
|
3063
3242
|
var started = s.started_at ? Date.parse(s.started_at) : null;
|
|
3064
3243
|
var elapsed = started ? Math.floor((Date.now()-started)/1000) : null;
|
|
3065
3244
|
html += '<tr>'
|
|
3066
|
-
+ '<td><b>'+esc(rid)+'</
|
|
3245
|
+
+ '<td><b>'+esc(robotLabel)+'</b><span class="t-abs">'+esc(rid)+(characterLabel?(' · '+esc(characterLabel)):'')+'</span></td>'
|
|
3067
3246
|
+ '<td><span class="sid" title="'+esc(sid)+'">'+esc(sid)+'</span></td>'
|
|
3068
3247
|
+ '<td><span class="t-rel"><span class="live-dot"></span>'+esc(rpFmtRelative(started))+'</span><span class="t-abs">'+esc(rpFmtAbs(started))+'</span></td>'
|
|
3069
3248
|
+ '<td data-started="'+started+'">'+esc(rpFmtDuration(elapsed))+'</td>'
|
|
@@ -3099,7 +3278,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
3099
3278
|
if(!b) return;
|
|
3100
3279
|
var act = b.getAttribute('data-act');
|
|
3101
3280
|
if(act==='end-session'){ endSessionFromTable(b.getAttribute('data-sid'), b.getAttribute('data-robot')); }
|
|
3102
|
-
if(act==='open-robot'){ var rid=b.getAttribute('data-robot'); if(rid){
|
|
3281
|
+
if(act==='open-robot'){ var rid=b.getAttribute('data-robot'); if(rid){ selectTab('park'); setTimeout(function(){Park.openDrawerForRobot(rid);},0); } }
|
|
3103
3282
|
});
|
|
3104
3283
|
})();
|
|
3105
3284
|
|
|
@@ -4320,13 +4499,19 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4320
4499
|
// looks up each robot node by its slot aliases and attaches a
|
|
4321
4500
|
// status + telemetry field used by drawRobot() to drive the
|
|
4322
4501
|
// status animations.
|
|
4323
|
-
function updateTelemetry(map){
|
|
4324
|
-
_telemetryByName = map || {};
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4502
|
+
function updateTelemetry(map){
|
|
4503
|
+
_telemetryByName = map || {};
|
|
4504
|
+
if(lastStatus) onStatus(lastStatus);
|
|
4505
|
+
}
|
|
4506
|
+
function lookupTelemetry(node){
|
|
4507
|
+
if(!node) return null;
|
|
4328
4508
|
// The hub is never a per-robot telemetry row — only robot slots are.
|
|
4329
|
-
if(node.kind !== 'robot') return null;
|
|
4509
|
+
if(node.kind !== 'robot') return null;
|
|
4510
|
+
var exactIds=[node.schedulerDeviceId,node.schedulerRobotId];
|
|
4511
|
+
for(var ei=0;ei<exactIds.length;ei++){
|
|
4512
|
+
var exactKey=String(exactIds[ei]||'').toLowerCase();
|
|
4513
|
+
if(exactKey&&_telemetryByName[exactKey]) return _telemetryByName[exactKey];
|
|
4514
|
+
}
|
|
4330
4515
|
// Try every alias the slot was built with: slot.label, slot.id,
|
|
4331
4516
|
// slot.sub, slot.keys, and the matched mesh node's displayName.
|
|
4332
4517
|
var slot = null;
|
|
@@ -4402,11 +4587,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4402
4587
|
}
|
|
4403
4588
|
|
|
4404
4589
|
function layout(src){
|
|
4405
|
-
var list=(src||[]).slice(), used={}, out=[];
|
|
4406
|
-
function prevHit(id){ for(var p=0;p<nodes.length;p++){ if(nodes[p].id===id) return nodes[p]._hit; } return null; }
|
|
4407
|
-
function bind(slot, wx, wy, kind){
|
|
4408
|
-
var match=null;
|
|
4409
|
-
|
|
4590
|
+
var list=(src||[]).slice(), used={}, out=[];
|
|
4591
|
+
function prevHit(id){ for(var p=0;p<nodes.length;p++){ if(nodes[p].id===id) return nodes[p]._hit; } return null; }
|
|
4592
|
+
function bind(slot, wx, wy, kind){
|
|
4593
|
+
var match=null;
|
|
4594
|
+
if(kind!=='hub'){
|
|
4595
|
+
for(var exactIndex=0;exactIndex<list.length;exactIndex++){
|
|
4596
|
+
var exactNode=list[exactIndex];
|
|
4597
|
+
if(used[exactNode.nodeId]) continue;
|
|
4598
|
+
if(String(exactNode.parkSlotId||'').toLowerCase()===String(slot.id||'').toLowerCase()){
|
|
4599
|
+
match=exactNode;
|
|
4600
|
+
break;
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
for(var i=0;i<list.length;i++){ var n=list[i]; if(used[n.nodeId]) continue;
|
|
4605
|
+
if(match) break;
|
|
4410
4606
|
var nm=(n.displayName||n.nodeId||'').toLowerCase(), isHub=kindOf(n)==='hub';
|
|
4411
4607
|
if(kind==='hub'){ if(isHub){ match=n; break; } }
|
|
4412
4608
|
else if(!isHub){ for(var k=0;k<slot.keys.length;k++){ if(slot.keys[k] && nm.indexOf(slot.keys[k])>-1){ match=n; break; } } if(match) break; }
|
|
@@ -4421,9 +4617,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4421
4617
|
load: (match&&match.load!=null)?match.load:0,
|
|
4422
4618
|
platform: match?(match.platform||''):'', version: match?(match.version||'?'):'—',
|
|
4423
4619
|
site: match?(match.site||''):'', caps: match?(match.capabilities||[]):[],
|
|
4424
|
-
schedulerRobotId: match?(match.schedulerRobotId||match.schedulerDeviceId||''):'',
|
|
4425
|
-
schedulerDeviceId: match?(match.schedulerDeviceId||match.schedulerRobotId||''):'',
|
|
4426
|
-
|
|
4620
|
+
schedulerRobotId: match?(match.schedulerRobotId||match.schedulerDeviceId||''):'',
|
|
4621
|
+
schedulerDeviceId: match?(match.schedulerDeviceId||match.schedulerRobotId||''):'',
|
|
4622
|
+
parkSlotId: match?(match.parkSlotId||slot.id):slot.id,
|
|
4623
|
+
telemKeys: [slot.label.toLowerCase(),String(slot.id||'').toLowerCase()]
|
|
4624
|
+
.concat(slot.keys||[])
|
|
4625
|
+
.concat(match?(match.telemKeys||[]):[])
|
|
4626
|
+
.concat(match?[(match.displayName||'').toLowerCase(),String(match.schedulerDeviceId||'').toLowerCase(),String(match.schedulerRobotId||'').toLowerCase()]:[])
|
|
4627
|
+
.filter(function(value,index,all){return value&&all.indexOf(value)===index;}),
|
|
4427
4628
|
// slotId is the stable handle we use to persist operator-
|
|
4428
4629
|
// chosen positions. slotNx/slotNy are the preset's default
|
|
4429
4630
|
// coordinates so the user can always reset back.
|
|
@@ -4448,12 +4649,19 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
4448
4649
|
SLOTS.forEach(function(s){ var w=W2(s.nx,s.ny); out.push(bind(s,w[0],w[1],'robot')); });
|
|
4449
4650
|
// Any extra live node that matched no pad → line it along the near edge.
|
|
4450
4651
|
var ex=0;
|
|
4451
|
-
list.forEach(function(n){ if(used[n.nodeId]) return; used[n.nodeId]=1;
|
|
4452
|
-
var w=W2(0.20+ex*0.13, 0.90); ex++;
|
|
4453
|
-
|
|
4454
|
-
sub:n.role||'', connected:!!n.connected, load:(n.load!=null?n.load:0), platform:n.platform||'', version:n.version||'?',
|
|
4455
|
-
site:n.site||'', caps:n.capabilities||[],
|
|
4456
|
-
|
|
4652
|
+
list.forEach(function(n){ if(used[n.nodeId]) return; used[n.nodeId]=1;
|
|
4653
|
+
var w=W2(0.20+ex*0.13, 0.90); ex++;
|
|
4654
|
+
var extraNode={ id:n.nodeId, kind:kindOf(n), label:(n.displayName||n.nodeId), name:(n.displayName||n.nodeId),
|
|
4655
|
+
sub:n.role||'', connected:!!n.connected, load:(n.load!=null?n.load:0), platform:n.platform||'', version:n.version||'?',
|
|
4656
|
+
site:n.site||'', caps:n.capabilities||[], schedulerRobotId:n.schedulerRobotId||'', schedulerDeviceId:n.schedulerDeviceId||'', parkSlotId:n.parkSlotId||'',
|
|
4657
|
+
telemKeys:[(n.displayName||'').toLowerCase()].concat(n.telemKeys||[]), wx:w[0], wy:w[1], _hit:prevHit(n.nodeId) };
|
|
4658
|
+
if(extraNode.kind==='robot'){
|
|
4659
|
+
var extraTele=lookupTelemetry(extraNode), extraStatus=computeStatus(extraTele,extraNode);
|
|
4660
|
+
extraNode.telemetry=extraTele; extraNode.status=extraStatus.status; extraNode.readiness=extraStatus.readiness;
|
|
4661
|
+
extraNode.readinessMessage=extraStatus.readinessMessage; extraNode.sessionAge=extraStatus.sessionAge; extraNode.activityAge=extraStatus.activityAge;
|
|
4662
|
+
}
|
|
4663
|
+
out.push(extraNode);
|
|
4664
|
+
});
|
|
4457
4665
|
return out;
|
|
4458
4666
|
}
|
|
4459
4667
|
|
|
@@ -6796,7 +7004,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6796
7004
|
// dashboard would only know about local (this-session) drags
|
|
6797
7005
|
// and would reset to slot defaults on every reload.
|
|
6798
7006
|
try{
|
|
6799
|
-
getJson('/api/character-presets'
|
|
7007
|
+
getJson('/robopark/api/character-presets').then(function(list){
|
|
6800
7008
|
if(Array.isArray(list)){
|
|
6801
7009
|
for(var pi=0; pi<list.length; pi++){
|
|
6802
7010
|
var p = list[pi];
|
|
@@ -6895,7 +7103,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6895
7103
|
micAnalyser=null;
|
|
6896
7104
|
}
|
|
6897
7105
|
|
|
6898
|
-
//
|
|
7106
|
+
// Resolve a Park pad to a real character preset. Mesh and scheduler node
|
|
7107
|
+
// ids are infrastructure identities and must never be emitted as presets.
|
|
7108
|
+
function resolvePadCharacterPreset(n){
|
|
7109
|
+
var device=schedulerDevice(n)||{},row=schedRow(n)||{};
|
|
7110
|
+
var candidates=[device.character_id,row.character_id,n.character_id,n.characterId,n.label,n.name,n.id];
|
|
7111
|
+
for(var ci=0;ci<candidates.length;ci++){
|
|
7112
|
+
var candidate=String(candidates[ci]||'').trim().toLowerCase();if(!candidate)continue;
|
|
7113
|
+
for(var pi=0;pi<characterPresets.length;pi++){
|
|
7114
|
+
var preset=characterPresets[pi],presetId=String(preset.id||'').trim(),presetName=String(preset.name||'').trim().toLowerCase();
|
|
7115
|
+
if(candidate===presetId.toLowerCase()||candidate===presetName)return presetId;
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
return '';
|
|
7119
|
+
}
|
|
7120
|
+
|
|
7121
|
+
// ── setup flow for unjoined park pads ──
|
|
6899
7122
|
function submitPadSetup(n){
|
|
6900
7123
|
var name=$('pk-setup-name').value.trim() || (n.label||n.id||'robot').toString().toLowerCase();
|
|
6901
7124
|
var body={
|
|
@@ -6912,7 +7135,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6912
7135
|
$('pk-setup-token').value=token;
|
|
6913
7136
|
$('pk-setup-result').style.display='block';
|
|
6914
7137
|
var command=$('pk-setup-clicmd'),label=$('pk-setup-clicmd-label');
|
|
6915
|
-
if(command)command.value=buildPadRobotCommand(name,body.character_id
|
|
7138
|
+
if(command)command.value=buildPadRobotCommand(name,body.character_id,token);
|
|
6916
7139
|
if(label)label.textContent='run this ON THE ROBOT (uses the token minted above)';
|
|
6917
7140
|
showOut('robot added: '+esc((j&&j.name)||name)+'\\ntoken: '+esc(token));
|
|
6918
7141
|
pollRoboparkQuiet();
|
|
@@ -7036,12 +7259,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7036
7259
|
if(visionRetryTimer){clearTimeout(visionRetryTimer);visionRetryTimer=null;}
|
|
7037
7260
|
var img=$('pk-vision-img'); if(img){ img.src=''; img.style.display='none'; }
|
|
7038
7261
|
}
|
|
7039
|
-
function pollRoboparkQuiet(){
|
|
7040
|
-
fetch('/robopark/api/telemetry'+QS,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
|
7041
|
-
var list=(Array.isArray(d)?d:(d&&d.robots)||[]);
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7262
|
+
function pollRoboparkQuiet(){
|
|
7263
|
+
fetch('/robopark/api/telemetry'+QS,{cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
|
7264
|
+
var list=(Array.isArray(d)?d:(d&&d.robots)||[]);
|
|
7265
|
+
lastSchedulerPollAt=Date.now();
|
|
7266
|
+
lastRobots=list;
|
|
7267
|
+
indexRoboparkTelemetry(list);
|
|
7268
|
+
updateTelemetry(roboparkByName);
|
|
7269
|
+
refreshDrawer();
|
|
7270
|
+
}).catch(function(){});
|
|
7045
7271
|
}
|
|
7046
7272
|
function schedulerDevice(n){
|
|
7047
7273
|
// Deliberately does not call schedRow: telemetry can contain a legacy
|
|
@@ -7438,11 +7664,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7438
7664
|
var SHELL_OUTPUT_LIMIT = 64 * 1024;
|
|
7439
7665
|
|
|
7440
7666
|
function buildDrawer(n){
|
|
7441
|
-
var b=$('pk-body');
|
|
7442
|
-
if(n.kind==='robot' && !n.connected){
|
|
7443
|
-
var defaultName=(n.label||n.id||'robot').toString().toLowerCase().replace(/\\s+/g,'');
|
|
7444
|
-
var
|
|
7445
|
-
|
|
7667
|
+
var b=$('pk-body');
|
|
7668
|
+
if(n.kind==='robot' && !n.connected){
|
|
7669
|
+
var defaultName=(n.label||n.id||'robot').toString().toLowerCase().replace(/\\s+/g,'');
|
|
7670
|
+
var defaultCharacter=resolvePadCharacterPreset(n);
|
|
7671
|
+
var presetOptions='', voiceOptions='';
|
|
7672
|
+
for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; presetOptions += '<option value="'+esc(p.id||'')+'"'+((p.id===defaultCharacter)?' selected':'')+'>'+esc(p.name||p.id||'')+'</option>'; }
|
|
7446
7673
|
for(var v=0;v<voiceStacks.length;v++){ var s=voiceStacks[v]; voiceOptions += '<option value="'+esc(s.id||'')+'">'+esc(s.name||s.id||'')+'</option>'; }
|
|
7447
7674
|
b.innerHTML =
|
|
7448
7675
|
sect('🅿 park pad · '+esc(n.label),
|
|
@@ -7468,9 +7695,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7468
7695
|
+ '</div>'
|
|
7469
7696
|
+ '<div class="pk-sect" id="pk-setup-result" style="display:none;"><h4>Enrollment token (copy once)</h4><input id="pk-setup-token" readonly style="margin-bottom:0.4rem;"><button class="rp-btn" id="pk-setup-copy">Copy</button></div>');
|
|
7470
7697
|
var setupBtn=$('pk-setup-btn'); if(setupBtn) setupBtn.addEventListener('click', function(){ submitPadSetup(n); });
|
|
7471
|
-
var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
|
|
7698
|
+
var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
|
|
7472
7699
|
function refreshPadCliCommand(){
|
|
7473
|
-
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||
|
|
7700
|
+
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||'';
|
|
7474
7701
|
var el=$('pk-setup-clicmd'),label=$('pk-setup-clicmd-label');if(el)el.value=buildPadRobotCommand(nm,cid,'');if(label)label.textContent='or run this ON THE ROBOT (mints its own token)';
|
|
7475
7702
|
}
|
|
7476
7703
|
var padNameEl=$('pk-setup-name'); if(padNameEl) padNameEl.addEventListener('input', refreshPadCliCommand);
|
|
@@ -7520,7 +7747,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7520
7747
|
+ buildShellSection(schedId(n))
|
|
7521
7748
|
+ sect('🎭 character / voice','<div class="pk-note" id="pk-eff-note" style="margin-bottom:0.45rem;">loading effective config…</div><div class="rp-field"><label>Character preset</label><select class="rp-char" id="pk-eff-char" data-robot="'+esc(schedId(n))+'">'+effPresetOptions+'</select></div><div class="rp-field"><label>Voice stack</label><select class="rp-voice" id="pk-eff-voice" data-robot="'+esc(schedId(n))+'">'+effVoiceOptions+'</select></div><div class="rp-config-actions" style="margin-top:.45rem"><button class="rp-btn primary" id="pk-eff-save" data-robot="'+esc(schedId(n))+'">Apply to robot</button><button class="rp-btn" id="pk-edit-character">Edit character</button><button class="rp-btn" id="pk-edit-stack">Edit stack</button><button class="rp-btn" id="pk-new-stack">New stack</button></div>');
|
|
7522
7749
|
b.innerHTML += sect('Microphone setup suite','<div class="pk-mic-suite"><div class="pk-mic-health" id="pk-mic-health"><div class="pk-mic-check"><div class="k">Hardware lock</div><div class="v">USB mic hw:3,0</div></div><div class="pk-mic-check"><div class="k">PCM capture</div><div class="v">not tested</div></div><div class="pk-mic-check"><div class="k">Production track</div><div class="v">waiting</div></div></div><div class="rp-form" style="grid-template-columns:1fr 1fr;gap:.4rem"><div class="rp-field"><label>Ground-truth capture</label><select id="pk-mic-duration"><option value="2">2 seconds</option><option value="4" selected>4 seconds</option><option value="8">8 seconds</option></select></div><div class="rp-field"><label>Locked input</label><div class="pk-note" style="margin:.45rem 0 0">USB microphone (plughw:3,0)</div></div></div><div class="pk-ctl"><button class="rp-btn primary" id="pk-mic-groundtruth">Run raw mic capture</button><button class="rp-btn" id="pk-mic-roundtrip">Run speaker + mic test</button><button class="rp-btn" id="pk-mic-apply">Reapply production profile</button></div><div class="pk-note" id="pk-mic-test-state">Raw capture bypasses LiveKit and tests the exact ALSA path. Round-trip then verifies the physical speaker-to-mic path.</div><div id="pk-mic-test-result"></div></div>');
|
|
7523
|
-
b.innerHTML += sect('Motor registry +
|
|
7750
|
+
b.innerHTML += sect('Motor registry + sequences','<div class="pk-note">Safe workflow: discover wired relays, build a short sequence, save, then test. Unknown GPIO pins are never pulsed.</div><div class="rp-field"><label>Robot-local motor server</label><input id="pk-motor-url" value="http://127.0.0.1:8001" readonly></div><div class="pk-ctl"><button class="rp-btn primary" id="pk-motor-discover">Discover wired relays</button><button class="rp-btn" id="pk-motor-add">Add relay manually</button></div><div class="pk-motor-registry" id="pk-motor-registry"></div><div class="rp-form" style="grid-template-columns:1fr 1fr;margin-top:.7rem"><div class="rp-field"><label>Saved sequence</label><select id="pk-motor-sequence"></select></div><div class="rp-field"><label>Sequence name</label><input id="pk-motor-sequence-name" placeholder="Greeting motion"></div></div><div class="pk-ctl"><button class="rp-btn" id="pk-motor-duplicate">Duplicate sequence</button><button class="rp-btn danger" id="pk-motor-delete">Delete sequence</button></div><div class="pk-motor-canvas" id="pk-motor-canvas"></div><div class="pk-ctl"><button class="rp-btn" id="pk-motor-step-add">+ Add pulse</button><button class="rp-btn primary" id="pk-motor-save">Save</button><button class="rp-btn" id="pk-motor-run">Run sequence</button><button class="rp-btn danger" id="pk-motor-stop">STOP ALL</button><button class="rp-btn danger" id="pk-motor-test-all">Test every registered relay</button></div><label class="pk-prodtoggle"><input type="checkbox" id="pk-motor-greeting"><span>Run this sequence with the greeting</span></label><div class="pk-note">Safety limits: registered BCM 2-27 only, one relay at a time, maximum 60 seconds per sequence.</div><div class="pk-motor-status" id="pk-motor-status">Loading motor profile...</div>');
|
|
7524
7751
|
b.innerHTML += sect('Production camera + audio','<div class="pk-note" id="pk-device-search" style="margin-bottom:.55rem">Hardware locked for launch. Camera, microphone, and speaker cannot drift between restarts.</div><div class="rp-field"><label>Camera input</label><select id="pk-video-device" disabled></select></div><div class="rp-field"><label>Microphone input</label><select id="pk-audio-input" disabled></select></div><div class="rp-field"><label>Speaker output</label><select id="pk-audio-output" disabled></select></div><button class="rp-btn primary" id="pk-device-test-speaker" style="margin-top:0.35rem">Test USB speaker now</button><div class="rp-field"><label>Preset greeting phrases <span class="pk-note">one per line, cached per voice</span></label><textarea id="pk-greeting-phrases" rows="3" placeholder="Hi, I am Jaguar! / Welcome to RoboPark."></textarea></div><button class="rp-btn" id="pk-device-save" style="margin-top:0.35rem">Save greetings + enforce hardware</button><div class="pk-note" id="pk-device-note">waiting for robot heartbeat inventory</div>');
|
|
7525
7752
|
// Put live hardware selection beside the camera and audio tests,
|
|
7526
7753
|
// ahead of persona and service administration in the Park drawer.
|
|
@@ -7547,13 +7774,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7547
7774
|
var micRound=$('pk-mic-roundtrip');if(micRound)micRound.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}runMicRoundtrip(n);});
|
|
7548
7775
|
var micApply=$('pk-mic-apply');if(micApply)micApply.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}saveDeviceSelectors(n);});
|
|
7549
7776
|
var motorAdd=$('pk-motor-add');if(motorAdd)motorAdd.addEventListener('click',function(){motorEditorAddMotor();});
|
|
7777
|
+
var motorDiscover=$('pk-motor-discover');if(motorDiscover)motorDiscover.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}discoverMotorRelays(n,false);});
|
|
7550
7778
|
var motorStep=$('pk-motor-step-add');if(motorStep)motorStep.addEventListener('click',function(){motorEditorAddStep();});
|
|
7779
|
+
var motorDuplicate=$('pk-motor-duplicate');if(motorDuplicate)motorDuplicate.addEventListener('click',function(){duplicateMotorSequence();});
|
|
7780
|
+
var motorDelete=$('pk-motor-delete');if(motorDelete)motorDelete.addEventListener('click',function(){deleteMotorSequence();});
|
|
7551
7781
|
var motorSave=$('pk-motor-save');if(motorSave)motorSave.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}saveMotorProfile(n);});
|
|
7552
7782
|
var motorRun=$('pk-motor-run');if(motorRun)motorRun.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}runMotorSequence(n);});
|
|
7553
7783
|
var motorTestAll=$('pk-motor-test-all');if(motorTestAll)motorTestAll.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}testAllMotorRelays(n);});
|
|
7784
|
+
var motorStop=$('pk-motor-stop');if(motorStop)motorStop.addEventListener('click',function(){if(rpReadOnly){showOut('read-only node');return;}stopAllMotors(n);});
|
|
7554
7785
|
var motorSeq=$('pk-motor-sequence');if(motorSeq)motorSeq.addEventListener('change',function(){selectMotorSequence(motorSeq.value);});
|
|
7555
7786
|
var motorRegistry=$('pk-motor-registry');if(motorRegistry)motorRegistry.addEventListener('click',function(ev){var button=ev.target.closest('[data-motor-action]');if(!button)return;var index=Number(button.dataset.index);if(button.dataset.motorAction==='remove'){motorProfile.registry.splice(index,1);renderMotorEditor();}else if(button.dataset.motorAction==='test'){testMotorRelay(n,index);}});
|
|
7556
|
-
var motorCanvas=$('pk-motor-canvas');if(motorCanvas)motorCanvas.addEventListener('click',function(ev){var button=ev.target.closest('[data-step-remove]');if(button)
|
|
7787
|
+
var motorCanvas=$('pk-motor-canvas');if(motorCanvas)motorCanvas.addEventListener('click',function(ev){var button=ev.target.closest('[data-step-remove],[data-step-move]');if(!button)return;captureMotorEditor();var sequence=motorEditorSequence(),index=Number(button.dataset.stepRemove!=null?button.dataset.stepRemove:button.dataset.stepIndex);if(button.dataset.stepRemove!=null)sequence.steps.splice(index,1);else{var direction=button.dataset.stepMove==='up'?-1:1,target=index+direction;if(target>=0&&target<sequence.steps.length){var item=sequence.steps.splice(index,1)[0];sequence.steps.splice(target,0,item);}}renderMotorEditor();});
|
|
7557
7788
|
loadMotorProfile(n);
|
|
7558
7789
|
startCam(n); startMeters(n); loadEffectiveConfig(schedId(n)); refreshSupervisorPanel(n);
|
|
7559
7790
|
_bindShellSection(n);
|
|
@@ -7632,7 +7863,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7632
7863
|
var sequence=motorEditorSequence(),name=$('pk-motor-sequence-name'),greeting=$('pk-motor-greeting'),url=$('pk-motor-url');
|
|
7633
7864
|
if(name)name.value=sequence.name||'';if(greeting)greeting.checked=motorProfile.greeting_sequence_id===sequence.id;if(url)url.value=motorProfile.motor_server_url||'http://127.0.0.1:8001';
|
|
7634
7865
|
var motorOptions=(motorProfile.registry||[]).map(function(motor){return '<option value="'+esc(motor.id)+'">'+esc(motor.name+' | BCM '+motor.gpio)+'</option>';}).join('');
|
|
7635
|
-
canvas.innerHTML=(sequence.steps||[]).map(function(step,index){return '<div class="pk-motor-node"><div class="pk-motor-node-num">'+(index+1)+'</div><label>Relay<select data-step-motor>'+motorOptions.replace('value="'+esc(step.motor_id)+'"','value="'+esc(step.motor_id)+'" selected')+'</select></label><label>
|
|
7866
|
+
canvas.innerHTML=(sequence.steps||[]).map(function(step,index){return '<div class="pk-motor-node"><div class="pk-motor-node-num">Step '+(index+1)+'</div><label>Relay<select data-step-motor>'+motorOptions.replace('value="'+esc(step.motor_id)+'"','value="'+esc(step.motor_id)+'" selected')+'</select></label><label>Wait before pulse (ms)<input data-step-delay type="number" min="0" max="30000" step="50" value="'+esc(step.delay_ms||0)+'"></label><label>Pulse length (ms)<input data-step-duration type="number" min="50" max="10000" step="50" value="'+esc(step.duration_ms||300)+'"></label><div class="pk-ctl"><button class="rp-btn" data-step-move="up" data-step-index="'+index+'" '+(index===0?'disabled':'')+'>Left</button><button class="rp-btn" data-step-move="down" data-step-index="'+index+'" '+(index===(sequence.steps.length-1)?'disabled':'')+'>Right</button><button class="rp-btn danger" data-step-remove="'+index+'">Remove</button></div></div>';}).join('')||'<div class="pk-note">No pulses yet. Click + Add pulse.</div>';
|
|
7636
7867
|
}
|
|
7637
7868
|
function selectMotorSequence(id){
|
|
7638
7869
|
captureMotorEditor();
|
|
@@ -7641,6 +7872,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7641
7872
|
}
|
|
7642
7873
|
function motorEditorAddMotor(){captureMotorEditor();var number=motorProfile.registry.length+1;motorProfile.registry.push({id:'motor-'+number,name:'Motor '+number,gpio:Math.min(27,Math.max(2,number+4)),active_high:true,max_duration_ms:3000});renderMotorEditor();}
|
|
7643
7874
|
function motorEditorAddStep(){captureMotorEditor();if(!motorProfile.registry.length){motorStatus('Register a motor before adding a timed node','bad');return;}motorEditorSequence().steps.push({motor_id:motorProfile.registry[0].id,delay_ms:0,duration_ms:300});renderMotorEditor();}
|
|
7875
|
+
function duplicateMotorSequence(){captureMotorEditor();var source=motorEditorSequence(),copy=JSON.parse(JSON.stringify(source));copy.id=slugify(source.name+' copy')+'-'+Date.now();copy.name=(source.name||'Sequence')+' copy';motorProfile.sequences.push(copy);motorSequenceId=copy.id;renderMotorEditor();motorStatus('Sequence duplicated. Save to persist it.');}
|
|
7876
|
+
function deleteMotorSequence(){captureMotorEditor();if(motorProfile.sequences.length<=1){motorStatus('Keep at least one sequence. Clear its pulses instead.','bad');return;}if(!window.confirm('Delete this saved sequence?'))return;var index=motorProfile.sequences.findIndex(function(item){return item.id===motorSequenceId;});if(index<0)return;motorProfile.sequences.splice(index,1);if(motorProfile.greeting_sequence_id===motorSequenceId)motorProfile.greeting_sequence_id=null;motorSequenceId=motorProfile.sequences[0].id;renderMotorEditor();motorStatus('Sequence removed. Save to persist the change.');}
|
|
7877
|
+
function validateMotorProfile(requireRunnable){
|
|
7878
|
+
var ids={},gpios={},errors=[];
|
|
7879
|
+
(motorProfile.registry||[]).forEach(function(motor){if(!motor.id)errors.push('Every relay needs an id');if(ids[motor.id])errors.push('Duplicate relay id: '+motor.id);ids[motor.id]=true;if(!Number.isInteger(motor.gpio)||motor.gpio<2||motor.gpio>27)errors.push('GPIO for '+motor.name+' must be BCM 2-27');if(gpios[motor.gpio])errors.push('GPIO '+motor.gpio+' is assigned twice');gpios[motor.gpio]=true;});
|
|
7880
|
+
(motorProfile.sequences||[]).forEach(function(sequence){var total=0;(sequence.steps||[]).forEach(function(step){if(!ids[step.motor_id])errors.push(sequence.name+' uses an unknown relay');total+=Number(step.delay_ms||0)+Number(step.duration_ms||0);});if(total>60000)errors.push(sequence.name+' exceeds 60 seconds');});
|
|
7881
|
+
if(requireRunnable&&!motorEditorSequence().steps.length)errors.push('Add at least one pulse before running this sequence');
|
|
7882
|
+
if(errors.length){motorStatus(errors[0],'bad');throw new Error(errors[0]);}
|
|
7883
|
+
return true;
|
|
7884
|
+
}
|
|
7644
7885
|
function motorStatus(message,kind){['pk-motor-status','pk-camera-ops-motor-status'].forEach(function(id){var host=$(id);if(host){host.textContent=message;host.className='pk-motor-status '+(kind||'');}});}
|
|
7645
7886
|
function motorErrorMessage(error){var message=error&&error.message?error.message:String(error||'unknown error');return /(?:\s|^)404(?:\s|$)|not found/i.test(message)?'Motor API is not loaded on the running livekit-1 scheduler. Update and restart the scheduler, then retry.':message;}
|
|
7646
7887
|
function loadMotorProfile(n){
|
|
@@ -7649,13 +7890,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7649
7890
|
}
|
|
7650
7891
|
function saveMotorProfile(n){
|
|
7651
7892
|
captureMotorEditor();var robotId=schedId(n),url=motorProfile.motor_server_url;
|
|
7893
|
+
try{validateMotorProfile(false);}catch(error){return Promise.reject(error);}
|
|
7652
7894
|
var parsedUrl=null;try{parsedUrl=new URL(url);}catch(e){}
|
|
7653
7895
|
if(!parsedUrl||parsedUrl.protocol!=='http:'||(parsedUrl.hostname!=='127.0.0.1'&&parsedUrl.hostname!=='localhost')||!parsedUrl.port){motorStatus('Motor server must be robot-local: http://127.0.0.1:PORT','bad');return Promise.reject(new Error('motor server must be localhost'));}
|
|
7654
7896
|
motorStatus('Validating and saving motor profile...');
|
|
7655
7897
|
return rpAction('PATCH','/robopark/api/devices/'+encodeURIComponent(robotId),{motor_server_url:url}).then(function(){return rpAction('PUT','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-profile',{registry:motorProfile.registry,sequences:motorProfile.sequences,greeting_sequence_id:motorProfile.greeting_sequence_id});}).then(function(data){motorProfile.registry=data.registry||[];motorProfile.sequences=data.sequences||[];motorProfile.greeting_sequence_id=data.greeting_sequence_id||null;renderMotorEditor();motorStatus('Profile saved. Greeting motion will start on first TTS playback.','ok');return data;}).catch(function(error){motorStatus('Save failed: '+error.message,'bad');throw error;});
|
|
7656
7898
|
}
|
|
7657
7899
|
function runMotorSequence(n){
|
|
7658
|
-
var robotId=schedId(n),sequence=motorEditorSequence();motorStatus('Saving and queueing '+sequence.name+'...');
|
|
7900
|
+
var robotId=schedId(n),sequence=motorEditorSequence();try{captureMotorEditor();validateMotorProfile(true);}catch(error){return;}motorStatus('Saving and queueing '+sequence.name+'...');
|
|
7659
7901
|
saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motor-sequences/'+encodeURIComponent(sequence.id)+'/run',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-sequence');}).then(function(result){motorStatus(result.ok?'Sequence completed on robot.':'Sequence failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Run failed: '+error.message,'bad');});
|
|
7660
7902
|
}
|
|
7661
7903
|
function testMotorRelay(n,index){
|
|
@@ -7667,9 +7909,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7667
7909
|
return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/discover',{}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-discover');}).then(function(result){
|
|
7668
7910
|
if(!result.ok)throw new Error(result.error||'robot motor discovery failed');
|
|
7669
7911
|
if(!result.registry||!result.registry.length)throw new Error('Robot motor server has no registered relays. Register the wired BCM pins once; unknown pins will not be pulsed.');
|
|
7670
|
-
motorProfile.registry=result.registry;renderMotorEditor();motorStatus('Discovered '+result.registry.length+'
|
|
7912
|
+
motorProfile.registry=result.registry;motorProfile.sequences=result.sequences||motorProfile.sequences;motorProfile.greeting_sequence_id=result.greeting_sequence_id||null;renderMotorEditor();motorStatus('Discovered and saved '+result.registry.length+' wired relay(s).','ok');return result;
|
|
7671
7913
|
});
|
|
7672
7914
|
}
|
|
7915
|
+
function stopAllMotors(n){
|
|
7916
|
+
var robotId=schedId(n);motorStatus('Sending all-off command...');
|
|
7917
|
+
return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/stop',{}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','motor-stop');}).then(function(result){motorStatus(result.ok?'All registered relays are OFF.':'Stop failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');}).catch(function(error){motorStatus('Stop failed: '+motorErrorMessage(error),'bad');});
|
|
7918
|
+
}
|
|
7673
7919
|
function testAllMotorRelays(n){
|
|
7674
7920
|
captureMotorEditor();var ready=motorProfile.registry.length?Promise.resolve():discoverMotorRelays(n,true);
|
|
7675
7921
|
ready.then(function(){if(!window.confirm('Pulse all '+motorProfile.registry.length+' registered relays one at a time? Ensure the motor area is clear.'))return null;var robotId=schedId(n);motorStatus('Saving profile and testing '+motorProfile.registry.length+' registered GPIO relay(s), one at a time...');return saveMotorProfile(n).then(function(){return rpAction('POST','/robopark/api/robots/'+encodeURIComponent(robotId)+'/motors/test-all?duration_ms=300&pause_ms=200',{});}).then(function(q){return _shellWaitForResult(robotId,q.request_id,q.device_id||robotId,'','gpio-test-all');}).then(function(result){motorStatus(result.ok?'Full registered GPIO test completed: '+(result.completed_steps||[]).length+' relay(s).':'GPIO test failed: '+(result.error||'unknown error'),result.ok?'ok':'bad');});}).catch(function(error){motorStatus('GPIO test failed: '+motorErrorMessage(error),'bad');});
|
|
@@ -7877,9 +8123,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
7877
8123
|
var host=$('pk-camera-ops'),img=$('pk-camera-ops-img');if(host)host.classList.remove('open');document.body.classList.remove('camera-ops-open');if(cameraOpsRetryTimer){clearTimeout(cameraOpsRetryTimer);cameraOpsRetryTimer=null;}if(img){img.src='';img.dataset.live='false';}if(cameraOpsTimer){clearInterval(cameraOpsTimer);cameraOpsTimer=null;}cameraOpsNode=null;if(document.fullscreenElement&&document.exitFullscreen)document.exitFullscreen().catch(function(){});
|
|
7878
8124
|
}
|
|
7879
8125
|
(function bindCameraOperations(){
|
|
7880
|
-
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end'),motorDiscover=$('pk-camera-ops-motor-discover'),motorRun=$('pk-camera-ops-motor-run'),motorTest=$('pk-camera-ops-motor-test'),motorSelect=$('pk-camera-ops-motor-sequence');
|
|
8126
|
+
var close=$('pk-camera-ops-close'),refresh=$('pk-camera-ops-refresh'),nativeBtn=$('pk-camera-ops-native'),trigger=$('pk-camera-ops-trigger'),prod=$('pk-camera-ops-production'),end=$('pk-camera-ops-end'),motorDiscover=$('pk-camera-ops-motor-discover'),motorRun=$('pk-camera-ops-motor-run'),motorStop=$('pk-camera-ops-motor-stop'),motorTest=$('pk-camera-ops-motor-test'),motorSelect=$('pk-camera-ops-motor-sequence');
|
|
7881
8127
|
if(close)close.addEventListener('click',closeCameraOperations);if(refresh)refresh.addEventListener('click',reloadCameraOperations);if(nativeBtn)nativeBtn.addEventListener('click',function(){var host=$('pk-camera-ops');if(host&&host.requestFullscreen)host.requestFullscreen().catch(function(e){showOut('fullscreen: '+e.message);});});if(trigger)trigger.addEventListener('click',function(){if(cameraOpsNode)triggerSession(schedId(cameraOpsNode));});if(prod)prod.addEventListener('click',function(){if(cameraOpsNode){var d=schedulerDevice(cameraOpsNode)||{};setProductionMode(cameraOpsNode,!d.production_mode);setTimeout(refreshCameraOperations,400);}});if(end)end.addEventListener('click',function(){if(cameraOpsNode)endSession(schedId(cameraOpsNode));});document.addEventListener('keydown',function(e){if(e.key==='Escape'&&cameraOpsNode)closeCameraOperations();});
|
|
7882
|
-
if(motorDiscover)motorDiscover.addEventListener('click',function(){if(cameraOpsNode)discoverMotorRelays(cameraOpsNode,false).catch(function(error){motorStatus('Discovery failed: '+error.message,'bad');});});if(motorRun)motorRun.addEventListener('click',function(){if(cameraOpsNode)runMotorSequence(cameraOpsNode);});if(motorTest)motorTest.addEventListener('click',function(){if(cameraOpsNode)testAllMotorRelays(cameraOpsNode);});if(motorSelect)motorSelect.addEventListener('change',function(){motorSequenceId=motorSelect.value;renderMotorEditor();refreshCameraOperations();});
|
|
8128
|
+
if(motorDiscover)motorDiscover.addEventListener('click',function(){if(cameraOpsNode)discoverMotorRelays(cameraOpsNode,false).catch(function(error){motorStatus('Discovery failed: '+error.message,'bad');});});if(motorRun)motorRun.addEventListener('click',function(){if(cameraOpsNode)runMotorSequence(cameraOpsNode);});if(motorStop)motorStop.addEventListener('click',function(){if(cameraOpsNode)stopAllMotors(cameraOpsNode);});if(motorTest)motorTest.addEventListener('click',function(){if(cameraOpsNode)testAllMotorRelays(cameraOpsNode);});if(motorSelect)motorSelect.addEventListener('change',function(){motorSequenceId=motorSelect.value;renderMotorEditor();refreshCameraOperations();});
|
|
7883
8129
|
})();
|
|
7884
8130
|
|
|
7885
8131
|
// ── cam: target a LiveKit track; fall back to a styled preview ──
|
|
@@ -8266,12 +8512,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
8266
8512
|
}
|
|
8267
8513
|
|
|
8268
8514
|
$('pk-close') && $('pk-close').addEventListener('click', closeDrawer);
|
|
8269
|
-
return { onStatus:onStatus, activate:activate, deactivate:deactivate, refreshDrawer:refreshDrawer, openDrawerForRobot:openDrawerForRobot, updateSlots:updateSlots, updateTelemetry:updateTelemetry };
|
|
8515
|
+
return { onStatus:onStatus, activate:activate, deactivate:deactivate, refreshDrawer:refreshDrawer, refreshTelemetry:pollRoboparkQuiet, openDrawerForRobot:openDrawerForRobot, updateSlots:updateSlots, updateTelemetry:updateTelemetry };
|
|
8270
8516
|
})();
|
|
8271
8517
|
|
|
8272
8518
|
poll();
|
|
8273
8519
|
setInterval(poll, 2000);
|
|
8274
|
-
setInterval(function(){
|
|
8520
|
+
setInterval(function(){
|
|
8521
|
+
if(activeTab==='robopark') pollRobopark();
|
|
8522
|
+
else if(activeTab==='park' && Park && Park.refreshTelemetry) Park.refreshTelemetry();
|
|
8523
|
+
}, 3000);
|
|
8275
8524
|
setInterval(loadParkSlots, 7000);
|
|
8276
8525
|
// Pre-load character presets and voice stacks on startup so the Park
|
|
8277
8526
|
// robot-detail drawer has populated dropdowns even if the user never visits the robopark tab.
|