@blueharford/scrypted-spatial-awareness 0.6.26 → 0.6.27

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,39 @@ 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 device 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 a specific LLM device is configured, use only that one
35420
+ if (this.config.llmDeviceId) {
35421
+ const device = systemManager.getDeviceById(this.config.llmDeviceId);
35422
+ if (device?.interfaces?.includes('ChatCompletion')) {
35423
+ const providerTypeEnum = this.detectProviderType(device);
35424
+ this.llmDevices.push({
35425
+ device: device,
35426
+ id: this.config.llmDeviceId,
35427
+ name: device.name || this.config.llmDeviceId,
35428
+ providerType: providerTypeEnum,
35429
+ lastUsed: 0,
35430
+ errorCount: 0,
35431
+ });
35432
+ this.console.log(`[LLM] Using configured LLM: ${device.name}`);
35433
+ return;
35434
+ }
35435
+ else {
35436
+ this.console.warn(`[LLM] Configured device ${this.config.llmDeviceId} not found or doesn't support ChatCompletion`);
35437
+ }
35438
+ }
35439
+ // Auto-discover all LLM devices for load balancing
35419
35440
  for (const id of Object.keys(systemManager.getSystemState())) {
35420
35441
  const device = systemManager.getDeviceById(id);
35421
35442
  if (!device)
35422
35443
  continue;
35423
35444
  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
- }
35445
+ const providerTypeEnum = this.detectProviderType(device);
35453
35446
  this.llmDevices.push({
35454
35447
  device: device,
35455
35448
  id,
@@ -35458,7 +35451,7 @@ class SpatialReasoningEngine {
35458
35451
  lastUsed: 0,
35459
35452
  errorCount: 0,
35460
35453
  });
35461
- this.console.log(`[LLM] Found ${providerType}: ${device.name}`);
35454
+ this.console.log(`[LLM] Found: ${device.name}`);
35462
35455
  }
35463
35456
  }
35464
35457
  if (this.llmDevices.length === 0) {
@@ -35472,6 +35465,30 @@ class SpatialReasoningEngine {
35472
35465
  this.console.error('[LLM] Error searching for LLM devices:', e);
35473
35466
  }
35474
35467
  }
35468
+ /** Detect the provider type from device name */
35469
+ detectProviderType(device) {
35470
+ const deviceName = device.name?.toLowerCase() || '';
35471
+ const pluginId = device.pluginId?.toLowerCase() || '';
35472
+ if (deviceName.includes('openai') || deviceName.includes('gpt')) {
35473
+ return 'openai';
35474
+ }
35475
+ else if (deviceName.includes('anthropic') || deviceName.includes('claude')) {
35476
+ return 'anthropic';
35477
+ }
35478
+ else if (deviceName.includes('ollama')) {
35479
+ return 'openai'; // Ollama uses OpenAI-compatible format
35480
+ }
35481
+ else if (deviceName.includes('gemini') || deviceName.includes('google')) {
35482
+ return 'openai'; // Google uses OpenAI-compatible format
35483
+ }
35484
+ else if (deviceName.includes('llama')) {
35485
+ return 'openai'; // llama.cpp uses OpenAI-compatible format
35486
+ }
35487
+ else if (pluginId.includes('@scrypted/llm') || pluginId.includes('llm')) {
35488
+ return 'unknown';
35489
+ }
35490
+ return 'unknown';
35491
+ }
35475
35492
  /** Get the next available LLM using round-robin with least-recently-used preference */
35476
35493
  async findLlmDevice() {
35477
35494
  await this.findAllLlmDevices();
@@ -37326,6 +37343,7 @@ class TrackingEngine {
37326
37343
  // Initialize spatial reasoning engine
37327
37344
  const spatialConfig = {
37328
37345
  enableLlm: config.useLlmDescriptions,
37346
+ llmDeviceId: config.llmDeviceId,
37329
37347
  enableLandmarkLearning: config.enableLandmarkLearning ?? true,
37330
37348
  landmarkConfidenceThreshold: config.landmarkConfidenceThreshold ?? 0.7,
37331
37349
  contextCacheTtl: 60000, // 1 minute cache
@@ -39439,20 +39457,21 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39439
39457
  defaultValue: 'scrypted/spatial-awareness',
39440
39458
  group: 'MQTT Integration',
39441
39459
  },
39442
- // Alert Settings
39443
- enableAlerts: {
39444
- title: 'Enable Alerts',
39445
- type: 'boolean',
39446
- defaultValue: true,
39447
- group: 'Alerts',
39460
+ // Integrations
39461
+ llmDevice: {
39462
+ title: 'LLM Provider',
39463
+ type: 'device',
39464
+ deviceFilter: `interfaces.includes('ChatCompletion')`,
39465
+ description: 'Select the LLM plugin to use for smart descriptions (e.g., OpenAI, Anthropic, Ollama)',
39466
+ group: 'Integrations',
39448
39467
  },
39449
39468
  defaultNotifiers: {
39450
- title: 'Notifiers',
39469
+ title: 'Notification Service',
39451
39470
  type: 'device',
39452
39471
  multiple: true,
39453
39472
  deviceFilter: `interfaces.includes('${sdk_1.ScryptedInterface.Notifier}')`,
39454
- description: 'Select one or more notifiers to receive alerts',
39455
- group: 'Alerts',
39473
+ description: 'Select one or more notifiers to receive alerts (e.g., Pushover, Home Assistant)',
39474
+ group: 'Integrations',
39456
39475
  },
39457
39476
  // Tracked Cameras
39458
39477
  trackedCameras: {
@@ -39463,12 +39482,6 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39463
39482
  group: 'Cameras',
39464
39483
  description: 'Select cameras with object detection to track',
39465
39484
  },
39466
- // Alert Rules (stored as JSON)
39467
- alertRules: {
39468
- title: 'Alert Rules',
39469
- type: 'string',
39470
- hide: true,
39471
- },
39472
39485
  });
39473
39486
  constructor(nativeId) {
39474
39487
  super(nativeId);
@@ -39555,7 +39568,8 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39555
39568
  loiteringThreshold: (this.storageSettings.values.loiteringThreshold || 3) * 1000,
39556
39569
  objectAlertCooldown: (this.storageSettings.values.objectAlertCooldown || 30) * 1000,
39557
39570
  useLlmDescriptions: this.storageSettings.values.useLlmDescriptions ?? true,
39558
- llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval || 10) * 1000,
39571
+ llmDeviceId: this.storageSettings.values.llmDevice || undefined,
39572
+ llmDebounceInterval: (this.storageSettings.values.llmDebounceInterval || 30) * 1000,
39559
39573
  llmFallbackEnabled: this.storageSettings.values.llmFallbackEnabled ?? true,
39560
39574
  llmFallbackTimeout: (this.storageSettings.values.llmFallbackTimeout || 3) * 1000,
39561
39575
  enableTransitTimeLearning: this.storageSettings.values.enableTransitTimeLearning ?? true,
@@ -39848,84 +39862,16 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39848
39862
  }
39849
39863
  // ==================== 5. Tracking ====================
39850
39864
  addGroup('Tracking');
39851
- // ==================== 6. AI & Spatial Reasoning ====================
39865
+ // ==================== 6. Integrations ====================
39866
+ addGroup('Integrations');
39867
+ // ==================== 7. AI & Spatial Reasoning ====================
39852
39868
  addGroup('AI & Spatial Reasoning');
39853
- // ==================== 7. Auto-Topology Discovery ====================
39869
+ // ==================== 8. Auto-Topology Discovery ====================
39854
39870
  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
39871
  // ==================== 9. MQTT Integration ====================
39868
39872
  addGroup('MQTT Integration');
39869
39873
  return settings;
39870
39874
  }
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
39875
  async putSetting(key, value) {
39930
39876
  await this.storageSettings.putSetting(key, value);
39931
39877
  // Handle setting changes that require engine restart
@@ -39940,6 +39886,7 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
39940
39886
  key === 'llmDebounceInterval' ||
39941
39887
  key === 'llmFallbackEnabled' ||
39942
39888
  key === 'llmFallbackTimeout' ||
39889
+ key === 'llmDevice' ||
39943
39890
  key === 'enableTransitTimeLearning' ||
39944
39891
  key === 'enableConnectionSuggestions' ||
39945
39892
  key === 'enableLandmarkLearning' ||