infinicode 2.8.31 → 2.8.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +263 -33
- package/dist/kernel/federation/federation.d.ts +1 -0
- package/dist/kernel/federation/federation.js +1 -0
- package/dist/robopark/add-robot.d.ts +3 -0
- package/dist/robopark/add-robot.js +9 -3
- package/dist/robopark-cli.js +1 -0
- package/package.json +1 -1
- package/packages/robopark/scheduler/main.py +47 -8
|
@@ -198,11 +198,25 @@ 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.
|
|
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);}
|
|
206
220
|
.rp-card{border-radius:13px;padding:0.75rem 0.85rem;border-left:3px solid var(--gold-bright);
|
|
207
221
|
background:var(--panel);-webkit-backdrop-filter:blur(14px) saturate(1.25);backdrop-filter:blur(14px) saturate(1.25);
|
|
208
222
|
border:1px solid var(--glass-border);box-shadow:var(--shadow),inset 0 1px 0 var(--glass-hi);}
|
|
@@ -379,26 +393,47 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
379
393
|
<!-- Add LiveKit server -->
|
|
380
394
|
<div class="rp-form" id="rp-addserver">
|
|
381
395
|
<h3>Add LiveKit server</h3>
|
|
382
|
-
<div class="rp-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<div class="rp-
|
|
388
|
-
<
|
|
396
|
+
<div class="rp-quickrow">
|
|
397
|
+
<div class="rp-field"><label>Name</label><input id="srv-name" placeholder="LiveKit Server 1"></div>
|
|
398
|
+
<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>
|
|
399
|
+
<button class="rp-btn" id="srv-add" style="height:fit-content">Add server</button>
|
|
400
|
+
</div>
|
|
401
|
+
<div class="rp-autonote" id="srv-autonote"></div>
|
|
402
|
+
<details class="rp-advanced">
|
|
403
|
+
<summary>Advanced — ID, URL, credentials, capacity</summary>
|
|
404
|
+
<div class="rp-advanced-grid">
|
|
405
|
+
<div class="rp-field"><label>ID</label><input id="srv-id" placeholder="auto-filled from name"></div>
|
|
406
|
+
<div class="rp-field"><label>URL (ws:// or wss://)</label><input id="srv-url" placeholder="ws://host:7880 — auto-detect fills this"></div>
|
|
407
|
+
<div class="rp-field"><label>API key</label><input id="srv-key" placeholder="devkey" value="devkey"></div>
|
|
408
|
+
<div class="rp-field"><label>API secret</label><input type="password" id="srv-secret" placeholder="secretsecretsecretsecretsecretsecret"></div>
|
|
409
|
+
<div class="rp-field"><label>Max sessions</label><input id="srv-max" type="number" value="8"></div>
|
|
410
|
+
</div>
|
|
411
|
+
</details>
|
|
389
412
|
</div>
|
|
390
413
|
|
|
391
414
|
<!-- Add robot / device -->
|
|
392
415
|
<div class="rp-form" id="rp-addrobot">
|
|
393
416
|
<h3>Add robot (mint enrollment token)</h3>
|
|
394
|
-
<div class="rp-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
<div class="rp-
|
|
400
|
-
<div class="rp-
|
|
401
|
-
|
|
417
|
+
<div class="rp-quickrow">
|
|
418
|
+
<div class="rp-field"><label>Robot name</label><input id="rb-name" placeholder="jaguar"></div>
|
|
419
|
+
<div class="rp-field"><label>Character preset</label><select id="rb-char"><option value="">— auto (matched from name) —</option></select></div>
|
|
420
|
+
<button class="rp-btn primary" id="rb-add" style="height:fit-content">Add robot</button>
|
|
421
|
+
</div>
|
|
422
|
+
<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>
|
|
423
|
+
<div class="rp-quickrow" id="rb-clicmd-row" style="display:none;">
|
|
424
|
+
<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>
|
|
425
|
+
<button class="rp-btn" id="rb-clicmd-copy" style="height:fit-content">📋 Copy</button>
|
|
426
|
+
</div>
|
|
427
|
+
<details class="rp-advanced">
|
|
428
|
+
<summary>Advanced — pre-set network/voice config before the robot connects</summary>
|
|
429
|
+
<div class="rp-advanced-grid">
|
|
430
|
+
<div class="rp-field"><label>Tailscale IP</label><input id="rb-tsip" placeholder="auto-detected once connected"></div>
|
|
431
|
+
<div class="rp-field"><label>LAN IP</label><input id="rb-lanip" placeholder="auto-detected once connected"></div>
|
|
432
|
+
<div class="rp-field"><label>Motor server URL</label><input id="rb-motor" placeholder="http://...:5000"></div>
|
|
433
|
+
<div class="rp-field"><label>LiveKit URL</label><input id="rb-lk" placeholder="— uses the registered server above —"></div>
|
|
434
|
+
<div class="rp-field"><label>Voice stack override</label><select id="rb-voice"><option value="">— from character preset —</option></select></div>
|
|
435
|
+
</div>
|
|
436
|
+
</details>
|
|
402
437
|
</div>
|
|
403
438
|
|
|
404
439
|
<div class="rp-grid" id="rp-grid"></div>
|
|
@@ -413,6 +448,31 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
413
448
|
<input id="rp-newvoice-name" placeholder="new stack name" style="flex:1;min-width:0;">
|
|
414
449
|
<button class="rp-btn" id="rp-addvoice">Add</button>
|
|
415
450
|
</div>
|
|
451
|
+
<div class="rp-form" id="rp-voice-edit" style="display:none;grid-template-columns:1fr 1fr;gap:0.4rem;margin-top:0.6rem;padding:0.6rem;border-color:var(--border-strong);">
|
|
452
|
+
<h4 style="grid-column:1/-1;margin:0 0 0.2rem;">Edit voice stack: <span id="rpve-name-label"></span></h4>
|
|
453
|
+
<div class="rp-field"><label>STT provider</label><select id="rpve-stt-provider"><option value="speaches">speaches</option><option value="groq">groq</option><option value="vibevoice">vibevoice</option></select></div>
|
|
454
|
+
<div class="rp-field"><label>STT model</label><input id="rpve-stt-model"></div>
|
|
455
|
+
<div class="rp-field"><label>STT language</label><input id="rpve-stt-language" placeholder="en / auto"></div>
|
|
456
|
+
<div class="rp-field"><label>LLM provider</label><select id="rpve-llm-provider"><option value="ollama">ollama</option><option value="groq">groq</option><option value="gemini">gemini</option><option value="moltbot">moltbot</option></select></div>
|
|
457
|
+
<div class="rp-field"><label>LLM model</label><input id="rpve-llm-model"></div>
|
|
458
|
+
<div class="rp-field"><label>LLM API key</label><input type="password" id="rpve-llm-key" placeholder="(uses agent's own if blank)"></div>
|
|
459
|
+
<div class="rp-field"><label>Temperature</label><input id="rpve-temperature" type="number" step="0.1" value="0.7"></div>
|
|
460
|
+
<div class="rp-field"><label>Context size (num_ctx)</label><input id="rpve-num-ctx" type="number" value="8192"></div>
|
|
461
|
+
<div class="rp-field"><label>TTS provider</label><select id="rpve-tts-provider"><option value="elevenlabs">elevenlabs</option><option value="kokoro">kokoro</option><option value="piper">piper</option></select></div>
|
|
462
|
+
<div class="rp-field"><label>TTS voice</label><input id="rpve-tts-voice"></div>
|
|
463
|
+
<div class="rp-field"><label>TTS language</label><input id="rpve-tts-language" placeholder="en"></div>
|
|
464
|
+
<div class="rp-field"><label>Max turns</label><input id="rpve-max-turns" type="number" value="20"></div>
|
|
465
|
+
<div class="rp-field"><label>Min endpointing delay (s)</label><input id="rpve-endpointing" type="number" step="0.1" value="0.7"></div>
|
|
466
|
+
<div class="rp-field"><label><input type="checkbox" id="rpve-interruptions" checked style="width:auto;margin-right:0.35rem;">Allow interruptions</label></div>
|
|
467
|
+
<div class="rp-field"><label><input type="checkbox" id="rpve-wake-enabled" style="width:auto;margin-right:0.35rem;">Wake word enabled</label></div>
|
|
468
|
+
<div class="rp-field"><label>Wake word model</label><input id="rpve-wake-model" placeholder="models/hey_jarvis.onnx"></div>
|
|
469
|
+
<div class="rp-field"><label>Wake threshold</label><input id="rpve-wake-threshold" type="number" step="0.05" value="0.5"></div>
|
|
470
|
+
<div class="rp-field"><label>Wake timeout (s)</label><input id="rpve-wake-timeout" type="number" step="0.5" value="3.0"></div>
|
|
471
|
+
<div style="grid-column:1/-1;display:flex;gap:0.4rem;">
|
|
472
|
+
<button class="rp-btn" id="rpve-save">Save</button>
|
|
473
|
+
<button class="rp-btn" id="rpve-cancel">Cancel</button>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
416
476
|
</div>
|
|
417
477
|
<div>
|
|
418
478
|
<h4 style="font-family:var(--mono);font-size:0.75rem;color:var(--gold-bright);margin:0.3rem 0 0.5rem;">Character presets</h4>
|
|
@@ -423,6 +483,23 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
423
483
|
<datalist id="rp-newchar-stack-list"></datalist>
|
|
424
484
|
<button class="rp-btn" id="rp-addchar">Add</button>
|
|
425
485
|
</div>
|
|
486
|
+
<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);">
|
|
487
|
+
<h4 style="margin:0 0 0.2rem;">Edit character: <span id="rpce-name-label"></span></h4>
|
|
488
|
+
<div class="rp-field"><label>Name</label><input id="rpce-name"></div>
|
|
489
|
+
<div class="rp-field"><label>Description</label><input id="rpce-description"></div>
|
|
490
|
+
<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>
|
|
491
|
+
<div class="rp-field"><label>Voice stack</label><select id="rpce-stack"></select></div>
|
|
492
|
+
<div class="rp-field"><label>Motors (comma-separated)</label><input id="rpce-motors" placeholder="drive, wave"></div>
|
|
493
|
+
<div style="display:flex;gap:0.4rem;">
|
|
494
|
+
<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>
|
|
495
|
+
<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>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="rp-field"><label>Avatar image path</label><input id="rpce-img" placeholder="/static/robopark/jaguar.png"></div>
|
|
498
|
+
<div style="display:flex;gap:0.4rem;">
|
|
499
|
+
<button class="rp-btn" id="rpce-save">Save</button>
|
|
500
|
+
<button class="rp-btn" id="rpce-cancel">Cancel</button>
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
426
503
|
</div>
|
|
427
504
|
</div>
|
|
428
505
|
</div>
|
|
@@ -895,17 +972,74 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
895
972
|
}
|
|
896
973
|
}
|
|
897
974
|
|
|
975
|
+
function slugify(s){ return (s||'').toLowerCase().replace(/[^a-z0-9_-]+/g,'-').replace(/^-|-$/g,'').slice(0,40); }
|
|
976
|
+
|
|
977
|
+
// ── LiveKit auto-detect: probe this hub's own host + localhost on the
|
|
978
|
+
// standard LiveKit port. A no-cors fetch that *resolves* (even opaquely)
|
|
979
|
+
// means something answered HTTP there; one that *throws* means nothing did.
|
|
980
|
+
// Best-effort only — the Advanced section stays editable so the user can
|
|
981
|
+
// correct a false positive/negative before submitting. ──
|
|
982
|
+
function detectLocalLiveKit(){
|
|
983
|
+
var note=$('srv-autonote'); note.className='rp-autonote busy'; note.textContent='probing for a local LiveKit instance…';
|
|
984
|
+
var hosts=[]; var h=location.hostname||'localhost';
|
|
985
|
+
hosts.push(h); if(h!=='localhost') hosts.push('localhost');
|
|
986
|
+
var tried=0, found=null;
|
|
987
|
+
function tryNext(i){
|
|
988
|
+
if(i>=hosts.length){
|
|
989
|
+
if(found){
|
|
990
|
+
$('srv-url').value='ws://'+found+':7880';
|
|
991
|
+
if(!$('srv-key').value) $('srv-key').value='devkey';
|
|
992
|
+
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).';
|
|
993
|
+
} else {
|
|
994
|
+
note.className='rp-autonote bad'; note.textContent='✗ no local LiveKit found on '+hosts.join(', ')+':7880 — fill the URL manually in Advanced.';
|
|
995
|
+
}
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
var host=hosts[i];
|
|
999
|
+
fetch('http://'+host+':7880/', {mode:'no-cors', signal: AbortSignal.timeout ? AbortSignal.timeout(2500) : undefined})
|
|
1000
|
+
.then(function(){ found=host; tryNext(hosts.length); })
|
|
1001
|
+
.catch(function(){ tryNext(i+1); });
|
|
1002
|
+
}
|
|
1003
|
+
tryNext(0);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
898
1006
|
function addServer(){
|
|
899
1007
|
var body={
|
|
900
|
-
id:$('srv-id').value.trim(), name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
|
|
901
|
-
api_key:$('srv-key').value.trim(), api_secret:$('srv-secret').value,
|
|
1008
|
+
id:$('srv-id').value.trim()||slugify($('srv-name').value)+'-server', name:$('srv-name').value.trim(), url:$('srv-url').value.trim(),
|
|
1009
|
+
api_key:$('srv-key').value.trim()||'devkey', api_secret:$('srv-secret').value,
|
|
902
1010
|
max_sessions:parseInt($('srv-max').value,10)||8
|
|
903
1011
|
};
|
|
904
|
-
if(!body.
|
|
1012
|
+
if(!body.name||!body.url){ showOut('Server name and URL are required (use Auto-detect, or fill URL under Advanced)'); return; }
|
|
905
1013
|
body.webhook_url=body.url;
|
|
906
1014
|
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); });
|
|
907
1015
|
}
|
|
908
|
-
function clearServerForm(){ $('srv-id').value=''; $('srv-name').value=''; $('srv-url').value=''; $('srv-key').value=''; $('srv-secret').value=''; $('srv-max').value='8'; }
|
|
1016
|
+
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'; }
|
|
1017
|
+
|
|
1018
|
+
// ── robust CLI one-liner generation, live as you type — no API call
|
|
1019
|
+
// needed to see it; 'robopark add-robot' mints its own token when run ──
|
|
1020
|
+
function buildAddRobotCommand(name, characterId){
|
|
1021
|
+
if(!name) return '';
|
|
1022
|
+
var hubUrl=location.origin;
|
|
1023
|
+
var schedulerUrl=(lastStatus && lastStatus.control && lastStatus.control.schedulerUrl) || '';
|
|
1024
|
+
var argv=['robopark','add-robot',name];
|
|
1025
|
+
if(characterId) argv.push('--character', characterId);
|
|
1026
|
+
if(hubUrl) argv.push('--hub-url', hubUrl);
|
|
1027
|
+
if(TOKEN) argv.push('--token', TOKEN);
|
|
1028
|
+
if(schedulerUrl) argv.push('--scheduler-url', schedulerUrl);
|
|
1029
|
+
argv.push('--start');
|
|
1030
|
+
return argv.map(function(a){ return /[^a-zA-Z0-9_./:=,-]/.test(a) ? '"'+a.replace(/"/g,'\\"')+'"' : a; }).join(' ');
|
|
1031
|
+
}
|
|
1032
|
+
function refreshRobotCliCommand(){
|
|
1033
|
+
var name=$('rb-name').value.trim(); var cid=$('rb-char').value||'';
|
|
1034
|
+
var row=$('rb-clicmd-row'), inp=$('rb-clicmd');
|
|
1035
|
+
if(!name){ row.style.display='none'; return; }
|
|
1036
|
+
inp.value=buildAddRobotCommand(name, cid);
|
|
1037
|
+
row.style.display='flex';
|
|
1038
|
+
}
|
|
1039
|
+
var rbNameEl=$('rb-name'); if(rbNameEl) rbNameEl.addEventListener('input', refreshRobotCliCommand);
|
|
1040
|
+
var rbCharEl=$('rb-char'); if(rbCharEl) rbCharEl.addEventListener('change', refreshRobotCliCommand);
|
|
1041
|
+
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'); });
|
|
1042
|
+
|
|
909
1043
|
function addRobot(){
|
|
910
1044
|
var body={
|
|
911
1045
|
name:$('rb-name').value.trim(), character_id:$('rb-char').value||null,
|
|
@@ -916,7 +1050,8 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
916
1050
|
if(!body.name){ showOut('Robot name is required'); return; }
|
|
917
1051
|
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); });
|
|
918
1052
|
}
|
|
919
|
-
function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; }
|
|
1053
|
+
function clearRobotForm(){ $('rb-name').value=''; $('rb-char').value=''; $('rb-tsip').value=''; $('rb-lanip').value=''; $('rb-motor').value=''; $('rb-lk').value=''; $('rb-voice').value=''; refreshRobotCliCommand(); }
|
|
1054
|
+
var srvDetectBtn=$('srv-detect'); if(srvDetectBtn) srvDetectBtn.addEventListener('click', detectLocalLiveKit);
|
|
920
1055
|
function triggerSession(id){
|
|
921
1056
|
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); });
|
|
922
1057
|
}
|
|
@@ -942,7 +1077,7 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
942
1077
|
function fillTemplateVoiceList(){
|
|
943
1078
|
var el=$('rp-voice-list'); if(!el) return;
|
|
944
1079
|
var html='';
|
|
945
|
-
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<div class="rp-card" style="padding:0.5rem 0.6rem;border-left-color:var(--info)"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(s.name||s.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">'+esc(s.stt_provider)+' → '+esc(s.llm_provider)+' → '+esc(s.tts_provider)+'</div></div>'; }
|
|
1080
|
+
for(var i=0;i<voiceStacks.length;i++){ var s=voiceStacks[i]; html += '<div class="rp-card" data-vsid="'+esc(s.id)+'" style="padding:0.5rem 0.6rem;border-left-color:var(--info);cursor:pointer;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(s.name||s.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">'+esc(s.stt_provider)+' → '+esc(s.llm_provider)+'/'+esc(s.llm_model||'')+' → '+esc(s.tts_provider)+'</div></div>'; }
|
|
946
1081
|
if(!voiceStacks.length) html='<div class="pk-note">no voice stacks loaded</div>';
|
|
947
1082
|
el.innerHTML=html;
|
|
948
1083
|
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); } }
|
|
@@ -950,11 +1085,93 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
950
1085
|
function fillTemplateCharacterList(){
|
|
951
1086
|
var el=$('rp-char-list'); if(!el) return;
|
|
952
1087
|
var html='';
|
|
953
|
-
for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; html += '<div class="rp-card" style="padding:0.5rem 0.6rem;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(p.name||p.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">stack: '+esc(p.voice_stack_id||'—')+' · motors: '+esc((p.motors||[]).join(', '))+'</div></div>'; }
|
|
1088
|
+
for(var i=0;i<characterPresets.length;i++){ var p=characterPresets[i]; html += '<div class="rp-card" data-cpid="'+esc(p.id)+'" style="padding:0.5rem 0.6rem;cursor:pointer;"><div style="font-family:var(--mono);font-size:0.74rem;font-weight:600;">'+esc(p.name||p.id)+'</div><div style="font-size:0.6rem;color:var(--muted);margin-top:0.15rem;">stack: '+esc(p.voice_stack_id||'—')+' · motors: '+esc((p.motors||[]).join(', '))+(p.system_prompt?' · has prompt':' · no prompt set')+'</div></div>'; }
|
|
954
1089
|
if(!characterPresets.length) html='<div class="pk-note">no character presets loaded</div>';
|
|
955
1090
|
el.innerHTML=html;
|
|
956
1091
|
}
|
|
957
1092
|
|
|
1093
|
+
// ── voice-stack edit panel ──
|
|
1094
|
+
var editingVoiceStackId=null;
|
|
1095
|
+
function openVoiceStackEdit(id){
|
|
1096
|
+
var s=null; for(var i=0;i<voiceStacks.length;i++){ if(voiceStacks[i].id===id){ s=voiceStacks[i]; break; } }
|
|
1097
|
+
if(!s) return;
|
|
1098
|
+
editingVoiceStackId=id;
|
|
1099
|
+
$('rpve-name-label').textContent=s.name||s.id;
|
|
1100
|
+
$('rpve-stt-provider').value=s.stt_provider||'speaches';
|
|
1101
|
+
$('rpve-stt-model').value=s.stt_model||'';
|
|
1102
|
+
$('rpve-stt-language').value=s.stt_language||'en';
|
|
1103
|
+
$('rpve-llm-provider').value=s.llm_provider||'ollama';
|
|
1104
|
+
$('rpve-llm-model').value=s.llm_model||'';
|
|
1105
|
+
$('rpve-llm-key').value=s.llm_api_key||'';
|
|
1106
|
+
$('rpve-temperature').value=(s.temperature!=null?s.temperature:0.7);
|
|
1107
|
+
$('rpve-num-ctx').value=(s.num_ctx!=null?s.num_ctx:8192);
|
|
1108
|
+
$('rpve-tts-provider').value=s.tts_provider||'elevenlabs';
|
|
1109
|
+
$('rpve-tts-voice').value=s.tts_voice||'';
|
|
1110
|
+
$('rpve-tts-language').value=s.tts_language||'en';
|
|
1111
|
+
$('rpve-max-turns').value=(s.max_turns!=null?s.max_turns:20);
|
|
1112
|
+
$('rpve-endpointing').value=(s.min_endpointing_delay!=null?s.min_endpointing_delay:0.7);
|
|
1113
|
+
$('rpve-interruptions').checked=!!s.allow_interruptions;
|
|
1114
|
+
$('rpve-wake-enabled').checked=!!s.wake_word_enabled;
|
|
1115
|
+
$('rpve-wake-model').value=s.wake_word_model||'';
|
|
1116
|
+
$('rpve-wake-threshold').value=(s.wake_word_threshold!=null?s.wake_word_threshold:0.5);
|
|
1117
|
+
$('rpve-wake-timeout').value=(s.wake_word_timeout!=null?s.wake_word_timeout:3.0);
|
|
1118
|
+
$('rp-voice-edit').style.display='grid';
|
|
1119
|
+
}
|
|
1120
|
+
function saveVoiceStackEdit(){
|
|
1121
|
+
if(!editingVoiceStackId) return;
|
|
1122
|
+
var body={
|
|
1123
|
+
name:$('rpve-name-label').textContent,
|
|
1124
|
+
stt_provider:$('rpve-stt-provider').value, stt_model:$('rpve-stt-model').value.trim(), stt_language:$('rpve-stt-language').value.trim()||'en',
|
|
1125
|
+
llm_provider:$('rpve-llm-provider').value, llm_model:$('rpve-llm-model').value.trim(), llm_api_key:$('rpve-llm-key').value.trim()||null,
|
|
1126
|
+
temperature:parseFloat($('rpve-temperature').value)||0.7, num_ctx:parseInt($('rpve-num-ctx').value,10)||8192,
|
|
1127
|
+
tts_provider:$('rpve-tts-provider').value, tts_voice:$('rpve-tts-voice').value.trim(), tts_language:$('rpve-tts-language').value.trim()||'en',
|
|
1128
|
+
max_turns:parseInt($('rpve-max-turns').value,10)||20, min_endpointing_delay:parseFloat($('rpve-endpointing').value)||0.7,
|
|
1129
|
+
allow_interruptions:$('rpve-interruptions').checked, wake_word_enabled:$('rpve-wake-enabled').checked,
|
|
1130
|
+
wake_word_model:$('rpve-wake-model').value.trim()||null, wake_word_threshold:parseFloat($('rpve-wake-threshold').value)||0.5,
|
|
1131
|
+
wake_word_timeout:parseFloat($('rpve-wake-timeout').value)||3.0
|
|
1132
|
+
};
|
|
1133
|
+
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); });
|
|
1134
|
+
}
|
|
1135
|
+
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) openVoiceStackEdit(id); });
|
|
1136
|
+
var vsSave=$('rpve-save'); if(vsSave) vsSave.addEventListener('click', saveVoiceStackEdit);
|
|
1137
|
+
var vsCancel=$('rpve-cancel'); if(vsCancel) vsCancel.addEventListener('click', function(){ $('rp-voice-edit').style.display='none'; editingVoiceStackId=null; });
|
|
1138
|
+
|
|
1139
|
+
// ── character-preset edit panel ──
|
|
1140
|
+
var editingCharPresetId=null;
|
|
1141
|
+
function fillCharEditStackOptions(cur){
|
|
1142
|
+
var sel=$('rpce-stack'); if(!sel) return;
|
|
1143
|
+
sel.innerHTML='<option value="">— default —</option>';
|
|
1144
|
+
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); }
|
|
1145
|
+
}
|
|
1146
|
+
function openCharPresetEdit(id){
|
|
1147
|
+
var p=null; for(var i=0;i<characterPresets.length;i++){ if(characterPresets[i].id===id){ p=characterPresets[i]; break; } }
|
|
1148
|
+
if(!p) return;
|
|
1149
|
+
editingCharPresetId=id;
|
|
1150
|
+
$('rpce-name-label').textContent=p.name||p.id;
|
|
1151
|
+
$('rpce-name').value=p.name||'';
|
|
1152
|
+
$('rpce-description').value=p.description||'';
|
|
1153
|
+
$('rpce-prompt').value=p.system_prompt||'';
|
|
1154
|
+
fillCharEditStackOptions(p.voice_stack_id);
|
|
1155
|
+
$('rpce-motors').value=(p.motors||[]).join(', ');
|
|
1156
|
+
$('rpce-nx').value=(p.nx!=null?p.nx:0.5);
|
|
1157
|
+
$('rpce-ny').value=(p.ny!=null?p.ny:0.5);
|
|
1158
|
+
$('rpce-img').value=p.img||'';
|
|
1159
|
+
$('rp-char-edit').style.display='grid';
|
|
1160
|
+
}
|
|
1161
|
+
function saveCharPresetEdit(){
|
|
1162
|
+
if(!editingCharPresetId) return;
|
|
1163
|
+
var motors=$('rpce-motors').value.split(',').map(function(x){return x.trim();}).filter(function(x){return !!x;});
|
|
1164
|
+
var body={
|
|
1165
|
+
name:$('rpce-name').value.trim()||editingCharPresetId, description:$('rpce-description').value.trim()||null,
|
|
1166
|
+
system_prompt:$('rpce-prompt').value.trim()||null, voice_stack_id:$('rpce-stack').value||null,
|
|
1167
|
+
motors:motors, nx:parseFloat($('rpce-nx').value), ny:parseFloat($('rpce-ny').value), img:$('rpce-img').value.trim()||null
|
|
1168
|
+
};
|
|
1169
|
+
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); });
|
|
1170
|
+
}
|
|
1171
|
+
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) openCharPresetEdit(id); });
|
|
1172
|
+
var cpSave=$('rpce-save'); if(cpSave) cpSave.addEventListener('click', saveCharPresetEdit);
|
|
1173
|
+
var cpCancel=$('rpce-cancel'); if(cpCancel) cpCancel.addEventListener('click', function(){ $('rp-char-edit').style.display='none'; editingCharPresetId=null; });
|
|
1174
|
+
|
|
958
1175
|
function renderRobotCardSelect(name, current){
|
|
959
1176
|
var sel=$('rp-char-'+name); if(!sel) return;
|
|
960
1177
|
sel.dataset.current=current||'';
|
|
@@ -1532,19 +1749,32 @@ button.act:disabled{opacity:0.5;cursor:default;}
|
|
|
1532
1749
|
+ '<div class="pk-kv"><div class="v">'+esc(n.label)+'</div><div class="l">pad</div></div>'
|
|
1533
1750
|
+ '<div class="pk-kv"><div class="v" style="color:var(--muted)">idle</div><div class="l">status</div></div>'
|
|
1534
1751
|
+ '</div>'
|
|
1535
|
-
+ '<div class="rp-form" style="padding:0.
|
|
1752
|
+
+ '<div class="rp-form" style="padding:0.7rem;grid-template-columns:1fr;gap:0.55rem;margin:0;border-color:var(--border-strong);">'
|
|
1536
1753
|
+ '<div class="rp-field"><label>Robot name</label><input id="pk-setup-name" value="'+esc(defaultName)+'"></div>'
|
|
1537
1754
|
+ '<div class="rp-field"><label>Character preset</label><select id="pk-setup-char"><option value="">— auto —</option>'+presetOptions+'</select></div>'
|
|
1538
|
-
+ '<div class="rp-
|
|
1539
|
-
+ '<div class="rp-field"><label>
|
|
1755
|
+
+ '<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>'
|
|
1756
|
+
+ '<div class="rp-field"><label>or run this ON THE ROBOT (mints its own token)</label><input id="pk-setup-clicmd" readonly></div>'
|
|
1757
|
+
+ '<div class="rp-quickrow" style="padding:0;"><button class="rp-btn" id="pk-setup-clicmd-copy" style="width:fit-content;">📋 Copy command</button></div>'
|
|
1758
|
+
+ '<details class="rp-advanced"><summary>Advanced — pre-set network/voice config</summary>'
|
|
1759
|
+
+ '<div class="rp-advanced-grid">'
|
|
1760
|
+
+ '<div class="rp-field"><label>Tailscale IP</label><input id="pk-setup-tsip" placeholder="auto-detected once connected"></div>'
|
|
1761
|
+
+ '<div class="rp-field"><label>LAN IP</label><input id="pk-setup-lanip" placeholder="auto-detected once connected"></div>'
|
|
1540
1762
|
+ '<div class="rp-field"><label>Motor server URL</label><input id="pk-setup-motor" placeholder="http://...:5000"></div>'
|
|
1541
|
-
+ '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="
|
|
1542
|
-
+ '<div class="rp-field"><label>Voice stack</label><select id="pk-setup-voice"><option value="">—
|
|
1543
|
-
+ '
|
|
1763
|
+
+ '<div class="rp-field"><label>LiveKit URL</label><input id="pk-setup-lk" placeholder="— uses the registered server —"></div>'
|
|
1764
|
+
+ '<div class="rp-field"><label>Voice stack override</label><select id="pk-setup-voice"><option value="">— from character preset —</option>'+voiceOptions+'</select></div>'
|
|
1765
|
+
+ '</div></details>'
|
|
1544
1766
|
+ '</div>'
|
|
1545
1767
|
+ '<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>');
|
|
1546
1768
|
var setupBtn=$('pk-setup-btn'); if(setupBtn) setupBtn.addEventListener('click', function(){ submitPadSetup(n); });
|
|
1547
1769
|
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'); } });
|
|
1770
|
+
function refreshPadCliCommand(){
|
|
1771
|
+
var nm=$('pk-setup-name').value.trim()||defaultName; var cid=$('pk-setup-char').value||n.id;
|
|
1772
|
+
var el=$('pk-setup-clicmd'); if(el) el.value=buildAddRobotCommand(nm, cid);
|
|
1773
|
+
}
|
|
1774
|
+
var padNameEl=$('pk-setup-name'); if(padNameEl) padNameEl.addEventListener('input', refreshPadCliCommand);
|
|
1775
|
+
var padCharEl=$('pk-setup-char'); if(padCharEl) padCharEl.addEventListener('change', refreshPadCliCommand);
|
|
1776
|
+
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'); });
|
|
1777
|
+
refreshPadCliCommand();
|
|
1548
1778
|
return;
|
|
1549
1779
|
}
|
|
1550
1780
|
if(n.kind==='robot'){
|
|
@@ -4,5 +4,8 @@ import { type ExplicitContextOpts } from './profile.js';
|
|
|
4
4
|
export interface AddRobotOptions extends ExplicitContextOpts {
|
|
5
5
|
name: string;
|
|
6
6
|
start?: boolean;
|
|
7
|
+
/** Force a specific park-map pad/character preset id instead of the
|
|
8
|
+
* fuzzy name-based match below — e.g. `--character jaguar`. */
|
|
9
|
+
character?: string;
|
|
7
10
|
}
|
|
8
11
|
export declare function roboparkAddRobot(config: Conf<InfinicodeConfig>, opts: AddRobotOptions): Promise<void>;
|
|
@@ -81,9 +81,12 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
81
81
|
console.log(` scheduler: ${chalk.cyan(ctx.schedulerUrl)}`);
|
|
82
82
|
console.log(` name: ${chalk.cyan(opts.name)}${ctx.site ? chalk.dim(' @ ' + ctx.site) : ''}`);
|
|
83
83
|
console.log();
|
|
84
|
-
const characterId = await pickCharacterPreset(ctx.schedulerUrl, opts.name);
|
|
84
|
+
const characterId = opts.character?.trim() || await pickCharacterPreset(ctx.schedulerUrl, opts.name);
|
|
85
85
|
if (characterId) {
|
|
86
|
-
console.log(` character: ${chalk.cyan(characterId)}`);
|
|
86
|
+
console.log(` character: ${chalk.cyan(characterId)}${opts.character ? chalk.dim(' (forced via --character)') : chalk.dim(' (auto-matched)')}`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(chalk.yellow(` ⚠ no character preset matched '${opts.name}' — pass --character <id> to bind this robot to a specific park-map pad`));
|
|
87
90
|
}
|
|
88
91
|
let minted;
|
|
89
92
|
try {
|
|
@@ -129,7 +132,10 @@ export async function roboparkAddRobot(config, opts) {
|
|
|
129
132
|
argv.push('--scheduler-port', schedulerPort);
|
|
130
133
|
}
|
|
131
134
|
argv.push('--enrollment-token', minted.enrollment_token, '--start', '--auto-start');
|
|
132
|
-
console.log(chalk.dim(
|
|
135
|
+
console.log(chalk.dim(` run this ON THE ROBOT${characterId ? ` (binds to pad: ${characterId})` : ''}:`));
|
|
133
136
|
console.log(' ' + chalk.cyan(commandString(argv)));
|
|
137
|
+
if (!ctx.hubUrl) {
|
|
138
|
+
console.log(chalk.yellow(' ⚠ no hub URL resolved — the command above omits --hub-url; run `robopark hub-use` first or pass --hub-url explicitly if the robot needs it.'));
|
|
139
|
+
}
|
|
134
140
|
console.log();
|
|
135
141
|
}
|
package/dist/robopark-cli.js
CHANGED
|
@@ -226,6 +226,7 @@ program
|
|
|
226
226
|
.option('--hub-url <url>', 'hub federation URL (defaults to saved hub profile / auto-discovery)')
|
|
227
227
|
.option('--token <token>', 'shared mesh token (defaults to saved hub profile / auto-discovery)')
|
|
228
228
|
.option('--scheduler-url <url>', 'scheduler base URL (defaults to saved hub profile / auto-discovery)')
|
|
229
|
+
.option('--character <id>', 'force a specific park-map pad/character preset id (default: fuzzy-matched from the robot name)')
|
|
229
230
|
.option('--start', 'start the robot node on this machine now, instead of printing the one-liner')
|
|
230
231
|
.action(async (name, opts) => {
|
|
231
232
|
await roboparkAddRobot(config, { name, ...opts });
|
package/package.json
CHANGED
|
@@ -424,6 +424,22 @@ async def init_db():
|
|
|
424
424
|
pass
|
|
425
425
|
await db.commit()
|
|
426
426
|
|
|
427
|
+
# ---- voice_stacks: options ROBOVOICE actually reads at runtime but
|
|
428
|
+
# this table didn't expose yet (llm auth + sampling + wake-word tuning) ----
|
|
429
|
+
for _table, _column, _coldef in (
|
|
430
|
+
("voice_stacks", "llm_api_key", "TEXT"),
|
|
431
|
+
("voice_stacks", "temperature", "REAL DEFAULT 0.7"),
|
|
432
|
+
("voice_stacks", "num_ctx", "INTEGER DEFAULT 8192"),
|
|
433
|
+
("voice_stacks", "wake_word_model", "TEXT"),
|
|
434
|
+
("voice_stacks", "wake_word_threshold", "REAL DEFAULT 0.5"),
|
|
435
|
+
("voice_stacks", "wake_word_timeout", "REAL DEFAULT 3.0"),
|
|
436
|
+
):
|
|
437
|
+
try:
|
|
438
|
+
await db.execute(f"ALTER TABLE {_table} ADD COLUMN {_column} {_coldef}")
|
|
439
|
+
except Exception:
|
|
440
|
+
pass
|
|
441
|
+
await db.commit()
|
|
442
|
+
|
|
427
443
|
# No demo robots are seeded. Actual robots enroll via the device
|
|
428
444
|
# enrollment API or are added through the scheduler UI. This keeps the
|
|
429
445
|
# fleet view honest — only real connected hardware appears.
|
|
@@ -2272,6 +2288,9 @@ class VoiceStack(BaseModel):
|
|
|
2272
2288
|
stt_language: str = "en"
|
|
2273
2289
|
llm_provider: str = "ollama"
|
|
2274
2290
|
llm_model: str = "gemma4:e4b"
|
|
2291
|
+
llm_api_key: Optional[str] = None
|
|
2292
|
+
temperature: float = 0.7
|
|
2293
|
+
num_ctx: int = 8192
|
|
2275
2294
|
tts_provider: str = "elevenlabs"
|
|
2276
2295
|
tts_voice: str = "21m00Tcm4TlvDq8ikWAM"
|
|
2277
2296
|
tts_language: str = "en"
|
|
@@ -2279,6 +2298,9 @@ class VoiceStack(BaseModel):
|
|
|
2279
2298
|
min_endpointing_delay: float = 0.7
|
|
2280
2299
|
max_turns: int = 20
|
|
2281
2300
|
wake_word_enabled: bool = False
|
|
2301
|
+
wake_word_model: Optional[str] = None
|
|
2302
|
+
wake_word_threshold: float = 0.5
|
|
2303
|
+
wake_word_timeout: float = 3.0
|
|
2282
2304
|
created_at: Optional[datetime] = None
|
|
2283
2305
|
updated_at: Optional[datetime] = None
|
|
2284
2306
|
|
|
@@ -2290,6 +2312,9 @@ class VoiceStackCreate(BaseModel):
|
|
|
2290
2312
|
stt_language: str = "en"
|
|
2291
2313
|
llm_provider: str = "ollama"
|
|
2292
2314
|
llm_model: str = "gemma4:e4b"
|
|
2315
|
+
llm_api_key: Optional[str] = None
|
|
2316
|
+
temperature: float = 0.7
|
|
2317
|
+
num_ctx: int = 8192
|
|
2293
2318
|
tts_provider: str = "elevenlabs"
|
|
2294
2319
|
tts_voice: str = "21m00Tcm4TlvDq8ikWAM"
|
|
2295
2320
|
tts_language: str = "en"
|
|
@@ -2297,6 +2322,9 @@ class VoiceStackCreate(BaseModel):
|
|
|
2297
2322
|
min_endpointing_delay: float = 0.7
|
|
2298
2323
|
max_turns: int = 20
|
|
2299
2324
|
wake_word_enabled: bool = False
|
|
2325
|
+
wake_word_model: Optional[str] = None
|
|
2326
|
+
wake_word_threshold: float = 0.5
|
|
2327
|
+
wake_word_timeout: float = 3.0
|
|
2300
2328
|
|
|
2301
2329
|
# Effective configuration returned to the ROBOVOICE agent for a given room/robot.
|
|
2302
2330
|
class RobotVoiceConfig(BaseModel):
|
|
@@ -2493,14 +2521,20 @@ async def create_voice_stack(payload: VoiceStackCreate):
|
|
|
2493
2521
|
await db.execute(
|
|
2494
2522
|
"""INSERT INTO voice_stacks
|
|
2495
2523
|
(id, name, stt_provider, stt_model, stt_language, llm_provider, llm_model,
|
|
2524
|
+
llm_api_key, temperature, num_ctx,
|
|
2496
2525
|
tts_provider, tts_voice, tts_language, allow_interruptions,
|
|
2497
|
-
min_endpointing_delay, max_turns, wake_word_enabled,
|
|
2498
|
-
|
|
2526
|
+
min_endpointing_delay, max_turns, wake_word_enabled,
|
|
2527
|
+
wake_word_model, wake_word_threshold, wake_word_timeout,
|
|
2528
|
+
created_at, updated_at)
|
|
2529
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
2499
2530
|
(
|
|
2500
2531
|
stack_id, payload.name, payload.stt_provider, payload.stt_model, payload.stt_language,
|
|
2501
|
-
payload.llm_provider, payload.llm_model, payload.
|
|
2532
|
+
payload.llm_provider, payload.llm_model, payload.llm_api_key, payload.temperature, payload.num_ctx,
|
|
2533
|
+
payload.tts_provider, payload.tts_voice,
|
|
2502
2534
|
payload.tts_language, int(payload.allow_interruptions), payload.min_endpointing_delay,
|
|
2503
|
-
payload.max_turns, int(payload.wake_word_enabled),
|
|
2535
|
+
payload.max_turns, int(payload.wake_word_enabled),
|
|
2536
|
+
payload.wake_word_model, payload.wake_word_threshold, payload.wake_word_timeout,
|
|
2537
|
+
now, now,
|
|
2504
2538
|
),
|
|
2505
2539
|
)
|
|
2506
2540
|
await db.commit()
|
|
@@ -2532,15 +2566,20 @@ async def update_voice_stack(stack_id: str, payload: VoiceStackCreate):
|
|
|
2532
2566
|
await db.execute(
|
|
2533
2567
|
"""UPDATE voice_stacks SET
|
|
2534
2568
|
name = ?, stt_provider = ?, stt_model = ?, stt_language = ?, llm_provider = ?,
|
|
2535
|
-
llm_model = ?,
|
|
2569
|
+
llm_model = ?, llm_api_key = ?, temperature = ?, num_ctx = ?,
|
|
2570
|
+
tts_provider = ?, tts_voice = ?, tts_language = ?,
|
|
2536
2571
|
allow_interruptions = ?, min_endpointing_delay = ?, max_turns = ?,
|
|
2537
|
-
wake_word_enabled = ?,
|
|
2572
|
+
wake_word_enabled = ?, wake_word_model = ?, wake_word_threshold = ?, wake_word_timeout = ?,
|
|
2573
|
+
updated_at = ?
|
|
2538
2574
|
WHERE id = ?""",
|
|
2539
2575
|
(
|
|
2540
2576
|
payload.name, payload.stt_provider, payload.stt_model, payload.stt_language,
|
|
2541
|
-
payload.llm_provider, payload.llm_model, payload.
|
|
2577
|
+
payload.llm_provider, payload.llm_model, payload.llm_api_key, payload.temperature, payload.num_ctx,
|
|
2578
|
+
payload.tts_provider, payload.tts_voice,
|
|
2542
2579
|
payload.tts_language, int(payload.allow_interruptions), payload.min_endpointing_delay,
|
|
2543
|
-
payload.max_turns, int(payload.wake_word_enabled),
|
|
2580
|
+
payload.max_turns, int(payload.wake_word_enabled),
|
|
2581
|
+
payload.wake_word_model, payload.wake_word_threshold, payload.wake_word_timeout,
|
|
2582
|
+
now, stack_id,
|
|
2544
2583
|
),
|
|
2545
2584
|
)
|
|
2546
2585
|
await db.commit()
|