@blueharford/scrypted-spatial-awareness 0.6.15 → 0.6.17
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/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +114 -3
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/core/topology-discovery.ts +112 -3
- package/src/ui/editor-html.ts +10 -0
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -36481,18 +36481,31 @@ class TopologyDiscoveryEngine {
|
|
|
36481
36481
|
for (const formatType of formatsToTry) {
|
|
36482
36482
|
try {
|
|
36483
36483
|
this.console.log(`[Discovery] Trying ${formatType} image format for ${cameraName}...`);
|
|
36484
|
+
// Build prompt with camera context (height)
|
|
36485
|
+
const cameraNode = this.topology ? (0, topology_1.findCamera)(this.topology, cameraId) : null;
|
|
36486
|
+
const mountHeight = cameraNode?.context?.mountHeight || 8;
|
|
36487
|
+
const cameraRange = cameraNode?.fov?.range || 80;
|
|
36488
|
+
// Add camera-specific context to the prompt
|
|
36489
|
+
const contextPrefix = `CAMERA INFORMATION:
|
|
36490
|
+
- Camera Name: ${cameraName}
|
|
36491
|
+
- Mount Height: ${mountHeight} feet above ground
|
|
36492
|
+
- Approximate viewing range: ${cameraRange} feet
|
|
36493
|
+
|
|
36494
|
+
Use the mount height to help estimate distances - objects at ground level will appear at different angles depending on distance from a camera mounted at ${mountHeight} feet.
|
|
36495
|
+
|
|
36496
|
+
`;
|
|
36484
36497
|
// Build multimodal message with provider-specific image format
|
|
36485
36498
|
const result = await llm.getChatCompletion({
|
|
36486
36499
|
messages: [
|
|
36487
36500
|
{
|
|
36488
36501
|
role: 'user',
|
|
36489
36502
|
content: [
|
|
36490
|
-
{ type: 'text', text: SCENE_ANALYSIS_PROMPT },
|
|
36503
|
+
{ type: 'text', text: contextPrefix + SCENE_ANALYSIS_PROMPT },
|
|
36491
36504
|
(0, spatial_reasoning_1.buildImageContent)(imageData, formatType),
|
|
36492
36505
|
],
|
|
36493
36506
|
},
|
|
36494
36507
|
],
|
|
36495
|
-
max_tokens:
|
|
36508
|
+
max_tokens: 4000, // Increased for detailed scene analysis
|
|
36496
36509
|
temperature: 0.3,
|
|
36497
36510
|
});
|
|
36498
36511
|
const content = result?.choices?.[0]?.message?.content;
|
|
@@ -36503,7 +36516,8 @@ class TopologyDiscoveryEngine {
|
|
|
36503
36516
|
if (jsonStr.startsWith('```')) {
|
|
36504
36517
|
jsonStr = jsonStr.replace(/```json?\n?/g, '').replace(/```$/g, '').trim();
|
|
36505
36518
|
}
|
|
36506
|
-
|
|
36519
|
+
// Try to recover truncated JSON
|
|
36520
|
+
const parsed = this.parseJsonWithRecovery(jsonStr, cameraName);
|
|
36507
36521
|
// Map parsed data to our types
|
|
36508
36522
|
if (Array.isArray(parsed.landmarks)) {
|
|
36509
36523
|
analysis.landmarks = parsed.landmarks.map((l) => ({
|
|
@@ -36643,6 +36657,93 @@ class TopologyDiscoveryEngine {
|
|
|
36643
36657
|
return 'distant';
|
|
36644
36658
|
return 'medium'; // Default to medium if not specified
|
|
36645
36659
|
}
|
|
36660
|
+
/** Try to parse JSON with recovery for truncated responses */
|
|
36661
|
+
parseJsonWithRecovery(jsonStr, context) {
|
|
36662
|
+
// First, try direct parse
|
|
36663
|
+
try {
|
|
36664
|
+
return JSON.parse(jsonStr);
|
|
36665
|
+
}
|
|
36666
|
+
catch (e) {
|
|
36667
|
+
// Log the raw response for debugging (first 500 chars)
|
|
36668
|
+
this.console.log(`[Discovery] Raw LLM response for ${context} (first 500 chars): ${jsonStr.substring(0, 500)}...`);
|
|
36669
|
+
}
|
|
36670
|
+
// Try to recover truncated JSON by finding complete sections
|
|
36671
|
+
try {
|
|
36672
|
+
// Find where valid JSON might end (look for last complete object/array)
|
|
36673
|
+
let recoveredJson = jsonStr;
|
|
36674
|
+
// Try to close unclosed strings
|
|
36675
|
+
const lastQuote = recoveredJson.lastIndexOf('"');
|
|
36676
|
+
const lastColon = recoveredJson.lastIndexOf(':');
|
|
36677
|
+
if (lastQuote > lastColon) {
|
|
36678
|
+
// We might be in the middle of a string value
|
|
36679
|
+
const beforeQuote = recoveredJson.substring(0, lastQuote);
|
|
36680
|
+
const afterLastCompleteEntry = beforeQuote.lastIndexOf('},');
|
|
36681
|
+
if (afterLastCompleteEntry > 0) {
|
|
36682
|
+
recoveredJson = beforeQuote.substring(0, afterLastCompleteEntry + 1);
|
|
36683
|
+
}
|
|
36684
|
+
}
|
|
36685
|
+
// Close any unclosed arrays/objects
|
|
36686
|
+
let openBraces = (recoveredJson.match(/{/g) || []).length;
|
|
36687
|
+
let closeBraces = (recoveredJson.match(/}/g) || []).length;
|
|
36688
|
+
let openBrackets = (recoveredJson.match(/\[/g) || []).length;
|
|
36689
|
+
let closeBrackets = (recoveredJson.match(/\]/g) || []).length;
|
|
36690
|
+
// Add missing closing brackets/braces
|
|
36691
|
+
while (closeBrackets < openBrackets) {
|
|
36692
|
+
recoveredJson += ']';
|
|
36693
|
+
closeBrackets++;
|
|
36694
|
+
}
|
|
36695
|
+
while (closeBraces < openBraces) {
|
|
36696
|
+
recoveredJson += '}';
|
|
36697
|
+
closeBraces++;
|
|
36698
|
+
}
|
|
36699
|
+
const recovered = JSON.parse(recoveredJson);
|
|
36700
|
+
this.console.log(`[Discovery] Recovered truncated JSON for ${context}`);
|
|
36701
|
+
return recovered;
|
|
36702
|
+
}
|
|
36703
|
+
catch (recoveryError) {
|
|
36704
|
+
// Last resort: try to extract just landmarks array
|
|
36705
|
+
try {
|
|
36706
|
+
const landmarksMatch = jsonStr.match(/"landmarks"\s*:\s*\[([\s\S]*?)(?:\]|$)/);
|
|
36707
|
+
const zonesMatch = jsonStr.match(/"zones"\s*:\s*\[([\s\S]*?)(?:\]|$)/);
|
|
36708
|
+
const result = { landmarks: [], zones: [], edges: {}, orientation: 'unknown' };
|
|
36709
|
+
if (landmarksMatch) {
|
|
36710
|
+
// Try to parse individual landmark objects
|
|
36711
|
+
const landmarksStr = landmarksMatch[1];
|
|
36712
|
+
const landmarkObjects = landmarksStr.match(/\{[^{}]*\}/g) || [];
|
|
36713
|
+
result.landmarks = landmarkObjects.map((obj) => {
|
|
36714
|
+
try {
|
|
36715
|
+
return JSON.parse(obj);
|
|
36716
|
+
}
|
|
36717
|
+
catch {
|
|
36718
|
+
return null;
|
|
36719
|
+
}
|
|
36720
|
+
}).filter(Boolean);
|
|
36721
|
+
this.console.log(`[Discovery] Extracted ${result.landmarks.length} landmarks from partial response for ${context}`);
|
|
36722
|
+
}
|
|
36723
|
+
if (zonesMatch) {
|
|
36724
|
+
const zonesStr = zonesMatch[1];
|
|
36725
|
+
const zoneObjects = zonesStr.match(/\{[^{}]*\}/g) || [];
|
|
36726
|
+
result.zones = zoneObjects.map((obj) => {
|
|
36727
|
+
try {
|
|
36728
|
+
return JSON.parse(obj);
|
|
36729
|
+
}
|
|
36730
|
+
catch {
|
|
36731
|
+
return null;
|
|
36732
|
+
}
|
|
36733
|
+
}).filter(Boolean);
|
|
36734
|
+
this.console.log(`[Discovery] Extracted ${result.zones.length} zones from partial response for ${context}`);
|
|
36735
|
+
}
|
|
36736
|
+
if (result.landmarks.length > 0 || result.zones.length > 0) {
|
|
36737
|
+
return result;
|
|
36738
|
+
}
|
|
36739
|
+
}
|
|
36740
|
+
catch (extractError) {
|
|
36741
|
+
// Give up
|
|
36742
|
+
}
|
|
36743
|
+
this.console.warn(`[Discovery] Could not recover JSON for ${context}`);
|
|
36744
|
+
throw new Error(`Failed to parse LLM response: truncated or malformed JSON`);
|
|
36745
|
+
}
|
|
36746
|
+
}
|
|
36646
36747
|
/** Resolve a camera reference (name or deviceId) to its deviceId */
|
|
36647
36748
|
resolveCameraRef(ref) {
|
|
36648
36749
|
if (!this.topology?.cameras || !ref)
|
|
@@ -43631,8 +43732,12 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
|
|
|
43631
43732
|
const fov = camera.fov || { mode: 'simple', angle: 90, direction: 0, range: 80 };
|
|
43632
43733
|
// Convert stored pixel range to feet for display
|
|
43633
43734
|
const rangeInFeet = Math.round(pixelsToFeet(fov.range || 80));
|
|
43735
|
+
// Get mount height from context or default to 8
|
|
43736
|
+
const mountHeight = camera.context?.mountHeight || 8;
|
|
43634
43737
|
panel.innerHTML = '<h3>Camera Properties</h3>' +
|
|
43635
43738
|
'<div class="form-group"><label>Name</label><input type="text" value="' + camera.name + '" onchange="updateCameraName(\\'' + camera.deviceId + '\\', this.value)"></div>' +
|
|
43739
|
+
'<div class="form-group"><label>Mount Height (feet)</label><input type="number" value="' + mountHeight + '" min="1" max="40" step="0.5" onchange="updateCameraMountHeight(\\'' + camera.deviceId + '\\', this.value)"></div>' +
|
|
43740
|
+
'<div style="font-size: 11px; color: #666; margin-top: -10px; margin-bottom: 10px;">Height affects distance estimation in discovery</div>' +
|
|
43636
43741
|
'<div class="form-group"><label class="checkbox-group"><input type="checkbox" ' + (camera.isEntryPoint ? 'checked' : '') + ' onchange="updateCameraEntry(\\'' + camera.deviceId + '\\', this.checked)">Entry Point</label></div>' +
|
|
43637
43742
|
'<div class="form-group"><label class="checkbox-group"><input type="checkbox" ' + (camera.isExitPoint ? 'checked' : '') + ' onchange="updateCameraExit(\\'' + camera.deviceId + '\\', this.checked)">Exit Point</label></div>' +
|
|
43638
43743
|
'<h4 style="margin-top: 15px; margin-bottom: 10px; color: #888;">Field of View</h4>' +
|
|
@@ -43651,6 +43756,12 @@ exports.EDITOR_HTML = `<!DOCTYPE html>
|
|
|
43651
43756
|
function updateCameraName(id, value) { const camera = topology.cameras.find(c => c.deviceId === id); if (camera) camera.name = value; updateUI(); }
|
|
43652
43757
|
function updateCameraEntry(id, value) { const camera = topology.cameras.find(c => c.deviceId === id); if (camera) camera.isEntryPoint = value; }
|
|
43653
43758
|
function updateCameraExit(id, value) { const camera = topology.cameras.find(c => c.deviceId === id); if (camera) camera.isExitPoint = value; }
|
|
43759
|
+
function updateCameraMountHeight(id, value) {
|
|
43760
|
+
const camera = topology.cameras.find(c => c.deviceId === id);
|
|
43761
|
+
if (!camera) return;
|
|
43762
|
+
if (!camera.context) camera.context = {};
|
|
43763
|
+
camera.context.mountHeight = parseFloat(value) || 8;
|
|
43764
|
+
}
|
|
43654
43765
|
function updateCameraFov(id, field, value) {
|
|
43655
43766
|
const camera = topology.cameras.find(c => c.deviceId === id);
|
|
43656
43767
|
if (!camera) return;
|