infinicode 2.8.32 โ 2.8.35
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.
|
@@ -198,15 +198,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
198
198
|
.rp-head{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:0.6rem;padding:1rem 1.1rem 0.2rem;}
|
|
199
199
|
.rp-totals{display:flex;gap:0.5rem;flex-wrap:wrap;}
|
|
200
200
|
.rp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:0.75rem;padding:0.8rem 1.1rem;}
|
|
201
|
-
.rp-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.
|
|
202
|
-
.rp-form h3{grid-column:1/-1;font-family:var(--mono);font-size:0.
|
|
203
|
-
.rp-field{display:flex;flex-direction:column;gap:0.
|
|
204
|
-
.rp-field label{font-size:0.68rem;color:var(--muted);}
|
|
205
|
-
.rp-field input,.rp-field select{font-family:var(--mono);font-size:0.
|
|
206
|
-
.rp-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
.rp-
|
|
201
|
+
.rp-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:0.7rem;align-items:end;background:var(--panel-2);padding:1rem 1.05rem;border-radius:14px;border:1px solid var(--glass-border);margin:1rem 1.1rem;box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
|
|
202
|
+
.rp-form h3{grid-column:1/-1;font-family:var(--mono);font-size:0.72rem;letter-spacing:0.09em;text-transform:uppercase;color:var(--gold-bright);margin-bottom:0.35rem;padding-bottom:0.5rem;border-bottom:1px solid var(--glass-border);}
|
|
203
|
+
.rp-field{display:flex;flex-direction:column;gap:0.3rem;}
|
|
204
|
+
.rp-field label{font-size:0.68rem;color:var(--muted);letter-spacing:0.01em;}
|
|
205
|
+
.rp-field input,.rp-field select,.rp-field textarea{font-family:var(--mono);font-size:0.74rem;background:var(--panel-solid);border:1px solid var(--glass-border);color:var(--ink);border-radius:8px;padding:0.48rem 0.6rem;transition:border-color .15s,box-shadow .15s;}
|
|
206
|
+
.rp-field input:focus,.rp-field select:focus,.rp-field textarea:focus{outline:none;border-color:var(--gold-bright);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold-bright) 18%,transparent);}
|
|
207
|
+
.rp-quickrow{grid-column:1/-1;display:flex;gap:0.6rem;flex-wrap:wrap;align-items:flex-end;padding-bottom:0.2rem;}
|
|
208
|
+
.rp-quickrow .rp-field{flex:1;min-width:180px;}
|
|
209
|
+
.rp-autonote{grid-column:1/-1;font-family:var(--mono);font-size:0.66rem;color:var(--muted);display:flex;align-items:center;gap:0.4rem;min-height:1.2em;}
|
|
210
|
+
.rp-autonote.ok{color:var(--ok);} .rp-autonote.bad{color:var(--bad);} .rp-autonote.busy{color:var(--info);}
|
|
211
|
+
.rp-advanced{grid-column:1/-1;margin-top:0.3rem;border-top:1px dashed var(--glass-border);padding-top:0.7rem;}
|
|
212
|
+
.rp-advanced summary{cursor:pointer;font-family:var(--mono);font-size:0.66rem;letter-spacing:0.05em;text-transform:uppercase;color:var(--muted);list-style:none;display:flex;align-items:center;gap:0.4rem;user-select:none;}
|
|
213
|
+
.rp-advanced summary::-webkit-details-marker{display:none;}
|
|
214
|
+
.rp-advanced summary::before{content:"โธ";display:inline-block;transition:transform .15s;color:var(--gold);}
|
|
215
|
+
.rp-advanced[open] summary::before{transform:rotate(90deg);}
|
|
216
|
+
.rp-advanced summary:hover{color:var(--gold-bright);}
|
|
217
|
+
.rp-advanced-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.6rem;margin-top:0.7rem;}
|
|
218
|
+
.rp-btn.primary{background:linear-gradient(160deg,color-mix(in srgb,var(--gold-bright) 30%,var(--panel-2)),var(--panel-2));border-color:var(--gold-bright);color:var(--gold-bright);font-weight:600;}
|
|
219
|
+
.rp-btn.primary:hover{box-shadow:0 0 0 3px color-mix(in srgb,var(--gold-bright) 16%,transparent);}
|
|
220
|
+
.rp-card{border-radius:13px;padding:0.75rem 0.85rem;border-left:3px solid var(--gold-bright);
|
|
221
|
+
background:var(--panel);-webkit-backdrop-filter:blur(14px) saturate(1.25);backdrop-filter:blur(14px) saturate(1.25);
|
|
222
|
+
border:1px solid var(--glass-border);box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
|
|
223
|
+
.rp-card-top{display:flex;align-items:flex-start;gap:0.55rem;}
|
|
224
|
+
.rp-card-img{width:42px;height:42px;border-radius:11px;overflow:hidden;flex:none;border:1px solid var(--glass-border);background:var(--panel-2);}
|
|
225
|
+
.rp-card-img img{width:100%;height:100%;object-fit:cover;display:block;}
|
|
226
|
+
.rp-card h4{font-family:var(--mono);font-size:0.86rem;margin-bottom:0.5rem;word-break:break-word;}
|
|
210
227
|
.rp-metrics{display:grid;grid-template-columns:1fr 1fr;gap:0.4rem 0.6rem;}
|
|
211
228
|
.rp-m{display:flex;flex-direction:column;}
|
|
212
229
|
.rp-m .v{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:1.1rem;color:var(--ink);}
|
|
@@ -264,8 +281,10 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
264
281
|
transform:translateX(105%);transition:transform .32s cubic-bezier(.2,.8,.2,1);display:flex;flex-direction:column;}
|
|
265
282
|
.pk-drawer.open{transform:translateX(0);}
|
|
266
283
|
.pk-dh{display:flex;align-items:center;gap:0.6rem;padding:0.9rem 1rem;border-bottom:1px solid var(--glass-border);}
|
|
267
|
-
.pk-av{width:40px;height:40px;border-radius:11px;background:radial-gradient(circle at 30% 25%,rgba(255,214,112,0.28),rgba(0,0,0,0.25));
|
|
268
|
-
border:1px solid var(--border-strong);display:grid;place-items:center;font-size:1.2rem;}
|
|
284
|
+
.pk-av{width:40px;height:40px;border-radius:11px;background:radial-gradient(circle at 30% 25%,rgba(255,214,112,0.28),rgba(0,0,0,0.25));
|
|
285
|
+
border:1px solid var(--border-strong);display:grid;place-items:center;font-size:1.2rem;overflow:hidden;}
|
|
286
|
+
.pk-av.hasimg{background:var(--panel-2);}
|
|
287
|
+
.pk-av img{width:100%;height:100%;object-fit:cover;display:block;}
|
|
269
288
|
.pk-dh .nm{font-size:1.02rem;font-weight:700;font-family:var(--mono);}
|
|
270
289
|
.pk-dh .sub{font-family:var(--mono);font-size:0.64rem;color:var(--muted);}
|
|
271
290
|
.pk-dh .x{margin-left:auto;background:var(--panel-2);border:1px solid var(--glass-border);color:var(--muted);border-radius:9px;width:30px;height:30px;cursor:pointer;font-size:1rem;}
|
|
@@ -379,26 +398,47 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
379
398
|
<!-- Add LiveKit server -->
|
|
380
399
|
<div class="rp-form" id="rp-addserver">
|
|
381
400
|
<h3>Add LiveKit server</h3>
|
|
382
|
-
<div class="rp-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<div class="rp-
|
|
388
|
-
<
|
|
401
|
+
<div class="rp-quickrow">
|
|
402
|
+
<div class="rp-field"><label>Name</label><input id="srv-name" placeholder="LiveKit Server 1"></div>
|
|
403
|
+
<button class="rp-btn primary" id="srv-detect" style="height:fit-content" title="Probes this hub's own host + common local ports for a running LiveKit instance">๐ Auto-detect & fill</button>
|
|
404
|
+
<button class="rp-btn" id="srv-add" style="height:fit-content">Add server</button>
|
|
405
|
+
</div>
|
|
406
|
+
<div class="rp-autonote" id="srv-autonote"></div>
|
|
407
|
+
<details class="rp-advanced">
|
|
408
|
+
<summary>Advanced โ ID, URL, credentials, capacity</summary>
|
|
409
|
+
<div class="rp-advanced-grid">
|
|
410
|
+
<div class="rp-field"><label>ID</label><input id="srv-id" placeholder="auto-filled from name"></div>
|
|
411
|
+
<div class="rp-field"><label>URL (ws:// or wss://)</label><input id="srv-url" placeholder="ws://host:7880 โ auto-detect fills this"></div>
|
|
412
|
+
<div class="rp-field"><label>API key</label><input id="srv-key" placeholder="devkey" value="devkey"></div>
|
|
413
|
+
<div class="rp-field"><label>API secret</label><input type="password" id="srv-secret" placeholder="secretsecretsecretsecretsecretsecret"></div>
|
|
414
|
+
<div class="rp-field"><label>Max sessions</label><input id="srv-max" type="number" value="8"></div>
|
|
415
|
+
</div>
|
|
416
|
+
</details>
|
|
389
417
|
</div>
|
|
390
418
|
|
|
391
419
|
<!-- Add robot / device -->
|
|
392
420
|
<div class="rp-form" id="rp-addrobot">
|
|
393
421
|
<h3>Add robot (mint enrollment token)</h3>
|
|
394
|
-
<div class="rp-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
<div class="rp-
|
|
400
|
-
<div class="rp-
|
|
401
|
-
|
|
422
|
+
<div class="rp-quickrow">
|
|
423
|
+
<div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
|
|
424
|
+
<div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">โ auto (matched from name) โ</option></select></div>
|
|
425
|
+
<button class="rp-btn primary" id="rb-add" style="height:fit-content">Add robot</button>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="rp-autonote" id="rb-autonote">the robot self-reports its IP and hardware on first heartbeat โ you only need a name and (optionally) a character preset to get started.</div>
|
|
428
|
+
<div class="rp-quickrow" id="rb-clicmd-row" style="display:none;">
|
|
429
|
+
<div class="rp-field" style="flex:1;"><label>run this ON THE ROBOT (no need to click Add โ this mints its own token)</label><input id="rb-clicmd" readonly style="cursor:text;"></div>
|
|
430
|
+
<button class="rp-btn" id="rb-clicmd-copy" style="height:fit-content">๐ Copy</button>
|
|
431
|
+
</div>
|
|
432
|
+
<details class="rp-advanced">
|
|
433
|
+
<summary>Advanced โ pre-set network/voice config before the robot connects</summary>
|
|
434
|
+
<div class="rp-advanced-grid">
|
|
435
|
+
<div class="rp-field"><label>Tailscale IP</label><input id="rb-tsip" placeholder="auto-detected once connected"></div>
|
|
436
|
+
<div class="rp-field"><label>LAN IP</label><input id="rb-lanip" placeholder="auto-detected once connected"></div>
|
|
437
|
+
<div class="rp-field"><label>Motor server URL</label><input id="rb-motor" placeholder="http://...:5000"></div>
|
|
438
|
+
<div class="rp-field"><label>LiveKit URL</label><input id="rb-lk" placeholder="โ uses the registered server above โ"></div>
|
|
439
|
+
<div class="rp-field"><label>Voice stack override</label><select id="rb-voice"><option value="">โ from character preset โ</option></select></div>
|
|
440
|
+
</div>
|
|
441
|
+
</details>
|
|
402
442
|
</div>
|
|
403
443
|
|
|
404
444
|
<div class="rp-grid" id="rp-grid"></div>
|
|
@@ -937,17 +977,74 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
937
977
|
}
|
|
938
978
|
}
|
|
939
979
|
|
|
980
|
+
function slugify(s){ return (s||'').toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,40); }
|
|
981
|
+
|
|
982
|
+
// โโ LiveKit auto-detect: probe this hub's own host + localhost on the
|
|
983
|
+
// standard LiveKit port. A no-cors fetch that *resolves* (even opaquely)
|
|
984
|
+
// means something answered HTTP there; one that *throws* means nothing did.
|
|
985
|
+
// Best-effort only โ the Advanced section stays editable so the user can
|
|
986
|
+
// correct a false positive/negative before submitting. โโ
|
|
987
|
+
function detectLocalLiveKit(){
|
|
988
|
+
var note=$('srv-autonote'); note.className='rp-autonote busy'; note.textContent='probing for a local LiveKit instanceโฆ';
|
|
989
|
+
var hosts=[]; var h=location.hostname||'localhost';
|
|
990
|
+
hosts.push(h); if(h!=='localhost') hosts.push('localhost');
|
|
991
|
+
var tried=0, found=null;
|
|
992
|
+
function tryNext(i){
|
|
993
|
+
if(i>=hosts.length){
|
|
994
|
+
if(found){
|
|
995
|
+
$('srv-url').value='ws://'+found+':7880';
|
|
996
|
+
if(!$('srv-key').value) $('srv-key').value='devkey';
|
|
997
|
+
note.className='rp-autonote ok'; note.textContent='โ found a LiveKit instance at '+found+':7880 โ filled URL + default dev key (verify the secret in Advanced before adding).';
|
|
998
|
+
} else {
|
|
999
|
+
note.className='rp-autonote bad'; note.textContent='โ no local LiveKit found on '+hosts.join(', ')+':7880 โ fill the URL manually in Advanced.';
|
|
1000
|
+
}
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
var host=hosts[i];
|
|
1004
|
+
fetch('http://'+host+':7880/', {mode:'no-cors', signal: AbortSignal.timeout ? AbortSignal.timeout(2500) : undefined})
|
|
1005
|
+
.then(function(){ found=host; tryNext(hosts.length); })
|
|
1006
|
+
.catch(function(){ tryNext(i+1); });
|
|
1007
|
+
}
|
|
1008
|
+
tryNext(0);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
940
1011
|
function addServer(){
|
|
941
1012
|
var body={
|
|
942
|
-
id:$('srv-id').value.trim(), name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
|
|
943
|
-
api_key:$('srv-key').value.trim(), api_secret:$('srv-secret').value,
|
|
1013
|
+
id:$('srv-id').value.trim()||slugify($('srv-name').value)+'-server', name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
|
|
1014
|
+
api_key:$('srv-key').value.trim()||'devkey', api_secret:$('srv-secret').value,
|
|
944
1015
|
max_sessions:parseInt($('srv-max').value,10)||8
|
|
945
1016
|
};
|
|
946
|
-
if(!body.
|
|
1017
|
+
if(!body.name||!body.url){ showOut('Server name and URL are required (use Auto-detect, or fill URL under Advanced)'); return; }
|
|
947
1018
|
body.webhook_url=body.url;
|
|
948
1019
|
rpAction('POST','/robopark/api/servers',body).then(function(j){ showOut('server added: '+esc((j&&j.id)||body.id)); clearServerForm(); }).catch(function(e){ showOut('add server: '+e.message); });
|
|
949
1020
|
}
|
|
950
|
-
function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; }
|
|
1021
|
+
function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; $('srv-autonote').textContent=''; $('srv-autonote').className='rp-autonote'; }
|
|
1022
|
+
|
|
1023
|
+
// โโ robust CLI one-liner generation, live as you type โ no API call
|
|
1024
|
+
// needed to see it; 'robopark add-robot' mints its own token when run โโ
|
|
1025
|
+
function buildAddRobotCommand(name, characterId){
|
|
1026
|
+
if(!name) return '';
|
|
1027
|
+
var hubUrl=location.origin;
|
|
1028
|
+
var schedulerUrl=(lastStatus && lastStatus.control && lastStatus.control.schedulerUrl) || '';
|
|
1029
|
+
var argv=['robopark','add-robot',name];
|
|
1030
|
+
if(characterId) argv.push('--character', characterId);
|
|
1031
|
+
if(hubUrl) argv.push('--hub-url', hubUrl);
|
|
1032
|
+
if(TOKEN) argv.push('--token', TOKEN);
|
|
1033
|
+
if(schedulerUrl) argv.push('--scheduler-url', schedulerUrl);
|
|
1034
|
+
argv.push('--start');
|
|
1035
|
+
return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
|
|
1036
|
+
}
|
|
1037
|
+
function refreshRobotCliCommand(){
|
|
1038
|
+
var name=$('rb-name').value.trim(); var cid=$('rb-char').value||'';
|
|
1039
|
+
var row=$('rb-clicmd-row'), inp=$('rb-clicmd');
|
|
1040
|
+
if(!name){ row.style.display='none'; return; }
|
|
1041
|
+
inp.value=buildAddRobotCommand(name, cid);
|
|
1042
|
+
row.style.display='flex';
|
|
1043
|
+
}
|
|
1044
|
+
var rbNameEl=$('rb-name'); if(rbNameEl) rbNameEl.addEventListener('input', refreshRobotCliCommand);
|
|
1045
|
+
var rbCharEl=$('rb-char'); if(rbCharEl) rbCharEl.addEventListener('change', refreshRobotCliCommand);
|
|
1046
|
+
var rbCliCopy=$('rb-clicmd-copy'); if(rbCliCopy) rbCliCopy.addEventListener('click', function(){ var i=$('rb-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied โ run it on the robot itself'); });
|
|
1047
|
+
|
|
951
1048
|
function addRobot(){
|
|
952
1049
|
var body={
|
|
953
1050
|
name:$('rb-name').value.trim(), character_id:$('rb-char').value||null,
|
|
@@ -958,7 +1055,8 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
958
1055
|
if(!body.name){ showOut('Robot name is required'); return; }
|
|
959
1056
|
rpAction('POST','/robopark/api/devices',body).then(function(j){ showOut('robot added: '+esc((j&&j.name)||body.name)+'\\ntoken: '+esc((j&&j.enrollment_token)||'(none)')); clearRobotForm(); pollRobopark(); }).catch(function(e){ showOut('add robot: '+e.message); });
|
|
960
1057
|
}
|
|
961
|
-
function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; }
|
|
1058
|
+
function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; refreshRobotCliCommand(); }
|
|
1059
|
+
var srvDetectBtn=$('srv-detect'); if(srvDetectBtn) srvDetectBtn.addEventListener('click', detectLocalLiveKit);
|
|
962
1060
|
function triggerSession(id){
|
|
963
1061
|
rpAction('POST','/robopark/api/robots/'+encodeURIComponent(id)+'/trigger-session',{confidence:0.9}).then(function(){ showOut('triggered motion for '+id); pollRobopark(); Park.refreshDrawer(); }).catch(function(e){ showOut('trigger-session ('+id+'): '+e.message); });
|
|
964
1062
|
}
|
|
@@ -1112,13 +1210,14 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1112
1210
|
var html='';
|
|
1113
1211
|
for (var i=0;i<robots.length;i++){
|
|
1114
1212
|
var x = robots[i];
|
|
1115
|
-
var name = robotName(x);
|
|
1116
|
-
var
|
|
1213
|
+
var name = robotName(x);
|
|
1214
|
+
var avatar = avatarSrcForNode({label:(x.character_name||x.character_id||x.character_preset_id||name)});
|
|
1215
|
+
var lat = x.avg_latency_ms!=null?Math.round(x.avg_latency_ms)+'ms':'โ';
|
|
1117
1216
|
var dcls = x.drop_rate!=null?(x.drop_rate>=0.2?'bad':(x.drop_rate>=0.05?'warn':'ok')):'';
|
|
1118
1217
|
var drop = x.drop_rate!=null?Math.round(x.drop_rate*100)+'%':'โ';
|
|
1119
1218
|
var active = x.active_session||x.in_session||x.session_active;
|
|
1120
1219
|
var idAttr = esc(name);
|
|
1121
|
-
html += '<div class="rp-card" data-name="'+esc(name)+'"><h4>'+esc(name)+(active?' <span class="chip" style="color:var(--ok)">live</span>':'')+'</h4><div class="rp-metrics">'
|
|
1220
|
+
html += '<div class="rp-card" data-name="'+esc(name)+'"><div class="rp-card-top">'+(avatar?'<div class="rp-card-img"><img alt="'+esc(name)+'" src="'+esc(avatar+QS)+'"></div>':'')+'<div style="min-width:0;flex:1;"><h4>'+esc(name)+(active?' <span class="chip" style="color:var(--ok)">live</span>':'')+'</h4></div></div><div class="rp-metrics">'
|
|
1122
1221
|
+ '<div class="rp-m"><span class="v">'+(x.trigger_count!=null?x.trigger_count:0)+'</span><span class="l">triggers</span></div>'
|
|
1123
1222
|
+ '<div class="rp-m"><span class="v">'+(x.sessions_total!=null?x.sessions_total:0)+'</span><span class="l">sessions</span></div>'
|
|
1124
1223
|
+ '<div class="rp-m"><span class="v">'+lat+'</span><span class="l">avg latency</span></div>'
|
|
@@ -1184,10 +1283,59 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1184
1283
|
if(/robot|pi-client|scheduler/.test(caps) && r!=='peer') return 'robot';
|
|
1185
1284
|
return 'peer';
|
|
1186
1285
|
}
|
|
1187
|
-
function loadColor(l){ return l>=75?getVar('--bad'):l>=45?getVar('--warn'):getVar('--ok'); }
|
|
1188
|
-
function getVar(v){ try{ return getComputedStyle(document.documentElement).getPropertyValue(v).trim()||'#ccc'; }catch(e){ return '#ccc'; } }
|
|
1189
|
-
|
|
1190
|
-
|
|
1286
|
+
function loadColor(l){ return l>=75?getVar('--bad'):l>=45?getVar('--warn'):getVar('--ok'); }
|
|
1287
|
+
function getVar(v){ try{ return getComputedStyle(document.documentElement).getPropertyValue(v).trim()||'#ccc'; }catch(e){ return '#ccc'; } }
|
|
1288
|
+
function normalizeAvatarPath(path){
|
|
1289
|
+
var p=(path==null?'':String(path)).trim();
|
|
1290
|
+
if(!p) return '';
|
|
1291
|
+
if(/^https?:\/\//i.test(p) || p.indexOf('data:')===0 || p.indexOf('/')===0) return p;
|
|
1292
|
+
return '/' + p.replace(/^\.?\//, '');
|
|
1293
|
+
}
|
|
1294
|
+
function slotAliases(slot){
|
|
1295
|
+
var out=[], seen={};
|
|
1296
|
+
function add(v){
|
|
1297
|
+
var s=(v==null?'':String(v)).trim().toLowerCase();
|
|
1298
|
+
if(!s || seen[s]) return;
|
|
1299
|
+
seen[s]=1;
|
|
1300
|
+
out.push(s);
|
|
1301
|
+
}
|
|
1302
|
+
add(slot && slot.label);
|
|
1303
|
+
add(slot && slot.id);
|
|
1304
|
+
add(slot && slot.name);
|
|
1305
|
+
add(slot && slot.sub);
|
|
1306
|
+
(slot && slot.keys ? slot.keys : []).forEach(add);
|
|
1307
|
+
return out;
|
|
1308
|
+
}
|
|
1309
|
+
function slotForNode(n){
|
|
1310
|
+
var text=((n && (n.label || n.name || n.id || ''))+'').toLowerCase();
|
|
1311
|
+
if(!text) return null;
|
|
1312
|
+
var best=null;
|
|
1313
|
+
for(var i=0;i<SLOTS.length;i++){
|
|
1314
|
+
var slot=SLOTS[i], aliases=slotAliases(slot);
|
|
1315
|
+
for(var j=0;j<aliases.length;j++){
|
|
1316
|
+
var key=aliases[j];
|
|
1317
|
+
if(!key) continue;
|
|
1318
|
+
if(text===key || text.indexOf(key)>-1 || key.indexOf(text)>-1) return slot;
|
|
1319
|
+
if(!best && key.length>2 && text.indexOf(key.replace(/[^a-z0-9]/g,''))>-1) best=slot;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return best;
|
|
1323
|
+
}
|
|
1324
|
+
function avatarSrcForNode(n){
|
|
1325
|
+
var slot=slotForNode(n);
|
|
1326
|
+
return normalizeAvatarPath(slot && slot.img);
|
|
1327
|
+
}
|
|
1328
|
+
function avatarImageForNode(n){
|
|
1329
|
+
var slot=slotForNode(n);
|
|
1330
|
+
if(!slot) return null;
|
|
1331
|
+
var aliases=slotAliases(slot);
|
|
1332
|
+
var src=normalizeAvatarPath(slot.img);
|
|
1333
|
+
if(src) aliases.unshift(src);
|
|
1334
|
+
for(var i=0;i<aliases.length;i++){ var img=imgs[aliases[i]]; if(img) return img; }
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// Bind mesh nodes onto the fixed park pads; unmatched pads render as
|
|
1191
1339
|
// offline placeholders so the whole park is always visible.
|
|
1192
1340
|
function layout(src){
|
|
1193
1341
|
var list=(src||[]).slice(), used={}, out=[];
|
|
@@ -1395,12 +1543,12 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1395
1543
|
var p=iso(n.wx,n.wy), z=cam.z, bob=n.connected?Math.sin((performance.now()-t0)/500+n.wx)*2.3*z:0, bx=p.x, by=p.y-4*z-bob;
|
|
1396
1544
|
if(n.connected) ring(bx,p.y+3*z,18*z,n.load/100,loadColor(n.load)); else padMark(bx,p.y+3*z,z);
|
|
1397
1545
|
X.fillStyle='rgba(0,0,0,0.3)'; X.beginPath(); X.ellipse(bx,p.y+3*z,13*z,6*z,0,0,7); X.fill();
|
|
1398
|
-
var col=n.connected?getVar('--gold-bright'):'rgba(150,143,120,0.5)';
|
|
1399
|
-
X.globalAlpha=n.connected?1:0.7;
|
|
1400
|
-
var img = n
|
|
1401
|
-
if(img && img.complete && img.naturalWidth>0){
|
|
1402
|
-
var iw=26*z, ih=26*z*img.naturalHeight/img.naturalWidth;
|
|
1403
|
-
X.save();
|
|
1546
|
+
var col=n.connected?getVar('--gold-bright'):'rgba(150,143,120,0.5)';
|
|
1547
|
+
X.globalAlpha=n.connected?1:0.7;
|
|
1548
|
+
var img = avatarImageForNode(n);
|
|
1549
|
+
if(img && img.complete && img.naturalWidth>0){
|
|
1550
|
+
var iw=26*z, ih=26*z*img.naturalHeight/img.naturalWidth;
|
|
1551
|
+
X.save();
|
|
1404
1552
|
X.beginPath(); X.roundRect(bx-13*z,by-38*z,iw,ih+12*z,6*z); X.clip();
|
|
1405
1553
|
X.drawImage(img, bx-13*z, by-44*z, iw, ih+18*z);
|
|
1406
1554
|
X.restore();
|
|
@@ -1479,9 +1627,22 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1479
1627
|
};
|
|
1480
1628
|
document.head.appendChild(s);
|
|
1481
1629
|
}
|
|
1482
|
-
function loadImages(){
|
|
1483
|
-
|
|
1484
|
-
|
|
1630
|
+
function loadImages(){
|
|
1631
|
+
imgs={};
|
|
1632
|
+
var seen={};
|
|
1633
|
+
SLOTS.forEach(function(s){
|
|
1634
|
+
var src=normalizeAvatarPath(s && s.img);
|
|
1635
|
+
if(!src || seen[src]) return;
|
|
1636
|
+
seen[src]=1;
|
|
1637
|
+
var i=new Image();
|
|
1638
|
+
i.decoding='async';
|
|
1639
|
+
var aliases=slotAliases(s);
|
|
1640
|
+
aliases.push(src);
|
|
1641
|
+
i.onload=function(){ aliases.forEach(function(k){ imgs[k]=i; }); };
|
|
1642
|
+
i.onerror=function(){ aliases.forEach(function(k){ if(!(k in imgs)) imgs[k]=null; }); };
|
|
1643
|
+
i.src=src+QS;
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1485
1646
|
function updateSlots(newSlots){
|
|
1486
1647
|
SLOTS = newSlots.slice();
|
|
1487
1648
|
loadImages();
|
|
@@ -1500,11 +1661,20 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1500
1661
|
|
|
1501
1662
|
// โโ robot drawer โโ
|
|
1502
1663
|
function sect(title, inner){ return '<div class="pk-sect"><h4>'+title+'</h4>'+inner+'</div>'; }
|
|
1503
|
-
function openDrawer(id){
|
|
1504
|
-
var n=byId[id]; if(!n) return; drawerId=id;
|
|
1505
|
-
$('pk-drawer').classList.add('open');
|
|
1506
|
-
|
|
1507
|
-
|
|
1664
|
+
function openDrawer(id){
|
|
1665
|
+
var n=byId[id]; if(!n) return; drawerId=id;
|
|
1666
|
+
$('pk-drawer').classList.add('open');
|
|
1667
|
+
var av=$('pk-av'), avSrc=avatarSrcForNode(n), avImg=avatarImageForNode(n);
|
|
1668
|
+
if(av){
|
|
1669
|
+
if(avSrc){
|
|
1670
|
+
av.classList.add('hasimg');
|
|
1671
|
+
av.innerHTML = '<img alt="'+esc(n.label||n.name||'robot')+'" src="'+esc(avImg && avImg.src ? avImg.src : avSrc+QS)+'">';
|
|
1672
|
+
} else {
|
|
1673
|
+
av.classList.remove('hasimg');
|
|
1674
|
+
av.textContent = n.kind==='hub'?'๐ฅ':(n.kind==='robot'?'๐ค':'๐งฉ');
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
$('pk-name').textContent=n.label||n.name;
|
|
1508
1678
|
$('pk-sub').textContent = n.connected ? (n.name+' ยท '+(n.platform||'?')+' ยท v'+n.version+(n.site?(' ยท '+n.site):'')) : 'pad reserved ยท not joined';
|
|
1509
1679
|
var st=$('pk-state'); st.textContent=n.connected?'live':'offline'; st.className='pk-pill '+(n.connected?'live':'idle');
|
|
1510
1680
|
buildDrawer(n);
|
|
@@ -1656,19 +1826,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1656
1826
|
+ '<div class="pk-kv"><div class="v">'+esc(n.label)+'</div><div class="l">pad</div></div>'
|
|
1657
1827
|
+ '<div class="pk-kv"><div class="v" style="color:var(--muted)">idle</div><div class="l">status</div></div>'
|
|
1658
1828
|
+ '</div>'
|
|
1659
|
-
+ '<div class="rp-form" style="padding:0.
|
|
1829
|
+
+ '<div class="rp-form" style="padding:0.7rem;grid-template-columns:1fr;gap:0.55rem;margin:0;border-color:var(--border-strong);">'
|
|
1660
1830
|
+ '<div class="rp-field"><label>Robot name</label><input id="pk-setup-name" value="'+esc(defaultName)+'"></div>'
|
|
1661
1831
|
+ '<div class="rp-field"><label>Character preset</label><select id="pk-setup-char"><option value="">โ auto โ</option>'+presetOptions+'</select></div>'
|
|
1662
|
-
+ '<div class="rp-
|
|
1663
|
-
+ '<div class="rp-field"><label>
|
|
1832
|
+
+ '<div class="rp-quickrow" style="padding:0;"><button class="rp-btn primary" id="pk-setup-btn" data-preset="'+esc(n.id)+'" style="width:fit-content;">Mint enrollment token & add robot</button></div>'
|
|
1833
|
+
+ '<div class="rp-field"><label>or run this ON THE ROBOT (mints its own token)</label><input id="pk-setup-clicmd" readonly></div>'
|
|
1834
|
+
+ '<div class="rp-quickrow" style="padding:0;"><button class="rp-btn" id="pk-setup-clicmd-copy" style="width:fit-content;">๐ Copy command</button></div>'
|
|
1835
|
+
+ '<details class="rp-advanced"><summary>Advanced โ pre-set network/voice config</summary>'
|
|
1836
|
+
+ '<div class="rp-advanced-grid">'
|
|
1837
|
+
+ '<div class="rp-field"><label>Tailscale IP</label><input id="pk-setup-tsip" placeholder="auto-detected once connected"></div>'
|
|
1838
|
+
+ '<div class="rp-field"><label>LAN IP</label><input id="pk-setup-lanip" placeholder="auto-detected once connected"></div>'
|
|
1664
1839
|
+ '<div class="rp-field"><label>Motor server URL</label><input id="pk-setup-motor" placeholder="http://...:5000"></div>'
|
|
1665
|
-
+ '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="
|
|
1666
|
-
+ '<div class="rp-field"><label>Voice stack</label><select id="pk-setup-voice"><option value="">โ
|
|
1667
|
-
+ '
|
|
1840
|
+
+ '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="โ uses the registered server โ"></div>'
|
|
1841
|
+
+ '<div class="rp-field"><label>Voice stack override</label><select id="pk-setup-voice"><option value="">โ from character preset โ</option>'+voiceOptions+'</select></div>'
|
|
1842
|
+
+ '</div></details>'
|
|
1668
1843
|
+ '</div>'
|
|
1669
1844
|
+ '<div class="pk-sect" id="pk-setup-result" style="display:none;"><h4>Enrollment token (copy once)</h4><input id="pk-setup-token" class="rp-char" readonly style="margin-bottom:0.4rem;"><button class="rp-btn" id="pk-setup-copy">Copy</button></div>');
|
|
1670
1845
|
var setupBtn=$('pk-setup-btn'); if(setupBtn) setupBtn.addEventListener('click', function(){ submitPadSetup(n); });
|
|
1671
1846
|
var copyBtn=$('pk-setup-copy'); if(copyBtn) copyBtn.addEventListener('click', function(){ var t=$('pk-setup-token'); if(t){ t.select(); try{document.execCommand('copy');}catch(e){} showOut('token copied'); } });
|
|
1847
|
+
function refreshPadCliCommand(){
|
|
1848
|
+
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||n.id;
|
|
1849
|
+
var el=$('pk-setup-clicmd'); if(el) el.value=buildAddRobotCommand(nm, cid);
|
|
1850
|
+
}
|
|
1851
|
+
var padNameEl=$('pk-setup-name'); if(padNameEl) padNameEl.addEventListener('input', refreshPadCliCommand);
|
|
1852
|
+
var padCharEl=$('pk-setup-char'); if(padCharEl) padCharEl.addEventListener('change', refreshPadCliCommand);
|
|
1853
|
+
var padCliCopy=$('pk-setup-clicmd-copy'); if(padCliCopy) padCliCopy.addEventListener('click', function(){ var i=$('pk-setup-clicmd'); i.select(); try{document.execCommand('copy');}catch(e){} showOut('command copied โ run it on the robot itself'); });
|
|
1854
|
+
refreshPadCliCommand();
|
|
1672
1855
|
return;
|
|
1673
1856
|
}
|
|
1674
1857
|
if(n.kind==='robot'){
|
package/package.json
CHANGED