@blueharford/scrypted-spatial-awareness 0.6.23 → 0.6.25
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 +86 -54
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/alerts/alert-manager.ts +17 -1
- package/src/core/tracking-engine.ts +59 -46
- package/src/main.ts +2 -2
- package/src/models/alert.ts +5 -5
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -34496,6 +34496,7 @@ class AlertManager {
|
|
|
34496
34496
|
}
|
|
34497
34497
|
/**
|
|
34498
34498
|
* Get notification title based on alert type
|
|
34499
|
+
* For movement alerts with LLM descriptions, use the smart description as title
|
|
34499
34500
|
*/
|
|
34500
34501
|
getNotificationTitle(alert) {
|
|
34501
34502
|
const prefix = alert.severity === 'critical' ? '🚨 ' :
|
|
@@ -34506,11 +34507,27 @@ class AlertManager {
|
|
|
34506
34507
|
: 'Object';
|
|
34507
34508
|
switch (alert.type) {
|
|
34508
34509
|
case 'property_entry':
|
|
34510
|
+
// Legacy - use simple title
|
|
34509
34511
|
return `${prefix}${objectType} Arrived`;
|
|
34510
34512
|
case 'property_exit':
|
|
34513
|
+
// Legacy - use simple title
|
|
34511
34514
|
return `${prefix}${objectType} Left`;
|
|
34512
34515
|
case 'movement':
|
|
34513
|
-
//
|
|
34516
|
+
// For smart activity alerts, use the LLM description as title if available
|
|
34517
|
+
// This gives us rich context like "Person walking toward front door"
|
|
34518
|
+
if (alert.details.objectLabel && alert.details.usedLlm) {
|
|
34519
|
+
// Truncate to reasonable title length (first sentence or 60 chars)
|
|
34520
|
+
let smartTitle = alert.details.objectLabel;
|
|
34521
|
+
const firstPeriod = smartTitle.indexOf('.');
|
|
34522
|
+
if (firstPeriod > 0 && firstPeriod < 60) {
|
|
34523
|
+
smartTitle = smartTitle.substring(0, firstPeriod);
|
|
34524
|
+
}
|
|
34525
|
+
else if (smartTitle.length > 60) {
|
|
34526
|
+
smartTitle = smartTitle.substring(0, 57) + '...';
|
|
34527
|
+
}
|
|
34528
|
+
return `${prefix}${smartTitle}`;
|
|
34529
|
+
}
|
|
34530
|
+
// Fallback: include destination in title
|
|
34514
34531
|
const dest = alert.details.toCameraName || 'area';
|
|
34515
34532
|
return `${prefix}${objectType} → ${dest}`;
|
|
34516
34533
|
case 'unusual_path':
|
|
@@ -37334,14 +37351,23 @@ class TrackingEngine {
|
|
|
37334
37351
|
recordLlmCall() {
|
|
37335
37352
|
this.lastLlmCallTime = Date.now();
|
|
37336
37353
|
}
|
|
37354
|
+
/** Check and record LLM call - returns false if rate limited */
|
|
37355
|
+
tryLlmCall() {
|
|
37356
|
+
if (!this.isLlmCallAllowed()) {
|
|
37357
|
+
this.console.log('[LLM] Rate limited, skipping LLM call');
|
|
37358
|
+
return false;
|
|
37359
|
+
}
|
|
37360
|
+
this.recordLlmCall();
|
|
37361
|
+
return true;
|
|
37362
|
+
}
|
|
37337
37363
|
/** Get spatial reasoning result for movement (uses RAG + LLM) with debouncing and fallback */
|
|
37338
37364
|
async getSpatialDescription(tracked, fromCameraId, toCameraId, transitTime, currentCameraId) {
|
|
37339
37365
|
const fallbackEnabled = this.config.llmFallbackEnabled ?? true;
|
|
37340
37366
|
const fallbackTimeout = this.config.llmFallbackTimeout ?? 3000;
|
|
37341
37367
|
try {
|
|
37342
37368
|
// Check rate limiting - if not allowed, return null to use basic description
|
|
37343
|
-
if (!this.
|
|
37344
|
-
this.console.log('LLM rate-limited, using basic notification');
|
|
37369
|
+
if (!this.tryLlmCall()) {
|
|
37370
|
+
this.console.log('[Movement] LLM rate-limited, using basic notification');
|
|
37345
37371
|
return null;
|
|
37346
37372
|
}
|
|
37347
37373
|
// Get snapshot from camera for LLM analysis (if LLM is enabled)
|
|
@@ -37352,8 +37378,6 @@ class TrackingEngine {
|
|
|
37352
37378
|
mediaObject = await camera.takePicture();
|
|
37353
37379
|
}
|
|
37354
37380
|
}
|
|
37355
|
-
// Record that we're making an LLM call
|
|
37356
|
-
this.recordLlmCall();
|
|
37357
37381
|
// Use spatial reasoning engine for rich context-aware description
|
|
37358
37382
|
// Apply timeout if fallback is enabled
|
|
37359
37383
|
let result;
|
|
@@ -37507,45 +37531,39 @@ class TrackingEngine {
|
|
|
37507
37531
|
this.console.log(`[Entry Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37508
37532
|
}
|
|
37509
37533
|
catch (e) {
|
|
37510
|
-
this.console.warn(`[Entry Alert] Prefetch failed,
|
|
37534
|
+
this.console.warn(`[Entry Alert] Prefetch failed, using basic description: ${e}`);
|
|
37535
|
+
// Don't make another LLM call - use basic description (no mediaObject = no LLM)
|
|
37511
37536
|
spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
|
|
37512
37537
|
}
|
|
37513
37538
|
this.pendingDescriptions.delete(globalId);
|
|
37514
37539
|
}
|
|
37515
37540
|
else {
|
|
37516
|
-
//
|
|
37517
|
-
this.
|
|
37518
|
-
|
|
37519
|
-
|
|
37520
|
-
|
|
37521
|
-
|
|
37522
|
-
|
|
37523
|
-
|
|
37524
|
-
|
|
37525
|
-
|
|
37526
|
-
|
|
37527
|
-
|
|
37528
|
-
objectLabel: spatialResult.description,
|
|
37529
|
-
detectionId: sighting.detectionId,
|
|
37530
|
-
involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
|
|
37531
|
-
usedLlm: spatialResult.usedLlm,
|
|
37532
|
-
});
|
|
37533
|
-
}
|
|
37534
|
-
else {
|
|
37535
|
-
// Non-entry point - still alert about activity using movement alert type
|
|
37536
|
-
// This notifies about any activity around the property using topology context
|
|
37537
|
-
await this.alertManager.checkAndAlert('movement', tracked, {
|
|
37538
|
-
cameraId: sighting.cameraId,
|
|
37539
|
-
cameraName: sighting.cameraName,
|
|
37540
|
-
toCameraId: sighting.cameraId,
|
|
37541
|
-
toCameraName: sighting.cameraName,
|
|
37542
|
-
objectClass: sighting.detection.className,
|
|
37543
|
-
objectLabel: spatialResult.description, // Use spatial reasoning description (topology-based)
|
|
37544
|
-
detectionId: sighting.detectionId,
|
|
37545
|
-
involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
|
|
37546
|
-
usedLlm: spatialResult.usedLlm,
|
|
37547
|
-
});
|
|
37541
|
+
// No prefetch available - only call LLM if rate limit allows
|
|
37542
|
+
if (this.tryLlmCall()) {
|
|
37543
|
+
this.console.log(`[Entry Alert] No prefetch, generating with LLM`);
|
|
37544
|
+
const mediaObject = this.snapshotCache.get(globalId);
|
|
37545
|
+
spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
|
|
37546
|
+
this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37547
|
+
}
|
|
37548
|
+
else {
|
|
37549
|
+
// Rate limited - use basic description (no LLM)
|
|
37550
|
+
this.console.log(`[Entry Alert] Rate limited, using basic description`);
|
|
37551
|
+
spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
|
|
37552
|
+
}
|
|
37548
37553
|
}
|
|
37554
|
+
// Always use movement alert type for smart notifications with LLM descriptions
|
|
37555
|
+
// The property_entry/property_exit types are legacy and disabled by default
|
|
37556
|
+
await this.alertManager.checkAndAlert('movement', tracked, {
|
|
37557
|
+
cameraId: sighting.cameraId,
|
|
37558
|
+
cameraName: sighting.cameraName,
|
|
37559
|
+
toCameraId: sighting.cameraId,
|
|
37560
|
+
toCameraName: sighting.cameraName,
|
|
37561
|
+
objectClass: sighting.detection.className,
|
|
37562
|
+
objectLabel: spatialResult.description, // Smart LLM-generated description
|
|
37563
|
+
detectionId: sighting.detectionId,
|
|
37564
|
+
involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
|
|
37565
|
+
usedLlm: spatialResult.usedLlm,
|
|
37566
|
+
});
|
|
37549
37567
|
this.recordAlertTime(globalId);
|
|
37550
37568
|
}, this.config.loiteringThreshold);
|
|
37551
37569
|
}
|
|
@@ -37558,9 +37576,9 @@ class TrackingEngine {
|
|
|
37558
37576
|
if (mediaObject) {
|
|
37559
37577
|
this.snapshotCache.set(globalId, mediaObject);
|
|
37560
37578
|
this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
|
|
37561
|
-
// Start LLM analysis immediately in parallel (don't await)
|
|
37579
|
+
// Start LLM analysis immediately in parallel (don't await) - but respect rate limits
|
|
37562
37580
|
const tracked = this.state.getObject(globalId);
|
|
37563
|
-
if (tracked && this.config.useLlmDescriptions) {
|
|
37581
|
+
if (tracked && this.config.useLlmDescriptions && this.tryLlmCall()) {
|
|
37564
37582
|
this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
|
|
37565
37583
|
const descriptionPromise = eventType === 'exit'
|
|
37566
37584
|
? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
|
|
@@ -37573,6 +37591,9 @@ class TrackingEngine {
|
|
|
37573
37591
|
this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
|
|
37574
37592
|
});
|
|
37575
37593
|
}
|
|
37594
|
+
else if (tracked && this.config.useLlmDescriptions) {
|
|
37595
|
+
this.console.log(`[LLM Prefetch] Skipped for ${globalId.slice(0, 8)} - rate limited`);
|
|
37596
|
+
}
|
|
37576
37597
|
}
|
|
37577
37598
|
}
|
|
37578
37599
|
}
|
|
@@ -37641,21 +37662,32 @@ class TrackingEngine {
|
|
|
37641
37662
|
this.console.log(`[Exit Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37642
37663
|
}
|
|
37643
37664
|
catch (e) {
|
|
37644
|
-
this.console.warn(`[Exit Alert] Prefetch failed,
|
|
37665
|
+
this.console.warn(`[Exit Alert] Prefetch failed, using basic description: ${e}`);
|
|
37666
|
+
// Don't make another LLM call - use basic description
|
|
37645
37667
|
spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
|
|
37646
37668
|
}
|
|
37647
37669
|
this.pendingDescriptions.delete(tracked.globalId);
|
|
37648
37670
|
}
|
|
37649
37671
|
else {
|
|
37650
|
-
//
|
|
37651
|
-
this.
|
|
37652
|
-
|
|
37653
|
-
|
|
37654
|
-
|
|
37672
|
+
// No prefetch available - only call LLM if rate limit allows
|
|
37673
|
+
if (this.tryLlmCall()) {
|
|
37674
|
+
this.console.log(`[Exit Alert] No prefetch, generating with LLM`);
|
|
37675
|
+
const mediaObject = this.snapshotCache.get(tracked.globalId);
|
|
37676
|
+
spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
|
|
37677
|
+
this.console.log(`[Exit Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
|
|
37678
|
+
}
|
|
37679
|
+
else {
|
|
37680
|
+
// Rate limited - use basic description (no LLM)
|
|
37681
|
+
this.console.log(`[Exit Alert] Rate limited, using basic description`);
|
|
37682
|
+
spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
|
|
37683
|
+
}
|
|
37655
37684
|
}
|
|
37656
|
-
|
|
37685
|
+
// Use movement alert for exit too - smart notifications with LLM descriptions
|
|
37686
|
+
await this.alertManager.checkAndAlert('movement', current, {
|
|
37657
37687
|
cameraId: sighting.cameraId,
|
|
37658
37688
|
cameraName: sighting.cameraName,
|
|
37689
|
+
toCameraId: sighting.cameraId,
|
|
37690
|
+
toCameraName: sighting.cameraName,
|
|
37659
37691
|
objectClass: current.className,
|
|
37660
37692
|
objectLabel: spatialResult.description,
|
|
37661
37693
|
involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
|
|
@@ -39191,8 +39223,8 @@ class SpatialAwarenessPlugin extends sdk_1.ScryptedDeviceBase {
|
|
|
39191
39223
|
llmDebounceInterval: {
|
|
39192
39224
|
title: 'LLM Rate Limit (seconds)',
|
|
39193
39225
|
type: 'number',
|
|
39194
|
-
defaultValue:
|
|
39195
|
-
description: 'Minimum time between LLM calls to prevent API
|
|
39226
|
+
defaultValue: 30,
|
|
39227
|
+
description: 'Minimum time between LLM calls to prevent API rate limiting. Increase if you get rate limit errors. (0 = no limit)',
|
|
39196
39228
|
group: 'AI & Spatial Reasoning',
|
|
39197
39229
|
},
|
|
39198
39230
|
llmFallbackEnabled: {
|
|
@@ -41207,8 +41239,8 @@ function createDefaultRules() {
|
|
|
41207
41239
|
return [
|
|
41208
41240
|
{
|
|
41209
41241
|
id: 'property-entry',
|
|
41210
|
-
name: 'Property Entry',
|
|
41211
|
-
enabled:
|
|
41242
|
+
name: 'Property Entry (Legacy)',
|
|
41243
|
+
enabled: false, // Disabled - use movement alerts with LLM descriptions instead
|
|
41212
41244
|
type: 'property_entry',
|
|
41213
41245
|
conditions: [],
|
|
41214
41246
|
severity: 'info',
|
|
@@ -41217,8 +41249,8 @@ function createDefaultRules() {
|
|
|
41217
41249
|
},
|
|
41218
41250
|
{
|
|
41219
41251
|
id: 'property-exit',
|
|
41220
|
-
name: 'Property Exit',
|
|
41221
|
-
enabled:
|
|
41252
|
+
name: 'Property Exit (Legacy)',
|
|
41253
|
+
enabled: false, // Disabled - use movement alerts with LLM descriptions instead
|
|
41222
41254
|
type: 'property_exit',
|
|
41223
41255
|
conditions: [],
|
|
41224
41256
|
severity: 'info',
|
|
@@ -41227,7 +41259,7 @@ function createDefaultRules() {
|
|
|
41227
41259
|
},
|
|
41228
41260
|
{
|
|
41229
41261
|
id: 'movement',
|
|
41230
|
-
name: '
|
|
41262
|
+
name: 'Smart Activity Alerts',
|
|
41231
41263
|
enabled: true,
|
|
41232
41264
|
type: 'movement',
|
|
41233
41265
|
conditions: [],
|