infinicode 2.8.69 → 2.8.71
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.
|
@@ -132,8 +132,56 @@ section h2 .count{color:var(--muted);}
|
|
|
132
132
|
.tbadge{font-family:var(--mono);font-size:0.6rem;padding:0.08em 0.45em;border-radius:5px;border:1px solid var(--glass-border);color:var(--muted);}
|
|
133
133
|
.tbadge.ok{color:var(--ok);} .tbadge.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 40%,transparent);}
|
|
134
134
|
.tbadge.bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent);background:var(--bad-bg);}
|
|
135
|
-
.chip.alert{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent);background:var(--bad-bg);}
|
|
136
|
-
.chip.alert b{color:var(--bad);}
|
|
135
|
+
.chip.alert{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent);background:var(--bad-bg);}
|
|
136
|
+
.chip.alert b{color:var(--bad);}
|
|
137
|
+
|
|
138
|
+
/* fleet operations overview */
|
|
139
|
+
.fleet-ops{display:flex;flex-direction:column;gap:.8rem;margin-bottom:1rem;}
|
|
140
|
+
.fleet-hero{position:relative;overflow:hidden;display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;padding:1.05rem 1.15rem;border:1px solid var(--glass-border);border-radius:18px;background:radial-gradient(520px 170px at 5% 0%,rgba(246,215,123,.15),transparent 70%),linear-gradient(145deg,rgba(29,24,14,.8),rgba(7,6,4,.72));box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
|
|
141
|
+
.fleet-hero:after{content:"";position:absolute;width:180px;height:180px;right:9%;top:-120px;border:1px solid rgba(246,215,123,.13);border-radius:50%;box-shadow:0 0 0 30px rgba(246,215,123,.025),0 0 0 62px rgba(246,215,123,.015);}
|
|
142
|
+
.fleet-hero .eyebrow{font-family:var(--mono);font-size:.56rem;letter-spacing:.17em;text-transform:uppercase;color:var(--gold);}
|
|
143
|
+
.fleet-hero h1{font-family:var(--display);font-size:clamp(1.45rem,2.5vw,2.25rem);line-height:1.05;color:var(--gold-pale);font-weight:500;margin:.18rem 0;}
|
|
144
|
+
.fleet-hero p{font-size:.72rem;color:var(--muted);max-width:62ch;}
|
|
145
|
+
.fleet-hero-actions{display:flex;gap:.45rem;flex-wrap:wrap;justify-content:flex-end;position:relative;z-index:1;}
|
|
146
|
+
.fleet-kpis{display:grid;grid-template-columns:repeat(6,minmax(105px,1fr));gap:.55rem;}
|
|
147
|
+
.fleet-kpi{padding:.75rem .8rem;border:1px solid var(--glass-border);border-radius:14px;background:linear-gradient(150deg,rgba(34,28,16,.58),rgba(8,7,5,.64));box-shadow:inset 0 1px 0 var(--glass-hi);min-width:0;}
|
|
148
|
+
.fleet-kpi .label{font-family:var(--mono);font-size:.5rem;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
149
|
+
.fleet-kpi .value{font-family:var(--display);font-size:1.55rem;line-height:1.15;color:var(--gold-pale);font-variant-numeric:tabular-nums;}
|
|
150
|
+
.fleet-kpi .detail{font-family:var(--mono);font-size:.52rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
151
|
+
.fleet-kpi.ok .value{color:var(--ok)}.fleet-kpi.warn .value{color:var(--warn)}.fleet-kpi.bad .value{color:var(--bad)}
|
|
152
|
+
.fleet-insights{display:grid;grid-template-columns:1.05fr 1.35fr 1.25fr;gap:.65rem;}
|
|
153
|
+
.fleet-panel{min-width:0;border:1px solid var(--glass-border);border-radius:16px;background:rgba(12,10,7,.68);box-shadow:inset 0 1px 0 var(--glass-hi);padding:.8rem;}
|
|
154
|
+
.fleet-panel-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding-bottom:.55rem;margin-bottom:.55rem;border-bottom:1px solid var(--glass-border);}
|
|
155
|
+
.fleet-panel-head b{font-family:var(--display);font-size:.92rem;color:var(--gold-pale);font-weight:500}.fleet-panel-head span{font-family:var(--mono);font-size:.5rem;color:var(--muted)}
|
|
156
|
+
.fleet-readiness{display:grid;grid-template-columns:104px 1fr;gap:.75rem;align-items:center;}
|
|
157
|
+
.fleet-donut{width:100px;height:100px;border-radius:50%;position:relative;display:grid;place-items:center;background:conic-gradient(var(--ok) 0 var(--ready-angle,0deg),var(--warn) var(--ready-angle,0deg) var(--warn-angle,0deg),var(--bad) var(--warn-angle,0deg) 360deg);box-shadow:0 0 30px rgba(246,215,123,.08);}
|
|
158
|
+
.fleet-donut:after{content:"";position:absolute;inset:15px;border-radius:50%;background:#0c0a07;border:1px solid var(--glass-border)}.fleet-donut strong{position:relative;z-index:1;font-family:var(--display);font-size:1.15rem;color:var(--gold-pale)}
|
|
159
|
+
.fleet-legend{display:grid;gap:.32rem}.fleet-legend div{display:flex;align-items:center;justify-content:space-between;gap:.5rem;font-family:var(--mono);font-size:.56rem;color:var(--muted)}.fleet-legend i{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:.35rem}.fleet-legend b{color:var(--ink)}
|
|
160
|
+
.fleet-bars{display:grid;gap:.4rem;max-height:154px;overflow:auto}.fleet-bar-row{display:grid;grid-template-columns:minmax(58px,90px) 1fr 48px;gap:.45rem;align-items:center;font-family:var(--mono);font-size:.54rem;color:var(--muted)}.fleet-bar-row .nm{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink)}.fleet-bar-row .track{height:7px;border-radius:10px;background:rgba(255,255,255,.045);overflow:hidden}.fleet-bar-row .fill{height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--gold-deep),var(--gold-bright));box-shadow:0 0 9px rgba(246,215,123,.2)}.fleet-bar-row .val{text-align:right}
|
|
161
|
+
.fleet-attention{display:grid;gap:.36rem;max-height:154px;overflow:auto}.fleet-alert-row{display:grid;grid-template-columns:7px 1fr auto;gap:.48rem;align-items:start;padding:.36rem .42rem;border-radius:8px;background:rgba(255,255,255,.018);font-size:.61rem}.fleet-alert-row i{width:7px;height:7px;border-radius:50%;margin-top:.28rem;background:var(--warn)}.fleet-alert-row.bad i{background:var(--bad)}.fleet-alert-row.ok i{background:var(--ok)}.fleet-alert-row b{display:block;color:var(--ink);font-size:.62rem}.fleet-alert-row span{color:var(--muted);font-size:.56rem}.fleet-alert-row em{font-family:var(--mono);font-style:normal;font-size:.5rem;color:var(--muted);white-space:nowrap}
|
|
162
|
+
.fleet-release{grid-column:1/-1;display:flex;align-items:center;gap:.55rem;flex-wrap:wrap;padding:.55rem .7rem}.fleet-release .version-pill{font-family:var(--mono);font-size:.56rem;border:1px solid var(--glass-border);border-radius:100px;padding:.18rem .5rem;color:var(--muted)}.fleet-release .version-pill.current{color:var(--ok);border-color:rgba(111,224,166,.28)}
|
|
163
|
+
.node .fleet-card-actions{display:flex;gap:.35rem;flex-wrap:wrap;margin-top:.65rem;padding-top:.55rem;border-top:1px solid var(--glass-border)}
|
|
164
|
+
@media(max-width:1150px){.fleet-kpis{grid-template-columns:repeat(3,1fr)}.fleet-insights{grid-template-columns:1fr 1fr}.fleet-insights .fleet-panel:last-of-type{grid-column:1/-1}}
|
|
165
|
+
@media(max-width:720px){.fleet-hero{align-items:flex-start;flex-direction:column}.fleet-hero-actions{justify-content:flex-start}.fleet-kpis{grid-template-columns:repeat(2,1fr)}.fleet-insights{display:block}.fleet-panel{margin-bottom:.65rem}.fleet-readiness{grid-template-columns:90px 1fr}.fleet-donut{width:86px;height:86px}}
|
|
166
|
+
|
|
167
|
+
/* fullscreen RoboVision operations view */
|
|
168
|
+
.pk-camera-ops{position:fixed;inset:0;z-index:80;display:none;background:#030302;color:var(--ink);overflow:hidden;}
|
|
169
|
+
.pk-camera-ops.open{display:grid;grid-template-rows:64px 1fr;}
|
|
170
|
+
.pk-camera-ops-head{display:flex;align-items:center;gap:.75rem;padding:.65rem 1rem;border-bottom:1px solid var(--glass-border);background:rgba(12,10,7,.9);backdrop-filter:blur(22px);}
|
|
171
|
+
.pk-camera-ops-head .avatar{width:40px;height:40px;object-fit:contain;filter:drop-shadow(0 7px 14px rgba(0,0,0,.5))}.pk-camera-ops-head .title{font-family:var(--display);font-size:1.08rem;color:var(--gold-pale)}.pk-camera-ops-head .sub{font-family:var(--mono);font-size:.54rem;color:var(--muted)}
|
|
172
|
+
.pk-camera-ops-actions{margin-left:auto;display:flex;gap:.4rem;align-items:center;flex-wrap:wrap;justify-content:flex-end}
|
|
173
|
+
.pk-camera-ops-grid{display:grid;grid-template-columns:minmax(0,1fr) 320px;min-height:0;}
|
|
174
|
+
.pk-camera-stage{position:relative;min-width:0;min-height:0;background:radial-gradient(circle at 50% 50%,#17130b,#030302 72%);overflow:hidden;display:grid;place-items:center;}
|
|
175
|
+
.pk-camera-stage img{width:100%;height:100%;object-fit:contain;display:block}.pk-camera-stage .empty{position:absolute;border:0;font-family:var(--mono);font-size:.68rem;letter-spacing:.08em;text-transform:uppercase}
|
|
176
|
+
.pk-camera-stage:after{content:"";position:absolute;inset:18px;border:1px solid rgba(246,215,123,.13);pointer-events:none;clip-path:polygon(0 0,13% 0,13% 1px,1px 1px,1px 15%,0 15%,0 0,100% 0,100% 15%,calc(100% - 1px) 15%,calc(100% - 1px) 1px,87% 1px,87% 0,100% 0,100% 100%,87% 100%,87% calc(100% - 1px),calc(100% - 1px) calc(100% - 1px),calc(100% - 1px) 85%,100% 85%,100% 100%,0 100%,0 85%,1px 85%,1px calc(100% - 1px),13% calc(100% - 1px),13% 100%,0 100%)}
|
|
177
|
+
.pk-camera-rec{position:absolute;left:34px;top:31px;display:flex;align-items:center;gap:.45rem;padding:.28rem .48rem;border-radius:7px;background:rgba(0,0,0,.58);backdrop-filter:blur(8px);font-family:var(--mono);font-size:.57rem;color:#fff}.pk-camera-rec i{width:7px;height:7px;border-radius:50%;background:var(--bad);box-shadow:0 0 10px var(--bad);animation:pulse 1.4s infinite}
|
|
178
|
+
.pk-camera-clock{position:absolute;right:34px;top:31px;padding:.28rem .48rem;border-radius:7px;background:rgba(0,0,0,.58);font-family:var(--mono);font-size:.57rem;color:var(--gold-pale)}
|
|
179
|
+
.pk-camera-overlay{position:absolute;left:34px;bottom:31px;width:min(660px,calc(100% - 68px));display:grid;grid-template-columns:repeat(4,1fr);gap:.45rem}.pk-camera-overlay .metric{padding:.55rem .6rem;border:1px solid rgba(246,215,123,.18);border-radius:10px;background:rgba(5,4,3,.68);backdrop-filter:blur(12px)}.pk-camera-overlay b{display:block;font-family:var(--display);font-size:.98rem;color:var(--gold-pale)}.pk-camera-overlay span{font-family:var(--mono);font-size:.46rem;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
|
|
180
|
+
.pk-camera-side{min-height:0;overflow:auto;padding:.8rem;background:linear-gradient(180deg,rgba(20,16,9,.94),rgba(7,6,4,.98));border-left:1px solid var(--glass-border)}
|
|
181
|
+
.pk-camera-side .section{padding:.7rem;margin-bottom:.65rem;border:1px solid var(--glass-border);border-radius:13px;background:rgba(255,255,255,.018)}.pk-camera-side h3{font-family:var(--mono);font-size:.55rem;letter-spacing:.13em;text-transform:uppercase;color:var(--gold);margin-bottom:.5rem}.pk-camera-insight{display:grid;grid-template-columns:1fr auto;gap:.38rem .55rem;font-family:var(--mono);font-size:.56rem}.pk-camera-insight span{color:var(--muted)}.pk-camera-insight b{color:var(--ink);text-align:right;font-weight:600}.pk-camera-insight b.ok{color:var(--ok)}.pk-camera-insight b.bad{color:var(--bad)}
|
|
182
|
+
.pk-camera-pipeline{display:grid;gap:.34rem}.pk-camera-pipeline div{display:grid;grid-template-columns:8px 1fr auto;gap:.42rem;align-items:center;font-family:var(--mono);font-size:.53rem;color:var(--muted)}.pk-camera-pipeline i{width:7px;height:7px;border-radius:50%;background:var(--muted)}.pk-camera-pipeline i.ok{background:var(--ok);box-shadow:0 0 8px rgba(111,224,166,.45)}.pk-camera-pipeline i.warn{background:var(--warn)}
|
|
183
|
+
body.camera-ops-open{overflow:hidden}
|
|
184
|
+
@media(max-width:850px){.pk-camera-ops.open{grid-template-rows:auto 1fr}.pk-camera-ops-head{align-items:flex-start;flex-wrap:wrap}.pk-camera-ops-actions{width:100%;margin-left:0;justify-content:flex-start}.pk-camera-ops-grid{display:block;overflow:auto}.pk-camera-stage{height:58vh;min-height:340px}.pk-camera-side{border-left:0;border-top:1px solid var(--glass-border)}.pk-camera-overlay{grid-template-columns:repeat(2,1fr)}}
|
|
137
185
|
|
|
138
186
|
/* hardware meters */
|
|
139
187
|
.hw{border-radius:13px;padding:0.85rem;margin-top:1rem;
|
|
@@ -546,6 +594,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
546
594
|
.rp-call-service .ready{font-family:var(--mono);font-size:.48rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ok);}
|
|
547
595
|
.rp-call-device{display:grid;gap:.22rem;margin-top:.45rem}.rp-call-device:first-child{margin-top:0}.rp-call-device label{font-family:var(--mono);font-size:.51rem;text-transform:uppercase;letter-spacing:.1em;color:var(--muted)}
|
|
548
596
|
.rp-call-device select{width:100%;min-width:0;font-family:var(--mono);font-size:.62rem;background:#0c0a07;border:1px solid var(--glass-border);color:var(--ink);border-radius:9px;padding:.42rem .5rem;}
|
|
597
|
+
.rp-call-device-note{margin-top:.55rem;padding:.52rem .58rem;border:1px solid var(--glass-border);border-radius:10px;background:rgba(0,0,0,.22);font-family:var(--mono);font-size:.54rem;line-height:1.45;color:var(--muted)}
|
|
598
|
+
.rp-call-device-note.ok{color:var(--ok);border-color:rgba(87,224,156,.28);background:rgba(28,64,50,.28)}.rp-call-device-note.bad{color:var(--bad);border-color:rgba(255,126,110,.34);background:rgba(64,24,20,.34)}.rp-call-device-note.warn{color:var(--warn);border-color:rgba(242,193,78,.3);background:rgba(64,50,20,.3)}
|
|
599
|
+
.rp-call-device-actions{display:grid;grid-template-columns:1fr 1fr;gap:.4rem;margin-top:.55rem}.rp-call-device-actions .rp-btn{width:100%}
|
|
549
600
|
.rp-call-stage{display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:hidden;}
|
|
550
601
|
.rp-call-stage:before{content:"";position:absolute;width:420px;height:420px;border:1px solid rgba(246,215,123,.08);border-radius:50%;box-shadow:0 0 0 42px rgba(246,215,123,.018),0 0 0 92px rgba(246,215,123,.012);}
|
|
551
602
|
.rp-call-visual{position:relative;width:min(330px,80%);aspect-ratio:1;border-radius:50%;display:grid;place-items:center;background:radial-gradient(circle,rgba(246,215,123,.14),rgba(10,8,5,.58) 52%,rgba(0,0,0,.82) 72%);border:1px solid rgba(246,215,123,.24);box-shadow:0 0 70px rgba(201,150,46,.13),inset 0 0 48px rgba(246,215,123,.06);z-index:1;transition:box-shadow .3s,border-color .3s;}
|
|
@@ -606,9 +657,40 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
606
657
|
.rp-vp-id{font-size:0.62rem;color:var(--muted);margin-top:0.1rem;word-break:break-all;}
|
|
607
658
|
.rp-vp-desc{font-size:0.64rem;color:var(--muted);margin-top:0.2rem;line-height:1.4;}
|
|
608
659
|
.rp-voice-picker-foot{font-size:0.6rem;color:var(--muted);text-align:center;padding-top:0.2rem;border-top:1px solid var(--glass-border);}
|
|
609
|
-
.rp-voice-picker-foot .rp-muted{color:var(--muted);}
|
|
610
|
-
|
|
611
|
-
|
|
660
|
+
.rp-voice-picker-foot .rp-muted{color:var(--muted);}
|
|
661
|
+
|
|
662
|
+
/* Character and voice configuration workspace */
|
|
663
|
+
.rp-config-workspace{grid-column:1/-1;display:grid;gap:1rem;}
|
|
664
|
+
.rp-config-hero{display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;padding:1rem 1.1rem;border:1px solid var(--border-strong);border-radius:18px;background:radial-gradient(circle at 8% 0,rgba(246,215,123,.14),transparent 34%),linear-gradient(135deg,rgba(39,31,17,.82),rgba(8,7,5,.72));box-shadow:inset 0 1px 0 rgba(255,238,194,.1),0 18px 50px rgba(0,0,0,.28);}
|
|
665
|
+
.rp-config-hero h3{font-family:var(--display);font-size:1.25rem;color:var(--gold-pale);margin:0;}
|
|
666
|
+
.rp-config-hero p{max-width:72ch;margin:.25rem 0 0;color:var(--muted);font-size:.68rem;line-height:1.55;}
|
|
667
|
+
.rp-config-hero-actions,.rp-config-toolbar,.rp-config-actions{display:flex;align-items:center;gap:.45rem;flex-wrap:wrap;}
|
|
668
|
+
.rp-config-columns{display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start;}
|
|
669
|
+
.rp-config-library{min-width:0;padding:.9rem;border:1px solid var(--glass-border);border-radius:18px;background:linear-gradient(145deg,rgba(31,26,18,.62),rgba(9,8,6,.7));box-shadow:inset 0 1px 0 var(--glass-hi),0 14px 44px rgba(0,0,0,.24);}
|
|
670
|
+
.rp-config-library-head{display:flex;align-items:flex-start;gap:.7rem;margin-bottom:.7rem;}
|
|
671
|
+
.rp-config-library-head .icon{width:38px;height:38px;display:grid;place-items:center;border-radius:11px;background:rgba(246,215,123,.1);border:1px solid rgba(246,215,123,.22);color:var(--gold-bright);font-size:1rem;flex:none;}
|
|
672
|
+
.rp-config-library-head h4{font-family:var(--display);font-size:1rem;color:var(--gold-pale);margin:0;}
|
|
673
|
+
.rp-config-library-head p{font-size:.58rem;color:var(--muted);margin:.1rem 0 0;line-height:1.4;}
|
|
674
|
+
.rp-config-library-head .count{margin-left:auto;font-family:var(--mono);font-size:.56rem;color:var(--gold);padding:.25rem .5rem;border:1px solid var(--glass-border);border-radius:999px;}
|
|
675
|
+
.rp-config-toolbar{margin-bottom:.65rem;}.rp-config-toolbar input{flex:1;min-width:150px;}
|
|
676
|
+
.rp-config-cards{display:grid;gap:.55rem;max-height:560px;overflow:auto;padding-right:.2rem;scrollbar-color:var(--gold-deep) transparent;}
|
|
677
|
+
.rp-config-card{position:relative;padding:.72rem;border:1px solid var(--glass-border);border-radius:13px;background:rgba(255,255,255,.022);transition:border-color .15s,transform .15s,background .15s;cursor:pointer;overflow:hidden;}
|
|
678
|
+
.rp-config-card:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(var(--gold-pale),var(--gold-deep));opacity:.65;}
|
|
679
|
+
.rp-config-card:hover{transform:translateY(-1px);border-color:rgba(246,215,123,.38);background:rgba(246,215,123,.045);}
|
|
680
|
+
.rp-config-card-head{display:flex;align-items:center;gap:.55rem;min-width:0;}.rp-config-card-head img{width:46px;height:46px;object-fit:contain;filter:drop-shadow(0 8px 12px rgba(0,0,0,.45));flex:none;}
|
|
681
|
+
.rp-config-card-title{min-width:0;flex:1}.rp-config-card-title b{display:block;font-family:var(--display);font-size:.9rem;color:var(--gold-pale);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.rp-config-card-title small{display:block;font-family:var(--mono);font-size:.5rem;color:var(--muted);margin-top:.08rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
682
|
+
.rp-config-badges{display:flex;gap:.3rem;flex-wrap:wrap;margin-top:.55rem}.rp-config-badge{font-family:var(--mono);font-size:.48rem;letter-spacing:.04em;text-transform:uppercase;padding:.18rem .38rem;border-radius:999px;border:1px solid var(--glass-border);color:var(--muted);background:rgba(0,0,0,.18)}.rp-config-badge.ok{color:var(--ok);border-color:rgba(87,224,156,.28)}.rp-config-badge.gold{color:var(--gold-bright);border-color:rgba(246,215,123,.28)}
|
|
683
|
+
.rp-config-pipeline{display:grid;grid-template-columns:1fr auto 1fr auto 1fr;gap:.32rem;align-items:center;margin-top:.55rem;font-family:var(--mono);font-size:.53rem}.rp-config-pipeline span{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:.28rem .4rem;border-radius:7px;background:rgba(0,0,0,.24);border:1px solid var(--glass-border);color:var(--ink)}.rp-config-pipeline i{font-style:normal;color:var(--gold)}
|
|
684
|
+
.rp-config-summary{margin-top:.5rem;color:var(--muted);font-size:.57rem;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
|
685
|
+
.rp-config-card .rp-config-actions{margin-top:.6rem}.rp-config-card .rp-btn{font-size:.53rem;padding:.32rem .5rem;}
|
|
686
|
+
.rp-config-editor{display:grid;gap:.8rem}.rp-config-editor-intro{display:flex;gap:.75rem;padding:.72rem;border:1px solid rgba(246,215,123,.22);border-radius:13px;background:radial-gradient(circle at 0 0,rgba(246,215,123,.12),transparent 48%),rgba(0,0,0,.18)}.rp-config-editor-intro .mark{width:42px;height:42px;display:grid;place-items:center;border:1px solid rgba(246,215,123,.25);border-radius:12px;color:var(--gold-bright);background:rgba(246,215,123,.08);font-size:1rem;flex:none}.rp-config-editor-intro b{display:block;font-family:var(--display);font-size:.95rem;color:var(--gold-pale)}.rp-config-editor-intro span{display:block;color:var(--muted);font-size:.59rem;line-height:1.45;margin-top:.15rem}
|
|
687
|
+
.rp-config-section{padding:.75rem;border:1px solid var(--glass-border);border-radius:13px;background:rgba(255,255,255,.018)}.rp-config-section-head{display:flex;align-items:center;gap:.45rem;margin-bottom:.55rem;font-family:var(--mono);font-size:.58rem;text-transform:uppercase;letter-spacing:.09em;color:var(--gold-bright)}.rp-config-section-head span{margin-left:auto;color:var(--muted);font-size:.48rem;letter-spacing:.03em;text-transform:none}.rp-config-fields{display:grid;grid-template-columns:1fr 1fr;gap:.55rem}.rp-config-fields .wide{grid-column:1/-1}.rp-config-fields textarea{width:100%;resize:vertical;min-height:110px;font-family:var(--mono);font-size:.67rem;line-height:1.5}.rp-config-checks{display:flex;gap:.8rem;flex-wrap:wrap}.rp-config-checks label{display:flex;align-items:center;gap:.35rem;color:var(--ink);font-size:.62rem}.rp-config-checks input{width:auto;margin:0;accent-color:var(--gold-bright)}
|
|
688
|
+
.rp-config-editor-foot{position:sticky;bottom:-1rem;display:flex;justify-content:flex-end;gap:.45rem;padding:.7rem 0 .1rem;background:linear-gradient(transparent,var(--panel) 28%)}
|
|
689
|
+
.rp-modal.config-modal{width:min(980px,100%);max-height:90vh}.rp-modal.config-modal .rp-modal-body{padding:1rem 1.1rem 1.2rem;}
|
|
690
|
+
@media(max-width:900px){.rp-config-columns{grid-template-columns:1fr}.rp-config-hero{align-items:flex-start;flex-direction:column}.rp-config-cards{max-height:430px}}
|
|
691
|
+
@media(max-width:620px){.rp-config-fields{grid-template-columns:1fr}.rp-config-fields .wide{grid-column:auto}.rp-config-pipeline{grid-template-columns:1fr}.rp-config-pipeline i{display:none}.rp-config-hero-actions{width:100%}.rp-config-hero-actions .rp-btn{flex:1}}
|
|
692
|
+
|
|
693
|
+
.rp-fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:0.7rem;padding:0;}
|
|
612
694
|
.rp-fleet-card{position:relative;}
|
|
613
695
|
.rp-fleet-card .spark{margin-top:0.5rem;height:36px;}
|
|
614
696
|
.rp-fleet-card .rp-actions{margin-top:0.6rem;}
|
|
@@ -648,12 +730,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
648
730
|
<button class="tab" data-tab="robopark" id="tabbtn-robopark">Sessions & Telemetry</button>
|
|
649
731
|
</div>
|
|
650
732
|
|
|
651
|
-
<div class="pane active" id="pane-fleet">
|
|
652
|
-
<div class="wrap">
|
|
653
|
-
<section>
|
|
654
|
-
<
|
|
655
|
-
|
|
656
|
-
|
|
733
|
+
<div class="pane active" id="pane-fleet">
|
|
734
|
+
<div class="wrap">
|
|
735
|
+
<section>
|
|
736
|
+
<div class="fleet-ops">
|
|
737
|
+
<div class="fleet-hero">
|
|
738
|
+
<div><div class="eyebrow">Production command center</div><h1>Fleet intelligence</h1><p>Live readiness, workload, release compliance, hardware pressure, and operator actions across every RoboPark device.</p></div>
|
|
739
|
+
<div class="fleet-hero-actions"><button class="rp-btn primary" id="fleet-open-park">Open Park map</button><button class="rp-btn" id="fleet-open-sessions">Live sessions</button><button class="rp-btn" id="fleet-open-incidents">Incidents</button><button class="rp-btn" id="fleet-refresh">Refresh</button></div>
|
|
740
|
+
</div>
|
|
741
|
+
<div class="fleet-kpis" id="fleet-kpis"></div>
|
|
742
|
+
<div class="fleet-insights">
|
|
743
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Operational readiness</b><span id="fleet-readiness-meta">live</span></div><div id="fleet-readiness"></div></div>
|
|
744
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Robot workload</b><span>sessions / triggers</span></div><div class="fleet-bars" id="fleet-workload"></div></div>
|
|
745
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Needs attention</b><span id="fleet-attention-meta">0 issues</span></div><div class="fleet-attention" id="fleet-attention"></div></div>
|
|
746
|
+
<div class="fleet-panel fleet-release" id="fleet-release"></div>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
<h2>Fleet <span class="count" id="fleetcount"></span></h2>
|
|
750
|
+
<div class="fleet" id="fleet"></div>
|
|
751
|
+
<div class="hw" id="hw"></div>
|
|
657
752
|
</section>
|
|
658
753
|
<aside>
|
|
659
754
|
<h2>Live activity</h2>
|
|
@@ -678,16 +773,39 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
678
773
|
</aside>
|
|
679
774
|
<div class="pk-hint">drag to pan · scroll to zoom · <b>click a robot</b> for cam / mic / speaker</div>
|
|
680
775
|
</div>
|
|
681
|
-
<div class="pk-drawer" id="pk-drawer">
|
|
776
|
+
<div class="pk-drawer" id="pk-drawer">
|
|
682
777
|
<div class="pk-dh">
|
|
683
778
|
<div class="pk-av" id="pk-av">🤖</div>
|
|
684
779
|
<div><div class="nm" id="pk-name">robot</div><div class="sub" id="pk-sub">satellite</div></div>
|
|
685
780
|
<span class="pk-pill idle" id="pk-state" style="margin-left:0.4rem">idle</span>
|
|
686
781
|
<button class="x" id="pk-close">✕</button>
|
|
687
782
|
</div>
|
|
688
|
-
<div class="pk-dbody" id="pk-body"></div>
|
|
689
|
-
</div>
|
|
690
|
-
</div>
|
|
783
|
+
<div class="pk-dbody" id="pk-body"></div>
|
|
784
|
+
</div>
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
<div class="pk-camera-ops" id="pk-camera-ops" role="dialog" aria-modal="true" aria-label="Fullscreen robot camera operations view">
|
|
788
|
+
<div class="pk-camera-ops-head">
|
|
789
|
+
<img class="avatar" id="pk-camera-ops-avatar" alt="">
|
|
790
|
+
<div><div class="title" id="pk-camera-ops-name">Robot camera</div><div class="sub" id="pk-camera-ops-sub">RoboVision mesh relay</div></div>
|
|
791
|
+
<div class="pk-camera-ops-actions"><button class="rp-btn primary" id="pk-camera-ops-trigger">Simulate motion</button><button class="rp-btn" id="pk-camera-ops-production">Production mode</button><button class="rp-btn danger" id="pk-camera-ops-end">End session</button><button class="rp-btn" id="pk-camera-ops-refresh">Refresh feed</button><button class="rp-btn" id="pk-camera-ops-native">Browser fullscreen</button><button class="rp-btn" id="pk-camera-ops-close">Close</button></div>
|
|
792
|
+
</div>
|
|
793
|
+
<div class="pk-camera-ops-grid">
|
|
794
|
+
<div class="pk-camera-stage" id="pk-camera-stage">
|
|
795
|
+
<img id="pk-camera-ops-img" alt="Live robot camera feed">
|
|
796
|
+
<div class="empty" id="pk-camera-ops-empty">opening RoboVision camera...</div>
|
|
797
|
+
<div class="pk-camera-rec"><i></i><span id="pk-camera-ops-rec">REC / CONNECTING</span></div>
|
|
798
|
+
<div class="pk-camera-clock" id="pk-camera-ops-clock">--:--:--</div>
|
|
799
|
+
<div class="pk-camera-overlay" id="pk-camera-ops-overlay"></div>
|
|
800
|
+
</div>
|
|
801
|
+
<aside class="pk-camera-side">
|
|
802
|
+
<div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
|
|
803
|
+
<div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
|
|
804
|
+
<div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
|
|
805
|
+
<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>
|
|
806
|
+
</aside>
|
|
807
|
+
</div>
|
|
808
|
+
</div>
|
|
691
809
|
|
|
692
810
|
<div class="pane" id="pane-robopark">
|
|
693
811
|
<div class="rp-head">
|
|
@@ -709,9 +827,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
709
827
|
<button class="rp-subtab" data-sub="live" id="rpsbtn-live" role="tab" aria-selected="false">Live Sessions <span class="badge" id="rps-live-badge">0</span></button>
|
|
710
828
|
<button class="rp-subtab" data-sub="voice" id="rpsbtn-voice" role="tab" aria-selected="false">Voice Call</button>
|
|
711
829
|
<button class="rp-subtab" data-sub="history" id="rpsbtn-history" role="tab" aria-selected="false">History</button>
|
|
712
|
-
<button class="rp-subtab" data-sub="audit" id="rpsbtn-audit" role="tab" aria-selected="false">Audit Log</button>
|
|
713
|
-
<button class="rp-subtab" data-sub="fleet" id="rpsbtn-fleet" role="tab" aria-selected="false">Fleet</button>
|
|
714
|
-
<button class="rp-subtab" data-sub="
|
|
830
|
+
<button class="rp-subtab" data-sub="audit" id="rpsbtn-audit" role="tab" aria-selected="false">Audit Log</button>
|
|
831
|
+
<button class="rp-subtab" data-sub="fleet" id="rpsbtn-fleet" role="tab" aria-selected="false">Fleet</button>
|
|
832
|
+
<button class="rp-subtab" data-sub="config" id="rpsbtn-config" role="tab" aria-selected="false">Characters & Voice</button>
|
|
833
|
+
<button class="rp-subtab" data-sub="setup" id="rpsbtn-setup" role="tab" aria-selected="false">Setup</button>
|
|
715
834
|
</div>
|
|
716
835
|
|
|
717
836
|
<!-- ═══ Overview ═══ -->
|
|
@@ -761,6 +880,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
761
880
|
<div class="rp-call-card">
|
|
762
881
|
<div class="rp-call-device"><label>Character preset</label><select id="rp-call-character-select"><option value="">select character...</option></select></div>
|
|
763
882
|
<div class="rp-call-device"><label>Voice stack</label><select id="rp-call-stack-select"><option value="">character default</option></select></div>
|
|
883
|
+
<div class="rp-config-actions" style="margin-top:.6rem"><button class="rp-btn" id="rp-call-edit-character">Edit character</button><button class="rp-btn" id="rp-call-new-character">New</button><button class="rp-btn" id="rp-call-edit-stack">Edit stack</button><button class="rp-btn" id="rp-call-new-stack">New stack</button></div>
|
|
764
884
|
</div>
|
|
765
885
|
<div class="rp-call-card">
|
|
766
886
|
<div class="rp-section-h" style="margin:0 0 .5rem">Voice pipeline <span class="meta">selected stack</span></div>
|
|
@@ -771,11 +891,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
771
891
|
</div>
|
|
772
892
|
</div>
|
|
773
893
|
<div class="rp-call-card">
|
|
774
|
-
<div class="rp-section-h" style="margin:0 0 .5rem">Browser devices <span class="meta" id="rp-call-device-status">permission
|
|
894
|
+
<div class="rp-section-h" style="margin:0 0 .5rem">Browser devices <span class="meta" id="rp-call-device-status">permission required</span></div>
|
|
775
895
|
<div class="rp-call-device"><label>Microphone</label><select id="rp-call-mic-device"><option value="">system default</option></select></div>
|
|
776
896
|
<div class="rp-call-device"><label>Camera</label><select id="rp-call-cam-device"><option value="">system default</option></select></div>
|
|
777
897
|
<div class="rp-call-device"><label>Speaker output</label><select id="rp-call-speaker-device"><option value="">system default</option></select></div>
|
|
778
|
-
<
|
|
898
|
+
<div class="rp-call-device-note" id="rp-call-device-note">Enable media access to reveal the actual microphone, camera, and speaker names available to this browser.</div>
|
|
899
|
+
<div class="rp-call-device-actions"><button class="rp-btn primary" id="rp-call-refresh-devices">Enable & scan</button><button class="rp-btn" id="rp-call-test-mic">Test microphone</button></div>
|
|
779
900
|
</div>
|
|
780
901
|
</aside>
|
|
781
902
|
<main class="rp-call-stage">
|
|
@@ -936,8 +1057,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
936
1057
|
</div>
|
|
937
1058
|
|
|
938
1059
|
<!-- ═══ Fleet ═══ -->
|
|
939
|
-
<div class="rp-sub" id="rp-sub-fleet">
|
|
940
|
-
<div class="
|
|
1060
|
+
<div class="rp-sub" id="rp-sub-fleet">
|
|
1061
|
+
<div class="fleet-ops" style="margin-bottom:1rem">
|
|
1062
|
+
<div class="fleet-kpis" id="rp-fleet-kpis"></div>
|
|
1063
|
+
<div class="fleet-insights">
|
|
1064
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Operational readiness</b><span>scheduler</span></div><div id="rp-fleet-readiness"></div></div>
|
|
1065
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Robot workload</b><span>sessions / triggers</span></div><div class="fleet-bars" id="rp-fleet-workload"></div></div>
|
|
1066
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Needs attention</b><span id="rp-fleet-attention-meta">0 issues</span></div><div class="fleet-attention" id="rp-fleet-attention"></div></div>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
<div class="rp-section-h">Robots <span class="meta" id="rp-fleet-meta">live + idle + offline</span></div>
|
|
941
1070
|
<div class="rp-fleet-grid" id="rp-grid"></div>
|
|
942
1071
|
</div>
|
|
943
1072
|
|
|
@@ -1000,79 +1129,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1000
1129
|
<div class="rp-autonote" id="rp-runtime-note">Docker controls are protected by the hub token. Logs are limited to the latest 100 lines.</div>
|
|
1001
1130
|
</div>
|
|
1002
1131
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
<div
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
<
|
|
1014
|
-
<
|
|
1015
|
-
<div class="rp-
|
|
1016
|
-
<div class="rp-
|
|
1017
|
-
<
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
<div class="rp-
|
|
1021
|
-
<div class="rp-
|
|
1022
|
-
<div class="rp-
|
|
1023
|
-
<
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
</div>
|
|
1030
|
-
<div class="rp-autonote" id="rpve-tts-voice-note" style="margin-top:0.35rem;"></div>
|
|
1031
|
-
</div>
|
|
1032
|
-
<div class="rp-field"><label>TTS language</label><input id="rpve-tts-language" placeholder="en"></div>
|
|
1033
|
-
<div class="rp-field"><label>Max turns</label><input id="rpve-max-turns" type="number" value="20"></div>
|
|
1034
|
-
<div class="rp-field"><label>Min endpointing delay (s)</label><input id="rpve-endpointing" type="number" step="0.1" value="0.7"></div>
|
|
1035
|
-
<div class="rp-field"><label><input type="checkbox" id="rpve-interruptions" checked style="width:auto;margin-right:0.35rem;">Allow interruptions</label></div>
|
|
1036
|
-
<div class="rp-field"><label><input type="checkbox" id="rpve-wake-enabled" style="width:auto;margin-right:0.35rem;">Wake word enabled</label></div>
|
|
1037
|
-
<div class="rp-field"><label>Wake word model</label><input id="rpve-wake-model" placeholder="models/hey_jarvis.onnx"></div>
|
|
1038
|
-
<div class="rp-field"><label>Wake threshold</label><input id="rpve-wake-threshold" type="number" step="0.05" value="0.5"></div>
|
|
1039
|
-
<div class="rp-field"><label>Wake timeout (s)</label><input id="rpve-wake-timeout" type="number" step="0.5" value="3.0"></div>
|
|
1040
|
-
<div style="grid-column:1/-1;display:flex;gap:0.4rem;">
|
|
1041
|
-
<button class="rp-btn" id="rpve-save">Save</button>
|
|
1042
|
-
<button class="rp-btn" id="rpve-cancel">Cancel</button>
|
|
1043
|
-
</div>
|
|
1044
|
-
</div>
|
|
1045
|
-
</div>
|
|
1046
|
-
<div>
|
|
1047
|
-
<h4 style="font-family:var(--mono);font-size:0.75rem;color:var(--gold-bright);margin:0.3rem 0 0.5rem;">Character presets</h4>
|
|
1048
|
-
<div id="rp-char-list" style="display:flex;flex-direction:column;gap:0.4rem;max-height:260px;overflow:auto;"></div>
|
|
1049
|
-
<div style="display:flex;gap:0.4rem;margin-top:0.55rem;">
|
|
1050
|
-
<input id="rp-newchar-name" placeholder="new preset name" style="flex:1;min-width:0;">
|
|
1051
|
-
<input id="rp-newchar-stack" list="rp-newchar-stack-list" placeholder="voice stack id" style="flex:1;min-width:0;">
|
|
1052
|
-
<datalist id="rp-newchar-stack-list"></datalist>
|
|
1053
|
-
<button class="rp-btn" id="rp-addchar">Add</button>
|
|
1054
|
-
</div>
|
|
1055
|
-
<div class="rp-form" id="rp-char-edit" style="display:none;grid-template-columns:1fr;gap:0.4rem;margin-top:0.6rem;padding:0.6rem;border-color:var(--border-strong);">
|
|
1056
|
-
<h4 style="margin:0 0 0.2rem;">Edit character: <span id="rpce-name-label"></span></h4>
|
|
1057
|
-
<div class="rp-field"><label>Name</label><input id="rpce-name"></div>
|
|
1058
|
-
<div class="rp-field"><label>Description</label><input id="rpce-description"></div>
|
|
1059
|
-
<div class="rp-field"><label>System prompt</label><textarea id="rpce-prompt" rows="4" style="font-family:var(--mono);font-size:0.72rem;width:100%;resize:vertical;"></textarea></div>
|
|
1060
|
-
<div class="rp-field"><label>Voice stack</label><select id="rpce-stack"></select></div>
|
|
1061
|
-
<div class="rp-field"><label>Motors (comma-separated)</label><input id="rpce-motors" placeholder="drive, wave"></div>
|
|
1062
|
-
<div style="display:flex;gap:0.4rem;">
|
|
1063
|
-
<div class="rp-field" style="flex:1"><label>Pad X (0-1)</label><input id="rpce-nx" type="number" step="0.01" min="0" max="1"></div>
|
|
1064
|
-
<div class="rp-field" style="flex:1"><label>Pad Y (0-1)</label><input id="rpce-ny" type="number" step="0.01" min="0" max="1"></div>
|
|
1065
|
-
</div>
|
|
1066
|
-
<div class="rp-field"><label>Avatar image path</label><input id="rpce-img" placeholder="/static/robopark/jaguar.png"></div>
|
|
1067
|
-
<div style="display:flex;gap:0.4rem;">
|
|
1068
|
-
<button class="rp-btn" id="rpce-save">Save</button>
|
|
1069
|
-
<button class="rp-btn" id="rpce-cancel">Cancel</button>
|
|
1070
|
-
</div>
|
|
1071
|
-
</div>
|
|
1072
|
-
</div>
|
|
1073
|
-
</div>
|
|
1074
|
-
</div>
|
|
1075
|
-
</div>
|
|
1132
|
+
</div>
|
|
1133
|
+
|
|
1134
|
+
<!-- Character + voice management -->
|
|
1135
|
+
<div class="rp-sub" id="rp-sub-config">
|
|
1136
|
+
<div class="rp-config-workspace" id="rp-templates">
|
|
1137
|
+
<div class="rp-config-hero">
|
|
1138
|
+
<div><h3>Character & Voice Studio</h3><p>Build reusable personas and production voice pipelines in one place. Changes are applied centrally and become available immediately in Voice Studio, Fleet, and every Park robot panel.</p></div>
|
|
1139
|
+
<div class="rp-config-hero-actions"><button class="rp-btn primary" id="rp-new-character">New character</button><button class="rp-btn" id="rp-new-voice-stack">New voice stack</button></div>
|
|
1140
|
+
</div>
|
|
1141
|
+
<div class="rp-config-columns">
|
|
1142
|
+
<section class="rp-config-library">
|
|
1143
|
+
<div class="rp-config-library-head"><span class="icon">VOX</span><div><h4>Voice stacks</h4><p>Speech recognition, language model, synthesis, turn-taking, and wake behavior.</p></div><span class="count" id="rp-voice-count">0 stacks</span></div>
|
|
1144
|
+
<div class="rp-config-toolbar"><input id="rp-voice-search" type="search" placeholder="Search provider, model, voice, or stack…"><button class="rp-btn primary" id="rp-addvoice">Create</button></div>
|
|
1145
|
+
<div class="rp-config-cards" id="rp-voice-list"></div>
|
|
1146
|
+
<input id="rp-newvoice-name" type="hidden"><div id="rp-voice-edit" style="display:none"></div>
|
|
1147
|
+
</section>
|
|
1148
|
+
<section class="rp-config-library">
|
|
1149
|
+
<div class="rp-config-library-head"><span class="icon">AI</span><div><h4>Character presets</h4><p>Persona, prompt, robot avatar, park placement, motors, and default voice stack.</p></div><span class="count" id="rp-char-count">0 presets</span></div>
|
|
1150
|
+
<div class="rp-config-toolbar"><input id="rp-char-search" type="search" placeholder="Search character, stack, motor, or prompt…"><button class="rp-btn primary" id="rp-addchar">Create</button></div>
|
|
1151
|
+
<div class="rp-config-cards" id="rp-char-list"></div>
|
|
1152
|
+
<input id="rp-newchar-name" type="hidden"><input id="rp-newchar-stack" type="hidden"><datalist id="rp-newchar-stack-list"></datalist><div id="rp-char-edit" style="display:none"></div>
|
|
1153
|
+
</section>
|
|
1154
|
+
</div>
|
|
1155
|
+
</div>
|
|
1156
|
+
</div>
|
|
1157
|
+
</div>
|
|
1076
1158
|
|
|
1077
1159
|
<!-- Generic modal: title + scrollable body, used for session + audit detail views -->
|
|
1078
1160
|
<div class="rp-modal-back" id="rp-modal-back" role="dialog" aria-modal="true">
|
|
@@ -1282,9 +1364,70 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1282
1364
|
}
|
|
1283
1365
|
return null;
|
|
1284
1366
|
}
|
|
1367
|
+
function renderFleetOperations(nodes, prefix, telemetryRows){
|
|
1368
|
+
prefix=prefix||'fleet'; nodes=Array.isArray(nodes)?nodes:[]; telemetryRows=Array.isArray(telemetryRows)?telemetryRows:null;
|
|
1369
|
+
var rows=[];
|
|
1370
|
+
function readinessOf(t,connected){ return String((t&&(t.readiness_code||t.status))||(!connected?'offline':'mesh_online')).toLowerCase(); }
|
|
1371
|
+
if(telemetryRows){
|
|
1372
|
+
for(var ti=0;ti<telemetryRows.length;ti++){
|
|
1373
|
+
var t=telemetryRows[ti], rn=robotName(t), rc=readinessOf(t,true), offline=rc==='offline'||rc==='robot_offline'||rc==='unreachable';
|
|
1374
|
+
rows.push({name:rn,connected:!offline,tele:t,hw:null,version:t.version||'',node:null,readiness:rc});
|
|
1375
|
+
}
|
|
1376
|
+
} else {
|
|
1377
|
+
for(var ni=0;ni<nodes.length;ni++){
|
|
1378
|
+
var n=nodes[ni], tele=rpTelemetryForNode(n), role=String(n.role||'').toLowerCase();
|
|
1379
|
+
if(role!=='satellite'&&role!=='robot'&&!tele) continue;
|
|
1380
|
+
rows.push({name:n.displayName||n.name||n.nodeId,connected:!!n.connected,tele:tele,hw:hwByNode[n.nodeId]||null,version:n.version||'',node:n,readiness:readinessOf(tele,!!n.connected)});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
var connected=0,ready=0,active=0,totalSessions=0,totalTriggers=0,latencies=[],drops=[],attention=[];
|
|
1384
|
+
for(var i=0;i<rows.length;i++){
|
|
1385
|
+
var r=rows[i], t=r.tele||{}, code=r.readiness;
|
|
1386
|
+
if(r.connected)connected++;
|
|
1387
|
+
if(code==='ready'||code==='in_session')ready++;
|
|
1388
|
+
if(code==='in_session'||t.active_session||t.in_session||t.session_active||t.current_session_id||t.status==='running'||t.status==='connecting')active++;
|
|
1389
|
+
totalSessions+=Number(t.sessions_total||0);totalTriggers+=Number(t.trigger_count||0);
|
|
1390
|
+
if(t.avg_latency_ms!=null)latencies.push(Number(t.avg_latency_ms));if(t.drop_rate!=null)drops.push(Number(t.drop_rate));
|
|
1391
|
+
var issues=[];
|
|
1392
|
+
if(!r.connected)issues.push('mesh or heartbeat offline');
|
|
1393
|
+
if(code!=='ready'&&code!=='in_session'&&code!=='mesh_online')issues.push(String(t.readiness_message||code).replace(/_/g,' '));
|
|
1394
|
+
if(t.drop_rate!=null&&Number(t.drop_rate)>=.05)issues.push(Math.round(Number(t.drop_rate)*100)+'% session drop rate');
|
|
1395
|
+
if(t.avg_latency_ms!=null&&Number(t.avg_latency_ms)>=3000)issues.push(Math.round(Number(t.avg_latency_ms))+'ms average response');
|
|
1396
|
+
if(r.hw&&r.hw.temperatureC!=null&&r.hw.temperatureC>=70)issues.push(Math.round(r.hw.temperatureC)+'C device temperature');
|
|
1397
|
+
if(r.hw&&r.hw.cpu&&r.hw.cpu.percent>=90)issues.push(Math.round(r.hw.cpu.percent)+'% CPU utilization');
|
|
1398
|
+
if(issues.length)attention.push({name:r.name,message:issues.join(' / '),bad:!r.connected||code==='stuck_session'||code==='join_timeout'||code==='robot_offline'});
|
|
1399
|
+
}
|
|
1400
|
+
var avgLatency=latencies.length?Math.round(latencies.reduce(function(a,b){return a+b;},0)/latencies.length):null;
|
|
1401
|
+
var avgDrop=drops.length?drops.reduce(function(a,b){return a+b;},0)/drops.length:null;
|
|
1402
|
+
var kpis=$(prefix+'-kpis');
|
|
1403
|
+
if(kpis){
|
|
1404
|
+
function k(label,value,detail,cls){return '<div class="fleet-kpi '+(cls||'')+'"><div class="label">'+esc(label)+'</div><div class="value">'+esc(value)+'</div><div class="detail">'+esc(detail)+'</div></div>';}
|
|
1405
|
+
kpis.innerHTML=k('Robots online',connected+'/'+rows.length,rows.length-connected+' unavailable',connected===rows.length?'ok':connected?'warn':'bad')+k('Ready',ready,rows.length?Math.round(ready/rows.length*100)+'% fleet readiness':'no robots',ready===rows.length?'ok':ready?'warn':'bad')+k('Live sessions',active,active?'operator oversight active':'fleet standing by',active?'ok':'')+k('Sessions',totalSessions,totalTriggers+' motion triggers','')+k('Response',avgLatency!=null?avgLatency+'ms':'--','fleet average',avgLatency==null?'':avgLatency>=3000?'bad':avgLatency>=1800?'warn':'ok')+k('Drop rate',avgDrop!=null?Math.round(avgDrop*100)+'%':'--','fleet average',avgDrop==null?'':avgDrop>=.15?'bad':avgDrop>=.05?'warn':'ok');
|
|
1406
|
+
}
|
|
1407
|
+
var read=$(prefix+'-readiness');
|
|
1408
|
+
if(read){
|
|
1409
|
+
var warn=Math.max(0,connected-ready),bad=Math.max(0,rows.length-connected),den=Math.max(1,rows.length),a1=ready/den*360,a2=(ready+warn)/den*360;
|
|
1410
|
+
read.innerHTML='<div class="fleet-readiness"><div class="fleet-donut" style="--ready-angle:'+a1+'deg;--warn-angle:'+a2+'deg"><strong>'+Math.round(ready/den*100)+'%</strong></div><div class="fleet-legend"><div><span><i style="background:var(--ok)"></i>ready / in session</span><b>'+ready+'</b></div><div><span><i style="background:var(--warn)"></i>degraded</span><b>'+warn+'</b></div><div><span><i style="background:var(--bad)"></i>offline</span><b>'+bad+'</b></div><div><span>active conversations</span><b>'+active+'</b></div></div></div>';
|
|
1411
|
+
}
|
|
1412
|
+
var workload=$(prefix+'-workload');
|
|
1413
|
+
if(workload){
|
|
1414
|
+
var maxWork=1;for(var wi=0;wi<rows.length;wi++){var wt=rows[wi].tele||{},wv=Number(wt.sessions_total||0)+Number(wt.trigger_count||0);if(wv>maxWork)maxWork=wv;}
|
|
1415
|
+
workload.innerHTML=rows.length?rows.slice().sort(function(a,b){var at=a.tele||{},bt=b.tele||{};return (Number(bt.sessions_total||0)+Number(bt.trigger_count||0))-(Number(at.sessions_total||0)+Number(at.trigger_count||0));}).map(function(x){var xt=x.tele||{},sessions=Number(xt.sessions_total||0),triggers=Number(xt.trigger_count||0),work=sessions+triggers;return '<div class="fleet-bar-row"><span class="nm">'+esc(x.name)+'</span><span class="track"><i class="fill" style="display:block;width:'+Math.max(3,work/maxWork*100)+'%"></i></span><span class="val">'+sessions+' / '+triggers+'</span></div>';}).join(''):'<div class="pk-note">No robot workload reported yet.</div>';
|
|
1416
|
+
}
|
|
1417
|
+
var attentionEl=$(prefix+'-attention'), attentionMeta=$(prefix+'-attention-meta');
|
|
1418
|
+
if(attentionMeta)attentionMeta.textContent=attention.length+' issue'+(attention.length===1?'':'s');
|
|
1419
|
+
if(attentionEl)attentionEl.innerHTML=attention.length?attention.map(function(a){return '<div class="fleet-alert-row '+(a.bad?'bad':'')+'"><i></i><div><b>'+esc(a.name)+'</b><span>'+esc(a.message)+'</span></div><em>inspect</em></div>';}).join(''):'<div class="fleet-alert-row ok"><i></i><div><b>All systems nominal</b><span>No active fleet readiness, latency, drop, temperature, or CPU alerts.</span></div><em>live</em></div>';
|
|
1420
|
+
var release=$(prefix+'-release');
|
|
1421
|
+
if(release){
|
|
1422
|
+
var versions={};for(var vi=0;vi<nodes.length;vi++){var version=nodes[vi].version||'unknown';versions[version]=(versions[version]||0)+1;}
|
|
1423
|
+
var versionKeys=Object.keys(versions).sort().reverse(),current=versionKeys[0]||'unknown';
|
|
1424
|
+
release.innerHTML='<div class="fleet-panel-head" style="border:0;margin:0;padding:0"><b>Release compliance</b><span>'+nodes.length+' mesh nodes</span></div>'+versionKeys.map(function(v){return '<span class="version-pill '+(v===current?'current':'')+'">v'+esc(v)+' / '+versions[v]+' node'+(versions[v]===1?'':'s')+'</span>';}).join('')+'<span class="pk-note" style="margin-left:auto">updated '+clock(Date.now())+'</span>';
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1285
1427
|
function renderFleet(nodes){
|
|
1286
|
-
var el = $('fleet');
|
|
1287
|
-
|
|
1428
|
+
var el = $('fleet');
|
|
1429
|
+
renderFleetOperations(nodes||[],'fleet');
|
|
1430
|
+
if(!nodes || !nodes.length){ el.innerHTML = '<div class="empty">No nodes reporting yet. Start peers with <span class="mono">infinicode serve</span> or <span class="mono">robopark setup</span>.</div>'; return; }
|
|
1288
1431
|
// connected first, then robots/satellites above hubs so the fleet reads robot-first
|
|
1289
1432
|
nodes.sort(function(a,b){ return (b.connected?1:0)-(a.connected?1:0); });
|
|
1290
1433
|
var accent = '#6FE0A6';
|
|
@@ -1332,18 +1475,21 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1332
1475
|
} else {
|
|
1333
1476
|
html += '<div class="stats"><span style="color:var(--muted)">offline'+(n.lastSeenAt?(' · last seen '+clock(n.lastSeenAt)):'')+'</span></div>';
|
|
1334
1477
|
}
|
|
1335
|
-
html += (capHtml?('<div class="caps">'+capHtml+'</div>'):'')
|
|
1478
|
+
html += (capHtml?('<div class="caps">'+capHtml+'</div>'):'')
|
|
1479
|
+
+ ((tele||String(n.role||'').toLowerCase()==='satellite')?('<div class="fleet-card-actions"><button class="rp-btn primary" data-fleet-action="camera" data-robot="'+esc(n.displayName)+'">Live camera</button><button class="rp-btn" data-fleet-action="sessions" data-robot="'+esc(n.displayName)+'">Sessions</button><button class="rp-btn" data-fleet-action="incidents" data-robot="'+esc(n.displayName)+'">Health</button>'+(tele&&(tele.character_id||tele.character_preset_id)?'<button class="rp-btn" data-config-edit-character="'+esc(tele.character_id||tele.character_preset_id)+'">Edit character</button>':'')+(tele&&tele.voice_stack_id?'<button class="rp-btn" data-config-edit-stack="'+esc(tele.voice_stack_id)+'">Edit voice</button>':'')+'</div>'):'')+'</div>';
|
|
1336
1480
|
}
|
|
1337
1481
|
el.innerHTML = html;
|
|
1338
1482
|
var cards = el.querySelectorAll('.node');
|
|
1339
1483
|
for(var j=0;j<cards.length;j++){
|
|
1340
1484
|
(function(card){
|
|
1341
|
-
function pick(){ selectNode(card.getAttribute('data-id'), card.getAttribute('data-name')); }
|
|
1485
|
+
function pick(ev){ if(ev&&ev.target&&ev.target.closest&&ev.target.closest('[data-fleet-action],[data-config-edit-stack],[data-config-edit-character]'))return; selectNode(card.getAttribute('data-id'), card.getAttribute('data-name')); }
|
|
1342
1486
|
card.addEventListener('click', pick);
|
|
1343
1487
|
card.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ ev.preventDefault(); pick(); } });
|
|
1344
|
-
})(cards[j]);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1488
|
+
})(cards[j]);
|
|
1489
|
+
}
|
|
1490
|
+
var fleetButtons=el.querySelectorAll('[data-fleet-action]');
|
|
1491
|
+
for(var fb=0;fb<fleetButtons.length;fb++)(function(btn){btn.addEventListener('click',function(ev){ev.stopPropagation();var act=btn.getAttribute('data-fleet-action'),name=btn.getAttribute('data-robot');if(act==='camera'){selectTab('park');Park.openDrawerForRobot(name);}else{selectTab('robopark');setRpSubTab(act==='sessions'?'live':'incidents');}});})(fleetButtons[fb]);
|
|
1492
|
+
}
|
|
1347
1493
|
|
|
1348
1494
|
function renderSummary(s){
|
|
1349
1495
|
var nodes = s.nodes||[];
|
|
@@ -1623,7 +1769,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1623
1769
|
btn.addEventListener('click', function(){ selectTab(btn.getAttribute('data-tab')); });
|
|
1624
1770
|
})(tabs[ti]);
|
|
1625
1771
|
}
|
|
1626
|
-
function selectTab(name){
|
|
1772
|
+
function selectTab(name){
|
|
1627
1773
|
activeTab = name;
|
|
1628
1774
|
for (var i=0;i<tabs.length;i++) tabs[i].classList.toggle('active', tabs[i].getAttribute('data-tab')===name);
|
|
1629
1775
|
$('pane-fleet').classList.toggle('active', name==='fleet');
|
|
@@ -1634,8 +1780,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1634
1780
|
pollRobopark();
|
|
1635
1781
|
initRpSubTabs();
|
|
1636
1782
|
}
|
|
1637
|
-
if (name==='park') Park.activate(); else Park.deactivate();
|
|
1638
|
-
}
|
|
1783
|
+
if (name==='park') Park.activate(); else Park.deactivate();
|
|
1784
|
+
}
|
|
1785
|
+
var fleetParkBtn=$('fleet-open-park'); if(fleetParkBtn) fleetParkBtn.addEventListener('click',function(){selectTab('park');});
|
|
1786
|
+
var fleetSessionsBtn=$('fleet-open-sessions'); if(fleetSessionsBtn) fleetSessionsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('live');});
|
|
1787
|
+
var fleetIncidentsBtn=$('fleet-open-incidents'); if(fleetIncidentsBtn) fleetIncidentsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('incidents');});
|
|
1788
|
+
var fleetRefreshBtn=$('fleet-refresh'); if(fleetRefreshBtn) fleetRefreshBtn.addEventListener('click',function(){poll();pollRobopark();});
|
|
1639
1789
|
var initHash = location.hash.replace('#','');
|
|
1640
1790
|
// Defer tab init until Park module is defined (same outer IIFE, later in source).
|
|
1641
1791
|
function initTab(){
|
|
@@ -2038,8 +2188,66 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2038
2188
|
}
|
|
2039
2189
|
$('srv-add').addEventListener('click', addServer);
|
|
2040
2190
|
$('rb-add').addEventListener('click', addRobot);
|
|
2041
|
-
$('rp-addvoice').addEventListener('click', function(){
|
|
2042
|
-
$('rp-addchar').addEventListener('click', function(){
|
|
2191
|
+
$('rp-addvoice').addEventListener('click', function(){ openVoiceStackManager(); });
|
|
2192
|
+
$('rp-addchar').addEventListener('click', function(){ openCharacterManager(); });
|
|
2193
|
+
var newVoiceStackBtn=$('rp-new-voice-stack'); if(newVoiceStackBtn) newVoiceStackBtn.addEventListener('click',function(){ openVoiceStackManager(); });
|
|
2194
|
+
var newCharacterBtn=$('rp-new-character'); if(newCharacterBtn) newCharacterBtn.addEventListener('click',function(){ openCharacterManager(); });
|
|
2195
|
+
|
|
2196
|
+
function configOption(value,label,current){ return '<option value="'+esc(value)+'"'+(String(value)===String(current)?' selected':'')+'>'+esc(label||value)+'</option>'; }
|
|
2197
|
+
function configNumber(id,fallback){ var el=$(id), n=el?Number(el.value):NaN; return Number.isFinite(n)?n:fallback; }
|
|
2198
|
+
function voiceStackDraft(){
|
|
2199
|
+
return {
|
|
2200
|
+
id:$('cfg-vs-id').value.trim(),name:$('cfg-vs-name').value.trim(),
|
|
2201
|
+
stt_provider:$('cfg-vs-stt-provider').value,stt_model:$('cfg-vs-stt-model').value.trim(),stt_language:$('cfg-vs-stt-language').value.trim()||'en',
|
|
2202
|
+
llm_provider:$('cfg-vs-llm-provider').value,llm_model:$('cfg-vs-llm-model').value.trim(),llm_api_key:$('cfg-vs-llm-key').value.trim()||null,
|
|
2203
|
+
temperature:configNumber('cfg-vs-temperature',0.7),num_ctx:configNumber('cfg-vs-num-ctx',8192),
|
|
2204
|
+
tts_provider:$('cfg-vs-tts-provider').value,tts_voice:$('cfg-vs-tts-voice').value.trim(),tts_language:$('cfg-vs-tts-language').value.trim()||'en',
|
|
2205
|
+
max_turns:configNumber('cfg-vs-max-turns',20),min_endpointing_delay:configNumber('cfg-vs-endpointing',0.7),
|
|
2206
|
+
allow_interruptions:$('cfg-vs-interruptions').checked,wake_word_enabled:$('cfg-vs-wake-enabled').checked,
|
|
2207
|
+
wake_word_model:$('cfg-vs-wake-model').value.trim()||null,wake_word_threshold:configNumber('cfg-vs-wake-threshold',0.5),wake_word_timeout:configNumber('cfg-vs-wake-timeout',3)
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
function openVoiceStackManager(id,draft){
|
|
2211
|
+
var existing=null;
|
|
2212
|
+
for(var i=0;i<voiceStacks.length;i++) if(String(voiceStacks[i].id)===String(id)){ existing=voiceStacks[i]; break; }
|
|
2213
|
+
var s=draft||existing||{id:'',name:'',stt_provider:'groq',stt_model:'whisper-large-v3-turbo',stt_language:'en',llm_provider:'ollama',llm_model:'gemma3:27b',temperature:0.7,num_ctx:8192,tts_provider:'elevenlabs',tts_voice:'',tts_language:'en',max_turns:20,min_endpointing_delay:0.7,allow_interruptions:true,wake_word_enabled:false,wake_word_threshold:0.5,wake_word_timeout:3};
|
|
2214
|
+
var editing=!!existing;
|
|
2215
|
+
var html='<div class="rp-config-editor">'
|
|
2216
|
+
+'<div class="rp-config-editor-intro"><span class="mark">VOX</span><div><b>'+(editing?'Edit production voice stack':'Create production voice stack')+'</b><span>A stack is the complete conversational audio pipeline. Robot assignments keep referencing this ID when settings change.</span></div></div>'
|
|
2217
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Identity <span>stable assignment key</span></div><div class="rp-config-fields"><div class="rp-field"><label>Stack ID</label><input id="cfg-vs-id" value="'+esc(s.id||'')+'" placeholder="english-fast"'+(editing?' disabled':'')+'></div><div class="rp-field"><label>Display name</label><input id="cfg-vs-name" value="'+esc(s.name||'')+'" placeholder="English fast"></div></div></div>'
|
|
2218
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Speech recognition <span>audio to text</span></div><div class="rp-config-fields"><div class="rp-field"><label>STT provider</label><select id="cfg-vs-stt-provider">'+configOption('groq','Groq',s.stt_provider)+configOption('speaches','Speaches / local',s.stt_provider)+configOption('vibevoice','VibeVoice',s.stt_provider)+'</select></div><div class="rp-field"><label>STT model</label><input id="cfg-vs-stt-model" value="'+esc(s.stt_model||'')+'"></div><div class="rp-field"><label>Language</label><input id="cfg-vs-stt-language" value="'+esc(s.stt_language||'en')+'" placeholder="en or auto"></div></div></div>'
|
|
2219
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Intelligence <span>prompt execution and response generation</span></div><div class="rp-config-fields"><div class="rp-field"><label>LLM provider</label><select id="cfg-vs-llm-provider">'+configOption('ollama','Ollama / Ollama Cloud',s.llm_provider)+configOption('groq','Groq',s.llm_provider)+configOption('gemini','Gemini',s.llm_provider)+configOption('moltbot','Moltbot',s.llm_provider)+'</select></div><div class="rp-field"><label>LLM model</label><input id="cfg-vs-llm-model" value="'+esc(s.llm_model||'')+'"></div><div class="rp-field"><label>Temperature</label><input id="cfg-vs-temperature" type="number" min="0" max="2" step="0.1" value="'+esc(s.temperature!=null?s.temperature:0.7)+'"></div><div class="rp-field"><label>Context size</label><input id="cfg-vs-num-ctx" type="number" min="512" step="512" value="'+esc(s.num_ctx!=null?s.num_ctx:8192)+'"></div><div class="rp-field wide"><label>Provider API key override</label><input id="cfg-vs-llm-key" type="password" value="'+esc(s.llm_api_key||'')+'" placeholder="Leave blank to use the ROBOVOICE agent secret"></div></div></div>'
|
|
2220
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice synthesis <span>text to robot speech</span></div><div class="rp-config-fields"><div class="rp-field"><label>TTS provider</label><select id="cfg-vs-tts-provider">'+configOption('elevenlabs','ElevenLabs',s.tts_provider)+configOption('kokoro','Kokoro',s.tts_provider)+configOption('piper','Piper',s.tts_provider)+'</select></div><div class="rp-field"><label>Language</label><input id="cfg-vs-tts-language" value="'+esc(s.tts_language||'en')+'"></div><div class="rp-field wide"><label>Voice ID</label><div class="rp-config-actions"><input id="cfg-vs-tts-voice" style="flex:1" value="'+esc(s.tts_voice||'')+'" placeholder="Choose or paste provider voice ID"><button type="button" class="rp-btn" id="cfg-vs-browse">Browse catalog</button></div></div></div></div>'
|
|
2221
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Conversation behavior <span>latency and turn-taking</span></div><div class="rp-config-fields"><div class="rp-field"><label>Max turns</label><input id="cfg-vs-max-turns" type="number" min="1" value="'+esc(s.max_turns!=null?s.max_turns:20)+'"></div><div class="rp-field"><label>Endpointing delay (seconds)</label><input id="cfg-vs-endpointing" type="number" min="0.1" step="0.1" value="'+esc(s.min_endpointing_delay!=null?s.min_endpointing_delay:0.7)+'"></div><div class="rp-config-checks wide"><label><input id="cfg-vs-interruptions" type="checkbox"'+(s.allow_interruptions!==false?' checked':'')+'>Allow natural interruptions</label><label><input id="cfg-vs-wake-enabled" type="checkbox"'+(s.wake_word_enabled?' checked':'')+'>Require wake word</label></div></div></div>'
|
|
2222
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Wake word <span>optional local activation gate</span></div><div class="rp-config-fields"><div class="rp-field wide"><label>Model path</label><input id="cfg-vs-wake-model" value="'+esc(s.wake_word_model||'')+'" placeholder="models/hey_jarvis.onnx"></div><div class="rp-field"><label>Threshold</label><input id="cfg-vs-wake-threshold" type="number" min="0" max="1" step="0.05" value="'+esc(s.wake_word_threshold!=null?s.wake_word_threshold:0.5)+'"></div><div class="rp-field"><label>Timeout (seconds)</label><input id="cfg-vs-wake-timeout" type="number" min="0" step="0.5" value="'+esc(s.wake_word_timeout!=null?s.wake_word_timeout:3)+'"></div></div></div>'
|
|
2223
|
+
+'<div class="rp-config-editor-foot"><button class="rp-btn" id="cfg-vs-cancel">Cancel</button><button class="rp-btn primary" id="cfg-vs-save">'+(editing?'Save changes':'Create stack')+'</button></div></div>';
|
|
2224
|
+
showModal(editing?'Edit voice stack · '+(s.name||s.id):'New voice stack',html); $('rp-modal').classList.add('config-modal');
|
|
2225
|
+
$('cfg-vs-cancel').addEventListener('click',closeModal);
|
|
2226
|
+
$('cfg-vs-browse').addEventListener('click',function(){ var next=voiceStackDraft(); openTtsVoicePicker(next.tts_provider,function(voiceId){ next.tts_voice=voiceId; openVoiceStackManager(editing?id:null,next); }); });
|
|
2227
|
+
$('cfg-vs-save').addEventListener('click',function(){
|
|
2228
|
+
var body=voiceStackDraft(); if(!body.name){ showOut('Voice stack name is required'); return; }
|
|
2229
|
+
if(!editing && !body.id) body.id=body.name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,48);
|
|
2230
|
+
var btn=$('cfg-vs-save'); btn.disabled=true; btn.textContent='Saving…';
|
|
2231
|
+
rpAction(editing?'PUT':'POST',editing?'/robopark/api/voice-stacks/'+encodeURIComponent(id):'/robopark/api/voice-stacks',body).then(function(){ closeModal(); showOut((editing?'Updated ':'Created ')+body.name); return loadVoiceStacks(); }).then(function(){ pollRobopark(); if(Park&&Park.refreshDrawer) Park.refreshDrawer(); }).catch(function(e){ btn.disabled=false; btn.textContent=editing?'Save changes':'Create stack'; showOut('voice stack: '+e.message); });
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
function characterDraft(){
|
|
2236
|
+
return {id:$('cfg-cp-id').value.trim(),name:$('cfg-cp-name').value.trim(),description:$('cfg-cp-description').value.trim()||null,system_prompt:$('cfg-cp-prompt').value.trim()||null,voice_stack_id:$('cfg-cp-stack').value||null,motors:$('cfg-cp-motors').value.split(',').map(function(x){return x.trim();}).filter(Boolean),nx:configNumber('cfg-cp-nx',0.5),ny:configNumber('cfg-cp-ny',0.5),img:$('cfg-cp-img').value.trim()||null};
|
|
2237
|
+
}
|
|
2238
|
+
function openCharacterManager(id,draft){
|
|
2239
|
+
var existing=null; for(var i=0;i<characterPresets.length;i++) if(String(characterPresets[i].id)===String(id)){ existing=characterPresets[i]; break; }
|
|
2240
|
+
var p=draft||existing||{id:'',name:'',description:'',system_prompt:'',voice_stack_id:'',motors:['drive'],nx:0.5,ny:0.5,img:''}, editing=!!existing, stackOptions='<option value="">No default stack</option>';
|
|
2241
|
+
for(var j=0;j<voiceStacks.length;j++) stackOptions+=configOption(voiceStacks[j].id,voiceStacks[j].name||voiceStacks[j].id,p.voice_stack_id);
|
|
2242
|
+
var html='<div class="rp-config-editor">'
|
|
2243
|
+
+'<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>'
|
|
2244
|
+
+'<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>'
|
|
2245
|
+
+'<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>'
|
|
2246
|
+
+'<div class="rp-config-section"><div class="rp-config-section-head">Voice & embodiment <span>pipeline and physical capabilities</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>Motor capabilities</label><input id="cfg-cp-motors" value="'+esc((p.motors||[]).join(', '))+'" placeholder="drive, wave, look"></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>'
|
|
2247
|
+
+'<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>';
|
|
2248
|
+
showModal(editing?'Edit character · '+(p.name||p.id):'New character preset',html); $('rp-modal').classList.add('config-modal'); $('cfg-cp-cancel').addEventListener('click',closeModal);
|
|
2249
|
+
$('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);}); });
|
|
2250
|
+
}
|
|
2043
2251
|
|
|
2044
2252
|
function addVoiceStackFromTemplate(){
|
|
2045
2253
|
var name=$('rp-newvoice-name').value.trim(); if(!name){ showOut('Voice stack name required'); return; }
|
|
@@ -2051,25 +2259,28 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2051
2259
|
var id=name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,32);
|
|
2052
2260
|
rpAction('POST','/robopark/api/character-presets',{id:id,name:name,voice_stack_id:voice_stack_id,motors:['drive']}).then(function(j){ showOut('character preset added: '+esc(j.id||id)); $('rp-newchar-name').value=''; $('rp-newchar-stack').value=''; loadCharacterPresets(); loadParkSlots(); }).catch(function(e){ showOut('add character preset: '+e.message); });
|
|
2053
2261
|
}
|
|
2054
|
-
function fillTemplateVoiceList(){
|
|
2055
|
-
var el=$('rp-voice-list'); if(!el) return;
|
|
2056
|
-
var html='';
|
|
2057
|
-
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<
|
|
2058
|
-
if(!
|
|
2059
|
-
el.innerHTML=html;
|
|
2060
|
-
var
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
var
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2262
|
+
function fillTemplateVoiceList(){
|
|
2263
|
+
var el=$('rp-voice-list'); if(!el) return;
|
|
2264
|
+
var query=String(($('rp-voice-search')&&$('rp-voice-search').value)||'').trim().toLowerCase(), html='',shown=0;
|
|
2265
|
+
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i], hay=[s.id,s.name,s.stt_provider,s.stt_model,s.llm_provider,s.llm_model,s.tts_provider,s.tts_voice].join(' ').toLowerCase(); if(query&&hay.indexOf(query)<0) continue; shown++; html += '<article class="rp-config-card" data-vsid="'+esc(s.id)+'"><div class="rp-config-card-head"><div class="rp-config-card-title"><b>'+esc(s.name||s.id)+'</b><small>'+esc(s.id)+' · '+esc(s.tts_voice||'voice not selected')+'</small></div><span class="rp-config-badge '+(s.allow_interruptions?'ok':'')+'">'+(s.allow_interruptions?'interruptible':'fixed turn')+'</span></div><div class="rp-config-pipeline"><span>'+esc(s.stt_provider||'STT')+' / '+esc(s.stt_model||'default')+'</span><i>→</i><span>'+esc(s.llm_provider||'LLM')+' / '+esc(s.llm_model||'default')+'</span><i>→</i><span>'+esc(s.tts_provider||'TTS')+'</span></div><div class="rp-config-badges"><span class="rp-config-badge gold">'+esc(s.stt_language||'auto')+'</span><span class="rp-config-badge">'+esc(String(s.min_endpointing_delay!=null?s.min_endpointing_delay:0.7))+'s endpoint</span><span class="rp-config-badge">'+esc(String(s.max_turns||20))+' turns</span>'+(s.wake_word_enabled?'<span class="rp-config-badge ok">wake word</span>':'')+'</div><div class="rp-config-actions"><button class="rp-btn primary" data-config-edit-stack="'+esc(s.id)+'">Edit stack</button><button class="rp-btn" data-config-use-stack="'+esc(s.id)+'">Open in Voice Studio</button></div></article>'; }
|
|
2266
|
+
if(!shown) html='<div class="rp-voice-picker-empty">'+(query?'No voice stacks match this search.':'No voice stacks yet. Create the first production pipeline.')+'</div>';
|
|
2267
|
+
el.innerHTML=html;
|
|
2268
|
+
var count=$('rp-voice-count'); if(count) count.textContent=voiceStacks.length+' stack'+(voiceStacks.length===1?'':'s');
|
|
2269
|
+
var dl=$('rp-newchar-stack-list'); if(dl){ dl.innerHTML=''; for(var j=0;j<voiceStacks.length;j++){ var o=document.createElement('option'); o.value=voiceStacks[j].id; dl.appendChild(o); } }
|
|
2270
|
+
}
|
|
2271
|
+
function fillTemplateCharacterList(){
|
|
2272
|
+
var el=$('rp-char-list'); if(!el) return;
|
|
2273
|
+
var query=String(($('rp-char-search')&&$('rp-char-search').value)||'').trim().toLowerCase(), html='',shown=0;
|
|
2274
|
+
for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i], hay=[p.id,p.name,p.description,p.system_prompt,p.voice_stack_id,(p.motors||[]).join(' ')].join(' ').toLowerCase(); if(query&&hay.indexOf(query)<0) continue; shown++; var avatar=p.img||rpAvatarFor(p.id||p.name); html += '<article class="rp-config-card" data-cpid="'+esc(p.id)+'"><div class="rp-config-card-head">'+(avatar?'<img src="'+esc(avatar+QS)+'" alt="'+esc(p.name||p.id)+'">':'')+'<div class="rp-config-card-title"><b>'+esc(p.name||p.id)+'</b><small>'+esc(p.id)+' · '+esc(p.description||'No operator description')+'</small></div><span class="rp-config-badge '+(p.system_prompt?'ok':'')+'">'+(p.system_prompt?'prompt ready':'prompt missing')+'</span></div><div class="rp-config-summary">'+esc(p.system_prompt||'Add a system prompt to define this character’s speaking style, constraints, and behavior.')+'</div><div class="rp-config-badges"><span class="rp-config-badge gold">'+esc(p.voice_stack_id||'default stack')+'</span>'+((p.motors||[]).map(function(m){return '<span class="rp-config-badge">'+esc(m)+'</span>';}).join(''))+'<span class="rp-config-badge">pad '+Math.round((p.nx||0)*100)+' / '+Math.round((p.ny||0)*100)+'</span></div><div class="rp-config-actions"><button class="rp-btn primary" data-config-edit-character="'+esc(p.id)+'">Edit character</button><button class="rp-btn" data-config-use-character="'+esc(p.id)+'">Open in Voice Studio</button>'+(p.voice_stack_id?'<button class="rp-btn" data-config-edit-stack="'+esc(p.voice_stack_id)+'">Edit voice</button>':'')+'</div></article>'; }
|
|
2275
|
+
if(!shown) html='<div class="rp-voice-picker-empty">'+(query?'No character presets match this search.':'No character presets yet. Create the first park persona.')+'</div>';
|
|
2276
|
+
el.innerHTML=html;
|
|
2277
|
+
var count=$('rp-char-count'); if(count) count.textContent=characterPresets.length+' preset'+(characterPresets.length===1?'':'s');
|
|
2278
|
+
}
|
|
2069
2279
|
|
|
2070
2280
|
// ── voice-stack edit panel ──
|
|
2071
|
-
var editingVoiceStackId=null;
|
|
2072
|
-
function openVoiceStackEdit(id){
|
|
2281
|
+
var editingVoiceStackId=null;
|
|
2282
|
+
function openVoiceStackEdit(id){
|
|
2283
|
+
openVoiceStackManager(id); return;
|
|
2073
2284
|
var s=null; for(var i=0;i<voiceStacks.length;i++){ if(voiceStacks[i].id===id){ s=voiceStacks[i]; break; } }
|
|
2074
2285
|
if(!s) return;
|
|
2075
2286
|
editingVoiceStackId=id;
|
|
@@ -2113,7 +2324,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2113
2324
|
};
|
|
2114
2325
|
rpAction('PUT','/robopark/api/voice-stacks/'+encodeURIComponent(editingVoiceStackId),body).then(function(){ showOut('voice stack saved'); $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; loadVoiceStacks(); }).catch(function(e){ showOut('save voice stack: '+e.message); });
|
|
2115
2326
|
}
|
|
2116
|
-
var vl=$('rp-voice-list'); if(vl) vl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-card'):null; if(!c) return; var id=c.getAttribute('data-vsid'); if(id)
|
|
2327
|
+
var vl=$('rp-voice-list'); if(vl) vl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-config-card'):null; if(!c) return; var id=c.getAttribute('data-vsid'); if(id&&!e.target.closest('[data-config-use-stack]')) openVoiceStackManager(id); });
|
|
2117
2328
|
var vsSave=$('rpve-save'); if(vsSave) vsSave.addEventListener('click', saveVoiceStackEdit);
|
|
2118
2329
|
var vsCancel=$('rpve-cancel'); if(vsCancel) vsCancel.addEventListener('click', function(){ $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; });
|
|
2119
2330
|
// TTS provider change: re-populate the voice <select> for the new provider.
|
|
@@ -2150,13 +2361,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2150
2361
|
});
|
|
2151
2362
|
|
|
2152
2363
|
// ── character-preset edit panel ──
|
|
2153
|
-
var editingCharPresetId=null;
|
|
2364
|
+
var editingCharPresetId=null;
|
|
2154
2365
|
function fillCharEditStackOptions(cur){
|
|
2155
2366
|
var sel=$('rpce-stack'); if(!sel) return;
|
|
2156
2367
|
sel.innerHTML='<option value="">— default —</option>';
|
|
2157
2368
|
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; var o=document.createElement('option'); o.value=s.id; o.textContent=s.name||s.id; if(s.id===cur) o.selected=true; sel.appendChild(o); }
|
|
2158
2369
|
}
|
|
2159
|
-
function openCharPresetEdit(id){
|
|
2370
|
+
function openCharPresetEdit(id){
|
|
2371
|
+
openCharacterManager(id); return;
|
|
2160
2372
|
var p=null; for(var i=0;i<characterPresets.length;i++){ if(characterPresets[i].id===id){ p=characterPresets[i]; break; } }
|
|
2161
2373
|
if(!p) return;
|
|
2162
2374
|
editingCharPresetId=id;
|
|
@@ -2181,9 +2393,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2181
2393
|
};
|
|
2182
2394
|
rpAction('PUT','/robopark/api/character-presets/'+encodeURIComponent(editingCharPresetId),body).then(function(){ showOut('character preset saved'); $('rp-char-edit').style.display='none'; editingCharPresetId=null; loadCharacterPresets(); loadParkSlots(); }).catch(function(e){ showOut('save character preset: '+e.message); });
|
|
2183
2395
|
}
|
|
2184
|
-
var cl=$('rp-char-list'); if(cl) cl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-card'):null; if(!c) return; var id=c.getAttribute('data-cpid'); if(id)
|
|
2396
|
+
var cl=$('rp-char-list'); if(cl) cl.addEventListener('click', function(e){ var c=e.target.closest?e.target.closest('.rp-config-card'):null; if(!c) return; var id=c.getAttribute('data-cpid'); if(id&&!e.target.closest('[data-config-use-character],[data-config-edit-stack]')) openCharacterManager(id); });
|
|
2185
2397
|
var cpSave=$('rpce-save'); if(cpSave) cpSave.addEventListener('click', saveCharPresetEdit);
|
|
2186
|
-
var cpCancel=$('rpce-cancel'); if(cpCancel) cpCancel.addEventListener('click', function(){ $('rp-char-edit').style.display='none'; editingCharPresetId=null; });
|
|
2398
|
+
var cpCancel=$('rpce-cancel'); if(cpCancel) cpCancel.addEventListener('click', function(){ $('rp-char-edit').style.display='none'; editingCharPresetId=null; });
|
|
2399
|
+
var voiceSearch=$('rp-voice-search'); if(voiceSearch) voiceSearch.addEventListener('input',fillTemplateVoiceList);
|
|
2400
|
+
var charSearch=$('rp-char-search'); if(charSearch) charSearch.addEventListener('input',fillTemplateCharacterList);
|
|
2401
|
+
document.addEventListener('click',function(e){
|
|
2402
|
+
var stackEdit=e.target.closest&&e.target.closest('[data-config-edit-stack]'); if(stackEdit){ e.preventDefault();e.stopPropagation();openVoiceStackManager(stackEdit.getAttribute('data-config-edit-stack'));return; }
|
|
2403
|
+
var charEdit=e.target.closest&&e.target.closest('[data-config-edit-character]'); if(charEdit){ e.preventDefault();e.stopPropagation();openCharacterManager(charEdit.getAttribute('data-config-edit-character'));return; }
|
|
2404
|
+
var stackUse=e.target.closest&&e.target.closest('[data-config-use-stack]'); if(stackUse){ e.preventDefault();e.stopPropagation();setRpSubTab('voice');setTimeout(function(){var s=$('rp-call-stack-select');if(s){s.value=stackUse.getAttribute('data-config-use-stack');renderVoiceServices();}},0);return; }
|
|
2405
|
+
var charUse=e.target.closest&&e.target.closest('[data-config-use-character]'); if(charUse){ e.preventDefault();e.stopPropagation();setRpSubTab('voice');setTimeout(function(){var s=$('rp-call-character-select');if(s){s.value=charUse.getAttribute('data-config-use-character');renderVoiceCharacter();renderVoiceServices();}},0); }
|
|
2406
|
+
});
|
|
2187
2407
|
|
|
2188
2408
|
function renderRobotCardSelect(name, current){
|
|
2189
2409
|
var sel=$('rp-char-'+name); if(!sel) return;
|
|
@@ -2213,8 +2433,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2213
2433
|
grid.innerHTML = '<div class="empty" style="grid-column:1/-1">'+esc(d.error||'no scheduler linked')+' — start the hub with <span class="mono">--scheduler-url http://<scheduler>:8080</span></div>';
|
|
2214
2434
|
return;
|
|
2215
2435
|
}
|
|
2216
|
-
var robots = robotsFrom(d);
|
|
2217
|
-
|
|
2436
|
+
var robots = robotsFrom(d);
|
|
2437
|
+
renderFleetOperations([], 'rp-fleet', robots);
|
|
2438
|
+
var tot = (d && d.totals) || {};
|
|
2218
2439
|
var totalSessions = tot.sessions_total!=null?tot.sessions_total:robots.reduce(function(a,x){return a+(x.sessions_total||0);},0);
|
|
2219
2440
|
var totalTrig = tot.trigger_count!=null?tot.trigger_count:robots.reduce(function(a,x){return a+(x.trigger_count||0);},0);
|
|
2220
2441
|
totals.innerHTML =
|
|
@@ -2242,8 +2463,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2242
2463
|
+ '<div class="rp-m"><span class="v '+dcls+'">'+drop+'</span><span class="l">drop rate</span></div>'
|
|
2243
2464
|
+ '</div><div class="rp-actions">'
|
|
2244
2465
|
+ '<button class="rp-btn" data-act="trigger" data-robot="'+idAttr+'"'+(rpReadOnly?' disabled':'')+'>Trigger motion</button>'
|
|
2245
|
-
+ '<button class="rp-btn" data-act="preview" data-robot="'+idAttr+'">Preview</button>'
|
|
2246
|
-
+ '<button class="rp-btn danger" data-act="end" data-robot="'+idAttr+'"'+((rpReadOnly||!active)?' disabled':'')+'>End session</button>'
|
|
2466
|
+
+ '<button class="rp-btn" data-act="preview" data-robot="'+idAttr+'">Preview</button>'
|
|
2467
|
+
+ '<button class="rp-btn danger" data-act="end" data-robot="'+idAttr+'"'+((rpReadOnly||!active)?' disabled':'')+'>End session</button>'
|
|
2468
|
+
+ ((x.character_id||x.character_preset_id)?'<button class="rp-btn" data-config-edit-character="'+esc(x.character_id||x.character_preset_id)+'">Edit character</button>':'')
|
|
2469
|
+
+ (x.voice_stack_id?'<button class="rp-btn" data-config-edit-stack="'+esc(x.voice_stack_id)+'">Edit voice</button>':'')
|
|
2247
2470
|
+ '</div>'
|
|
2248
2471
|
+ '<div class="pk-note '+readinessClass+'" style="margin-top:0.5rem;color:'+(readinessBad?'var(--bad)':'var(--ok)')+'">'+esc(readiness.replace(/_/g,' '))+' · '+esc(x.readiness_message||'no readiness detail')+'</div>'
|
|
2249
2472
|
+ (readiness==='stuck_session'||readiness==='stale_session'||readiness==='join_timeout'?'<div class="rp-actions"><button class="rp-btn danger" data-act="recover" data-robot="'+idAttr+'"'+(rpReadOnly?' disabled':'')+'>Recover robot</button></div>':'')
|
|
@@ -2338,7 +2561,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2338
2561
|
s.classList.toggle('active', s.id==='rp-sub-'+name);
|
|
2339
2562
|
}
|
|
2340
2563
|
// stop every sub-tab interval, then start only the active one
|
|
2341
|
-
var names = ['overview','incidents','live','voice','history','audit','fleet','setup'];
|
|
2564
|
+
var names = ['overview','incidents','live','voice','history','audit','fleet','config','setup'];
|
|
2342
2565
|
for(var k=0;k<names.length;k++){
|
|
2343
2566
|
var n=names[k];
|
|
2344
2567
|
if(rpIntervals[n]){ clearInterval(rpIntervals[n]); rpIntervals[n]=null; }
|
|
@@ -2365,7 +2588,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2365
2588
|
var h=String(location.hash||'').replace('#','');
|
|
2366
2589
|
var m = h.match(/^robopark\\/(.+)$/);
|
|
2367
2590
|
var initial = (m && m[1]) ? m[1] : 'overview';
|
|
2368
|
-
if(['overview','incidents','live','voice','history','audit','fleet','setup'].indexOf(initial)<0) initial='overview';
|
|
2591
|
+
if(['overview','incidents','live','voice','history','audit','fleet','config','setup'].indexOf(initial)<0) initial='overview';
|
|
2369
2592
|
setRpSubTab(initial);
|
|
2370
2593
|
}
|
|
2371
2594
|
|
|
@@ -2377,12 +2600,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2377
2600
|
$('rp-modal-body').innerHTML = bodyHtml || '';
|
|
2378
2601
|
back.classList.add('open');
|
|
2379
2602
|
}
|
|
2380
|
-
function closeModal(){
|
|
2381
|
-
var back = $('rp-modal-back');
|
|
2382
|
-
if(!back) return;
|
|
2383
|
-
back.classList.remove('open');
|
|
2384
|
-
|
|
2385
|
-
|
|
2603
|
+
function closeModal(){
|
|
2604
|
+
var back = $('rp-modal-back');
|
|
2605
|
+
if(!back) return;
|
|
2606
|
+
back.classList.remove('open');
|
|
2607
|
+
var modal=$('rp-modal'); if(modal) modal.classList.remove('config-modal');
|
|
2608
|
+
$('rp-modal-body').innerHTML = '';
|
|
2609
|
+
}
|
|
2386
2610
|
// wire the close handlers (once)
|
|
2387
2611
|
(function(){
|
|
2388
2612
|
var closeBtn = $('rp-modal-close');
|
|
@@ -2542,13 +2766,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2542
2766
|
// input. Bound once on the document so re-opening the modal doesn't
|
|
2543
2767
|
// stack listeners.
|
|
2544
2768
|
document.addEventListener('click', function(e){
|
|
2545
|
-
var btn = e.target.closest && e.target.closest('.rp-vp-item');
|
|
2546
|
-
if(btn){
|
|
2547
|
-
var vid = btn.getAttribute('data-voice-id');
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2769
|
+
var btn = e.target.closest && e.target.closest('.rp-vp-item');
|
|
2770
|
+
if(btn){
|
|
2771
|
+
var vid = btn.getAttribute('data-voice-id');
|
|
2772
|
+
closeModal();
|
|
2773
|
+
if(vid && ttsPickerState.onPick){ ttsPickerState.onPick(vid); }
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2552
2776
|
var r = e.target.closest && e.target.closest('#rp-voice-picker-refresh');
|
|
2553
2777
|
if(r){
|
|
2554
2778
|
// Re-fetch the catalog, ignore cache, then re-render.
|
|
@@ -2714,18 +2938,46 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2714
2938
|
wrap.appendChild(div); wrap.scrollTop=wrap.scrollHeight;
|
|
2715
2939
|
rpVoiceCall.messages++; if($('rp-call-message-count'))$('rp-call-message-count').textContent=rpVoiceCall.messages+' message'+(rpVoiceCall.messages===1?'':'s');
|
|
2716
2940
|
}
|
|
2941
|
+
var VOICE_DEVICE_PREFS_KEY='rp-voice-device-prefs-v1';
|
|
2942
|
+
function voiceDevicePrefs(){try{return JSON.parse(localStorage.getItem(VOICE_DEVICE_PREFS_KEY)||'{}')||{};}catch(_){return {};}}
|
|
2943
|
+
function saveVoiceDevicePrefs(){try{localStorage.setItem(VOICE_DEVICE_PREFS_KEY,JSON.stringify({audioinput:$('rp-call-mic-device').value||'',videoinput:$('rp-call-cam-device').value||'',audiooutput:$('rp-call-speaker-device').value||''}));}catch(_){}}
|
|
2944
|
+
function voiceDeviceMessage(text,state){var note=$('rp-call-device-note');if(note){note.textContent=text;note.className='rp-call-device-note '+(state||'');}}
|
|
2945
|
+
function voiceMediaSupportError(){
|
|
2946
|
+
if(!window.isSecureContext && location.hostname!=='localhost' && location.hostname!=='127.0.0.1') return 'Browser microphone access is blocked on insecure HTTP. Open this control center over HTTPS (or localhost), then scan again.';
|
|
2947
|
+
if(!navigator.mediaDevices) return 'This browser does not expose media devices. Use current Edge or Chrome over HTTPS.';
|
|
2948
|
+
if(!navigator.mediaDevices.getUserMedia) return 'Microphone capture is unavailable in this browser context.';
|
|
2949
|
+
return '';
|
|
2950
|
+
}
|
|
2717
2951
|
function fillDeviceSelect(id,devices,kind){
|
|
2718
|
-
var sel=$(id);if(!sel)return;var current=sel.value,rows=devices.filter(function(d){return d.kind===kind;});
|
|
2719
|
-
|
|
2952
|
+
var sel=$(id);if(!sel)return 0;var prefs=voiceDevicePrefs(),current=sel.value||prefs[kind]||'',rows=devices.filter(function(d){return d.kind===kind;});
|
|
2953
|
+
var fallback=kind==='audioinput'?'System default microphone':kind==='videoinput'?'System default camera':'System default speaker';
|
|
2954
|
+
sel.innerHTML='<option value="">'+fallback+'</option>'+rows.filter(function(d){return d.deviceId&&d.deviceId!=='default';}).map(function(d,i){var label=d.label||((kind==='audioinput'?'Microphone ':kind==='videoinput'?'Camera ':'Speaker ')+(i+1));return '<option value="'+esc(d.deviceId)+'">'+esc(label)+'</option>';}).join('');
|
|
2955
|
+
if(current){sel.value=current;if(sel.value!==current)sel.value='';} return rows.length;
|
|
2720
2956
|
}
|
|
2721
|
-
function refreshBrowserDevices(requestPermission){
|
|
2722
|
-
|
|
2957
|
+
function refreshBrowserDevices(requestPermission,includeCamera){
|
|
2958
|
+
var unsupported=voiceMediaSupportError();
|
|
2959
|
+
if(unsupported){$('rp-call-device-status').textContent='blocked';voiceDeviceMessage(unsupported,'bad');return requestPermission?Promise.reject(new Error(unsupported)):Promise.resolve([]);}
|
|
2960
|
+
$('rp-call-device-status').textContent=requestPermission?'requesting access…':'scanning…';
|
|
2723
2961
|
var permission=Promise.resolve();
|
|
2724
|
-
if(requestPermission)
|
|
2725
|
-
|
|
2962
|
+
if(requestPermission){
|
|
2963
|
+
var selected=$('rp-call-mic-device').value, audio=selected?{deviceId:{exact:selected}}:true;
|
|
2964
|
+
permission=navigator.mediaDevices.getUserMedia({audio:audio}).then(function(stream){stream.getTracks().forEach(function(t){t.stop();});if(includeCamera)return navigator.mediaDevices.getUserMedia({video:true}).then(function(cam){cam.getTracks().forEach(function(t){t.stop();});}).catch(function(){return null;});});
|
|
2965
|
+
}
|
|
2966
|
+
return permission.then(function(){return navigator.mediaDevices.enumerateDevices();}).then(function(devices){
|
|
2967
|
+
var mic=fillDeviceSelect('rp-call-mic-device',devices,'audioinput'),cam=fillDeviceSelect('rp-call-cam-device',devices,'videoinput'),speaker=fillDeviceSelect('rp-call-speaker-device',devices,'audiooutput');
|
|
2968
|
+
saveVoiceDevicePrefs();$('rp-call-device-status').textContent=mic+' mic · '+cam+' cam · '+speaker+' out';
|
|
2969
|
+
voiceDeviceMessage('Ready: '+mic+' microphone'+(mic===1?'':'s')+', '+cam+' camera'+(cam===1?'':'s')+', and '+speaker+' speaker output'+(speaker===1?'':'s')+' detected. Device choices are saved in this browser.','ok');return devices;
|
|
2970
|
+
}).catch(function(e){var message=e&&e.name==='NotAllowedError'?'Media permission was denied. Allow microphone access in the browser address bar and scan again.':(e&&e.message||'Media device scan failed');$('rp-call-device-status').textContent='permission failed';voiceDeviceMessage(message,'bad');setVoiceEvent('Browser media',message);throw new Error(message);});
|
|
2971
|
+
}
|
|
2972
|
+
function switchVoiceDevice(kind,id){saveVoiceDevicePrefs();if(!rpVoiceCall.room||!id)return Promise.resolve();return rpVoiceCall.room.switchActiveDevice(kind,id).catch(function(e){setVoiceEvent('Device switch failed',e.message);voiceDeviceMessage('Could not switch device: '+e.message,'bad');});}
|
|
2973
|
+
function applyVoiceOutputDevice(){saveVoiceDevicePrefs();var id=$('rp-call-speaker-device').value;if(!id)return;var els=$('rp-call-audio').querySelectorAll('audio'),supported=false;for(var i=0;i<els.length;i++)if(typeof els[i].setSinkId==='function'){supported=true;els[i].setSinkId(id).catch(function(e){voiceDeviceMessage('Speaker selection failed: '+e.message,'bad');});}if(!supported&&els.length)voiceDeviceMessage('This browser cannot route call audio to a selected speaker. System default output is active.','warn');}
|
|
2974
|
+
function testVoiceMicrophone(){
|
|
2975
|
+
var btn=$('rp-call-test-mic');btn.disabled=true;btn.textContent='Listening…';
|
|
2976
|
+
refreshBrowserDevices(true,false).then(function(){var id=$('rp-call-mic-device').value,constraints=id?{deviceId:{exact:id}}:true;return navigator.mediaDevices.getUserMedia({audio:constraints});}).then(function(stream){
|
|
2977
|
+
var C=window.AudioContext||window.webkitAudioContext,ctx=new C(),src=ctx.createMediaStreamSource(stream),an=ctx.createAnalyser(),data=new Uint8Array(128),peak=0;an.fftSize=256;src.connect(an);var started=Date.now();
|
|
2978
|
+
return new Promise(function(resolve){function sample(){an.getByteTimeDomainData(data);var sum=0;for(var i=0;i<data.length;i++){var v=(data[i]-128)/128;sum+=v*v;}peak=Math.max(peak,Math.sqrt(sum/data.length));voiceDeviceMessage('Microphone test live · level '+Math.round(peak*300)+'%','ok');if(Date.now()-started<2500)requestAnimationFrame(sample);else{stream.getTracks().forEach(function(t){t.stop();});ctx.close().catch(function(){});resolve(peak);}}sample();});
|
|
2979
|
+
}).then(function(peak){voiceDeviceMessage(peak>.01?'Microphone test passed · signal detected.':'Microphone opened but no signal was detected. Check the selected input and gain.',peak>.01?'ok':'warn');}).catch(function(e){voiceDeviceMessage(e.message||'Microphone test failed','bad');}).finally(function(){btn.disabled=false;btn.textContent='Test microphone';});
|
|
2726
2980
|
}
|
|
2727
|
-
function switchVoiceDevice(kind,id){if(!rpVoiceCall.room||!id)return Promise.resolve();return rpVoiceCall.room.switchActiveDevice(kind,id).catch(function(e){setVoiceEvent('Device switch failed',e.message);});}
|
|
2728
|
-
function applyVoiceOutputDevice(){var id=$('rp-call-speaker-device').value;if(!id)return;var els=$('rp-call-audio').querySelectorAll('audio');for(var i=0;i<els.length;i++)if(typeof els[i].setSinkId==='function')els[i].setSinkId(id).catch(function(){});}
|
|
2729
2981
|
function startVoiceVisualizer(track){
|
|
2730
2982
|
stopVoiceVisualizer();try{var C=window.AudioContext||window.webkitAudioContext;if(!C||!track||!track.mediaStreamTrack)return;var ctx=new C(),an=ctx.createAnalyser(),source=ctx.createMediaStreamSource(new MediaStream([track.mediaStreamTrack]));an.fftSize=128;source.connect(an);rpVoiceCall.audioContext=ctx;rpVoiceCall.analyser=an;var data=new Uint8Array(an.frequencyBinCount),bars=$('rp-call-bars').querySelectorAll('i');
|
|
2731
2983
|
function draw(){an.getByteFrequencyData(data);var sum=0;for(var x=0;x<data.length;x++)sum+=data[x];var avg=sum/data.length;for(var i=0;i<bars.length;i++){var idx=Math.floor(i*data.length/bars.length);bars[i].style.height=(12+Math.min(70,data[idx]*.28))+'px';}$('rp-call-visual').classList.toggle('speaking',avg>10);rpVoiceCall.audioRaf=requestAnimationFrame(draw);}draw();
|
|
@@ -2759,7 +3011,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2759
3011
|
if(!cid){setVoiceCallStatus('select a character first','');return;}
|
|
2760
3012
|
if(rpVoiceCall.room)return;
|
|
2761
3013
|
setVoiceCallStatus('requesting microphone...','busy');setVoiceAgentState('Opening channel','requesting browser media permission'); $('rp-call-start').disabled=true;
|
|
2762
|
-
refreshBrowserDevices(true).then(function(){return rpAction('POST','/robopark/api/sessions/voice-call',{character_preset_id:cid,voice_stack_id:sid||null,client_name:getOperatorName()||'RoboPark Operator'});})
|
|
3014
|
+
refreshBrowserDevices(true,false).then(function(devices){if(!devices.some(function(d){return d.kind==='audioinput';}))throw new Error('No browser microphone was detected');return rpAction('POST','/robopark/api/sessions/voice-call',{character_preset_id:cid,voice_stack_id:sid||null,client_name:getOperatorName()||'RoboPark Operator'});})
|
|
2763
3015
|
.then(function(call){
|
|
2764
3016
|
if(!call)throw new Error('scheduler returned no call');
|
|
2765
3017
|
rpVoiceCall.session=call.session_id; rpVoiceCall.eventToken=call.event_token; rpVoiceCall.seen={};rpVoiceCall.messages=0;rpVoiceCall.ending=false;
|
|
@@ -2779,16 +3031,19 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2779
3031
|
if(LK.RoomEvent.ParticipantAttributesChanged)room.on(LK.RoomEvent.ParticipantAttributesChanged,function(changed,p){updateAgentFromParticipant(p);});
|
|
2780
3032
|
room.on(LK.RoomEvent.ParticipantConnected,function(p){updateAgentFromParticipant(p);setVoiceEvent('Agent connected',p.name||p.identity||'ROBOVOICE worker');});
|
|
2781
3033
|
room.on(LK.RoomEvent.Disconnected,function(){ if(!rpVoiceCall.ending) finishVoiceCall('connection_lost'); });
|
|
2782
|
-
return room.connect(call.server_url,call.token).then(function(){var mic=$('rp-call-mic-device').value,opts=mic?{deviceId:mic}:undefined;return room.localParticipant.setMicrophoneEnabled(true,opts);});
|
|
3034
|
+
return room.connect(call.server_url,call.token).then(function(){var mic=$('rp-call-mic-device').value,opts=mic?{deviceId:mic}:undefined;setVoiceEvent('LiveKit connected','publishing '+(mic?'selected microphone':'system default microphone'));return room.localParticipant.setMicrophoneEnabled(true,opts);});
|
|
2783
3035
|
}).then(function(){
|
|
2784
3036
|
setVoiceCallStatus('secure channel connected','live');setVoiceAgentState('Listening','speak naturally');setVoiceEvent('Connected','waiting for ROBOVOICE agent');rpVoiceCall.startedAt=Date.now();rpVoiceCall.timer=setInterval(updateVoiceTimer,1000);updateVoiceTimer();
|
|
2785
3037
|
$('rp-call-mute').disabled=false;$('rp-call-camera').disabled=false;$('rp-call-screen').disabled=false;$('rp-call-end').disabled=false;$('rp-call-message').disabled=false;$('rp-call-send').disabled=false;
|
|
2786
3038
|
return fetch('/robopark/api/sessions/'+encodeURIComponent(rpVoiceCall.session)+'/joined'+QS,{method:'POST',headers:{'X-RoboPark-Session-Token':rpVoiceCall.eventToken}}).catch(function(){});
|
|
2787
3039
|
});
|
|
2788
3040
|
}).catch(function(e){
|
|
3041
|
+
if(window.console)console.error('RoboPark voice call failed',e);
|
|
2789
3042
|
setVoiceCallStatus('call failed','');setVoiceAgentState('Unable to connect',e&&e.message||String(e));setVoiceEvent('Call failed',e&&e.message||String(e)); $('rp-call-start').disabled=false;
|
|
2790
|
-
|
|
2791
|
-
if(
|
|
3043
|
+
var failedSession=rpVoiceCall.session,failedRoom=rpVoiceCall.room;rpVoiceCall.ending=true;rpVoiceCall.room=null;rpVoiceCall.session=null;rpVoiceCall.eventToken=null;
|
|
3044
|
+
if(failedSession)rpAction('POST','/robopark/api/sessions/'+encodeURIComponent(failedSession)+'/end?reason=connection_failed').catch(function(){});
|
|
3045
|
+
if(failedRoom){try{failedRoom.disconnect();}catch(_){}}
|
|
3046
|
+
setTimeout(function(){rpVoiceCall.ending=false;},300);
|
|
2792
3047
|
});
|
|
2793
3048
|
}
|
|
2794
3049
|
function toggleVoiceMute(){
|
|
@@ -2806,12 +3061,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2806
3061
|
var cs=$('rp-call-character-select'),ss=$('rp-call-stack-select'),start=$('rp-call-start'),mute=$('rp-call-mute'),end=$('rp-call-end');
|
|
2807
3062
|
if(cs)cs.addEventListener('change',paintVoiceCallCharacter); if(start)start.addEventListener('click',startVoiceCall);
|
|
2808
3063
|
if(ss)ss.addEventListener('change',paintVoiceCallStack);if(mute)mute.addEventListener('click',toggleVoiceMute);if(end)end.addEventListener('click',function(){finishVoiceCall('operator');});
|
|
2809
|
-
$('rp-call-camera').addEventListener('click',toggleVoiceCamera);$('rp-call-screen').addEventListener('click',toggleVoiceScreen);$('rp-call-refresh-devices').addEventListener('click',function(){refreshBrowserDevices(true);});
|
|
3064
|
+
$('rp-call-camera').addEventListener('click',toggleVoiceCamera);$('rp-call-screen').addEventListener('click',toggleVoiceScreen);$('rp-call-refresh-devices').addEventListener('click',function(){refreshBrowserDevices(true,true).catch(function(){});});$('rp-call-test-mic').addEventListener('click',testVoiceMicrophone);
|
|
3065
|
+
$('rp-call-edit-character').addEventListener('click',function(){var id=$('rp-call-character-select').value;if(id)openCharacterManager(id);else showOut('Choose a character to edit');});
|
|
3066
|
+
$('rp-call-new-character').addEventListener('click',function(){openCharacterManager();});
|
|
3067
|
+
$('rp-call-edit-stack').addEventListener('click',function(){var s=selectedVoiceStack();if(s)openVoiceStackManager(s.id);else showOut('Choose a voice stack to edit');});
|
|
3068
|
+
$('rp-call-new-stack').addEventListener('click',function(){openVoiceStackManager();});
|
|
2810
3069
|
$('rp-call-mic-device').addEventListener('change',function(){switchVoiceDevice('audioinput',this.value);});$('rp-call-cam-device').addEventListener('change',function(){switchVoiceDevice('videoinput',this.value);});$('rp-call-speaker-device').addEventListener('change',applyVoiceOutputDevice);
|
|
2811
3070
|
function toggleTranscript(){rpVoiceCall.transcriptCollapsed=!rpVoiceCall.transcriptCollapsed;$('rp-call-transcript').classList.toggle('collapsed',rpVoiceCall.transcriptCollapsed);$('rp-call-collapse').textContent=rpVoiceCall.transcriptCollapsed?'expand transcript':'collapse transcript';}
|
|
2812
3071
|
$('rp-call-chat-toggle').addEventListener('click',toggleTranscript);$('rp-call-collapse').addEventListener('click',toggleTranscript);
|
|
2813
3072
|
$('rp-call-compose').addEventListener('submit',function(e){e.preventDefault();var input=$('rp-call-message'),text=input.value.trim();if(!text)return;$('rp-call-send').disabled=true;sendVoiceMessage(text).then(function(){input.value='';}).catch(function(err){setVoiceEvent('Message failed',err.message);}).finally(function(){$('rp-call-send').disabled=!rpVoiceCall.room;});});
|
|
2814
3073
|
refreshBrowserDevices(false);
|
|
3074
|
+
if(navigator.mediaDevices&&navigator.mediaDevices.addEventListener)navigator.mediaDevices.addEventListener('devicechange',function(){refreshBrowserDevices(false).catch(function(){});});
|
|
2815
3075
|
})();
|
|
2816
3076
|
|
|
2817
3077
|
var rpHistoryFilter = { robot: '', endReason: '', range: '7d', search: '' };
|
|
@@ -6245,7 +6505,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6245
6505
|
+'<img id="pk-vision-img" style="width:100%;height:100%;object-fit:cover;position:absolute;inset:0;display:none;border-radius:inherit" alt="vision feed"/>'
|
|
6246
6506
|
+'<div class="hud"><div class="rec"><i></i>REC · '+esc(n.name)+'</div><div class="tr" id="pk-camres">— · —</div><div class="br" id="pk-camts">--:--:--</div></div></div>'
|
|
6247
6507
|
+'<div class="pk-note" id="pk-camnote">seeking live feed…</div>'
|
|
6248
|
-
+'<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn" id="pk-golive" data-robot="'+esc(n.name)+'">▶ Go live</button><span class="pk-note" id="pk-golivenote" style="margin:0"></span></div>'
|
|
6508
|
+
+'<div class="pk-ctl" style="margin-top:0.5rem"><button class="rp-btn" id="pk-golive" data-robot="'+esc(n.name)+'">▶ Go live</button><button class="rp-btn primary" id="pk-camera-fullscreen">Fullscreen operations view</button><span class="pk-note" id="pk-golivenote" style="margin:0"></span></div>'
|
|
6249
6509
|
+'<label class="pk-prodtoggle"><input type="checkbox" id="pk-prodmode"'+(devForPm.production_mode?' checked':'')+'><span>Production mode — continuous motion-triggered loop, camera always live</span></label>')
|
|
6250
6510
|
+ sect('Remote production test','<div id="pk-pipeline-summary" class="pk-pipeline-summary">Checking robot, camera, scheduler and LiveKit...</div>'
|
|
6251
6511
|
+ '<div class="pk-ctl" style="margin-top:0.55rem"><button class="rp-btn primary" id="pk-pipeline-start">Start full test</button><button class="rp-btn danger" id="pk-pipeline-stop">Stop conversation</button></div>'
|
|
@@ -6268,7 +6528,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6268
6528
|
+ sect('⚙ controls','<div class="pk-ctl"><button class="rp-btn danger" id="pk-end" data-robot="'+esc(n.name)+'">End session</button><button class="rp-btn" id="pk-follow">Follow run</button></div><div class="pk-note">end-session is live via the scheduler; cam/mic/speaker fill in as the robot plugin publishes them</div>')
|
|
6269
6529
|
+ sect('🩺 robot services','<div id="pk-svc-list" class="pk-svc-list"><div class="pk-note">waiting for supervisor report…</div></div><div class="pk-note" id="pk-svc-age" style="margin-top:0.35rem;"></div><div class="rp-field" style="margin-top:0.55rem;"><label>Quick run (on the robot, from packages/robopark/scheduler)</label><input id="pk-svc-clicmd" readonly value="python robot_supervisor.py"></div><button class="rp-btn" id="pk-svc-clicmd-copy" style="margin-top:0.35rem;">📋 Copy command</button>')
|
|
6270
6530
|
+ buildShellSection(schedId(n))
|
|
6271
|
-
+ 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><button class="rp-btn" id="pk-eff-save" data-robot="'+esc(schedId(n))+'"
|
|
6531
|
+
+ 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>');
|
|
6272
6532
|
b.innerHTML += sect('Camera + audio devices','<div class="rp-field"><label>Search devices</label><input id="pk-device-search" placeholder="camera, USB, microphone, HDMI..." autocomplete="off"></div><div class="rp-field"><label>Camera input</label><select id="pk-video-device"></select></div><div class="rp-field"><label>Microphone input</label><select id="pk-audio-input"></select></div><div class="rp-field"><label>Speaker / output inventory</label><select id="pk-audio-output"></select></div><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 device selection + greetings</button><div class="pk-note" id="pk-device-note">waiting for robot heartbeat inventory</div>');
|
|
6273
6533
|
// Put live hardware selection beside the camera and audio tests,
|
|
6274
6534
|
// ahead of persona and service administration in the Park drawer.
|
|
@@ -6290,6 +6550,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6290
6550
|
vl.addEventListener('click', function(){ tryVisionOverlay(n, true); if(!visionOn) showOut('RoboVision feed unavailable: robot has no reachable LAN/Tailscale address'); });
|
|
6291
6551
|
gl.after(vl);
|
|
6292
6552
|
}
|
|
6553
|
+
var fullCam=$('pk-camera-fullscreen'); if(fullCam) fullCam.addEventListener('click',function(){ openCameraOperations(n); });
|
|
6554
|
+
var editCharacter=$('pk-edit-character'); if(editCharacter) editCharacter.addEventListener('click',function(){var id=$('pk-eff-char').value;if(id)openCharacterManager(id);else showOut('Choose a character to edit');});
|
|
6555
|
+
var editStack=$('pk-edit-stack'); if(editStack) editStack.addEventListener('click',function(){var id=$('pk-eff-voice').value;if(id)openVoiceStackManager(id);else showOut('Choose a voice stack to edit');});
|
|
6556
|
+
var newStack=$('pk-new-stack'); if(newStack) newStack.addEventListener('click',function(){openVoiceStackManager();});
|
|
6293
6557
|
var pm=$('pk-prodmode'); if(pm) pm.addEventListener('change', function(){ if(rpReadOnly){ pm.checked=!pm.checked; showOut('read-only node'); return; } setProductionMode(n, pm.checked); });
|
|
6294
6558
|
var svcCopy=$('pk-svc-clicmd-copy'); if(svcCopy) svcCopy.addEventListener('click', function(){ var i=$('pk-svc-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied — run it on the robot itself'); });
|
|
6295
6559
|
var svcList=$('pk-svc-list'); if(svcList) svcList.addEventListener('click', function(ev){
|
|
@@ -6406,7 +6670,38 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6406
6670
|
function set(id,v){ var e=$(id); if(e) e.textContent=v; }
|
|
6407
6671
|
function setFill(id,pct){ var e=$(id); if(!e||pct==null) return; e.style.width=Math.max(2,Math.min(100,pct))+'%'; e.style.background=pct>=75?'var(--bad)':pct>=45?'var(--warn)':'var(--gold-bright)'; }
|
|
6408
6672
|
|
|
6409
|
-
|
|
6673
|
+
var cameraOpsNode=null,cameraOpsTimer=null;
|
|
6674
|
+
function cameraOpsFeedUrl(n){
|
|
6675
|
+
var ref=n&&(n.displayName||n.name||n.nodeId||n.id); if(!ref)return '';
|
|
6676
|
+
return '/fed/media/robots/'+encodeURIComponent(ref)+'/video_feed'+QS+(QS?'&':'?')+'_='+Date.now();
|
|
6677
|
+
}
|
|
6678
|
+
function refreshCameraOperations(){
|
|
6679
|
+
var n=cameraOpsNode;if(!n)return;var row=schedRow(n)||{},device=schedulerDevice(n)||{},hw=hwByNode[n.nodeId]||{};
|
|
6680
|
+
var readiness=String(row.readiness_code||(!n.connected?'offline':'mesh_online')),active=!!(row.active_session||row.in_session||row.session_active||row.current_session_id||row.status==='running'||row.status==='connecting');
|
|
6681
|
+
var img=$('pk-camera-ops-img'),cameraLive=!!(img&&img.dataset.live==='true'),clockEl=$('pk-camera-ops-clock');if(clockEl)clockEl.textContent=clock(Date.now());
|
|
6682
|
+
var rec=$('pk-camera-ops-rec');if(rec)rec.textContent=(cameraLive?'REC / LIVE':'REC / CONNECTING')+' / '+String(n.displayName||n.name||'robot').toUpperCase();
|
|
6683
|
+
var overlay=$('pk-camera-ops-overlay');if(overlay)overlay.innerHTML='<div class="metric"><b>'+esc(readiness.replace(/_/g,' '))+'</b><span>readiness</span></div><div class="metric"><b>'+esc(active?'in session':'standing by')+'</b><span>conversation</span></div><div class="metric"><b>'+(row.avg_latency_ms!=null?Math.round(row.avg_latency_ms)+'ms':'--')+'</b><span>average latency</span></div><div class="metric"><b>'+(row.drop_rate!=null?Math.round(row.drop_rate*100)+'%':'--')+'</b><span>session drops</span></div>';
|
|
6684
|
+
var insights=$('pk-camera-ops-insights');if(insights)insights.innerHTML='<span>Camera relay</span><b class="'+(cameraLive?'ok':'bad')+'">'+(cameraLive?'LIVE':'CONNECTING')+'</b><span>Mesh node</span><b class="'+(n.connected?'ok':'bad')+'">'+(n.connected?'connected':'offline')+'</b><span>Production mode</span><b class="'+(device.production_mode?'ok':'')+'">'+(device.production_mode?'armed':'disabled')+'</b><span>Readiness</span><b>'+esc(readiness.replace(/_/g,' '))+'</b><span>Heartbeat</span><b>'+(row.heartbeat_age_seconds!=null?Math.round(row.heartbeat_age_seconds)+'s ago':'--')+'</b><span>Current room</span><b>'+esc(String(row.current_session_id||row.active_session_id||'none').slice(0,24))+'</b><span>Triggers</span><b>'+esc(row.trigger_count!=null?row.trigger_count:0)+'</b><span>Sessions</span><b>'+esc(row.sessions_total!=null?row.sessions_total:0)+'</b>';
|
|
6685
|
+
var stages=[['Robot online',!!n.connected],['Camera streaming',cameraLive],['Motion detector armed',!!device.production_mode],['Scheduler session',active],['Voice pipeline',readiness==='in_session'||active]],pipe=$('pk-camera-ops-pipeline');if(pipe)pipe.innerHTML=stages.map(function(s){return '<div><i class="'+(s[1]?'ok':'warn')+'"></i><span>'+esc(s[0])+'</span><b>'+(s[1]?'ready':'waiting')+'</b></div>';}).join('');
|
|
6686
|
+
var inv=device.device_inventory||{},hardware=$('pk-camera-ops-hardware');if(hardware)hardware.innerHTML='<span>CPU</span><b>'+(hw.cpu&&hw.cpu.percent!=null?Math.round(hw.cpu.percent)+'%':'--')+'</b><span>Memory</span><b>'+(hw.memory&&hw.memory.percent!=null?Math.round(hw.memory.percent)+'%':'--')+'</b><span>Temperature</span><b>'+(hw.temperatureC!=null?Math.round(hw.temperatureC)+'C':'--')+'</b><span>Camera inputs</span><b>'+((inv.video||[]).length||0)+'</b><span>Microphones</span><b>'+((inv.audio_input||[]).length||0)+'</b><span>Speaker outputs</span><b>'+((inv.audio_output||[]).length||0)+'</b><span>Selected camera</span><b>'+esc(device.video_device||'auto')+'</b><span>Selected microphone</span><b>'+esc(device.audio_device||'default')+'</b>';
|
|
6687
|
+
var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
|
|
6688
|
+
}
|
|
6689
|
+
function reloadCameraOperations(){
|
|
6690
|
+
if(!cameraOpsNode)return;var img=$('pk-camera-ops-img'),empty=$('pk-camera-ops-empty');if(!img)return;img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='opening RoboVision camera...';}
|
|
6691
|
+
img.onload=function(){img.dataset.live='true';if(empty)empty.style.display='none';refreshCameraOperations();};img.onerror=function(){img.dataset.live='false';if(empty){empty.style.display='block';empty.textContent='camera relay unavailable / retrying';}refreshCameraOperations();};img.src=cameraOpsFeedUrl(cameraOpsNode);refreshCameraOperations();
|
|
6692
|
+
}
|
|
6693
|
+
function openCameraOperations(n){
|
|
6694
|
+
cameraOpsNode=n;var host=$('pk-camera-ops');if(!host)return;host.classList.add('open');document.body.classList.add('camera-ops-open');var name=n.displayName||n.name||'robot',avatar=rpAvatarFor(name),av=$('pk-camera-ops-avatar');$('pk-camera-ops-name').textContent=name+' / live operations';$('pk-camera-ops-sub').textContent='RoboVision mesh relay / '+(n.connected?'connected':'offline');if(av){if(avatar){av.src=avatar+QS;av.style.display='block';}else{av.removeAttribute('src');av.style.display='none';}}reloadCameraOperations();if(cameraOpsTimer)clearInterval(cameraOpsTimer);cameraOpsTimer=setInterval(refreshCameraOperations,1000);
|
|
6695
|
+
}
|
|
6696
|
+
function closeCameraOperations(){
|
|
6697
|
+
var host=$('pk-camera-ops'),img=$('pk-camera-ops-img');if(host)host.classList.remove('open');document.body.classList.remove('camera-ops-open');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(){});
|
|
6698
|
+
}
|
|
6699
|
+
(function bindCameraOperations(){
|
|
6700
|
+
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');
|
|
6701
|
+
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();});
|
|
6702
|
+
})();
|
|
6703
|
+
|
|
6704
|
+
// ── cam: target a LiveKit track; fall back to a styled preview ──
|
|
6410
6705
|
function startCam(n){
|
|
6411
6706
|
idleCam('connecting');
|
|
6412
6707
|
tryVisionOverlay(n, true);
|