infinicode 2.8.45 → 2.8.46
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/package.json
CHANGED
|
@@ -4230,13 +4230,24 @@ async def dashboard():
|
|
|
4230
4230
|
const buildDeviceInventory = () => {
|
|
4231
4231
|
const map = {};
|
|
4232
4232
|
for (const robot of robots.value) {
|
|
4233
|
-
const
|
|
4233
|
+
const robotName = String(robot.name || '').toLowerCase();
|
|
4234
|
+
const device = devices.value.find(d => String(d.name || '').toLowerCase() === robotName || d.id === robot.id);
|
|
4234
4235
|
const savedVideo = device?.video_device || 'auto';
|
|
4235
4236
|
const savedAudio = device?.audio_device || 'default';
|
|
4236
4237
|
const hardware = device?.device_inventory || {};
|
|
4237
|
-
const videoOptions = (hardware.video || []).
|
|
4238
|
-
const audioOptions = (hardware.audio_input || []).
|
|
4239
|
-
const audioOutputOptions = (hardware.audio_output || []).
|
|
4238
|
+
const videoOptions = (hardware.video || []).filter(Boolean);
|
|
4239
|
+
const audioOptions = (hardware.audio_input || []).filter(Boolean);
|
|
4240
|
+
const audioOutputOptions = (hardware.audio_output || []).filter(Boolean);
|
|
4241
|
+
const optionId = x => typeof x === 'string' ? x : x.id;
|
|
4242
|
+
const optionName = x => typeof x === 'string' ? x : (x.name || x.id);
|
|
4243
|
+
const uniqueOptions = (items, defaults) => {
|
|
4244
|
+
const seen = new Set();
|
|
4245
|
+
return defaults.concat(items).filter(x => {
|
|
4246
|
+
const id = optionId(x);
|
|
4247
|
+
if (!id || seen.has(id)) return false;
|
|
4248
|
+
seen.add(id); return true;
|
|
4249
|
+
});
|
|
4250
|
+
};
|
|
4240
4251
|
map[robot.id] = {
|
|
4241
4252
|
deviceId: device?.id || null,
|
|
4242
4253
|
robotName: robot.name,
|
|
@@ -4244,9 +4255,10 @@ async def dashboard():
|
|
|
4244
4255
|
selectedAudio: savedAudio,
|
|
4245
4256
|
selectedAudioOutput: device?.audio_output_device || 'default',
|
|
4246
4257
|
greetingText: (device?.greeting_phrases || []).join('\\n'),
|
|
4247
|
-
videoOptions:
|
|
4248
|
-
audioOptions:
|
|
4249
|
-
audioOutputOptions:
|
|
4258
|
+
videoOptions: uniqueOptions(videoOptions, ['auto', 'none', savedVideo]),
|
|
4259
|
+
audioOptions: uniqueOptions(audioOptions, ['default', 'none', savedAudio]),
|
|
4260
|
+
audioOutputOptions: uniqueOptions(audioOutputOptions, ['default', 'none', device?.audio_output_device || 'default']),
|
|
4261
|
+
optionId, optionName,
|
|
4250
4262
|
};
|
|
4251
4263
|
}
|
|
4252
4264
|
deviceInventory.value = map;
|
|
@@ -4366,7 +4378,7 @@ async def dashboard():
|
|
|
4366
4378
|
const inv = deviceInventory.value[robotId];
|
|
4367
4379
|
if (!inv || !inv.deviceId) return;
|
|
4368
4380
|
try {
|
|
4369
|
-
await fetch(`/api/devices/${inv.deviceId}`, {
|
|
4381
|
+
const response = await fetch(`/api/devices/${inv.deviceId}`, {
|
|
4370
4382
|
method: 'PATCH',
|
|
4371
4383
|
headers: {'Content-Type': 'application/json'},
|
|
4372
4384
|
body: JSON.stringify({
|
|
@@ -4416,6 +4428,31 @@ async def dashboard():
|
|
|
4416
4428
|
} catch (e) { console.warn('preview stop failed', e); }
|
|
4417
4429
|
}
|
|
4418
4430
|
};
|
|
4431
|
+
|
|
4432
|
+
const testSpeaker = async (robot) => {
|
|
4433
|
+
const inv = deviceInventory.value[robot.id];
|
|
4434
|
+
const deviceId = inv?.deviceId || robot.id;
|
|
4435
|
+
try {
|
|
4436
|
+
const response = await fetch(`/api/robots/${deviceId}/shell/speaker-test`, {
|
|
4437
|
+
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
4438
|
+
body: JSON.stringify({
|
|
4439
|
+
output: inv?.selectedAudioOutput || 'default',
|
|
4440
|
+
input: inv?.selectedAudio || 'default',
|
|
4441
|
+
duration: 0.6,
|
|
4442
|
+
}),
|
|
4443
|
+
});
|
|
4444
|
+
const queued = await response.json();
|
|
4445
|
+
if (!response.ok) throw new Error(queued.detail || `HTTP ${response.status}`);
|
|
4446
|
+
const result = await fetch(`/api/robots/${deviceId}/shell/wait/${queued.request_id}?timeout=12`).then(r => r.json());
|
|
4447
|
+
if (!result.completed) throw new Error('Robot did not answer. Check its heartbeat and supervisor.');
|
|
4448
|
+
const detail = result.result || {};
|
|
4449
|
+
alert(detail.ok
|
|
4450
|
+
? `Audio test passed\\nOutput: ${detail.output_device || 'default'}\\nInput: ${detail.input_device || 'default'}\\nPeak: ${detail.peak_db ?? 'n/a'} dB`
|
|
4451
|
+
: `Audio test failed: ${detail.error || 'no microphone signal'}`);
|
|
4452
|
+
} catch (e) {
|
|
4453
|
+
alert('Audio test failed: ' + e.message);
|
|
4454
|
+
}
|
|
4455
|
+
};
|
|
4419
4456
|
|
|
4420
4457
|
const simulateTrigger = async (robot) => {
|
|
4421
4458
|
if (!settings.value.production_mode || simulation.value.busy) return;
|
|
@@ -4766,14 +4803,14 @@ async def dashboard():
|
|
|
4766
4803
|
return {
|
|
4767
4804
|
robots, servers, sessions, stats, connected, serverMetrics,
|
|
4768
4805
|
devices, settings, livekit, showAddDevice, showDevices, showCommands, commandNetwork, commandHubLan, commandHubTailscale, commandRobotId, selectedCommandRobot, commandRows, copyCommand, newDevice, rotatedToken, enrollmentNetwork, enrollmentCommand, simulation,
|
|
4769
|
-
showLiveKitConfig, livekitForm, preview, deviceInventory,
|
|
4806
|
+
showLiveKitConfig, livekitForm, preview, deviceInventory,
|
|
4770
4807
|
voiceStacks, characterPresets, showVoiceStackForm, showCharacterForm,
|
|
4771
4808
|
editingVoiceStack, editingCharacter, voiceStackForm, characterForm,
|
|
4772
4809
|
loadModel, unloadModel, statusColor, formatDuration, formatTime,
|
|
4773
4810
|
toggleProductionMode, rotateEnrollmentToken, provisionAgentToken,
|
|
4774
4811
|
submitNewDevice, deleteDevice, rotateDeviceToken, dismissRotatedToken,
|
|
4775
4812
|
joinConversation, saveLiveKitConfig, openLiveKitConfig,
|
|
4776
|
-
startPreview, stopPreview, simulateTrigger, stopConversation, recoverRobot, toggleDeviceProduction, setDeviceProduction, saveDeviceGreetings, updateDeviceMedia,
|
|
4813
|
+
startPreview, stopPreview, simulateTrigger, stopConversation, recoverRobot, toggleDeviceProduction, setDeviceProduction, saveDeviceGreetings, updateDeviceMedia, testSpeaker,
|
|
4777
4814
|
openVoiceStackForm, saveVoiceStack, deleteVoiceStack,
|
|
4778
4815
|
openCharacterForm, saveCharacter, deleteCharacter,
|
|
4779
4816
|
assignRobotCharacter, stackName, characterName,
|
|
@@ -5118,7 +5155,7 @@ async def dashboard():
|
|
|
5118
5155
|
<select v-model="deviceInventory[robot.id].selectedVideo"
|
|
5119
5156
|
@change="updateDeviceMedia(robot.id)"
|
|
5120
5157
|
class="w-full bg-gray-800 rounded px-2 py-1 text-xs">
|
|
5121
|
-
<option v-for="opt in deviceInventory[robot.id].videoOptions" :key="opt" :value="opt">{{ opt }}</option>
|
|
5158
|
+
<option v-for="opt in deviceInventory[robot.id].videoOptions" :key="deviceInventory[robot.id].optionId(opt)" :value="deviceInventory[robot.id].optionId(opt)">{{ deviceInventory[robot.id].optionName(opt) }}</option>
|
|
5122
5159
|
</select>
|
|
5123
5160
|
</div>
|
|
5124
5161
|
<div>
|
|
@@ -5126,7 +5163,7 @@ async def dashboard():
|
|
|
5126
5163
|
<select v-model="deviceInventory[robot.id].selectedAudio"
|
|
5127
5164
|
@change="updateDeviceMedia(robot.id)"
|
|
5128
5165
|
class="w-full bg-gray-800 rounded px-2 py-1 text-xs">
|
|
5129
|
-
<option v-for="opt in deviceInventory[robot.id].audioOptions" :key="opt" :value="opt">{{ opt }}</option>
|
|
5166
|
+
<option v-for="opt in deviceInventory[robot.id].audioOptions" :key="deviceInventory[robot.id].optionId(opt)" :value="deviceInventory[robot.id].optionId(opt)">{{ deviceInventory[robot.id].optionName(opt) }}</option>
|
|
5130
5167
|
</select>
|
|
5131
5168
|
</div>
|
|
5132
5169
|
<div>
|
|
@@ -5134,7 +5171,7 @@ async def dashboard():
|
|
|
5134
5171
|
<select v-model="deviceInventory[robot.id].selectedAudioOutput"
|
|
5135
5172
|
@change="updateDeviceMedia(robot.id)"
|
|
5136
5173
|
class="w-full bg-gray-800 rounded px-2 py-1 text-xs">
|
|
5137
|
-
<option v-for="opt in deviceInventory[robot.id].audioOutputOptions" :key="opt" :value="opt">{{ opt }}</option>
|
|
5174
|
+
<option v-for="opt in deviceInventory[robot.id].audioOutputOptions" :key="deviceInventory[robot.id].optionId(opt)" :value="deviceInventory[robot.id].optionId(opt)">{{ deviceInventory[robot.id].optionName(opt) }}</option>
|
|
5138
5175
|
</select>
|
|
5139
5176
|
</div>
|
|
5140
5177
|
</div>
|
|
@@ -5143,12 +5180,16 @@ async def dashboard():
|
|
|
5143
5180
|
<textarea v-model="deviceInventory[robot.id].greetingText" rows="2" class="w-full bg-gray-800 rounded px-2 py-1 text-xs" placeholder="Hello! Want to go for a ride?"></textarea>
|
|
5144
5181
|
<button @click="saveDeviceGreetings(robot.id)" class="mt-1 px-2 py-1 rounded bg-gray-600 hover:bg-gray-500 text-xs">Save Greetings</button>
|
|
5145
5182
|
</div>
|
|
5146
|
-
<button @click="startPreview(robot)"
|
|
5183
|
+
<button @click="startPreview(robot)"
|
|
5147
5184
|
:disabled="!settings.production_mode || !livekit.url || !livekit.has_secret || preview.active"
|
|
5148
5185
|
:class="(settings.production_mode && livekit.url && livekit.has_secret && !preview.active) ? 'bg-cyan-600 hover:bg-cyan-700' : 'bg-gray-700 cursor-not-allowed'"
|
|
5149
5186
|
class="w-full px-2 py-1 rounded text-xs">
|
|
5150
|
-
{{ preview.active && preview.robot?.id === robot.id ? 'Preview Active' : 'Start Preview' }}
|
|
5151
|
-
</button>
|
|
5187
|
+
{{ preview.active && preview.robot?.id === robot.id ? 'Preview Active' : 'Start Preview' }}
|
|
5188
|
+
</button>
|
|
5189
|
+
<button @click="testSpeaker(robot)"
|
|
5190
|
+
class="w-full px-2 py-1 rounded text-xs bg-emerald-700 hover:bg-emerald-600">
|
|
5191
|
+
Test Speaker + Mic
|
|
5192
|
+
</button>
|
|
5152
5193
|
<div class="grid grid-cols-2 gap-2">
|
|
5153
5194
|
<button @click="simulateTrigger(robot)"
|
|
5154
5195
|
:disabled="!settings.production_mode || simulation.busy || robot.status === 'connecting' || robot.status === 'running'"
|
|
@@ -66,7 +66,9 @@ import httpx
|
|
|
66
66
|
|
|
67
67
|
logger = logging.getLogger("robopark.preview_agent")
|
|
68
68
|
|
|
69
|
-
_DEVICE_INVENTORY_CACHE: Optional[dict] = None
|
|
69
|
+
_DEVICE_INVENTORY_CACHE: Optional[dict] = None
|
|
70
|
+
_DEVICE_INVENTORY_CACHE_AT = 0.0
|
|
71
|
+
DEVICE_INVENTORY_CACHE_SECONDS = 30.0
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
def _normalize_livekit_url(url: Optional[str]) -> Optional[str]:
|
|
@@ -131,11 +133,13 @@ def _play_audio_effect(selected_output: str | None, effect: str) -> None:
|
|
|
131
133
|
pa.terminate()
|
|
132
134
|
|
|
133
135
|
|
|
134
|
-
def _get_device_inventory() -> dict:
|
|
135
|
-
"""Return discoverable camera and audio devices for dashboard selection."""
|
|
136
|
-
global _DEVICE_INVENTORY_CACHE
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
def _get_device_inventory() -> dict:
|
|
137
|
+
"""Return discoverable camera and audio devices for dashboard selection."""
|
|
138
|
+
global _DEVICE_INVENTORY_CACHE, _DEVICE_INVENTORY_CACHE_AT
|
|
139
|
+
now = time.monotonic()
|
|
140
|
+
if (_DEVICE_INVENTORY_CACHE is not None
|
|
141
|
+
and now - _DEVICE_INVENTORY_CACHE_AT < DEVICE_INVENTORY_CACHE_SECONDS):
|
|
142
|
+
return _DEVICE_INVENTORY_CACHE
|
|
139
143
|
|
|
140
144
|
inventory = {"video": [], "audio_input": [], "audio_output": [], "platform": sys.platform}
|
|
141
145
|
inventory["video"].append({"id": "auto", "name": "Auto detect"})
|
|
@@ -182,7 +186,8 @@ def _get_device_inventory() -> dict:
|
|
|
182
186
|
except Exception as e:
|
|
183
187
|
logger.debug(f"audio inventory unavailable: {e}")
|
|
184
188
|
|
|
185
|
-
_DEVICE_INVENTORY_CACHE = inventory
|
|
189
|
+
_DEVICE_INVENTORY_CACHE = inventory
|
|
190
|
+
_DEVICE_INVENTORY_CACHE_AT = now
|
|
186
191
|
logger.info(
|
|
187
192
|
f"device inventory: {len(inventory['video']) - 2} cameras, "
|
|
188
193
|
f"{len(inventory['audio_input']) - 1} inputs, "
|