infinicode 2.8.68 → 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.
|
@@ -29,46 +29,39 @@ export const DASHBOARD_HTML = `<!doctype html>
|
|
|
29
29
|
<title>RoboPark Control</title>
|
|
30
30
|
<style>
|
|
31
31
|
/* ── premium tech glass theme ── dark is the committed default; light is a frosted toggle ── */
|
|
32
|
-
:root{
|
|
33
|
-
--bg:#
|
|
34
|
-
--grad: radial-gradient(
|
|
35
|
-
radial-gradient(
|
|
36
|
-
radial-gradient(
|
|
37
|
-
linear-gradient(
|
|
38
|
-
--panel: rgba(
|
|
39
|
-
--glass-border: rgba(255,
|
|
40
|
-
--border: rgba(255,
|
|
41
|
-
--ink:#
|
|
42
|
-
--gold:#
|
|
32
|
+
:root{
|
|
33
|
+
--bg:#050504;
|
|
34
|
+
--grad: radial-gradient(900px 520px at 7% -8%, rgba(255,198,79,0.18), transparent 64%),
|
|
35
|
+
radial-gradient(760px 520px at 98% 4%, rgba(161,105,16,0.16), transparent 62%),
|
|
36
|
+
radial-gradient(650px 520px at 62% 112%, rgba(255,211,123,0.08), transparent 68%),
|
|
37
|
+
linear-gradient(145deg,#030303 0%,#0b0905 46%,#050504 100%);
|
|
38
|
+
--panel: rgba(16,14,10,0.64); --panel-2: rgba(27,23,15,0.68); --panel-solid:#0d0b08;
|
|
39
|
+
--glass-border: rgba(255,224,151,0.14); --glass-hi: rgba(255,241,204,0.09);
|
|
40
|
+
--border: rgba(255,224,151,0.12); --border-strong: rgba(255,205,93,0.52);
|
|
41
|
+
--ink:#F6F0E3; --muted:#A99D87;
|
|
42
|
+
--gold:#C9962E; --gold-bright:#F6D77B; --gold-pale:#FFF0BE; --gold-deep:#78520F;
|
|
43
43
|
--ok:#6FE0A6; --ok-bg:rgba(28,64,50,0.55); --warn:#F2C14E; --warn-bg:rgba(64,50,20,0.5);
|
|
44
44
|
--bad:#FF7E6E; --bad-bg:rgba(64,24,20,0.55); --info:#78BBFF; --info-bg:rgba(20,44,66,0.55);
|
|
45
|
-
--shadow: 0
|
|
46
|
-
--mono:
|
|
47
|
-
--sans:
|
|
45
|
+
--shadow: 0 18px 55px rgba(0,0,0,0.56),0 2px 12px rgba(0,0,0,0.36);
|
|
46
|
+
--mono:"Cascadia Code","SFMono-Regular",Consolas,monospace;
|
|
47
|
+
--sans:"Trebuchet MS","Segoe UI",sans-serif;
|
|
48
|
+
--display:"Palatino Linotype","Book Antiqua",Palatino,serif;
|
|
48
49
|
}
|
|
49
50
|
@media (prefers-color-scheme:dark){:root{}}
|
|
50
51
|
:root[data-theme="dark"]{}
|
|
51
|
-
:root[data-theme="light"]{
|
|
52
|
-
--bg:#
|
|
53
|
-
--
|
|
54
|
-
|
|
55
|
-
linear-gradient(160deg,#F3EFFB 0%,#ECEAF7 50%,#F5F0E4 100%);
|
|
56
|
-
--panel: rgba(255,255,255,0.62); --panel-2: rgba(255,255,255,0.78); --panel-solid:#FBF7EE;
|
|
57
|
-
--glass-border: rgba(90,70,20,0.16); --glass-hi: rgba(255,255,255,0.7);
|
|
58
|
-
--border: rgba(90,70,20,0.16); --border-strong: rgba(185,120,22,0.55);
|
|
59
|
-
--ink:#241A08; --muted:#6E5A3E;
|
|
60
|
-
--gold:#8A5A0C; --gold-bright:#B9781A;
|
|
61
|
-
--ok:#1F7A4C; --ok-bg:rgba(180,220,190,0.6); --warn:#8A6410; --warn-bg:rgba(240,225,180,0.7);
|
|
62
|
-
--bad:#B5382E; --bad-bg:rgba(240,205,200,0.7); --info:#1F6F9E; --info-bg:rgba(200,225,240,0.7);
|
|
63
|
-
--shadow: 0 10px 30px rgba(60,40,10,0.14);
|
|
52
|
+
:root[data-theme="light"]{
|
|
53
|
+
--bg:#090806;--panel:rgba(24,20,13,.72);--panel-2:rgba(38,31,18,.76);--panel-solid:#100e09;
|
|
54
|
+
--ink:#FFF8E8;--muted:#B5A88F;--glass-border:rgba(255,224,151,.17);--glass-hi:rgba(255,244,214,.1);
|
|
55
|
+
--border:rgba(255,224,151,.14);--border-strong:rgba(255,205,93,.58);--gold:#D3A43D;--gold-bright:#FFE094;
|
|
64
56
|
}
|
|
65
57
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
66
58
|
html,body{height:100%}
|
|
67
|
-
body{background:var(--bg);background-image:var(--grad);background-attachment:fixed;background-size:cover;
|
|
68
|
-
color:var(--ink);font-family:var(--sans);line-height:1.45;-webkit-font-smoothing:antialiased;}
|
|
59
|
+
body{background:var(--bg);background-image:var(--grad);background-attachment:fixed;background-size:cover;
|
|
60
|
+
color:var(--ink);font-family:var(--sans);line-height:1.45;-webkit-font-smoothing:antialiased;}
|
|
61
|
+
body:before{content:"";position:fixed;inset:0;pointer-events:none;z-index:-1;opacity:.22;background-image:linear-gradient(rgba(255,221,140,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,221,140,.025) 1px,transparent 1px);background-size:42px 42px;mask-image:linear-gradient(to bottom,black,transparent 82%);}
|
|
69
62
|
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums;}
|
|
70
|
-
.glass{background:var(--panel);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3);
|
|
71
|
-
border:1px solid var(--glass-border);box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
|
|
63
|
+
.glass{background:var(--panel);-webkit-backdrop-filter:blur(16px) saturate(1.3);backdrop-filter:blur(16px) saturate(1.3);
|
|
64
|
+
border:1px solid var(--glass-border);box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi),inset 0 -1px 0 rgba(0,0,0,.5);}
|
|
72
65
|
|
|
73
66
|
/* top bar */
|
|
74
67
|
header.bar{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;
|
|
@@ -80,7 +73,7 @@ header.bar{display:flex;align-items:center;justify-content:space-between;gap:1re
|
|
|
80
73
|
.livedot.stale{background:var(--muted);box-shadow:none;animation:none;}
|
|
81
74
|
@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--ok) 55%,transparent),0 0 12px var(--ok)}70%{box-shadow:0 0 0 7px transparent,0 0 12px var(--ok)}100%{box-shadow:0 0 0 0 transparent,0 0 12px var(--ok)}}
|
|
82
75
|
@media (prefers-reduced-motion:reduce){.livedot{animation:none}}
|
|
83
|
-
.brand b{font-size:
|
|
76
|
+
.brand b{font-family:var(--display);font-size:1.08rem;letter-spacing:.015em;color:var(--gold-pale);text-shadow:0 0 24px rgba(246,215,123,.2);}
|
|
84
77
|
.brand .who{font-family:var(--mono);font-size:0.72rem;color:var(--muted);}
|
|
85
78
|
.chips{display:flex;gap:0.45rem;flex-wrap:wrap;align-items:center;}
|
|
86
79
|
.chip{font-family:var(--mono);font-size:0.68rem;padding:0.28em 0.62em;border-radius:100px;border:1px solid var(--glass-border);
|
|
@@ -139,8 +132,56 @@ section h2 .count{color:var(--muted);}
|
|
|
139
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);}
|
|
140
133
|
.tbadge.ok{color:var(--ok);} .tbadge.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 40%,transparent);}
|
|
141
134
|
.tbadge.bad{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent);background:var(--bad-bg);}
|
|
142
|
-
.chip.alert{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 45%,transparent);background:var(--bad-bg);}
|
|
143
|
-
.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)}}
|
|
144
185
|
|
|
145
186
|
/* hardware meters */
|
|
146
187
|
.hw{border-radius:13px;padding:0.85rem;margin-top:1rem;
|
|
@@ -532,21 +573,49 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
532
573
|
.rp-modal-body pre{background:var(--panel-solid);border:1px solid var(--glass-border);border-radius:8px;padding:0.5rem 0.7rem;
|
|
533
574
|
font-size:0.66rem;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-word;color:var(--ink);}
|
|
534
575
|
|
|
535
|
-
.rp-call-shell{
|
|
536
|
-
.rp-call-
|
|
537
|
-
.rp-call-
|
|
538
|
-
.rp-call-
|
|
539
|
-
.rp-call-
|
|
540
|
-
.rp-call-
|
|
541
|
-
.rp-call-
|
|
542
|
-
.rp-call-
|
|
543
|
-
.rp-call-
|
|
544
|
-
.rp-call-
|
|
545
|
-
.rp-call-
|
|
546
|
-
.rp-
|
|
547
|
-
.rp-
|
|
548
|
-
.rp-
|
|
549
|
-
.rp-
|
|
576
|
+
.rp-call-shell{position:relative;min-height:720px;overflow:hidden;border:1px solid var(--glass-border);border-radius:24px;background:linear-gradient(145deg,rgba(18,15,9,.84),rgba(5,5,4,.9));box-shadow:0 30px 90px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,238,194,.11);}
|
|
577
|
+
.rp-call-shell:before{content:"";position:absolute;width:520px;height:520px;border-radius:50%;left:18%;top:-340px;background:rgba(246,215,123,.15);filter:blur(75px);pointer-events:none;}
|
|
578
|
+
.rp-call-top{height:58px;padding:0 1.1rem;display:flex;align-items:center;gap:.75rem;border-bottom:1px solid var(--glass-border);background:rgba(8,7,5,.56);backdrop-filter:blur(24px);position:relative;z-index:3;}
|
|
579
|
+
.rp-call-top .eyebrow{font-family:var(--mono);font-size:.57rem;letter-spacing:.16em;color:var(--gold);text-transform:uppercase;}
|
|
580
|
+
.rp-call-top .title{font-family:var(--display);font-size:1.02rem;color:var(--gold-pale);letter-spacing:.03em;}
|
|
581
|
+
.rp-call-top .session{margin-left:auto;font-family:var(--mono);font-size:.58rem;color:var(--muted);max-width:32ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
582
|
+
.rp-call-grid{display:grid;grid-template-columns:280px minmax(380px,1fr) 330px;gap:1px;background:var(--glass-border);min-height:662px;position:relative;z-index:2;}
|
|
583
|
+
.rp-call-rail,.rp-call-stage,.rp-call-chat{background:rgba(8,7,5,.82);padding:1rem;min-width:0;}
|
|
584
|
+
.rp-call-rail{display:flex;flex-direction:column;gap:.8rem;}
|
|
585
|
+
.rp-call-card{border:1px solid var(--glass-border);border-radius:16px;background:linear-gradient(145deg,rgba(38,31,18,.58),rgba(13,11,8,.66));box-shadow:inset 0 1px 0 var(--glass-hi),0 12px 35px rgba(0,0,0,.25);padding:.8rem;}
|
|
586
|
+
.rp-call-character{position:relative;display:flex;align-items:center;gap:.75rem;padding:.75rem;border:1px solid rgba(246,215,123,.2);border-radius:15px;background:radial-gradient(circle at 12% 10%,rgba(246,215,123,.15),transparent 52%),rgba(16,14,10,.65);overflow:hidden;}
|
|
587
|
+
.rp-call-character img{width:76px;height:76px;object-fit:contain;filter:drop-shadow(0 10px 20px rgba(0,0,0,.48));}
|
|
588
|
+
.rp-call-character .name{font-family:var(--display);font-size:1.18rem;color:var(--gold-pale);font-weight:700;}
|
|
589
|
+
.rp-call-character .meta{font-size:.64rem;line-height:1.4;}
|
|
590
|
+
.rp-call-service-grid{display:grid;gap:.4rem;}
|
|
591
|
+
.rp-call-service{display:grid;grid-template-columns:29px 1fr auto;align-items:center;gap:.45rem;padding:.48rem;border:1px solid var(--glass-border);border-radius:10px;background:rgba(255,255,255,.018);}
|
|
592
|
+
.rp-call-service .ico{width:29px;height:29px;display:grid;place-items:center;border-radius:8px;background:rgba(246,215,123,.09);color:var(--gold-bright);font-size:.75rem;}
|
|
593
|
+
.rp-call-service b{display:block;font-size:.64rem;color:var(--ink);}.rp-call-service small{display:block;font-family:var(--mono);font-size:.52rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:130px;}
|
|
594
|
+
.rp-call-service .ready{font-family:var(--mono);font-size:.48rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ok);}
|
|
595
|
+
.rp-call-device{display:grid;gap:.22rem;margin-top:.45rem}.rp-call-device:first-child{margin-top:0}.rp-call-device label{font-family:var(--mono);font-size:.51rem;text-transform:uppercase;letter-spacing:.1em;color:var(--muted)}
|
|
596
|
+
.rp-call-device select{width:100%;min-width:0;font-family:var(--mono);font-size:.62rem;background:#0c0a07;border:1px solid var(--glass-border);color:var(--ink);border-radius:9px;padding:.42rem .5rem;}
|
|
597
|
+
.rp-call-stage{display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;overflow:hidden;}
|
|
598
|
+
.rp-call-stage:before{content:"";position:absolute;width:420px;height:420px;border:1px solid rgba(246,215,123,.08);border-radius:50%;box-shadow:0 0 0 42px rgba(246,215,123,.018),0 0 0 92px rgba(246,215,123,.012);}
|
|
599
|
+
.rp-call-visual{position:relative;width:min(330px,80%);aspect-ratio:1;border-radius:50%;display:grid;place-items:center;background:radial-gradient(circle,rgba(246,215,123,.14),rgba(10,8,5,.58) 52%,rgba(0,0,0,.82) 72%);border:1px solid rgba(246,215,123,.24);box-shadow:0 0 70px rgba(201,150,46,.13),inset 0 0 48px rgba(246,215,123,.06);z-index:1;transition:box-shadow .3s,border-color .3s;}
|
|
600
|
+
.rp-call-visual.speaking{box-shadow:0 0 95px rgba(246,215,123,.28),inset 0 0 55px rgba(246,215,123,.11);border-color:rgba(255,229,157,.55)}
|
|
601
|
+
.rp-call-avatar{position:absolute;width:58%;height:58%;object-fit:contain;filter:drop-shadow(0 18px 30px rgba(0,0,0,.65));opacity:.96;}
|
|
602
|
+
.rp-call-avatar:not([src]){display:none}
|
|
603
|
+
.rp-call-avatar-fallback{position:absolute;width:128px;height:128px;border-radius:50%;display:grid;place-items:center;font-family:var(--display);font-size:2.8rem;letter-spacing:.08em;color:var(--gold-pale);background:radial-gradient(circle at 32% 24%,rgba(255,234,174,.18),rgba(201,150,46,.07) 48%,rgba(0,0,0,.2));border:1px solid rgba(246,215,123,.2);box-shadow:inset 0 0 34px rgba(246,215,123,.06),0 18px 45px rgba(0,0,0,.42);}
|
|
604
|
+
.rp-call-avatar[src]{z-index:1}.rp-call-avatar[src]+.rp-call-avatar-fallback{display:none}
|
|
605
|
+
.rp-call-bars{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:7px;opacity:.82}.rp-call-bars i{width:5px;height:16px;border-radius:8px;background:linear-gradient(var(--gold-pale),var(--gold));box-shadow:0 0 12px rgba(246,215,123,.35);transition:height .09s ease;}
|
|
606
|
+
.rp-call-ring{position:absolute;inset:-10px;border:1px solid rgba(246,215,123,.13);border-radius:50%;animation:rp-call-spin 18s linear infinite}.rp-call-ring:before,.rp-call-ring:after{content:"";position:absolute;width:7px;height:7px;border-radius:50%;background:var(--gold-bright);box-shadow:0 0 15px var(--gold-bright)}.rp-call-ring:before{left:15%;top:12%}.rp-call-ring:after{right:10%;bottom:18%}
|
|
607
|
+
@keyframes rp-call-spin{to{transform:rotate(360deg)}}
|
|
608
|
+
.rp-call-agent-state{position:relative;z-index:2;margin-top:1.15rem;text-align:center}.rp-call-agent-state strong{display:block;font-family:var(--display);font-size:1.35rem;color:var(--gold-pale);font-weight:500}.rp-call-agent-state span{font-family:var(--mono);font-size:.55rem;letter-spacing:.13em;color:var(--muted);text-transform:uppercase}
|
|
609
|
+
.rp-call-state{display:flex;align-items:center;justify-content:center;gap:.5rem;font-family:var(--mono);font-size:.62rem;padding:.55rem 0;position:relative;z-index:2;}
|
|
610
|
+
.rp-call-orb{width:8px;height:8px;border-radius:50%;background:var(--muted)}.rp-call-orb.live{background:var(--ok);box-shadow:0 0 12px var(--ok);animation:pulse 1.6s infinite}.rp-call-orb.busy{background:var(--gold-bright);box-shadow:0 0 12px var(--gold-bright);animation:pulse 1s infinite}
|
|
611
|
+
.rp-call-latency{position:relative;z-index:2;display:grid;grid-template-columns:repeat(3,1fr);gap:.4rem;width:min(420px,92%);margin-top:.65rem}.rp-call-latency div{padding:.48rem;border:1px solid var(--glass-border);border-radius:10px;background:rgba(0,0,0,.28);text-align:center}.rp-call-latency b{display:block;font-family:var(--mono);font-size:.72rem;color:var(--gold-bright)}.rp-call-latency span{font-family:var(--mono);font-size:.46rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
|
|
612
|
+
.rp-call-media{position:absolute;right:1rem;top:1rem;width:128px;aspect-ratio:4/3;border:1px solid var(--glass-border);border-radius:12px;background:#050504;overflow:hidden;display:none;z-index:4;box-shadow:var(--shadow)}.rp-call-media.on{display:block}.rp-call-media video{width:100%;height:100%;object-fit:cover}.rp-call-media .tag{position:absolute;left:.35rem;bottom:.3rem;font-family:var(--mono);font-size:.46rem;color:var(--gold-pale);background:rgba(0,0,0,.7);padding:.12rem .3rem;border-radius:4px}
|
|
613
|
+
.rp-call-controls{position:relative;z-index:3;display:flex;align-items:center;justify-content:center;gap:.52rem;margin-top:1rem;flex-wrap:wrap}.rp-call-ctl{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;border:1px solid var(--glass-border);background:rgba(255,255,255,.035);color:var(--ink);cursor:pointer;font-size:1rem;transition:.18s}.rp-call-ctl:hover:not(:disabled){transform:translateY(-2px);border-color:var(--gold-bright);color:var(--gold-bright);box-shadow:0 7px 24px rgba(0,0,0,.38)}.rp-call-ctl.on{background:rgba(246,215,123,.12);border-color:rgba(246,215,123,.46);color:var(--gold-bright)}.rp-call-ctl.danger{background:rgba(255,78,63,.13);border-color:rgba(255,126,110,.35);color:var(--bad)}.rp-call-ctl:disabled{opacity:.32;cursor:not-allowed}.rp-call-start{width:auto;border-radius:100px;padding:0 1.15rem;font-family:var(--mono);font-size:.61rem;text-transform:uppercase;letter-spacing:.08em;background:linear-gradient(135deg,#ffe59a,#b77b16);color:#201401;border-color:#ffdc7d;font-weight:700}
|
|
614
|
+
.rp-call-chat{display:flex;flex-direction:column;min-height:0}.rp-call-chat-head{display:flex;align-items:center;gap:.45rem;padding-bottom:.65rem;border-bottom:1px solid var(--glass-border)}.rp-call-chat-head b{font-family:var(--display);font-size:.94rem;color:var(--gold-pale)}.rp-call-chat-head button{margin-left:auto;background:none;border:0;color:var(--muted);font-family:var(--mono);font-size:.55rem;cursor:pointer}.rp-call-chat-head button:hover{color:var(--gold-bright)}
|
|
615
|
+
.rp-call-transcript{height:500px;overflow-y:auto;display:flex;flex-direction:column;gap:.55rem;padding:.7rem .15rem;scrollbar-color:var(--gold-deep) transparent}.rp-call-transcript.collapsed{display:none}
|
|
616
|
+
.rp-turn{max-width:90%;border:1px solid var(--glass-border);border-radius:14px;padding:.6rem .7rem;font-size:.71rem;line-height:1.5;background:rgba(255,255,255,.025);box-shadow:0 8px 22px rgba(0,0,0,.2)}.rp-turn.user{align-self:flex-end;border-color:rgba(246,215,123,.22);background:linear-gradient(145deg,rgba(201,150,46,.13),rgba(255,255,255,.025));border-bottom-right-radius:4px}.rp-turn.assistant{align-self:flex-start;border-color:rgba(246,215,123,.38);border-bottom-left-radius:4px}.rp-turn .who{font-family:var(--mono);font-size:.5rem;text-transform:uppercase;letter-spacing:.1em;color:var(--gold);margin-bottom:.2rem}
|
|
617
|
+
.rp-call-compose{display:flex;gap:.4rem;margin-top:auto;padding-top:.65rem;border-top:1px solid var(--glass-border)}.rp-call-compose input{flex:1;min-width:0;background:#0c0a07;border:1px solid var(--glass-border);color:var(--ink);border-radius:11px;padding:.58rem .65rem;font-size:.7rem}.rp-call-compose input:focus{outline:none;border-color:var(--gold-bright);box-shadow:0 0 0 3px rgba(246,215,123,.08)}.rp-call-compose button{width:38px;border-radius:11px;border:1px solid rgba(246,215,123,.38);background:rgba(246,215,123,.1);color:var(--gold-bright);cursor:pointer}
|
|
618
|
+
.rp-call-event{min-height:42px;margin-top:.65rem;padding:.5rem .6rem;border:1px solid var(--glass-border);border-radius:10px;background:rgba(0,0,0,.22);font-family:var(--mono);font-size:.54rem;color:var(--muted);line-height:1.45}.rp-call-event b{color:var(--gold-bright)}
|
|
550
619
|
.rp-detail-hero{display:flex;align-items:center;gap:.8rem;padding:.6rem;border:1px solid var(--glass-border);border-radius:12px;background:var(--panel-2);margin-bottom:.8rem;}
|
|
551
620
|
.rp-detail-hero img{width:72px;height:72px;object-fit:contain;}
|
|
552
621
|
.rp-pipeline{display:flex;flex-direction:column;gap:.3rem;margin-top:.5rem;}
|
|
@@ -556,7 +625,8 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
556
625
|
.node .node-avatar{width:68px;height:68px;object-fit:contain;flex:none;filter:drop-shadow(0 7px 12px rgba(0,0,0,.3));}
|
|
557
626
|
.node .node-health{font-family:var(--mono);font-size:.61rem;line-height:1.45;color:var(--muted);}
|
|
558
627
|
.node .node-health b{color:var(--ink);}
|
|
559
|
-
@media(max-width:
|
|
628
|
+
@media(max-width:1150px){.rp-call-grid{grid-template-columns:250px minmax(360px,1fr)}.rp-call-chat{grid-column:1/-1;min-height:390px}.rp-call-transcript{height:280px}}
|
|
629
|
+
@media(max-width:760px){.rp-call-shell{border-radius:15px}.rp-call-grid{display:block}.rp-call-rail,.rp-call-stage,.rp-call-chat{border-bottom:1px solid var(--glass-border)}.rp-call-stage{min-height:590px}.rp-call-top .session{display:none}.rp-call-visual{width:min(285px,78vw)}.rp-call-transcript{height:320px}.rp-pipeline-row{grid-template-columns:62px 105px 58px 1fr}}
|
|
560
630
|
|
|
561
631
|
/* ── TTS voice picker (used inside the generic modal) ── */
|
|
562
632
|
.rp-voice-picker{display:flex;flex-direction:column;gap:0.6rem;min-height:320px;}
|
|
@@ -584,9 +654,40 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
584
654
|
.rp-vp-id{font-size:0.62rem;color:var(--muted);margin-top:0.1rem;word-break:break-all;}
|
|
585
655
|
.rp-vp-desc{font-size:0.64rem;color:var(--muted);margin-top:0.2rem;line-height:1.4;}
|
|
586
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);}
|
|
587
|
-
.rp-voice-picker-foot .rp-muted{color:var(--muted);}
|
|
588
|
-
|
|
589
|
-
|
|
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;}
|
|
590
691
|
.rp-fleet-card{position:relative;}
|
|
591
692
|
.rp-fleet-card .spark{margin-top:0.5rem;height:36px;}
|
|
592
693
|
.rp-fleet-card .rp-actions{margin-top:0.6rem;}
|
|
@@ -626,12 +727,25 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
626
727
|
<button class="tab" data-tab="robopark" id="tabbtn-robopark">Sessions & Telemetry</button>
|
|
627
728
|
</div>
|
|
628
729
|
|
|
629
|
-
<div class="pane active" id="pane-fleet">
|
|
630
|
-
<div class="wrap">
|
|
631
|
-
<section>
|
|
632
|
-
<
|
|
633
|
-
|
|
634
|
-
|
|
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>
|
|
635
749
|
</section>
|
|
636
750
|
<aside>
|
|
637
751
|
<h2>Live activity</h2>
|
|
@@ -656,16 +770,39 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
656
770
|
</aside>
|
|
657
771
|
<div class="pk-hint">drag to pan · scroll to zoom · <b>click a robot</b> for cam / mic / speaker</div>
|
|
658
772
|
</div>
|
|
659
|
-
<div class="pk-drawer" id="pk-drawer">
|
|
773
|
+
<div class="pk-drawer" id="pk-drawer">
|
|
660
774
|
<div class="pk-dh">
|
|
661
775
|
<div class="pk-av" id="pk-av">🤖</div>
|
|
662
776
|
<div><div class="nm" id="pk-name">robot</div><div class="sub" id="pk-sub">satellite</div></div>
|
|
663
777
|
<span class="pk-pill idle" id="pk-state" style="margin-left:0.4rem">idle</span>
|
|
664
778
|
<button class="x" id="pk-close">✕</button>
|
|
665
779
|
</div>
|
|
666
|
-
<div class="pk-dbody" id="pk-body"></div>
|
|
667
|
-
</div>
|
|
668
|
-
</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>
|
|
669
806
|
|
|
670
807
|
<div class="pane" id="pane-robopark">
|
|
671
808
|
<div class="rp-head">
|
|
@@ -687,9 +824,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
687
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>
|
|
688
825
|
<button class="rp-subtab" data-sub="voice" id="rpsbtn-voice" role="tab" aria-selected="false">Voice Call</button>
|
|
689
826
|
<button class="rp-subtab" data-sub="history" id="rpsbtn-history" role="tab" aria-selected="false">History</button>
|
|
690
|
-
<button class="rp-subtab" data-sub="audit" id="rpsbtn-audit" role="tab" aria-selected="false">Audit Log</button>
|
|
691
|
-
<button class="rp-subtab" data-sub="fleet" id="rpsbtn-fleet" role="tab" aria-selected="false">Fleet</button>
|
|
692
|
-
<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>
|
|
693
831
|
</div>
|
|
694
832
|
|
|
695
833
|
<!-- ═══ Overview ═══ -->
|
|
@@ -729,23 +867,65 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
729
867
|
<!-- ═══ History ═══ -->
|
|
730
868
|
<div class="rp-sub" id="rp-sub-voice">
|
|
731
869
|
<div class="rp-call-shell">
|
|
732
|
-
<div class="rp-call-
|
|
733
|
-
<div class="
|
|
734
|
-
<div class="
|
|
735
|
-
<div class="rp-field"><label>Character preset</label><select id="rp-call-character-select"><option value="">select character...</option></select></div>
|
|
736
|
-
<div class="rp-field" style="margin-top:.55rem"><label>Voice stack</label><select id="rp-call-stack-select"><option value="">character default</option></select></div>
|
|
737
|
-
<div class="rp-call-state"><span class="rp-call-orb" id="rp-call-orb"></span><span id="rp-call-status">ready to start</span></div>
|
|
738
|
-
<div class="rp-call-actions">
|
|
739
|
-
<button class="rp-btn primary" id="rp-call-start">Start voice call</button>
|
|
740
|
-
<button class="rp-btn" id="rp-call-mute" disabled>Mute microphone</button>
|
|
741
|
-
<button class="rp-btn danger" id="rp-call-end" disabled>End call</button>
|
|
742
|
-
</div>
|
|
743
|
-
<div class="pk-note" style="margin-top:.7rem">Uses your browser microphone. Agent audio plays here and every final transcript turn is saved to session history.</div>
|
|
870
|
+
<div class="rp-call-top">
|
|
871
|
+
<div><div class="eyebrow">RoboVoice operator channel</div><div class="title">Royal Voice Studio</div></div>
|
|
872
|
+
<div class="session" id="rp-call-session">No active session</div>
|
|
744
873
|
</div>
|
|
745
|
-
<div class="rp-call-
|
|
746
|
-
<
|
|
747
|
-
|
|
748
|
-
|
|
874
|
+
<div class="rp-call-grid">
|
|
875
|
+
<aside class="rp-call-rail">
|
|
876
|
+
<div class="rp-call-character" id="rp-call-character"><div><div class="name">Choose a character</div><div class="meta">Preset and voice stack are frozen into session history.</div></div></div>
|
|
877
|
+
<div class="rp-call-card">
|
|
878
|
+
<div class="rp-call-device"><label>Character preset</label><select id="rp-call-character-select"><option value="">select character...</option></select></div>
|
|
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>
|
|
881
|
+
</div>
|
|
882
|
+
<div class="rp-call-card">
|
|
883
|
+
<div class="rp-section-h" style="margin:0 0 .5rem">Voice pipeline <span class="meta">selected stack</span></div>
|
|
884
|
+
<div class="rp-call-service-grid" id="rp-call-services">
|
|
885
|
+
<div class="rp-call-service"><span class="ico">STT</span><div><b>Speech recognition</b><small>choose a stack</small></div><span class="ready">standby</span></div>
|
|
886
|
+
<div class="rp-call-service"><span class="ico">LLM</span><div><b>Language model</b><small>choose a stack</small></div><span class="ready">standby</span></div>
|
|
887
|
+
<div class="rp-call-service"><span class="ico">TTS</span><div><b>Voice synthesis</b><small>choose a stack</small></div><span class="ready">standby</span></div>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="rp-call-card">
|
|
891
|
+
<div class="rp-section-h" style="margin:0 0 .5rem">Browser devices <span class="meta" id="rp-call-device-status">permission on start</span></div>
|
|
892
|
+
<div class="rp-call-device"><label>Microphone</label><select id="rp-call-mic-device"><option value="">system default</option></select></div>
|
|
893
|
+
<div class="rp-call-device"><label>Camera</label><select id="rp-call-cam-device"><option value="">system default</option></select></div>
|
|
894
|
+
<div class="rp-call-device"><label>Speaker output</label><select id="rp-call-speaker-device"><option value="">system default</option></select></div>
|
|
895
|
+
<button class="rp-btn" id="rp-call-refresh-devices" style="width:100%;margin-top:.55rem">Refresh browser devices</button>
|
|
896
|
+
</div>
|
|
897
|
+
</aside>
|
|
898
|
+
<main class="rp-call-stage">
|
|
899
|
+
<div class="rp-call-media" id="rp-call-media"><video id="rp-call-video" autoplay muted playsinline></video><span class="tag" id="rp-call-media-tag">CAMERA</span></div>
|
|
900
|
+
<div class="rp-call-visual" id="rp-call-visual">
|
|
901
|
+
<div class="rp-call-ring"></div>
|
|
902
|
+
<img class="rp-call-avatar" id="rp-call-avatar" alt="" onerror="this.removeAttribute('src')">
|
|
903
|
+
<div class="rp-call-avatar-fallback" id="rp-call-avatar-fallback" aria-hidden="true">RP</div>
|
|
904
|
+
<div class="rp-call-bars" id="rp-call-bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
|
905
|
+
</div>
|
|
906
|
+
<div class="rp-call-agent-state"><strong id="rp-call-agent-state">Ready when you are</strong><span id="rp-call-agent-detail">select character and begin</span></div>
|
|
907
|
+
<div class="rp-call-state"><span class="rp-call-orb" id="rp-call-orb"></span><span id="rp-call-status">ready to start</span><span class="mono" id="rp-call-timer">00:00</span></div>
|
|
908
|
+
<div class="rp-call-latency">
|
|
909
|
+
<div><b id="rp-call-lat-total">--</b><span>Total response</span></div>
|
|
910
|
+
<div><b id="rp-call-lat-stt">--</b><span>STT to LLM</span></div>
|
|
911
|
+
<div><b id="rp-call-lat-tts">--</b><span>LLM to TTS</span></div>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="rp-call-controls">
|
|
914
|
+
<button class="rp-call-ctl rp-call-start" id="rp-call-start">Start call</button>
|
|
915
|
+
<button class="rp-call-ctl" id="rp-call-mute" title="Microphone" disabled>MIC</button>
|
|
916
|
+
<button class="rp-call-ctl" id="rp-call-camera" title="Camera" disabled>CAM</button>
|
|
917
|
+
<button class="rp-call-ctl" id="rp-call-screen" title="Share screen" disabled>SCR</button>
|
|
918
|
+
<button class="rp-call-ctl" id="rp-call-chat-toggle" title="Toggle transcript">TXT</button>
|
|
919
|
+
<button class="rp-call-ctl danger" id="rp-call-end" title="End call" disabled>END</button>
|
|
920
|
+
</div>
|
|
921
|
+
<div class="rp-call-event" id="rp-call-event"><b>System</b> Voice pipeline is standing by.</div>
|
|
922
|
+
</main>
|
|
923
|
+
<section class="rp-call-chat">
|
|
924
|
+
<div class="rp-call-chat-head"><b>Conversation</b><span class="chip" id="rp-call-message-count">0 messages</span><button id="rp-call-collapse">collapse transcript</button></div>
|
|
925
|
+
<div class="rp-call-transcript" id="rp-call-transcript"><div class="rp-tbl-empty">Your live transcript and typed messages appear here.</div></div>
|
|
926
|
+
<form class="rp-call-compose" id="rp-call-compose"><input id="rp-call-message" autocomplete="off" placeholder="Type a message to the character..." disabled><button type="submit" id="rp-call-send" disabled>SEND</button></form>
|
|
927
|
+
<div id="rp-call-audio" hidden></div>
|
|
928
|
+
</section>
|
|
749
929
|
</div>
|
|
750
930
|
</div>
|
|
751
931
|
</div>
|
|
@@ -873,8 +1053,16 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
873
1053
|
</div>
|
|
874
1054
|
|
|
875
1055
|
<!-- ═══ Fleet ═══ -->
|
|
876
|
-
<div class="rp-sub" id="rp-sub-fleet">
|
|
877
|
-
<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>
|
|
878
1066
|
<div class="rp-fleet-grid" id="rp-grid"></div>
|
|
879
1067
|
</div>
|
|
880
1068
|
|
|
@@ -937,79 +1125,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
937
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>
|
|
938
1126
|
</div>
|
|
939
1127
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
<div
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
<
|
|
951
|
-
<
|
|
952
|
-
<div class="rp-
|
|
953
|
-
<div class="rp-
|
|
954
|
-
<
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
<div class="rp-
|
|
958
|
-
<div class="rp-
|
|
959
|
-
<div class="rp-
|
|
960
|
-
<
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
</div>
|
|
967
|
-
<div class="rp-autonote" id="rpve-tts-voice-note" style="margin-top:0.35rem;"></div>
|
|
968
|
-
</div>
|
|
969
|
-
<div class="rp-field"><label>TTS language</label><input id="rpve-tts-language" placeholder="en"></div>
|
|
970
|
-
<div class="rp-field"><label>Max turns</label><input id="rpve-max-turns" type="number" value="20"></div>
|
|
971
|
-
<div class="rp-field"><label>Min endpointing delay (s)</label><input id="rpve-endpointing" type="number" step="0.1" value="0.7"></div>
|
|
972
|
-
<div class="rp-field"><label><input type="checkbox" id="rpve-interruptions" checked style="width:auto;margin-right:0.35rem;">Allow interruptions</label></div>
|
|
973
|
-
<div class="rp-field"><label><input type="checkbox" id="rpve-wake-enabled" style="width:auto;margin-right:0.35rem;">Wake word enabled</label></div>
|
|
974
|
-
<div class="rp-field"><label>Wake word model</label><input id="rpve-wake-model" placeholder="models/hey_jarvis.onnx"></div>
|
|
975
|
-
<div class="rp-field"><label>Wake threshold</label><input id="rpve-wake-threshold" type="number" step="0.05" value="0.5"></div>
|
|
976
|
-
<div class="rp-field"><label>Wake timeout (s)</label><input id="rpve-wake-timeout" type="number" step="0.5" value="3.0"></div>
|
|
977
|
-
<div style="grid-column:1/-1;display:flex;gap:0.4rem;">
|
|
978
|
-
<button class="rp-btn" id="rpve-save">Save</button>
|
|
979
|
-
<button class="rp-btn" id="rpve-cancel">Cancel</button>
|
|
980
|
-
</div>
|
|
981
|
-
</div>
|
|
982
|
-
</div>
|
|
983
|
-
<div>
|
|
984
|
-
<h4 style="font-family:var(--mono);font-size:0.75rem;color:var(--gold-bright);margin:0.3rem 0 0.5rem;">Character presets</h4>
|
|
985
|
-
<div id="rp-char-list" style="display:flex;flex-direction:column;gap:0.4rem;max-height:260px;overflow:auto;"></div>
|
|
986
|
-
<div style="display:flex;gap:0.4rem;margin-top:0.55rem;">
|
|
987
|
-
<input id="rp-newchar-name" placeholder="new preset name" style="flex:1;min-width:0;">
|
|
988
|
-
<input id="rp-newchar-stack" list="rp-newchar-stack-list" placeholder="voice stack id" style="flex:1;min-width:0;">
|
|
989
|
-
<datalist id="rp-newchar-stack-list"></datalist>
|
|
990
|
-
<button class="rp-btn" id="rp-addchar">Add</button>
|
|
991
|
-
</div>
|
|
992
|
-
<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);">
|
|
993
|
-
<h4 style="margin:0 0 0.2rem;">Edit character: <span id="rpce-name-label"></span></h4>
|
|
994
|
-
<div class="rp-field"><label>Name</label><input id="rpce-name"></div>
|
|
995
|
-
<div class="rp-field"><label>Description</label><input id="rpce-description"></div>
|
|
996
|
-
<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>
|
|
997
|
-
<div class="rp-field"><label>Voice stack</label><select id="rpce-stack"></select></div>
|
|
998
|
-
<div class="rp-field"><label>Motors (comma-separated)</label><input id="rpce-motors" placeholder="drive, wave"></div>
|
|
999
|
-
<div style="display:flex;gap:0.4rem;">
|
|
1000
|
-
<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>
|
|
1001
|
-
<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>
|
|
1002
|
-
</div>
|
|
1003
|
-
<div class="rp-field"><label>Avatar image path</label><input id="rpce-img" placeholder="/static/robopark/jaguar.png"></div>
|
|
1004
|
-
<div style="display:flex;gap:0.4rem;">
|
|
1005
|
-
<button class="rp-btn" id="rpce-save">Save</button>
|
|
1006
|
-
<button class="rp-btn" id="rpce-cancel">Cancel</button>
|
|
1007
|
-
</div>
|
|
1008
|
-
</div>
|
|
1009
|
-
</div>
|
|
1010
|
-
</div>
|
|
1011
|
-
</div>
|
|
1012
|
-
</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>
|
|
1013
1154
|
|
|
1014
1155
|
<!-- Generic modal: title + scrollable body, used for session + audit detail views -->
|
|
1015
1156
|
<div class="rp-modal-back" id="rp-modal-back" role="dialog" aria-modal="true">
|
|
@@ -1219,9 +1360,70 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1219
1360
|
}
|
|
1220
1361
|
return null;
|
|
1221
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
|
+
}
|
|
1222
1423
|
function renderFleet(nodes){
|
|
1223
|
-
var el = $('fleet');
|
|
1224
|
-
|
|
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; }
|
|
1225
1427
|
// connected first, then robots/satellites above hubs so the fleet reads robot-first
|
|
1226
1428
|
nodes.sort(function(a,b){ return (b.connected?1:0)-(a.connected?1:0); });
|
|
1227
1429
|
var accent = '#6FE0A6';
|
|
@@ -1269,18 +1471,21 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1269
1471
|
} else {
|
|
1270
1472
|
html += '<div class="stats"><span style="color:var(--muted)">offline'+(n.lastSeenAt?(' · last seen '+clock(n.lastSeenAt)):'')+'</span></div>';
|
|
1271
1473
|
}
|
|
1272
|
-
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>';
|
|
1273
1476
|
}
|
|
1274
1477
|
el.innerHTML = html;
|
|
1275
1478
|
var cards = el.querySelectorAll('.node');
|
|
1276
1479
|
for(var j=0;j<cards.length;j++){
|
|
1277
1480
|
(function(card){
|
|
1278
|
-
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')); }
|
|
1279
1482
|
card.addEventListener('click', pick);
|
|
1280
1483
|
card.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ ev.preventDefault(); pick(); } });
|
|
1281
|
-
})(cards[j]);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
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
|
+
}
|
|
1284
1489
|
|
|
1285
1490
|
function renderSummary(s){
|
|
1286
1491
|
var nodes = s.nodes||[];
|
|
@@ -1560,7 +1765,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1560
1765
|
btn.addEventListener('click', function(){ selectTab(btn.getAttribute('data-tab')); });
|
|
1561
1766
|
})(tabs[ti]);
|
|
1562
1767
|
}
|
|
1563
|
-
function selectTab(name){
|
|
1768
|
+
function selectTab(name){
|
|
1564
1769
|
activeTab = name;
|
|
1565
1770
|
for (var i=0;i<tabs.length;i++) tabs[i].classList.toggle('active', tabs[i].getAttribute('data-tab')===name);
|
|
1566
1771
|
$('pane-fleet').classList.toggle('active', name==='fleet');
|
|
@@ -1571,8 +1776,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1571
1776
|
pollRobopark();
|
|
1572
1777
|
initRpSubTabs();
|
|
1573
1778
|
}
|
|
1574
|
-
if (name==='park') Park.activate(); else Park.deactivate();
|
|
1575
|
-
}
|
|
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();});
|
|
1576
1785
|
var initHash = location.hash.replace('#','');
|
|
1577
1786
|
// Defer tab init until Park module is defined (same outer IIFE, later in source).
|
|
1578
1787
|
function initTab(){
|
|
@@ -1975,8 +2184,66 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1975
2184
|
}
|
|
1976
2185
|
$('srv-add').addEventListener('click', addServer);
|
|
1977
2186
|
$('rb-add').addEventListener('click', addRobot);
|
|
1978
|
-
$('rp-addvoice').addEventListener('click', function(){
|
|
1979
|
-
$('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
|
+
}
|
|
1980
2247
|
|
|
1981
2248
|
function addVoiceStackFromTemplate(){
|
|
1982
2249
|
var name=$('rp-newvoice-name').value.trim(); if(!name){ showOut('Voice stack name required'); return; }
|
|
@@ -1988,25 +2255,28 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1988
2255
|
var id=name.toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,32);
|
|
1989
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); });
|
|
1990
2257
|
}
|
|
1991
|
-
function fillTemplateVoiceList(){
|
|
1992
|
-
var el=$('rp-voice-list'); if(!el) return;
|
|
1993
|
-
var html='';
|
|
1994
|
-
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<
|
|
1995
|
-
if(!
|
|
1996
|
-
el.innerHTML=html;
|
|
1997
|
-
var
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
var
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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
|
+
}
|
|
2006
2275
|
|
|
2007
2276
|
// ── voice-stack edit panel ──
|
|
2008
|
-
var editingVoiceStackId=null;
|
|
2009
|
-
function openVoiceStackEdit(id){
|
|
2277
|
+
var editingVoiceStackId=null;
|
|
2278
|
+
function openVoiceStackEdit(id){
|
|
2279
|
+
openVoiceStackManager(id); return;
|
|
2010
2280
|
var s=null; for(var i=0;i<voiceStacks.length;i++){ if(voiceStacks[i].id===id){ s=voiceStacks[i]; break; } }
|
|
2011
2281
|
if(!s) return;
|
|
2012
2282
|
editingVoiceStackId=id;
|
|
@@ -2050,7 +2320,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2050
2320
|
};
|
|
2051
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); });
|
|
2052
2322
|
}
|
|
2053
|
-
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); });
|
|
2054
2324
|
var vsSave=$('rpve-save'); if(vsSave) vsSave.addEventListener('click', saveVoiceStackEdit);
|
|
2055
2325
|
var vsCancel=$('rpve-cancel'); if(vsCancel) vsCancel.addEventListener('click', function(){ $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; });
|
|
2056
2326
|
// TTS provider change: re-populate the voice <select> for the new provider.
|
|
@@ -2087,13 +2357,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2087
2357
|
});
|
|
2088
2358
|
|
|
2089
2359
|
// ── character-preset edit panel ──
|
|
2090
|
-
var editingCharPresetId=null;
|
|
2360
|
+
var editingCharPresetId=null;
|
|
2091
2361
|
function fillCharEditStackOptions(cur){
|
|
2092
2362
|
var sel=$('rpce-stack'); if(!sel) return;
|
|
2093
2363
|
sel.innerHTML='<option value="">— default —</option>';
|
|
2094
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); }
|
|
2095
2365
|
}
|
|
2096
|
-
function openCharPresetEdit(id){
|
|
2366
|
+
function openCharPresetEdit(id){
|
|
2367
|
+
openCharacterManager(id); return;
|
|
2097
2368
|
var p=null; for(var i=0;i<characterPresets.length;i++){ if(characterPresets[i].id===id){ p=characterPresets[i]; break; } }
|
|
2098
2369
|
if(!p) return;
|
|
2099
2370
|
editingCharPresetId=id;
|
|
@@ -2118,9 +2389,17 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2118
2389
|
};
|
|
2119
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); });
|
|
2120
2391
|
}
|
|
2121
|
-
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); });
|
|
2122
2393
|
var cpSave=$('rpce-save'); if(cpSave) cpSave.addEventListener('click', saveCharPresetEdit);
|
|
2123
|
-
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
|
+
});
|
|
2124
2403
|
|
|
2125
2404
|
function renderRobotCardSelect(name, current){
|
|
2126
2405
|
var sel=$('rp-char-'+name); if(!sel) return;
|
|
@@ -2150,8 +2429,9 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2150
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>';
|
|
2151
2430
|
return;
|
|
2152
2431
|
}
|
|
2153
|
-
var robots = robotsFrom(d);
|
|
2154
|
-
|
|
2432
|
+
var robots = robotsFrom(d);
|
|
2433
|
+
renderFleetOperations([], 'rp-fleet', robots);
|
|
2434
|
+
var tot = (d && d.totals) || {};
|
|
2155
2435
|
var totalSessions = tot.sessions_total!=null?tot.sessions_total:robots.reduce(function(a,x){return a+(x.sessions_total||0);},0);
|
|
2156
2436
|
var totalTrig = tot.trigger_count!=null?tot.trigger_count:robots.reduce(function(a,x){return a+(x.trigger_count||0);},0);
|
|
2157
2437
|
totals.innerHTML =
|
|
@@ -2179,8 +2459,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2179
2459
|
+ '<div class="rp-m"><span class="v '+dcls+'">'+drop+'</span><span class="l">drop rate</span></div>'
|
|
2180
2460
|
+ '</div><div class="rp-actions">'
|
|
2181
2461
|
+ '<button class="rp-btn" data-act="trigger" data-robot="'+idAttr+'"'+(rpReadOnly?' disabled':'')+'>Trigger motion</button>'
|
|
2182
|
-
+ '<button class="rp-btn" data-act="preview" data-robot="'+idAttr+'">Preview</button>'
|
|
2183
|
-
+ '<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>':'')
|
|
2184
2466
|
+ '</div>'
|
|
2185
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>'
|
|
2186
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>':'')
|
|
@@ -2275,7 +2557,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2275
2557
|
s.classList.toggle('active', s.id==='rp-sub-'+name);
|
|
2276
2558
|
}
|
|
2277
2559
|
// stop every sub-tab interval, then start only the active one
|
|
2278
|
-
var names = ['overview','incidents','live','voice','history','audit','fleet','setup'];
|
|
2560
|
+
var names = ['overview','incidents','live','voice','history','audit','fleet','config','setup'];
|
|
2279
2561
|
for(var k=0;k<names.length;k++){
|
|
2280
2562
|
var n=names[k];
|
|
2281
2563
|
if(rpIntervals[n]){ clearInterval(rpIntervals[n]); rpIntervals[n]=null; }
|
|
@@ -2302,7 +2584,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2302
2584
|
var h=String(location.hash||'').replace('#','');
|
|
2303
2585
|
var m = h.match(/^robopark\\/(.+)$/);
|
|
2304
2586
|
var initial = (m && m[1]) ? m[1] : 'overview';
|
|
2305
|
-
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';
|
|
2306
2588
|
setRpSubTab(initial);
|
|
2307
2589
|
}
|
|
2308
2590
|
|
|
@@ -2314,12 +2596,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2314
2596
|
$('rp-modal-body').innerHTML = bodyHtml || '';
|
|
2315
2597
|
back.classList.add('open');
|
|
2316
2598
|
}
|
|
2317
|
-
function closeModal(){
|
|
2318
|
-
var back = $('rp-modal-back');
|
|
2319
|
-
if(!back) return;
|
|
2320
|
-
back.classList.remove('open');
|
|
2321
|
-
|
|
2322
|
-
|
|
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
|
+
}
|
|
2323
2606
|
// wire the close handlers (once)
|
|
2324
2607
|
(function(){
|
|
2325
2608
|
var closeBtn = $('rp-modal-close');
|
|
@@ -2479,13 +2762,13 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2479
2762
|
// input. Bound once on the document so re-opening the modal doesn't
|
|
2480
2763
|
// stack listeners.
|
|
2481
2764
|
document.addEventListener('click', function(e){
|
|
2482
|
-
var btn = e.target.closest && e.target.closest('.rp-vp-item');
|
|
2483
|
-
if(btn){
|
|
2484
|
-
var vid = btn.getAttribute('data-voice-id');
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
return;
|
|
2488
|
-
}
|
|
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
|
+
}
|
|
2489
2772
|
var r = e.target.closest && e.target.closest('#rp-voice-picker-refresh');
|
|
2490
2773
|
if(r){
|
|
2491
2774
|
// Re-fetch the catalog, ignore cache, then re-render.
|
|
@@ -2597,7 +2880,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2597
2880
|
})();
|
|
2598
2881
|
|
|
2599
2882
|
// ── History sub-tab ──
|
|
2600
|
-
var rpVoiceCall={room:null,session:null,eventToken:null,muted:false,ending:false,seen:{}};
|
|
2883
|
+
var rpVoiceCall={room:null,LK:null,session:null,eventToken:null,muted:false,camera:false,screen:false,ending:false,seen:{},messages:0,timer:null,startedAt:0,audioContext:null,analyser:null,audioRaf:0,transcriptCollapsed:false};
|
|
2601
2884
|
function refreshVoiceCallOptions(){
|
|
2602
2885
|
var cs=$('rp-call-character-select'), ss=$('rp-call-stack-select');
|
|
2603
2886
|
if(!cs||!ss) return;
|
|
@@ -2605,19 +2888,35 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2605
2888
|
cs.innerHTML='<option value="">select character...</option>'+(characterPresets||[]).map(function(c){return '<option value="'+esc(c.id)+'">'+esc(c.name||c.id)+'</option>';}).join('');
|
|
2606
2889
|
ss.innerHTML='<option value="">character default</option>'+(voiceStacks||[]).map(function(s){return '<option value="'+esc(s.id)+'">'+esc(s.name||s.id)+' - '+esc(s.stt_provider)+' / '+esc(s.llm_model)+' / '+esc(s.tts_provider)+'</option>';}).join('');
|
|
2607
2890
|
if(cv) cs.value=cv; if(sv) ss.value=sv;
|
|
2608
|
-
paintVoiceCallCharacter();
|
|
2891
|
+
paintVoiceCallCharacter(); paintVoiceCallStack();
|
|
2609
2892
|
}
|
|
2610
2893
|
function paintVoiceCallCharacter(){
|
|
2611
2894
|
var sel=$('rp-call-character-select'), box=$('rp-call-character'); if(!sel||!box) return;
|
|
2612
2895
|
var chosen=null; for(var i=0;i<characterPresets.length;i++) if(characterPresets[i].id===sel.value){chosen=characterPresets[i];break;}
|
|
2613
|
-
if(!chosen){ box.innerHTML='<div><div class="name">Choose a character</div><div class="meta">
|
|
2896
|
+
if(!chosen){ box.innerHTML='<div><div class="name">Choose a character</div><div class="meta">Preset and voice stack are frozen into session history.</div></div>'; $('rp-call-avatar').removeAttribute('src'); $('rp-call-avatar-fallback').textContent='RP'; return; }
|
|
2614
2897
|
var img=chosen.img||rpAvatarFor(chosen.id||chosen.name);
|
|
2615
2898
|
box.innerHTML=(img?'<img alt="'+esc(chosen.name)+'" src="'+esc(img+QS)+'">':'')+'<div><div class="name">'+esc(chosen.name||chosen.id)+'</div><div class="meta">'+esc(chosen.description||'Ready for an operator voice call')+'</div></div>';
|
|
2899
|
+
$('rp-call-avatar-fallback').textContent=String(chosen.name||chosen.id||'RP').slice(0,2).toUpperCase();
|
|
2900
|
+
if(img)$('rp-call-avatar').src=img+QS;else $('rp-call-avatar').removeAttribute('src');
|
|
2616
2901
|
var stack=$('rp-call-stack-select'); if(stack && !stack.value && chosen.voice_stack_id) stack.value=chosen.voice_stack_id;
|
|
2902
|
+
paintVoiceCallStack();
|
|
2903
|
+
}
|
|
2904
|
+
function selectedVoiceStack(){
|
|
2905
|
+
var sid=$('rp-call-stack-select').value, chosen=null;
|
|
2906
|
+
if(!sid){var cid=$('rp-call-character-select').value;for(var i=0;i<characterPresets.length;i++)if(characterPresets[i].id===cid){sid=characterPresets[i].voice_stack_id||'';break;}}
|
|
2907
|
+
for(var j=0;j<voiceStacks.length;j++)if(voiceStacks[j].id===sid){chosen=voiceStacks[j];break;} return chosen;
|
|
2908
|
+
}
|
|
2909
|
+
function paintVoiceCallStack(){
|
|
2910
|
+
var wrap=$('rp-call-services'); if(!wrap)return; var s=selectedVoiceStack();
|
|
2911
|
+
if(!s){wrap.innerHTML='<div class="rp-call-service"><span class="ico">STT</span><div><b>Speech recognition</b><small>choose a stack</small></div><span class="ready">standby</span></div><div class="rp-call-service"><span class="ico">LLM</span><div><b>Language model</b><small>choose a stack</small></div><span class="ready">standby</span></div><div class="rp-call-service"><span class="ico">TTS</span><div><b>Voice synthesis</b><small>choose a stack</small></div><span class="ready">standby</span></div>';return;}
|
|
2912
|
+
function svc(code,name,provider,model){return '<div class="rp-call-service"><span class="ico">'+code+'</span><div><b>'+esc(name)+'</b><small>'+esc((provider||'default')+' / '+(model||'default'))+'</small></div><span class="ready">ready</span></div>';}
|
|
2913
|
+
wrap.innerHTML=svc('STT','Speech recognition',s.stt_provider,s.stt_model)+svc('LLM','Language model',s.llm_provider,s.llm_model)+svc('TTS','Voice synthesis',s.tts_provider,s.tts_voice);
|
|
2617
2914
|
}
|
|
2618
2915
|
function setVoiceCallStatus(text,state){
|
|
2619
2916
|
var s=$('rp-call-status'), o=$('rp-call-orb'); if(s)s.textContent=text; if(o)o.className='rp-call-orb '+(state||'');
|
|
2620
2917
|
}
|
|
2918
|
+
function setVoiceAgentState(title,detail){if($('rp-call-agent-state'))$('rp-call-agent-state').textContent=title;if($('rp-call-agent-detail'))$('rp-call-agent-detail').textContent=detail||'';}
|
|
2919
|
+
function setVoiceEvent(title,detail){var e=$('rp-call-event');if(e)e.innerHTML='<b>'+esc(title)+'</b> '+esc(detail||'');}
|
|
2621
2920
|
function ensureVoiceLiveKit(){
|
|
2622
2921
|
if(window.LivekitClient) return Promise.resolve(window.LivekitClient);
|
|
2623
2922
|
return new Promise(function(resolve,reject){
|
|
@@ -2633,7 +2932,41 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2633
2932
|
var div=document.createElement('div'); div.className='rp-turn '+role;
|
|
2634
2933
|
div.innerHTML='<div class="who">'+esc(role==='user'?'operator':'character')+'</div><div>'+esc(text)+'</div>';
|
|
2635
2934
|
wrap.appendChild(div); wrap.scrollTop=wrap.scrollHeight;
|
|
2935
|
+
rpVoiceCall.messages++; if($('rp-call-message-count'))$('rp-call-message-count').textContent=rpVoiceCall.messages+' message'+(rpVoiceCall.messages===1?'':'s');
|
|
2936
|
+
}
|
|
2937
|
+
function fillDeviceSelect(id,devices,kind){
|
|
2938
|
+
var sel=$(id);if(!sel)return;var current=sel.value,rows=devices.filter(function(d){return d.kind===kind;});
|
|
2939
|
+
sel.innerHTML='<option value="">system default</option>'+rows.map(function(d,i){return '<option value="'+esc(d.deviceId)+'">'+esc(d.label||kind+' '+(i+1))+'</option>';}).join('');if(current)sel.value=current;
|
|
2636
2940
|
}
|
|
2941
|
+
function refreshBrowserDevices(requestPermission){
|
|
2942
|
+
if(!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices){$('rp-call-device-status').textContent='unsupported';return Promise.resolve([]);}
|
|
2943
|
+
var permission=Promise.resolve();
|
|
2944
|
+
if(requestPermission)permission=navigator.mediaDevices.getUserMedia({audio:true}).then(function(stream){stream.getTracks().forEach(function(t){t.stop();});}).catch(function(e){setVoiceEvent('Microphone',e.message||'permission denied');});
|
|
2945
|
+
return permission.then(function(){return navigator.mediaDevices.enumerateDevices();}).then(function(devices){fillDeviceSelect('rp-call-mic-device',devices,'audioinput');fillDeviceSelect('rp-call-cam-device',devices,'videoinput');fillDeviceSelect('rp-call-speaker-device',devices,'audiooutput');$('rp-call-device-status').textContent=devices.length+' detected';return devices;});
|
|
2946
|
+
}
|
|
2947
|
+
function switchVoiceDevice(kind,id){if(!rpVoiceCall.room||!id)return Promise.resolve();return rpVoiceCall.room.switchActiveDevice(kind,id).catch(function(e){setVoiceEvent('Device switch failed',e.message);});}
|
|
2948
|
+
function applyVoiceOutputDevice(){var id=$('rp-call-speaker-device').value;if(!id)return;var els=$('rp-call-audio').querySelectorAll('audio');for(var i=0;i<els.length;i++)if(typeof els[i].setSinkId==='function')els[i].setSinkId(id).catch(function(){});}
|
|
2949
|
+
function startVoiceVisualizer(track){
|
|
2950
|
+
stopVoiceVisualizer();try{var C=window.AudioContext||window.webkitAudioContext;if(!C||!track||!track.mediaStreamTrack)return;var ctx=new C(),an=ctx.createAnalyser(),source=ctx.createMediaStreamSource(new MediaStream([track.mediaStreamTrack]));an.fftSize=128;source.connect(an);rpVoiceCall.audioContext=ctx;rpVoiceCall.analyser=an;var data=new Uint8Array(an.frequencyBinCount),bars=$('rp-call-bars').querySelectorAll('i');
|
|
2951
|
+
function draw(){an.getByteFrequencyData(data);var sum=0;for(var x=0;x<data.length;x++)sum+=data[x];var avg=sum/data.length;for(var i=0;i<bars.length;i++){var idx=Math.floor(i*data.length/bars.length);bars[i].style.height=(12+Math.min(70,data[idx]*.28))+'px';}$('rp-call-visual').classList.toggle('speaking',avg>10);rpVoiceCall.audioRaf=requestAnimationFrame(draw);}draw();
|
|
2952
|
+
}catch(_){}
|
|
2953
|
+
}
|
|
2954
|
+
function stopVoiceVisualizer(){if(rpVoiceCall.audioRaf)cancelAnimationFrame(rpVoiceCall.audioRaf);rpVoiceCall.audioRaf=0;if(rpVoiceCall.audioContext)try{rpVoiceCall.audioContext.close();}catch(_){}rpVoiceCall.audioContext=null;rpVoiceCall.analyser=null;var bars=$('rp-call-bars');if(bars)Array.prototype.forEach.call(bars.querySelectorAll('i'),function(b){b.style.height='16px';});}
|
|
2955
|
+
function updateVoiceTimer(){if(!rpVoiceCall.startedAt)return;var sec=Math.floor((Date.now()-rpVoiceCall.startedAt)/1000),m=Math.floor(sec/60),s=sec%60;$('rp-call-timer').textContent=(m<10?'0':'')+m+':'+(s<10?'0':'')+s;}
|
|
2956
|
+
function updateAgentFromParticipant(participant){if(!participant)return;var a=participant.attributes||{},state=a['lk.agent.state']||a['agent.state']||'';if(state==='speaking')setVoiceAgentState('Speaking','character audio is streaming');else if(state==='thinking')setVoiceAgentState('Thinking','language model is composing');else if(state==='listening')setVoiceAgentState('Listening','speak naturally');else if(state==='initializing')setVoiceAgentState('Preparing agent','loading voice pipeline');}
|
|
2957
|
+
function handleVoiceData(payload,participant,kind,topic){
|
|
2958
|
+
try{var msg=JSON.parse(new TextDecoder().decode(payload));
|
|
2959
|
+
if(topic==='response_latency'){var total=Number(msg.total_ms)||0;$('rp-call-lat-total').textContent=total?total+'ms':'--';$('rp-call-lat-stt').textContent=msg.stt_to_llm_ms!=null?Math.round(msg.stt_to_llm_ms)+'ms':'--';$('rp-call-lat-tts').textContent=msg.llm_to_tts_ms!=null?Math.round(msg.llm_to_tts_ms)+'ms':'--';setVoiceEvent('Response complete',total+'ms round trip');return;}
|
|
2960
|
+
if(topic==='wakeword_state'){setVoiceEvent('Wake word',msg.state==='active'?'active - listening now':'waiting for wake word');return;}
|
|
2961
|
+
if(topic==='model_loading_status'){if(msg.is_loading){setVoiceAgentState('Loading model','cold start in progress');setVoiceEvent('Model status','loading model into memory');}return;}
|
|
2962
|
+
if(topic==='tool_status'){var names=msg.tool_names||[];setVoiceEvent('Tool activity',names.length?names.join(', '):'tool cycle complete');return;}
|
|
2963
|
+
if(topic && topic!=='lk-chat-topic')return;var text=String(msg.message||msg.text||'').trim();if(!text)return;var role=msg.role==='user'?'user':'assistant',key=role+'|'+text;if(rpVoiceCall.seen[key])return;rpVoiceCall.seen[key]=1;appendVoiceTurn(role,text);saveVoiceTurn(role,text,Object.keys(rpVoiceCall.seen).length);
|
|
2964
|
+
}catch(_){}
|
|
2965
|
+
}
|
|
2966
|
+
function attachVoiceMedia(track,tag){var video=$('rp-call-video'),box=$('rp-call-media');try{track.attach(video);box.classList.add('on');$('rp-call-media-tag').textContent=tag;}catch(_){} }
|
|
2967
|
+
function toggleVoiceCamera(){if(!rpVoiceCall.room)return;var next=!rpVoiceCall.camera,opts=$('rp-call-cam-device').value?{deviceId:$('rp-call-cam-device').value}:undefined;$('rp-call-camera').disabled=true;rpVoiceCall.room.localParticipant.setCameraEnabled(next,opts).then(function(){rpVoiceCall.camera=next;$('rp-call-camera').classList.toggle('on',next);$('rp-call-camera').textContent=next?'CAM ON':'CAM';if(next){var pub=rpVoiceCall.room.localParticipant.getTrackPublication(rpVoiceCall.LK.Track.Source.Camera);if(pub&&pub.track)attachVoiceMedia(pub.track,'CAMERA');refreshBrowserDevices(false);}else if(!rpVoiceCall.screen){$('rp-call-media').classList.remove('on');$('rp-call-video').srcObject=null;}}).catch(function(e){setVoiceEvent('Camera',e.message);}).finally(function(){$('rp-call-camera').disabled=false;});}
|
|
2968
|
+
function toggleVoiceScreen(){if(!rpVoiceCall.room)return;var next=!rpVoiceCall.screen;$('rp-call-screen').disabled=true;rpVoiceCall.room.localParticipant.setScreenShareEnabled(next).then(function(){rpVoiceCall.screen=next;$('rp-call-screen').classList.toggle('on',next);$('rp-call-screen').textContent=next?'SCR ON':'SCR';if(next){var pub=rpVoiceCall.room.localParticipant.getTrackPublication(rpVoiceCall.LK.Track.Source.ScreenShare);if(pub&&pub.track)attachVoiceMedia(pub.track,'SCREEN');}else if(!rpVoiceCall.camera){$('rp-call-media').classList.remove('on');$('rp-call-video').srcObject=null;}}).catch(function(e){setVoiceEvent('Screen share',e.message);}).finally(function(){$('rp-call-screen').disabled=false;});}
|
|
2969
|
+
function sendVoiceMessage(text){if(!rpVoiceCall.room||!text)return Promise.resolve();var payload=new TextEncoder().encode(JSON.stringify({message:text,role:'user',timestamp:Date.now()}));return rpVoiceCall.room.localParticipant.publishData(payload,{reliable:true,topic:'lk-chat-topic'}).then(function(){var key='user|'+text;if(!rpVoiceCall.seen[key]){rpVoiceCall.seen[key]=1;appendVoiceTurn('user',text);saveVoiceTurn('user',text,Object.keys(rpVoiceCall.seen).length);}});}
|
|
2637
2970
|
function saveVoiceTurn(role,text,sequence){
|
|
2638
2971
|
if(!rpVoiceCall.session||!rpVoiceCall.eventToken)return;
|
|
2639
2972
|
fetch('/robopark/api/sessions/'+encodeURIComponent(rpVoiceCall.session)+'/transcript'+QS,{
|
|
@@ -2645,15 +2978,15 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2645
2978
|
var cid=$('rp-call-character-select').value, sid=$('rp-call-stack-select').value;
|
|
2646
2979
|
if(!cid){setVoiceCallStatus('select a character first','');return;}
|
|
2647
2980
|
if(rpVoiceCall.room)return;
|
|
2648
|
-
setVoiceCallStatus('
|
|
2649
|
-
rpAction('POST','/robopark/api/sessions/voice-call',{character_preset_id:cid,voice_stack_id:sid||null,client_name:getOperatorName()||'RoboPark Operator'})
|
|
2981
|
+
setVoiceCallStatus('requesting microphone...','busy');setVoiceAgentState('Opening channel','requesting browser media permission'); $('rp-call-start').disabled=true;
|
|
2982
|
+
refreshBrowserDevices(true).then(function(){return rpAction('POST','/robopark/api/sessions/voice-call',{character_preset_id:cid,voice_stack_id:sid||null,client_name:getOperatorName()||'RoboPark Operator'});})
|
|
2650
2983
|
.then(function(call){
|
|
2651
2984
|
if(!call)throw new Error('scheduler returned no call');
|
|
2652
|
-
rpVoiceCall.session=call.session_id; rpVoiceCall.eventToken=call.event_token; rpVoiceCall.seen={};
|
|
2985
|
+
rpVoiceCall.session=call.session_id; rpVoiceCall.eventToken=call.event_token; rpVoiceCall.seen={};rpVoiceCall.messages=0;rpVoiceCall.ending=false;
|
|
2653
2986
|
$('rp-call-session').textContent=call.session_id; $('rp-call-transcript').innerHTML='<div class="rp-tbl-empty">Connecting to '+esc(call.character&&call.character.name||cid)+'...</div>';
|
|
2654
2987
|
return ensureVoiceLiveKit().then(function(LK){
|
|
2655
|
-
var room=new LK.Room({adaptiveStream:true,dynacast:true}); rpVoiceCall.room=room;
|
|
2656
|
-
room.on(LK.RoomEvent.TrackSubscribed,function(track){ if(track.kind==='audio'){var el=track.attach();el.autoplay=true;$('rp-call-audio').appendChild(el);} });
|
|
2988
|
+
var room=new LK.Room({adaptiveStream:true,dynacast:true}); rpVoiceCall.room=room;rpVoiceCall.LK=LK;
|
|
2989
|
+
room.on(LK.RoomEvent.TrackSubscribed,function(track){ if(track.kind==='audio'){var el=track.attach();el.autoplay=true;$('rp-call-audio').appendChild(el);applyVoiceOutputDevice();startVoiceVisualizer(track);setVoiceAgentState('Agent online','voice channel connected');} });
|
|
2657
2990
|
room.on(LK.RoomEvent.TranscriptionReceived,function(segments,participant){
|
|
2658
2991
|
for(var i=0;i<(segments||[]).length;i++){
|
|
2659
2992
|
var seg=segments[i], final=seg.final===true||seg.isFinal===true; if(!final||!String(seg.text||'').trim())continue;
|
|
@@ -2662,43 +2995,47 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
2662
2995
|
appendVoiceTurn(role,String(seg.text)); saveVoiceTurn(role,String(seg.text),Object.keys(rpVoiceCall.seen).length);
|
|
2663
2996
|
}
|
|
2664
2997
|
});
|
|
2665
|
-
room.on(LK.RoomEvent.DataReceived,
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
var msg=JSON.parse(new TextDecoder().decode(payload));
|
|
2669
|
-
var text=String(msg.message||msg.text||'').trim(); if(!text)return;
|
|
2670
|
-
var role=msg.role==='user'?'user':'assistant';
|
|
2671
|
-
var key=role+'|'+text; if(rpVoiceCall.seen[key])return; rpVoiceCall.seen[key]=1;
|
|
2672
|
-
appendVoiceTurn(role,text); saveVoiceTurn(role,text,Object.keys(rpVoiceCall.seen).length);
|
|
2673
|
-
}catch(_){}
|
|
2674
|
-
});
|
|
2998
|
+
room.on(LK.RoomEvent.DataReceived,handleVoiceData);
|
|
2999
|
+
if(LK.RoomEvent.ParticipantAttributesChanged)room.on(LK.RoomEvent.ParticipantAttributesChanged,function(changed,p){updateAgentFromParticipant(p);});
|
|
3000
|
+
room.on(LK.RoomEvent.ParticipantConnected,function(p){updateAgentFromParticipant(p);setVoiceEvent('Agent connected',p.name||p.identity||'ROBOVOICE worker');});
|
|
2675
3001
|
room.on(LK.RoomEvent.Disconnected,function(){ if(!rpVoiceCall.ending) finishVoiceCall('connection_lost'); });
|
|
2676
|
-
return room.connect(call.server_url,call.token).then(function(){return room.localParticipant.setMicrophoneEnabled(true);});
|
|
3002
|
+
return room.connect(call.server_url,call.token).then(function(){var mic=$('rp-call-mic-device').value,opts=mic?{deviceId:mic}:undefined;return room.localParticipant.setMicrophoneEnabled(true,opts);});
|
|
2677
3003
|
}).then(function(){
|
|
2678
|
-
setVoiceCallStatus('
|
|
3004
|
+
setVoiceCallStatus('secure channel connected','live');setVoiceAgentState('Listening','speak naturally');setVoiceEvent('Connected','waiting for ROBOVOICE agent');rpVoiceCall.startedAt=Date.now();rpVoiceCall.timer=setInterval(updateVoiceTimer,1000);updateVoiceTimer();
|
|
3005
|
+
$('rp-call-mute').disabled=false;$('rp-call-camera').disabled=false;$('rp-call-screen').disabled=false;$('rp-call-end').disabled=false;$('rp-call-message').disabled=false;$('rp-call-send').disabled=false;
|
|
2679
3006
|
return fetch('/robopark/api/sessions/'+encodeURIComponent(rpVoiceCall.session)+'/joined'+QS,{method:'POST',headers:{'X-RoboPark-Session-Token':rpVoiceCall.eventToken}}).catch(function(){});
|
|
2680
3007
|
});
|
|
2681
3008
|
}).catch(function(e){
|
|
2682
|
-
setVoiceCallStatus('call failed
|
|
3009
|
+
setVoiceCallStatus('call failed','');setVoiceAgentState('Unable to connect',e&&e.message||String(e));setVoiceEvent('Call failed',e&&e.message||String(e)); $('rp-call-start').disabled=false;
|
|
3010
|
+
if(rpVoiceCall.session)rpAction('POST','/robopark/api/sessions/'+encodeURIComponent(rpVoiceCall.session)+'/end?reason=connection_failed');
|
|
2683
3011
|
if(rpVoiceCall.room){try{rpVoiceCall.room.disconnect();}catch(_){} rpVoiceCall.room=null;}
|
|
2684
3012
|
});
|
|
2685
3013
|
}
|
|
2686
3014
|
function toggleVoiceMute(){
|
|
2687
3015
|
if(!rpVoiceCall.room)return; rpVoiceCall.muted=!rpVoiceCall.muted;
|
|
2688
3016
|
rpVoiceCall.room.localParticipant.setMicrophoneEnabled(!rpVoiceCall.muted);
|
|
2689
|
-
$('rp-call-mute').textContent=rpVoiceCall.muted?'
|
|
3017
|
+
$('rp-call-mute').textContent=rpVoiceCall.muted?'MIC OFF':'MIC';$('rp-call-mute').classList.toggle('on',!rpVoiceCall.muted);setVoiceCallStatus(rpVoiceCall.muted?'microphone muted':'secure channel connected','live');
|
|
2690
3018
|
}
|
|
2691
3019
|
function finishVoiceCall(reason){
|
|
2692
3020
|
if(rpVoiceCall.ending)return; rpVoiceCall.ending=true; var session=rpVoiceCall.session, room=rpVoiceCall.room;
|
|
2693
|
-
rpVoiceCall.room=null;
|
|
3021
|
+
rpVoiceCall.room=null;if(rpVoiceCall.timer)clearInterval(rpVoiceCall.timer);rpVoiceCall.timer=null;stopVoiceVisualizer();if(room){try{room.disconnect();}catch(_){}}
|
|
2694
3022
|
if(session) rpAction('POST','/robopark/api/sessions/'+encodeURIComponent(session)+'/end?reason='+encodeURIComponent(reason||'operator'));
|
|
2695
|
-
setVoiceCallStatus('call ended','');
|
|
2696
|
-
$('rp-call-mute').textContent='Mute microphone'; rpVoiceCall.session=null; rpVoiceCall.eventToken=null; rpVoiceCall.muted=false; setTimeout(function(){rpVoiceCall.ending=false;},300);
|
|
3023
|
+
setVoiceCallStatus('call ended','');setVoiceAgentState('Call complete','session transcript saved');setVoiceEvent('Session closed',reason||'operator');$('rp-call-start').disabled=false;['rp-call-mute','rp-call-camera','rp-call-screen','rp-call-end'].forEach(function(id){$(id).disabled=true;$(id).classList.remove('on');});$('rp-call-message').disabled=true;$('rp-call-send').disabled=true;$('rp-call-media').classList.remove('on');$('rp-call-video').srcObject=null;$('rp-call-mute').textContent='MIC';$('rp-call-camera').textContent='CAM';$('rp-call-screen').textContent='SCR';$('rp-call-session').textContent='No active session';$('rp-call-timer').textContent='00:00';rpVoiceCall.session=null;rpVoiceCall.eventToken=null;rpVoiceCall.muted=false;rpVoiceCall.camera=false;rpVoiceCall.screen=false;rpVoiceCall.startedAt=0;setTimeout(function(){rpVoiceCall.ending=false;},300);
|
|
2697
3024
|
}
|
|
2698
3025
|
(function(){
|
|
2699
|
-
var cs=$('rp-call-character-select'),
|
|
3026
|
+
var cs=$('rp-call-character-select'),ss=$('rp-call-stack-select'),start=$('rp-call-start'),mute=$('rp-call-mute'),end=$('rp-call-end');
|
|
2700
3027
|
if(cs)cs.addEventListener('change',paintVoiceCallCharacter); if(start)start.addEventListener('click',startVoiceCall);
|
|
2701
|
-
if(mute)mute.addEventListener('click',toggleVoiceMute);
|
|
3028
|
+
if(ss)ss.addEventListener('change',paintVoiceCallStack);if(mute)mute.addEventListener('click',toggleVoiceMute);if(end)end.addEventListener('click',function(){finishVoiceCall('operator');});
|
|
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();});
|
|
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);
|
|
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';}
|
|
3036
|
+
$('rp-call-chat-toggle').addEventListener('click',toggleTranscript);$('rp-call-collapse').addEventListener('click',toggleTranscript);
|
|
3037
|
+
$('rp-call-compose').addEventListener('submit',function(e){e.preventDefault();var input=$('rp-call-message'),text=input.value.trim();if(!text)return;$('rp-call-send').disabled=true;sendVoiceMessage(text).then(function(){input.value='';}).catch(function(err){setVoiceEvent('Message failed',err.message);}).finally(function(){$('rp-call-send').disabled=!rpVoiceCall.room;});});
|
|
3038
|
+
refreshBrowserDevices(false);
|
|
2702
3039
|
})();
|
|
2703
3040
|
|
|
2704
3041
|
var rpHistoryFilter = { robot: '', endReason: '', range: '7d', search: '' };
|
|
@@ -6132,7 +6469,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6132
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"/>'
|
|
6133
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>'
|
|
6134
6471
|
+'<div class="pk-note" id="pk-camnote">seeking live feed…</div>'
|
|
6135
|
-
+'<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>'
|
|
6136
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>')
|
|
6137
6474
|
+ sect('Remote production test','<div id="pk-pipeline-summary" class="pk-pipeline-summary">Checking robot, camera, scheduler and LiveKit...</div>'
|
|
6138
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>'
|
|
@@ -6155,7 +6492,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6155
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>')
|
|
6156
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>')
|
|
6157
6494
|
+ buildShellSection(schedId(n))
|
|
6158
|
-
+ 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>');
|
|
6159
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>');
|
|
6160
6497
|
// Put live hardware selection beside the camera and audio tests,
|
|
6161
6498
|
// ahead of persona and service administration in the Park drawer.
|
|
@@ -6177,6 +6514,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6177
6514
|
vl.addEventListener('click', function(){ tryVisionOverlay(n, true); if(!visionOn) showOut('RoboVision feed unavailable: robot has no reachable LAN/Tailscale address'); });
|
|
6178
6515
|
gl.after(vl);
|
|
6179
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();});
|
|
6180
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); });
|
|
6181
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'); });
|
|
6182
6523
|
var svcList=$('pk-svc-list'); if(svcList) svcList.addEventListener('click', function(ev){
|
|
@@ -6293,7 +6634,38 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
6293
6634
|
function set(id,v){ var e=$(id); if(e) e.textContent=v; }
|
|
6294
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)'; }
|
|
6295
6636
|
|
|
6296
|
-
|
|
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 ──
|
|
6297
6669
|
function startCam(n){
|
|
6298
6670
|
idleCam('connecting');
|
|
6299
6671
|
tryVisionOverlay(n, true);
|