@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/plugin.zip CHANGED
Binary file
@@ -35410,46 +35410,46 @@ class SpatialReasoningEngine {
35410
35410
  // Load balancing for multiple LLMs
35411
35411
  llmDevices = [];
35412
35412
  llmIndex = 0;
35413
- /** Find ALL LLM devices for load balancing */
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 deviceName = device.name?.toLowerCase() || '';
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] Found ${providerType}: ${device.name}`);
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
- // Alert Settings
39443
- enableAlerts: {
39444
- title: 'Enable Alerts',
39445
- type: 'boolean',
39446
- defaultValue: true,
39447
- group: 'Alerts',
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: 'Notifiers',
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: 'Alerts',
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
- llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval || 10) * 1000,
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. AI & Spatial Reasoning ====================
39901
+ // ==================== 6. Integrations ====================
39902
+ addGroup('Integrations');
39903
+ // ==================== 7. AI & Spatial Reasoning ====================
39852
39904
  addGroup('AI & Spatial Reasoning');
39853
- // ==================== 7. Auto-Topology Discovery ====================
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' ||