infinicode 2.8.69 → 2.8.70
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;
|
|
@@ -606,9 +654,40 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
606
654
|
.rp-vp-id{font-size:0.62rem;color:var(--muted);margin-top:0.1rem;word-break:break-all;}
|
|
607
655
|
.rp-vp-desc{font-size:0.64rem;color:var(--muted);margin-top:0.2rem;line-height:1.4;}
|
|
608
656
|
.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
|
-
|
|
657
|
+
.rp-voice-picker-foot .rp-muted{color:var(--muted);}
|
|
658
|
+
|
|
659
|
+
/* Character and voice configuration workspace */
|
|
660
|
+
.rp-config-workspace{grid-column:1/-1;display:grid;gap:1rem;}
|
|
661
|
+
.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);}
|
|
662
|
+
.rp-config-hero h3{font-family:var(--display);font-size:1.25rem;color:var(--gold-pale);margin:0;}
|
|
663
|
+
.rp-config-hero p{max-width:72ch;margin:.25rem 0 0;color:var(--muted);font-size:.68rem;line-height:1.55;}
|
|
664
|
+
.rp-config-hero-actions,.rp-config-toolbar,.rp-config-actions{display:flex;align-items:center;gap:.45rem;flex-wrap:wrap;}
|
|
665
|
+
.rp-config-columns{display:grid;grid-template-columns:1fr 1fr;gap:1rem;align-items:start;}
|
|
666
|
+
.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);}
|
|
667
|
+
.rp-config-library-head{display:flex;align-items:flex-start;gap:.7rem;margin-bottom:.7rem;}
|
|
668
|
+
.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;}
|
|
669
|
+
.rp-config-library-head h4{font-family:var(--display);font-size:1rem;color:var(--gold-pale);margin:0;}
|
|
670
|
+
.rp-config-library-head p{font-size:.58rem;color:var(--muted);margin:.1rem 0 0;line-height:1.4;}
|
|
671
|
+
.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;}
|
|
672
|
+
.rp-config-toolbar{margin-bottom:.65rem;}.rp-config-toolbar input{flex:1;min-width:150px;}
|
|
673
|
+
.rp-config-cards{display:grid;gap:.55rem;max-height:560px;overflow:auto;padding-right:.2rem;scrollbar-color:var(--gold-deep) transparent;}
|
|
674
|
+
.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;}
|
|
675
|
+
.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;}
|
|
676
|
+
.rp-config-card:hover{transform:translateY(-1px);border-color:rgba(246,215,123,.38);background:rgba(246,215,123,.045);}
|
|
677
|
+
.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;}
|
|
678
|
+
.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;}
|
|
679
|
+
.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)}
|
|
680
|
+
.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)}
|
|
681
|
+
.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;}
|
|
682
|
+
.rp-config-card .rp-config-actions{margin-top:.6rem}.rp-config-card .rp-btn{font-size:.53rem;padding:.32rem .5rem;}
|
|
683
|
+
.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}
|
|
684
|
+
.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)}
|
|
685
|
+
.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%)}
|
|
686
|
+
.rp-modal.config-modal{width:min(980px,100%);max-height:90vh}.rp-modal.config-modal .rp-modal-body{padding:1rem 1.1rem 1.2rem;}
|
|
687
|
+
@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}}
|
|
688
|
+
@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}}
|
|
689
|
+
|
|
690
|
+
.rp-fleet-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:0.7rem;padding:0;}
|
|
612
691
|
.rp-fleet-card{position:relative;}
|
|
613
692
|
.rp-fleet-card .spark{margin-top:0.5rem;height:36px;}
|
|
614
693
|
.rp-fleet-card .rp-actions{margin-top:0.6rem;}
|
|
@@ -648,12 +727,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
648
727
|
<button class="tab" data-tab="robopark" id="tabbtn-robopark">Sessions & Telemetry</button>
|
|
649
728
|
</div>
|
|
650
729
|
|
|
651
|
-
<div class="pane active" id="pane-fleet">
|
|
652
|
-
<div class="wrap">
|
|
653
|
-
<section>
|
|
654
|
-
<
|
|
655
|
-
|
|
656
|
-
|
|
730
|
+
<div class="pane active" id="pane-fleet">
|
|
731
|
+
<div class="wrap">
|
|
732
|
+
<section>
|
|
733
|
+
<div class="fleet-ops">
|
|
734
|
+
<div class="fleet-hero">
|
|
735
|
+
<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>
|
|
736
|
+
<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>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="fleet-kpis" id="fleet-kpis"></div>
|
|
739
|
+
<div class="fleet-insights">
|
|
740
|
+
<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>
|
|
741
|
+
<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>
|
|
742
|
+
<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>
|
|
743
|
+
<div class="fleet-panel fleet-release" id="fleet-release"></div>
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
<h2>Fleet <span class="count" id="fleetcount"></span></h2>
|
|
747
|
+
<div class="fleet" id="fleet"></div>
|
|
748
|
+
<div class="hw" id="hw"></div>
|
|
657
749
|
</section>
|
|
658
750
|
<aside>
|
|
659
751
|
<h2>Live activity</h2>
|
|
@@ -678,16 +770,39 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
678
770
|
</aside>
|
|
679
771
|
<div class="pk-hint">drag to pan · scroll to zoom · <b>click a robot</b> for cam / mic / speaker</div>
|
|
680
772
|
</div>
|
|
681
|
-
<div class="pk-drawer" id="pk-drawer">
|
|
773
|
+
<div class="pk-drawer" id="pk-drawer">
|
|
682
774
|
<div class="pk-dh">
|
|
683
775
|
<div class="pk-av" id="pk-av">🤖</div>
|
|
684
776
|
<div><div class="nm" id="pk-name">robot</div><div class="sub" id="pk-sub">satellite</div></div>
|
|
685
777
|
<span class="pk-pill idle" id="pk-state" style="margin-left:0.4rem">idle</span>
|
|
686
778
|
<button class="x" id="pk-close">✕</button>
|
|
687
779
|
</div>
|
|
688
|
-
<div class="pk-dbody" id="pk-body"></div>
|
|
689
|
-
</div>
|
|
690
|
-
</div>
|
|
780
|
+
<div class="pk-dbody" id="pk-body"></div>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
<div class="pk-camera-ops" id="pk-camera-ops" role="dialog" aria-modal="true" aria-label="Fullscreen robot camera operations view">
|
|
785
|
+
<div class="pk-camera-ops-head">
|
|
786
|
+
<img class="avatar" id="pk-camera-ops-avatar" alt="">
|
|
787
|
+
<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>
|
|
788
|
+
<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>
|
|
789
|
+
</div>
|
|
790
|
+
<div class="pk-camera-ops-grid">
|
|
791
|
+
<div class="pk-camera-stage" id="pk-camera-stage">
|
|
792
|
+
<img id="pk-camera-ops-img" alt="Live robot camera feed">
|
|
793
|
+
<div class="empty" id="pk-camera-ops-empty">opening RoboVision camera...</div>
|
|
794
|
+
<div class="pk-camera-rec"><i></i><span id="pk-camera-ops-rec">REC / CONNECTING</span></div>
|
|
795
|
+
<div class="pk-camera-clock" id="pk-camera-ops-clock">--:--:--</div>
|
|
796
|
+
<div class="pk-camera-overlay" id="pk-camera-ops-overlay"></div>
|
|
797
|
+
</div>
|
|
798
|
+
<aside class="pk-camera-side">
|
|
799
|
+
<div class="section"><h3>Live insights</h3><div class="pk-camera-insight" id="pk-camera-ops-insights"></div></div>
|
|
800
|
+
<div class="section"><h3>Conversation pipeline</h3><div class="pk-camera-pipeline" id="pk-camera-ops-pipeline"></div></div>
|
|
801
|
+
<div class="section"><h3>Hardware and media</h3><div class="pk-camera-insight" id="pk-camera-ops-hardware"></div></div>
|
|
802
|
+
<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>
|
|
803
|
+
</aside>
|
|
804
|
+
</div>
|
|
805
|
+
</div>
|
|
691
806
|
|
|
692
807
|
<div class="pane" id="pane-robopark">
|
|
693
808
|
<div class="rp-head">
|
|
@@ -709,9 +824,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
709
824
|
<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
825
|
<button class="rp-subtab" data-sub="voice" id="rpsbtn-voice" role="tab" aria-selected="false">Voice Call</button>
|
|
711
826
|
<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="
|
|
827
|
+
<button class="rp-subtab" data-sub="audit" id="rpsbtn-audit" role="tab" aria-selected="false">Audit Log</button>
|
|
828
|
+
<button class="rp-subtab" data-sub="fleet" id="rpsbtn-fleet" role="tab" aria-selected="false">Fleet</button>
|
|
829
|
+
<button class="rp-subtab" data-sub="config" id="rpsbtn-config" role="tab" aria-selected="false">Characters & Voice</button>
|
|
830
|
+
<button class="rp-subtab" data-sub="setup" id="rpsbtn-setup" role="tab" aria-selected="false">Setup</button>
|
|
715
831
|
</div>
|
|
716
832
|
|
|
717
833
|
<!-- ═══ Overview ═══ -->
|
|
@@ -761,6 +877,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
761
877
|
<div class="rp-call-card">
|
|
762
878
|
<div class="rp-call-device"><label>Character preset</label><select id="rp-call-character-select"><option value="">select character...</option></select></div>
|
|
763
879
|
<div class="rp-call-device"><label>Voice stack</label><select id="rp-call-stack-select"><option value="">character default</option></select></div>
|
|
880
|
+
<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
881
|
</div>
|
|
765
882
|
<div class="rp-call-card">
|
|
766
883
|
<div class="rp-section-h" style="margin:0 0 .5rem">Voice pipeline <span class="meta">selected stack</span></div>
|
|
@@ -936,8 +1053,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
936
1053
|
</div>
|
|
937
1054
|
|
|
938
1055
|
<!-- ═══ Fleet ═══ -->
|
|
939
|
-
<div class="rp-sub" id="rp-sub-fleet">
|
|
940
|
-
<div class="
|
|
1056
|
+
<div class="rp-sub" id="rp-sub-fleet">
|
|
1057
|
+
<div class="fleet-ops" style="margin-bottom:1rem">
|
|
1058
|
+
<div class="fleet-kpis" id="rp-fleet-kpis"></div>
|
|
1059
|
+
<div class="fleet-insights">
|
|
1060
|
+
<div class="fleet-panel"><div class="fleet-panel-head"><b>Operational readiness</b><span>scheduler</span></div><div id="rp-fleet-readiness"></div></div>
|
|
1061
|
+
<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>
|
|
1062
|
+
<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>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
<div class="rp-section-h">Robots <span class="meta" id="rp-fleet-meta">live + idle + offline</span></div>
|
|
941
1066
|
<div class="rp-fleet-grid" id="rp-grid"></div>
|
|
942
1067
|
</div>
|
|
943
1068
|
|
|
@@ -1000,79 +1125,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1000
1125
|
<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
1126
|
</div>
|
|
1002
1127
|
|
|
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>
|
|
1128
|
+
</div>
|
|
1129
|
+
|
|
1130
|
+
<!-- Character + voice management -->
|
|
1131
|
+
<div class="rp-sub" id="rp-sub-config">
|
|
1132
|
+
<div class="rp-config-workspace" id="rp-templates">
|
|
1133
|
+
<div class="rp-config-hero">
|
|
1134
|
+
<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>
|
|
1135
|
+
<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>
|
|
1136
|
+
</div>
|
|
1137
|
+
<div class="rp-config-columns">
|
|
1138
|
+
<section class="rp-config-library">
|
|
1139
|
+
<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>
|
|
1140
|
+
<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>
|
|
1141
|
+
<div class="rp-config-cards" id="rp-voice-list"></div>
|
|
1142
|
+
<input id="rp-newvoice-name" type="hidden"><div id="rp-voice-edit" style="display:none"></div>
|
|
1143
|
+
</section>
|
|
1144
|
+
<section class="rp-config-library">
|
|
1145
|
+
<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>
|
|
1146
|
+
<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>
|
|
1147
|
+
<div class="rp-config-cards" id="rp-char-list"></div>
|
|
1148
|
+
<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>
|
|
1149
|
+
</section>
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1076
1154
|
|
|
1077
1155
|
<!-- Generic modal: title + scrollable body, used for session + audit detail views -->
|
|
1078
1156
|
<div class="rp-modal-back" id="rp-modal-back" role="dialog" aria-modal="true">
|
|
@@ -1282,9 +1360,70 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1282
1360
|
}
|
|
1283
1361
|
return null;
|
|
1284
1362
|
}
|
|
1363
|
+
function renderFleetOperations(nodes, prefix, telemetryRows){
|
|
1364
|
+
prefix=prefix||'fleet'; nodes=Array.isArray(nodes)?nodes:[]; telemetryRows=Array.isArray(telemetryRows)?telemetryRows:null;
|
|
1365
|
+
var rows=[];
|
|
1366
|
+
function readinessOf(t,connected){ return String((t&&(t.readiness_code||t.status))||(!connected?'offline':'mesh_online')).toLowerCase(); }
|
|
1367
|
+
if(telemetryRows){
|
|
1368
|
+
for(var ti=0;ti<telemetryRows.length;ti++){
|
|
1369
|
+
var t=telemetryRows[ti], rn=robotName(t), rc=readinessOf(t,true), offline=rc==='offline'||rc==='robot_offline'||rc==='unreachable';
|
|
1370
|
+
rows.push({name:rn,connected:!offline,tele:t,hw:null,version:t.version||'',node:null,readiness:rc});
|
|
1371
|
+
}
|
|
1372
|
+
} else {
|
|
1373
|
+
for(var ni=0;ni<nodes.length;ni++){
|
|
1374
|
+
var n=nodes[ni], tele=rpTelemetryForNode(n), role=String(n.role||'').toLowerCase();
|
|
1375
|
+
if(role!=='satellite'&&role!=='robot'&&!tele) continue;
|
|
1376
|
+
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)});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
var connected=0,ready=0,active=0,totalSessions=0,totalTriggers=0,latencies=[],drops=[],attention=[];
|
|
1380
|
+
for(var i=0;i<rows.length;i++){
|
|
1381
|
+
var r=rows[i], t=r.tele||{}, code=r.readiness;
|
|
1382
|
+
if(r.connected)connected++;
|
|
1383
|
+
if(code==='ready'||code==='in_session')ready++;
|
|
1384
|
+
if(code==='in_session'||t.active_session||t.in_session||t.session_active||t.current_session_id||t.status==='running'||t.status==='connecting')active++;
|
|
1385
|
+
totalSessions+=Number(t.sessions_total||0);totalTriggers+=Number(t.trigger_count||0);
|
|
1386
|
+
if(t.avg_latency_ms!=null)latencies.push(Number(t.avg_latency_ms));if(t.drop_rate!=null)drops.push(Number(t.drop_rate));
|
|
1387
|
+
var issues=[];
|
|
1388
|
+
if(!r.connected)issues.push('mesh or heartbeat offline');
|
|
1389
|
+
if(code!=='ready'&&code!=='in_session'&&code!=='mesh_online')issues.push(String(t.readiness_message||code).replace(/_/g,' '));
|
|
1390
|
+
if(t.drop_rate!=null&&Number(t.drop_rate)>=.05)issues.push(Math.round(Number(t.drop_rate)*100)+'% session drop rate');
|
|
1391
|
+
if(t.avg_latency_ms!=null&&Number(t.avg_latency_ms)>=3000)issues.push(Math.round(Number(t.avg_latency_ms))+'ms average response');
|
|
1392
|
+
if(r.hw&&r.hw.temperatureC!=null&&r.hw.temperatureC>=70)issues.push(Math.round(r.hw.temperatureC)+'C device temperature');
|
|
1393
|
+
if(r.hw&&r.hw.cpu&&r.hw.cpu.percent>=90)issues.push(Math.round(r.hw.cpu.percent)+'% CPU utilization');
|
|
1394
|
+
if(issues.length)attention.push({name:r.name,message:issues.join(' / '),bad:!r.connected||code==='stuck_session'||code==='join_timeout'||code==='robot_offline'});
|
|
1395
|
+
}
|
|
1396
|
+
var avgLatency=latencies.length?Math.round(latencies.reduce(function(a,b){return a+b;},0)/latencies.length):null;
|
|
1397
|
+
var avgDrop=drops.length?drops.reduce(function(a,b){return a+b;},0)/drops.length:null;
|
|
1398
|
+
var kpis=$(prefix+'-kpis');
|
|
1399
|
+
if(kpis){
|
|
1400
|
+
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>';}
|
|
1401
|
+
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');
|
|
1402
|
+
}
|
|
1403
|
+
var read=$(prefix+'-readiness');
|
|
1404
|
+
if(read){
|
|
1405
|
+
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;
|
|
1406
|
+
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>';
|
|
1407
|
+
}
|
|
1408
|
+
var workload=$(prefix+'-workload');
|
|
1409
|
+
if(workload){
|
|
1410
|
+
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;}
|
|
1411
|
+
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>';
|
|
1412
|
+
}
|
|
1413
|
+
var attentionEl=$(prefix+'-attention'), attentionMeta=$(prefix+'-attention-meta');
|
|
1414
|
+
if(attentionMeta)attentionMeta.textContent=attention.length+' issue'+(attention.length===1?'':'s');
|
|
1415
|
+
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>';
|
|
1416
|
+
var release=$(prefix+'-release');
|
|
1417
|
+
if(release){
|
|
1418
|
+
var versions={};for(var vi=0;vi<nodes.length;vi++){var version=nodes[vi].version||'unknown';versions[version]=(versions[version]||0)+1;}
|
|
1419
|
+
var versionKeys=Object.keys(versions).sort().reverse(),current=versionKeys[0]||'unknown';
|
|
1420
|
+
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>';
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1285
1423
|
function renderFleet(nodes){
|
|
1286
|
-
var el = $('fleet');
|
|
1287
|
-
|
|
1424
|
+
var el = $('fleet');
|
|
1425
|
+
renderFleetOperations(nodes||[],'fleet');
|
|
1426
|
+
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
1427
|
// connected first, then robots/satellites above hubs so the fleet reads robot-first
|
|
1289
1428
|
nodes.sort(function(a,b){ return (b.connected?1:0)-(a.connected?1:0); });
|
|
1290
1429
|
var accent = '#6FE0A6';
|
|
@@ -1332,18 +1471,21 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1332
1471
|
} else {
|
|
1333
1472
|
html += '<div class="stats"><span style="color:var(--muted)">offline'+(n.lastSeenAt?(' · last seen '+clock(n.lastSeenAt)):'')+'</span></div>';
|
|
1334
1473
|
}
|
|
1335
|
-
html += (capHtml?('<div class="caps">'+capHtml+'</div>'):'')
|
|
1474
|
+
html += (capHtml?('<div class="caps">'+capHtml+'</div>'):'')
|
|
1475
|
+
+ ((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
1476
|
}
|
|
1337
1477
|
el.innerHTML = html;
|
|
1338
1478
|
var cards = el.querySelectorAll('.node');
|
|
1339
1479
|
for(var j=0;j<cards.length;j++){
|
|
1340
1480
|
(function(card){
|
|
1341
|
-
function pick(){ selectNode(card.getAttribute('data-id'), card.getAttribute('data-name')); }
|
|
1481
|
+
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
1482
|
card.addEventListener('click', pick);
|
|
1343
1483
|
card.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ ev.preventDefault(); pick(); } });
|
|
1344
|
-
})(cards[j]);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1484
|
+
})(cards[j]);
|
|
1485
|
+
}
|
|
1486
|
+
var fleetButtons=el.querySelectorAll('[data-fleet-action]');
|
|
1487
|
+
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]);
|
|
1488
|
+
}
|
|
1347
1489
|
|
|
1348
1490
|
function renderSummary(s){
|
|
1349
1491
|
var nodes = s.nodes||[];
|
|
@@ -1623,7 +1765,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1623
1765
|
btn.addEventListener('click', function(){ selectTab(btn.getAttribute('data-tab')); });
|
|
1624
1766
|
})(tabs[ti]);
|
|
1625
1767
|
}
|
|
1626
|
-
function selectTab(name){
|
|
1768
|
+
function selectTab(name){
|
|
1627
1769
|
activeTab = name;
|
|
1628
1770
|
for (var i=0;i<tabs.length;i++) tabs[i].classList.toggle('active', tabs[i].getAttribute('data-tab')===name);
|
|
1629
1771
|
$('pane-fleet').classList.toggle('active', name==='fleet');
|
|
@@ -1634,8 +1776,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1634
1776
|
pollRobopark();
|
|
1635
1777
|
initRpSubTabs();
|
|
1636
1778
|
}
|
|
1637
|
-
if (name==='park') Park.activate(); else Park.deactivate();
|
|
1638
|
-
}
|
|
1779
|
+
if (name==='park') Park.activate(); else Park.deactivate();
|
|
1780
|
+
}
|
|
1781
|
+
var fleetParkBtn=$('fleet-open-park'); if(fleetParkBtn) fleetParkBtn.addEventListener('click',function(){selectTab('park');});
|
|
1782
|
+
var fleetSessionsBtn=$('fleet-open-sessions'); if(fleetSessionsBtn) fleetSessionsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('live');});
|
|
1783
|
+
var fleetIncidentsBtn=$('fleet-open-incidents'); if(fleetIncidentsBtn) fleetIncidentsBtn.addEventListener('click',function(){selectTab('robopark');setRpSubTab('incidents');});
|
|
1784
|
+
var fleetRefreshBtn=$('fleet-refresh'); if(fleetRefreshBtn) fleetRefreshBtn.addEventListener('click',function(){poll();pollRobopark();});
|
|
1639
1785
|
var initHash = location.hash.replace('#','');
|
|
1640
1786
|
// Defer tab init until Park module is defined (same outer IIFE, later in source).
|
|
1641
1787
|
function initTab(){
|
|
@@ -2038,8 +2184,66 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2038
2184
|
}
|
|
2039
2185
|
$('srv-add').addEventListener('click', addServer);
|
|
2040
2186
|
$('rb-add').addEventListener('click', addRobot);
|
|
2041
|
-
$('rp-addvoice').addEventListener('click', function(){
|
|
2042
|
-
$('rp-addchar').addEventListener('click', function(){
|
|
2187
|
+
$('rp-addvoice').addEventListener('click', function(){ openVoiceStackManager(); });
|
|
2188
|
+
$('rp-addchar').addEventListener('click', function(){ openCharacterManager(); });
|
|
2189
|
+
var newVoiceStackBtn=$('rp-new-voice-stack'); if(newVoiceStackBtn) newVoiceStackBtn.addEventListener('click',function(){ openVoiceStackManager(); });
|
|
2190
|
+
var newCharacterBtn=$('rp-new-character'); if(newCharacterBtn) newCharacterBtn.addEventListener('click',function(){ openCharacterManager(); });
|
|
2191
|
+
|
|
2192
|
+
function configOption(value,label,current){ return '<option value="'+esc(value)+'"'+(String(value)===String(current)?' selected':'')+'>'+esc(label||value)+'</option>'; }
|
|
2193
|
+
function configNumber(id,fallback){ var el=$(id), n=el?Number(el.value):NaN; return Number.isFinite(n)?n:fallback; }
|
|
2194
|
+
function voiceStackDraft(){
|
|
2195
|
+
return {
|
|
2196
|
+
id:$('cfg-vs-id').value.trim(),name:$('cfg-vs-name').value.trim(),
|
|
2197
|
+
stt_provider:$('cfg-vs-stt-provider').value,stt_model:$('cfg-vs-stt-model').value.trim(),stt_language:$('cfg-vs-stt-language').value.trim()||'en',
|
|
2198
|
+
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,
|
|
2199
|
+
temperature:configNumber('cfg-vs-temperature',0.7),num_ctx:configNumber('cfg-vs-num-ctx',8192),
|
|
2200
|
+
tts_provider:$('cfg-vs-tts-provider').value,tts_voice:$('cfg-vs-tts-voice').value.trim(),tts_language:$('cfg-vs-tts-language').value.trim()||'en',
|
|
2201
|
+
max_turns:configNumber('cfg-vs-max-turns',20),min_endpointing_delay:configNumber('cfg-vs-endpointing',0.7),
|
|
2202
|
+
allow_interruptions:$('cfg-vs-interruptions').checked,wake_word_enabled:$('cfg-vs-wake-enabled').checked,
|
|
2203
|
+
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)
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
function openVoiceStackManager(id,draft){
|
|
2207
|
+
var existing=null;
|
|
2208
|
+
for(var i=0;i<voiceStacks.length;i++) if(String(voiceStacks[i].id)===String(id)){ existing=voiceStacks[i]; break; }
|
|
2209
|
+
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};
|
|
2210
|
+
var editing=!!existing;
|
|
2211
|
+
var html='<div class="rp-config-editor">'
|
|
2212
|
+
+'<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>'
|
|
2213
|
+
+'<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>'
|
|
2214
|
+
+'<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>'
|
|
2215
|
+
+'<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>'
|
|
2216
|
+
+'<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>'
|
|
2217
|
+
+'<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>'
|
|
2218
|
+
+'<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>'
|
|
2219
|
+
+'<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>';
|
|
2220
|
+
showModal(editing?'Edit voice stack · '+(s.name||s.id):'New voice stack',html); $('rp-modal').classList.add('config-modal');
|
|
2221
|
+
$('cfg-vs-cancel').addEventListener('click',closeModal);
|
|
2222
|
+
$('cfg-vs-browse').addEventListener('click',function(){ var next=voiceStackDraft(); openTtsVoicePicker(next.tts_provider,function(voiceId){ next.tts_voice=voiceId; openVoiceStackManager(editing?id:null,next); }); });
|
|
2223
|
+
$('cfg-vs-save').addEventListener('click',function(){
|
|
2224
|
+
var body=voiceStackDraft(); if(!body.name){ showOut('Voice stack name is required'); return; }
|
|
2225
|
+
if(!editing && !body.id) body.id=body.name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,48);
|
|
2226
|
+
var btn=$('cfg-vs-save'); btn.disabled=true; btn.textContent='Saving…';
|
|
2227
|
+
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); });
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
function characterDraft(){
|
|
2232
|
+
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};
|
|
2233
|
+
}
|
|
2234
|
+
function openCharacterManager(id,draft){
|
|
2235
|
+
var existing=null; for(var i=0;i<characterPresets.length;i++) if(String(characterPresets[i].id)===String(id)){ existing=characterPresets[i]; break; }
|
|
2236
|
+
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>';
|
|
2237
|
+
for(var j=0;j<voiceStacks.length;j++) stackOptions+=configOption(voiceStacks[j].id,voiceStacks[j].name||voiceStacks[j].id,p.voice_stack_id);
|
|
2238
|
+
var html='<div class="rp-config-editor">'
|
|
2239
|
+
+'<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>'
|
|
2240
|
+
+'<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>'
|
|
2241
|
+
+'<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>'
|
|
2242
|
+
+'<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>'
|
|
2243
|
+
+'<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>';
|
|
2244
|
+
showModal(editing?'Edit character · '+(p.name||p.id):'New character preset',html); $('rp-modal').classList.add('config-modal'); $('cfg-cp-cancel').addEventListener('click',closeModal);
|
|
2245
|
+
$('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);}); });
|
|
2246
|
+
}
|
|
2043
2247
|
|
|
2044
2248
|
function addVoiceStackFromTemplate(){
|
|
2045
2249
|
var name=$('rp-newvoice-name').value.trim(); if(!name){ showOut('Voice stack name required'); return; }
|
|
@@ -2051,25 +2255,28 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2051
2255
|
var id=name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,32);
|
|
2052
2256
|
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
2257
|
}
|
|
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
|
-
|
|
2258
|
+
function fillTemplateVoiceList(){
|
|
2259
|
+
var el=$('rp-voice-list'); if(!el) return;
|
|
2260
|
+
var query=String(($('rp-voice-search')&&$('rp-voice-search').value)||'').trim().toLowerCase(), html='',shown=0;
|
|
2261
|
+
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>'; }
|
|
2262
|
+
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>';
|
|
2263
|
+
el.innerHTML=html;
|
|
2264
|
+
var count=$('rp-voice-count'); if(count) count.textContent=voiceStacks.length+' stack'+(voiceStacks.length===1?'':'s');
|
|
2265
|
+
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); } }
|
|
2266
|
+
}
|
|
2267
|
+
function fillTemplateCharacterList(){
|
|
2268
|
+
var el=$('rp-char-list'); if(!el) return;
|
|
2269
|
+
var query=String(($('rp-char-search')&&$('rp-char-search').value)||'').trim().toLowerCase(), html='',shown=0;
|
|
2270
|
+
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>'; }
|
|
2271
|
+
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>';
|
|
2272
|
+
el.innerHTML=html;
|
|
2273
|
+
var count=$('rp-char-count'); if(count) count.textContent=characterPresets.length+' preset'+(characterPresets.length===1?'':'s');
|
|
2274
|
+
}
|
|
2069
2275
|
|
|
2070
2276
|
// ── voice-stack edit panel ──
|
|
2071
|
-
var editingVoiceStackId=null;
|
|
2072
|
-
function openVoiceStackEdit(id){
|
|
2277
|
+
var editingVoiceStackId=null;
|
|
2278
|
+
function openVoiceStackEdit(id){
|
|
2279
|
+
openVoiceStackManager(id); return;
|
|
2073
2280
|
var s=null; for(var i=0;i<voiceStacks.length;i++){ if(voiceStacks[i].id===id){ s=voiceStacks[i]; break; } }
|
|
2074
2281
|
if(!s) return;
|
|
2075
2282
|
editingVoiceStackId=id;
|
|
@@ -2113,7 +2320,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2113
2320
|
};
|
|
2114
2321
|
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
2322
|
}
|
|
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)
|
|
2323
|
+
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
2324
|
var vsSave=$('rpve-save'); if(vsSave) vsSave.addEventListener('click', saveVoiceStackEdit);
|
|
2118
2325
|
var vsCancel=$('rpve-cancel'); if(vsCancel) vsCancel.addEventListener('click', function(){ $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; });
|
|
2119
2326
|
// TTS provider change: re-populate the voice <select> for the new provider.
|
|
@@ -2150,13 +2357,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2150
2357
|
});
|
|
2151
2358
|
|
|
2152
2359
|
// ── character-preset edit panel ──
|
|
2153
|
-
var editingCharPresetId=null;
|
|
2360
|
+
var editingCharPresetId=null;
|
|
2154
2361
|
function fillCharEditStackOptions(cur){
|
|
2155
2362
|
var sel=$('rpce-stack'); if(!sel) return;
|
|
2156
2363
|
sel.innerHTML='<option value="">— default —</option>';
|
|
2157
2364
|
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
2365
|
}
|
|
2159
|
-
function openCharPresetEdit(id){
|
|
2366
|
+
function openCharPresetEdit(id){
|
|
2367
|
+
openCharacterManager(id); return;
|
|
2160
2368
|
var p=null; for(var i=0;i<characterPresets.length;i++){ if(characterPresets[i].id===id){ p=characterPresets[i]; break; } }
|
|
2161
2369
|
if(!p) return;
|
|
2162
2370
|
editingCharPresetId=id;
|
|
@@ -2181,9 +2389,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2181
2389
|
};
|
|
2182
2390
|
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
2391
|
}
|
|
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)
|
|
2392
|
+
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
2393
|
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; });
|
|
2394
|
+
var cpCancel=$('rpce-cancel'); if(cpCancel) cpCancel.addEventListener('click', function(){ $('rp-char-edit').style.display='none'; editingCharPresetId=null; });
|
|
2395
|
+
var voiceSearch=$('rp-voice-search'); if(voiceSearch) voiceSearch.addEventListener('input',fillTemplateVoiceList);
|
|
2396
|
+
var charSearch=$('rp-char-search'); if(charSearch) charSearch.addEventListener('input',fillTemplateCharacterList);
|
|
2397
|
+
document.addEventListener('click',function(e){
|
|
2398
|
+
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; }
|
|
2399
|
+
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; }
|
|
2400
|
+
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; }
|
|
2401
|
+
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); }
|
|
2402
|
+
});
|
|
2187
2403
|
|
|
2188
2404
|
function renderRobotCardSelect(name, current){
|
|
2189
2405
|
var sel=$('rp-char-'+name); if(!sel) return;
|
|
@@ -2213,8 +2429,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2213
2429
|
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
2430
|
return;
|
|
2215
2431
|
}
|
|
2216
|
-
var robots = robotsFrom(d);
|
|
2217
|
-
|
|
2432
|
+
var robots = robotsFrom(d);
|
|
2433
|
+
renderFleetOperations([], 'rp-fleet', robots);
|
|
2434
|
+
var tot = (d && d.totals) || {};
|
|
2218
2435
|
var totalSessions = tot.sessions_total!=null?tot.sessions_total:robots.reduce(function(a,x){return a+(x.sessions_total||0);},0);
|
|
2219
2436
|
var totalTrig = tot.trigger_count!=null?tot.trigger_count:robots.reduce(function(a,x){return a+(x.trigger_count||0);},0);
|
|
2220
2437
|
totals.innerHTML =
|
|
@@ -2242,8 +2459,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2242
2459
|
+ '<div class="rp-m"><span class="v '+dcls+'">'+drop+'</span><span class="l">drop rate</span></div>'
|
|
2243
2460
|
+ '</div><div class="rp-actions">'
|
|
2244
2461
|
+ '<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>'
|
|
2462
|
+
+ '<button class="rp-btn" data-act="preview" data-robot="'+idAttr+'">Preview</button>'
|
|
2463
|
+
+ '<button class="rp-btn danger" data-act="end" data-robot="'+idAttr+'"'+((rpReadOnly||!active)?' disabled':'')+'>End session</button>'
|
|
2464
|
+
+ ((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>':'')
|
|
2465
|
+
+ (x.voice_stack_id?'<button class="rp-btn" data-config-edit-stack="'+esc(x.voice_stack_id)+'">Edit voice</button>':'')
|
|
2247
2466
|
+ '</div>'
|
|
2248
2467
|
+ '<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
2468
|
+ (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 +2557,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2338
2557
|
s.classList.toggle('active', s.id==='rp-sub-'+name);
|
|
2339
2558
|
}
|
|
2340
2559
|
// stop every sub-tab interval, then start only the active one
|
|
2341
|
-
var names = ['overview','incidents','live','voice','history','audit','fleet','setup'];
|
|
2560
|
+
var names = ['overview','incidents','live','voice','history','audit','fleet','config','setup'];
|
|
2342
2561
|
for(var k=0;k<names.length;k++){
|
|
2343
2562
|
var n=names[k];
|
|
2344
2563
|
if(rpIntervals[n]){ clearInterval(rpIntervals[n]); rpIntervals[n]=null; }
|
|
@@ -2365,7 +2584,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2365
2584
|
var h=String(location.hash||'').replace('#','');
|
|
2366
2585
|
var m = h.match(/^robopark\\/(.+)$/);
|
|
2367
2586
|
var initial = (m && m[1]) ? m[1] : 'overview';
|
|
2368
|
-
if(['overview','incidents','live','voice','history','audit','fleet','setup'].indexOf(initial)<0) initial='overview';
|
|
2587
|
+
if(['overview','incidents','live','voice','history','audit','fleet','config','setup'].indexOf(initial)<0) initial='overview';
|
|
2369
2588
|
setRpSubTab(initial);
|
|
2370
2589
|
}
|
|
2371
2590
|
|
|
@@ -2377,12 +2596,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2377
2596
|
$('rp-modal-body').innerHTML = bodyHtml || '';
|
|
2378
2597
|
back.classList.add('open');
|
|
2379
2598
|
}
|
|
2380
|
-
function closeModal(){
|
|
2381
|
-
var back = $('rp-modal-back');
|
|
2382
|
-
if(!back) return;
|
|
2383
|
-
back.classList.remove('open');
|
|
2384
|
-
|
|
2385
|
-
|
|
2599
|
+
function closeModal(){
|
|
2600
|
+
var back = $('rp-modal-back');
|
|
2601
|
+
if(!back) return;
|
|
2602
|
+
back.classList.remove('open');
|
|
2603
|
+
var modal=$('rp-modal'); if(modal) modal.classList.remove('config-modal');
|
|
2604
|
+
$('rp-modal-body').innerHTML = '';
|
|
2605
|
+
}
|
|
2386
2606
|
// wire the close handlers (once)
|
|
2387
2607
|
(function(){
|
|
2388
2608
|
var closeBtn = $('rp-modal-close');
|
|
@@ -2542,13 +2762,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2542
2762
|
// input. Bound once on the document so re-opening the modal doesn't
|
|
2543
2763
|
// stack listeners.
|
|
2544
2764
|
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
|
-
}
|
|
2765
|
+
var btn = e.target.closest && e.target.closest('.rp-vp-item');
|
|
2766
|
+
if(btn){
|
|
2767
|
+
var vid = btn.getAttribute('data-voice-id');
|
|
2768
|
+
closeModal();
|
|
2769
|
+
if(vid && ttsPickerState.onPick){ ttsPickerState.onPick(vid); }
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2552
2772
|
var r = e.target.closest && e.target.closest('#rp-voice-picker-refresh');
|
|
2553
2773
|
if(r){
|
|
2554
2774
|
// Re-fetch the catalog, ignore cache, then re-render.
|
|
@@ -2807,6 +3027,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2807
3027
|
if(cs)cs.addEventListener('change',paintVoiceCallCharacter); if(start)start.addEventListener('click',startVoiceCall);
|
|
2808
3028
|
if(ss)ss.addEventListener('change',paintVoiceCallStack);if(mute)mute.addEventListener('click',toggleVoiceMute);if(end)end.addEventListener('click',function(){finishVoiceCall('operator');});
|
|
2809
3029
|
$('rp-call-camera').addEventListener('click',toggleVoiceCamera);$('rp-call-screen').addEventListener('click',toggleVoiceScreen);$('rp-call-refresh-devices').addEventListener('click',function(){refreshBrowserDevices(true);});
|
|
3030
|
+
$('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');});
|
|
3031
|
+
$('rp-call-new-character').addEventListener('click',function(){openCharacterManager();});
|
|
3032
|
+
$('rp-call-edit-stack').addEventListener('click',function(){var s=selectedVoiceStack();if(s)openVoiceStackManager(s.id);else showOut('Choose a voice stack to edit');});
|
|
3033
|
+
$('rp-call-new-stack').addEventListener('click',function(){openVoiceStackManager();});
|
|
2810
3034
|
$('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
3035
|
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
3036
|
$('rp-call-chat-toggle').addEventListener('click',toggleTranscript);$('rp-call-collapse').addEventListener('click',toggleTranscript);
|
|
@@ -6245,7 +6469,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6245
6469
|
+'<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
6470
|
+'<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
6471
|
+'<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>'
|
|
6472
|
+
+'<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
6473
|
+'<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
6474
|
+ sect('Remote production test','<div id="pk-pipeline-summary" class="pk-pipeline-summary">Checking robot, camera, scheduler and LiveKit...</div>'
|
|
6251
6475
|
+ '<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 +6492,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6268
6492
|
+ 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
6493
|
+ 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
6494
|
+ 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))+'"
|
|
6495
|
+
+ 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
6496
|
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
6497
|
// Put live hardware selection beside the camera and audio tests,
|
|
6274
6498
|
// ahead of persona and service administration in the Park drawer.
|
|
@@ -6290,6 +6514,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6290
6514
|
vl.addEventListener('click', function(){ tryVisionOverlay(n, true); if(!visionOn) showOut('RoboVision feed unavailable: robot has no reachable LAN/Tailscale address'); });
|
|
6291
6515
|
gl.after(vl);
|
|
6292
6516
|
}
|
|
6517
|
+
var fullCam=$('pk-camera-fullscreen'); if(fullCam) fullCam.addEventListener('click',function(){ openCameraOperations(n); });
|
|
6518
|
+
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');});
|
|
6519
|
+
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');});
|
|
6520
|
+
var newStack=$('pk-new-stack'); if(newStack) newStack.addEventListener('click',function(){openVoiceStackManager();});
|
|
6293
6521
|
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
6522
|
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
6523
|
var svcList=$('pk-svc-list'); if(svcList) svcList.addEventListener('click', function(ev){
|
|
@@ -6406,7 +6634,38 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6406
6634
|
function set(id,v){ var e=$(id); if(e) e.textContent=v; }
|
|
6407
6635
|
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
6636
|
|
|
6409
|
-
|
|
6637
|
+
var cameraOpsNode=null,cameraOpsTimer=null;
|
|
6638
|
+
function cameraOpsFeedUrl(n){
|
|
6639
|
+
var ref=n&&(n.displayName||n.name||n.nodeId||n.id); if(!ref)return '';
|
|
6640
|
+
return '/fed/media/robots/'+encodeURIComponent(ref)+'/video_feed'+QS+(QS?'&':'?')+'_='+Date.now();
|
|
6641
|
+
}
|
|
6642
|
+
function refreshCameraOperations(){
|
|
6643
|
+
var n=cameraOpsNode;if(!n)return;var row=schedRow(n)||{},device=schedulerDevice(n)||{},hw=hwByNode[n.nodeId]||{};
|
|
6644
|
+
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');
|
|
6645
|
+
var img=$('pk-camera-ops-img'),cameraLive=!!(img&&img.dataset.live==='true'),clockEl=$('pk-camera-ops-clock');if(clockEl)clockEl.textContent=clock(Date.now());
|
|
6646
|
+
var rec=$('pk-camera-ops-rec');if(rec)rec.textContent=(cameraLive?'REC / LIVE':'REC / CONNECTING')+' / '+String(n.displayName||n.name||'robot').toUpperCase();
|
|
6647
|
+
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>';
|
|
6648
|
+
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>';
|
|
6649
|
+
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('');
|
|
6650
|
+
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>';
|
|
6651
|
+
var prod=$('pk-camera-ops-production');if(prod){prod.textContent=device.production_mode?'Disable production':'Enable production';prod.classList.toggle('primary',!!device.production_mode);}
|
|
6652
|
+
}
|
|
6653
|
+
function reloadCameraOperations(){
|
|
6654
|
+
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...';}
|
|
6655
|
+
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();
|
|
6656
|
+
}
|
|
6657
|
+
function openCameraOperations(n){
|
|
6658
|
+
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);
|
|
6659
|
+
}
|
|
6660
|
+
function closeCameraOperations(){
|
|
6661
|
+
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(){});
|
|
6662
|
+
}
|
|
6663
|
+
(function bindCameraOperations(){
|
|
6664
|
+
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');
|
|
6665
|
+
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();});
|
|
6666
|
+
})();
|
|
6667
|
+
|
|
6668
|
+
// ── cam: target a LiveKit track; fall back to a styled preview ──
|
|
6410
6669
|
function startCam(n){
|
|
6411
6670
|
idleCam('connecting');
|
|
6412
6671
|
tryVisionOverlay(n, true);
|