@blueharford/scrypted-spatial-awareness 0.6.26 → 0.6.28
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 +102 -119
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/core/spatial-reasoning.ts +55 -28
- package/src/core/tracking-engine.ts +3 -0
- package/src/main.ts +49 -96
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -35410,46 +35410,46 @@ class SpatialReasoningEngine {
|
|
|
35410
35410
|
// Load balancing for multiple LLMs
|
|
35411
35411
|
llmDevices = [];
|
|
35412
35412
|
llmIndex = 0;
|
|
35413
|
-
/** Find
|
|
35413
|
+
/** Find LLM devices - uses configured devices or auto-discovers all for load balancing */
|
|
35414
35414
|
async findAllLlmDevices() {
|
|
35415
35415
|
if (this.llmSearched)
|
|
35416
35416
|
return;
|
|
35417
35417
|
this.llmSearched = true;
|
|
35418
35418
|
try {
|
|
35419
|
+
// If specific LLM devices are configured, use only those
|
|
35420
|
+
if (this.config.llmDeviceIds && this.config.llmDeviceIds.length > 0) {
|
|
35421
|
+
for (const deviceId of this.config.llmDeviceIds) {
|
|
35422
|
+
const device = systemManager.getDeviceById(deviceId);
|
|
35423
|
+
if (device?.interfaces?.includes('ChatCompletion')) {
|
|
35424
|
+
const providerTypeEnum = this.detectProviderType(device);
|
|
35425
|
+
this.llmDevices.push({
|
|
35426
|
+
device: device,
|
|
35427
|
+
id: deviceId,
|
|
35428
|
+
name: device.name || deviceId,
|
|
35429
|
+
providerType: providerTypeEnum,
|
|
35430
|
+
lastUsed: 0,
|
|
35431
|
+
errorCount: 0,
|
|
35432
|
+
});
|
|
35433
|
+
this.console.log(`[LLM] Using configured LLM: ${device.name}`);
|
|
35434
|
+
}
|
|
35435
|
+
else {
|
|
35436
|
+
this.console.warn(`[LLM] Configured device ${deviceId} not found or doesn't support ChatCompletion`);
|
|
35437
|
+
}
|
|
35438
|
+
}
|
|
35439
|
+
if (this.llmDevices.length > 0) {
|
|
35440
|
+
this.console.log(`[LLM] Using ${this.llmDevices.length} configured LLM provider(s)`);
|
|
35441
|
+
return;
|
|
35442
|
+
}
|
|
35443
|
+
// Fall through to auto-discovery if none of the configured devices worked
|
|
35444
|
+
this.console.warn('[LLM] No configured devices available, falling back to auto-discovery');
|
|
35445
|
+
}
|
|
35446
|
+
// Auto-discover all LLM devices for load balancing
|
|
35419
35447
|
for (const id of Object.keys(systemManager.getSystemState())) {
|
|
35420
35448
|
const device = systemManager.getDeviceById(id);
|
|
35421
35449
|
if (!device)
|
|
35422
35450
|
continue;
|
|
35423
35451
|
if (device.interfaces?.includes('ChatCompletion')) {
|
|
35424
|
-
const
|
|
35425
|
-
const pluginId = device.pluginId?.toLowerCase() || '';
|
|
35426
|
-
// Identify the provider type for image format selection
|
|
35427
|
-
let providerType = 'Unknown';
|
|
35428
|
-
let providerTypeEnum = 'unknown';
|
|
35429
|
-
if (deviceName.includes('openai') || deviceName.includes('gpt')) {
|
|
35430
|
-
providerType = 'OpenAI';
|
|
35431
|
-
providerTypeEnum = 'openai';
|
|
35432
|
-
}
|
|
35433
|
-
else if (deviceName.includes('anthropic') || deviceName.includes('claude')) {
|
|
35434
|
-
providerType = 'Anthropic';
|
|
35435
|
-
providerTypeEnum = 'anthropic';
|
|
35436
|
-
}
|
|
35437
|
-
else if (deviceName.includes('ollama')) {
|
|
35438
|
-
providerType = 'Ollama';
|
|
35439
|
-
providerTypeEnum = 'openai';
|
|
35440
|
-
}
|
|
35441
|
-
else if (deviceName.includes('gemini') || deviceName.includes('google')) {
|
|
35442
|
-
providerType = 'Google';
|
|
35443
|
-
providerTypeEnum = 'openai';
|
|
35444
|
-
}
|
|
35445
|
-
else if (deviceName.includes('llama')) {
|
|
35446
|
-
providerType = 'llama.cpp';
|
|
35447
|
-
providerTypeEnum = 'openai';
|
|
35448
|
-
}
|
|
35449
|
-
else if (pluginId.includes('@scrypted/llm') || pluginId.includes('llm')) {
|
|
35450
|
-
providerType = 'Scrypted LLM';
|
|
35451
|
-
providerTypeEnum = 'unknown';
|
|
35452
|
-
}
|
|
35452
|
+
const providerTypeEnum = this.detectProviderType(device);
|
|
35453
35453
|
this.llmDevices.push({
|
|
35454
35454
|
device: device,
|
|
35455
35455
|
id,
|
|
@@ -35458,7 +35458,7 @@ class SpatialReasoningEngine {
|
|
|
35458
35458
|
lastUsed: 0,
|
|
35459
35459
|
errorCount: 0,
|
|
35460
35460
|
});
|
|
35461
|
-
this.console.log(`[LLM]
|
|
35461
|
+
this.console.log(`[LLM] Auto-discovered: ${device.name}`);
|
|
35462
35462
|
}
|
|
35463
35463
|
}
|
|
35464
35464
|
if (this.llmDevices.length === 0) {
|
|
@@ -35472,6 +35472,30 @@ class SpatialReasoningEngine {
|
|
|
35472
35472
|
this.console.error('[LLM] Error searching for LLM devices:', e);
|
|
35473
35473
|
}
|
|
35474
35474
|
}
|
|
35475
|
+
/** Detect the provider type from device name */
|
|
35476
|
+
detectProviderType(device) {
|
|
35477
|
+
const deviceName = device.name?.toLowerCase() || '';
|
|
35478
|
+
const pluginId = device.pluginId?.toLowerCase() || '';
|
|
35479
|
+
if (deviceName.includes('openai') || deviceName.includes('gpt')) {
|
|
35480
|
+
return 'openai';
|
|
35481
|
+
}
|
|
35482
|
+
else if (deviceName.includes('anthropic') || deviceName.includes('claude')) {
|
|
35483
|
+
return 'anthropic';
|
|
35484
|
+
}
|
|
35485
|
+
else if (deviceName.includes('ollama')) {
|
|
35486
|
+
return 'openai'; // Ollama uses OpenAI-compatible format
|
|
35487
|
+
}
|
|
35488
|
+
else if (deviceName.includes('gemini') || deviceName.includes('google')) {
|
|
35489
|
+
return 'openai'; // Google uses OpenAI-compatible format
|
|
35490
|
+
}
|
|
35491
|
+
else if (deviceName.includes('llama')) {
|
|
35492
|
+
return 'openai'; // llama.cpp uses OpenAI-compatible format
|
|
35493
|
+
}
|
|
35494
|
+
else if (pluginId.includes('@scrypted/llm') || pluginId.includes('llm')) {
|
|
35495
|
+
return 'unknown';
|
|
35496
|
+
}
|
|
35497
|
+
return 'unknown';
|
|
35498
|
+
}
|
|
35475
35499
|
/** Get the next available LLM using round-robin with least-recently-used preference */
|
|
35476
35500
|
async findLlmDevice() {
|
|
35477
35501
|
await this.findAllLlmDevices();
|
|
@@ -37326,6 +37350,7 @@ class TrackingEngine {
|
|
|
37326
37350
|
// Initialize spatial reasoning engine
|
|
37327
37351
|
const spatialConfig = {
|
|
37328
37352
|
enableLlm: config.useLlmDescriptions,
|
|
37353
|
+
llmDeviceIds: config.llmDeviceIds,
|
|
37329
37354
|
enableLandmarkLearning: config.enableLandmarkLearning ?? true,
|
|
37330
37355
|
landmarkConfidenceThreshold: config.landmarkConfidenceThreshold ?? 0.7,
|
|
37331
37356
|
contextCacheTtl: 60000, // 1 minute cache
|
|
@@ -39439,20 +39464,22 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39439
39464
|
defaultValue: 'scrypted/spatial-awareness',
|
|
39440
39465
|
group: 'MQTT Integration',
|
|
39441
39466
|
},
|
|
39442
|
-
//
|
|
39443
|
-
|
|
39444
|
-
title: '
|
|
39445
|
-
type: '
|
|
39446
|
-
|
|
39447
|
-
|
|
39467
|
+
// Integrations
|
|
39468
|
+
llmDevices: {
|
|
39469
|
+
title: 'LLM Providers',
|
|
39470
|
+
type: 'device',
|
|
39471
|
+
multiple: true,
|
|
39472
|
+
deviceFilter: `interfaces.includes('ChatCompletion')`,
|
|
39473
|
+
description: 'Select one or more LLM providers for smart descriptions. Multiple providers will be load-balanced.',
|
|
39474
|
+
group: 'Integrations',
|
|
39448
39475
|
},
|
|
39449
39476
|
defaultNotifiers: {
|
|
39450
|
-
title: '
|
|
39477
|
+
title: 'Notification Service',
|
|
39451
39478
|
type: 'device',
|
|
39452
39479
|
multiple: true,
|
|
39453
39480
|
deviceFilter: `interfaces.includes('${sdk_1.ScryptedInterface.Notifier}')`,
|
|
39454
|
-
description: 'Select one or more notifiers to receive alerts',
|
|
39455
|
-
group: '
|
|
39481
|
+
description: 'Select one or more notifiers to receive alerts (e.g., Pushover, Home Assistant)',
|
|
39482
|
+
group: 'Integrations',
|
|
39456
39483
|
},
|
|
39457
39484
|
// Tracked Cameras
|
|
39458
39485
|
trackedCameras: {
|
|
@@ -39463,12 +39490,6 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39463
39490
|
group: 'Cameras',
|
|
39464
39491
|
description: 'Select cameras with object detection to track',
|
|
39465
39492
|
},
|
|
39466
|
-
// Alert Rules (stored as JSON)
|
|
39467
|
-
alertRules: {
|
|
39468
|
-
title: 'Alert Rules',
|
|
39469
|
-
type: 'string',
|
|
39470
|
-
hide: true,
|
|
39471
|
-
},
|
|
39472
39493
|
});
|
|
39473
39494
|
constructor(nativeId) {
|
|
39474
39495
|
super(nativeId);
|
|
@@ -39555,7 +39576,8 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39555
39576
|
loiteringThreshold: (this.storageSettings.values.loiteringThreshold || 3) * 1000,
|
|
39556
39577
|
objectAlertCooldown: (this.storageSettings.values.objectAlertCooldown || 30) * 1000,
|
|
39557
39578
|
useLlmDescriptions: this.storageSettings.values.useLlmDescriptions ?? true,
|
|
39558
|
-
|
|
39579
|
+
llmDeviceIds: this.parseLlmDevices(),
|
|
39580
|
+
llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval || 30) * 1000,
|
|
39559
39581
|
llmFallbackEnabled: this.storageSettings.values.llmFallbackEnabled ?? true,
|
|
39560
39582
|
llmFallbackTimeout: (this.storageSettings.values.llmFallbackTimeout || 3) * 1000,
|
|
39561
39583
|
enableTransitTimeLearning: this.storageSettings.values.enableTransitTimeLearning ?? true,
|
|
@@ -39596,6 +39618,34 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39596
39618
|
}
|
|
39597
39619
|
}
|
|
39598
39620
|
}
|
|
39621
|
+
/** Parse LLM devices from settings - handles both array and single value formats */
|
|
39622
|
+
parseLlmDevices() {
|
|
39623
|
+
const value = this.storageSettings.values.llmDevices;
|
|
39624
|
+
if (!value)
|
|
39625
|
+
return undefined;
|
|
39626
|
+
// Handle array format
|
|
39627
|
+
if (Array.isArray(value)) {
|
|
39628
|
+
const filtered = value.filter(Boolean);
|
|
39629
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
39630
|
+
}
|
|
39631
|
+
// Handle JSON string format
|
|
39632
|
+
if (typeof value === 'string') {
|
|
39633
|
+
try {
|
|
39634
|
+
const parsed = JSON.parse(value);
|
|
39635
|
+
if (Array.isArray(parsed)) {
|
|
39636
|
+
const filtered = parsed.filter(Boolean);
|
|
39637
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
39638
|
+
}
|
|
39639
|
+
// Single device ID string
|
|
39640
|
+
return value ? [value] : undefined;
|
|
39641
|
+
}
|
|
39642
|
+
catch {
|
|
39643
|
+
// Not JSON, treat as single device ID
|
|
39644
|
+
return value ? [value] : undefined;
|
|
39645
|
+
}
|
|
39646
|
+
}
|
|
39647
|
+
return undefined;
|
|
39648
|
+
}
|
|
39599
39649
|
// ==================== DeviceProvider Implementation ====================
|
|
39600
39650
|
async getDevice(nativeId) {
|
|
39601
39651
|
let device = this.devices.get(nativeId);
|
|
@@ -39848,84 +39898,16 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39848
39898
|
}
|
|
39849
39899
|
// ==================== 5. Tracking ====================
|
|
39850
39900
|
addGroup('Tracking');
|
|
39851
|
-
// ==================== 6.
|
|
39901
|
+
// ==================== 6. Integrations ====================
|
|
39902
|
+
addGroup('Integrations');
|
|
39903
|
+
// ==================== 7. AI & Spatial Reasoning ====================
|
|
39852
39904
|
addGroup('AI & Spatial Reasoning');
|
|
39853
|
-
// ====================
|
|
39905
|
+
// ==================== 8. Auto-Topology Discovery ====================
|
|
39854
39906
|
addGroup('Auto-Topology Discovery');
|
|
39855
|
-
// ==================== 8. Alerts ====================
|
|
39856
|
-
addGroup('Alerts');
|
|
39857
|
-
// Add alert rules configuration UI
|
|
39858
|
-
const alertRules = this.alertManager.getRules();
|
|
39859
|
-
const rulesHtml = this.generateAlertRulesHtml(alertRules);
|
|
39860
|
-
settings.push({
|
|
39861
|
-
key: 'alertRulesEditor',
|
|
39862
|
-
title: 'Alert Rules',
|
|
39863
|
-
type: 'html',
|
|
39864
|
-
value: rulesHtml,
|
|
39865
|
-
group: 'Alerts',
|
|
39866
|
-
});
|
|
39867
39907
|
// ==================== 9. MQTT Integration ====================
|
|
39868
39908
|
addGroup('MQTT Integration');
|
|
39869
39909
|
return settings;
|
|
39870
39910
|
}
|
|
39871
|
-
generateAlertRulesHtml(rules) {
|
|
39872
|
-
const ruleRows = rules.map(rule => `
|
|
39873
|
-
<tr data-rule-id="${rule.id}">
|
|
39874
|
-
<td style="padding:8px;border-bottom:1px solid #333;">
|
|
39875
|
-
<input type="checkbox" ${rule.enabled ? 'checked' : ''}
|
|
39876
|
-
onchange="(function(el){var rules=JSON.parse(localStorage.getItem('sa-temp-rules')||'[]');var r=rules.find(x=>x.id==='${rule.id}');if(r)r.enabled=el.checked;localStorage.setItem('sa-temp-rules',JSON.stringify(rules));})(this)" />
|
|
39877
|
-
</td>
|
|
39878
|
-
<td style="padding:8px;border-bottom:1px solid #333;color:#fff;">${rule.name}</td>
|
|
39879
|
-
<td style="padding:8px;border-bottom:1px solid #333;color:#888;">${rule.type}</td>
|
|
39880
|
-
<td style="padding:8px;border-bottom:1px solid #333;">
|
|
39881
|
-
<span style="padding:2px 8px;border-radius:4px;font-size:12px;background:${rule.severity === 'critical' ? '#e94560' :
|
|
39882
|
-
rule.severity === 'warning' ? '#f39c12' : '#3498db'};color:white;">${rule.severity}</span>
|
|
39883
|
-
</td>
|
|
39884
|
-
<td style="padding:8px;border-bottom:1px solid #333;color:#888;">${Math.round(rule.cooldown / 1000)}s</td>
|
|
39885
|
-
</tr>
|
|
39886
|
-
`).join('');
|
|
39887
|
-
const initCode = `localStorage.setItem('sa-temp-rules',JSON.stringify(${JSON.stringify(rules)}))`;
|
|
39888
|
-
const saveCode = `(function(){var rules=JSON.parse(localStorage.getItem('sa-temp-rules')||'[]');fetch('/endpoint/@blueharford/scrypted-spatial-awareness/api/alert-rules',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(rules)}).then(r=>r.json()).then(d=>{if(d.success)alert('Alert rules saved!');else alert('Error: '+d.error);}).catch(e=>alert('Error: '+e));})()`;
|
|
39889
|
-
return `
|
|
39890
|
-
<style>
|
|
39891
|
-
.sa-rules-table { width:100%; border-collapse:collapse; margin-top:10px; }
|
|
39892
|
-
.sa-rules-table th { text-align:left; padding:10px 8px; border-bottom:2px solid #e94560; color:#e94560; font-size:13px; }
|
|
39893
|
-
.sa-save-rules-btn {
|
|
39894
|
-
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
|
|
39895
|
-
color: white;
|
|
39896
|
-
border: none;
|
|
39897
|
-
padding: 10px 20px;
|
|
39898
|
-
border-radius: 6px;
|
|
39899
|
-
font-size: 14px;
|
|
39900
|
-
font-weight: 600;
|
|
39901
|
-
cursor: pointer;
|
|
39902
|
-
margin-top: 15px;
|
|
39903
|
-
}
|
|
39904
|
-
.sa-save-rules-btn:hover { opacity: 0.9; }
|
|
39905
|
-
.sa-rules-container { background:#16213e; border-radius:8px; padding:15px; }
|
|
39906
|
-
.sa-rules-desc { color:#888; font-size:13px; margin-bottom:10px; }
|
|
39907
|
-
</style>
|
|
39908
|
-
<div class="sa-rules-container">
|
|
39909
|
-
<p class="sa-rules-desc">Enable or disable alert types. Movement alerts notify you when someone moves between cameras.</p>
|
|
39910
|
-
<table class="sa-rules-table">
|
|
39911
|
-
<thead>
|
|
39912
|
-
<tr>
|
|
39913
|
-
<th style="width:40px;">On</th>
|
|
39914
|
-
<th>Alert Type</th>
|
|
39915
|
-
<th>Event</th>
|
|
39916
|
-
<th>Severity</th>
|
|
39917
|
-
<th>Cooldown</th>
|
|
39918
|
-
</tr>
|
|
39919
|
-
</thead>
|
|
39920
|
-
<tbody>
|
|
39921
|
-
${ruleRows}
|
|
39922
|
-
</tbody>
|
|
39923
|
-
</table>
|
|
39924
|
-
<button class="sa-save-rules-btn" onclick="${saveCode}">Save Alert Rules</button>
|
|
39925
|
-
<script>(function(){${initCode}})();</script>
|
|
39926
|
-
</div>
|
|
39927
|
-
`;
|
|
39928
|
-
}
|
|
39929
39911
|
async putSetting(key, value) {
|
|
39930
39912
|
await this.storageSettings.putSetting(key, value);
|
|
39931
39913
|
// Handle setting changes that require engine restart
|
|
@@ -39940,6 +39922,7 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39940
39922
|
key === 'llmDebounceInterval' ||
|
|
39941
39923
|
key === 'llmFallbackEnabled' ||
|
|
39942
39924
|
key === 'llmFallbackTimeout' ||
|
|
39925
|
+
key === 'llmDevices' ||
|
|
39943
39926
|
key === 'enableTransitTimeLearning' ||
|
|
39944
39927
|
key === 'enableConnectionSuggestions' ||
|
|
39945
39928
|
key === 'enableLandmarkLearning' ||
|