@blueharford/scrypted-spatial-awareness 0.6.30 → 0.6.32
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 +37 -16
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/session-manager-plugin.pkg +0 -0
- package/src/alerts/alert-manager.ts +18 -2
- package/src/core/spatial-reasoning.ts +2 -2
- package/src/core/tracking-engine.ts +21 -10
- package/src/main.ts +1 -1
package/out/plugin.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -110,6 +110,14 @@ export class AlertManager {
|
|
|
110
110
|
? rule.notifiers
|
|
111
111
|
: this.getDefaultNotifiers();
|
|
112
112
|
|
|
113
|
+
// Debug: log which notifiers we're using
|
|
114
|
+
this.console.log(`[Notification] Rule ${rule.id} has ${rule.notifiers.length} notifiers, using ${notifierIds.length} notifier(s): ${notifierIds.join(', ') || 'NONE'}`);
|
|
115
|
+
|
|
116
|
+
if (notifierIds.length === 0) {
|
|
117
|
+
this.console.warn(`[Notification] No notifiers configured! Configure a notifier in plugin settings.`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
// Try to get a thumbnail from the camera
|
|
114
122
|
let mediaObject: MediaObject | undefined;
|
|
115
123
|
const cameraId = alert.details.toCameraId || alert.details.cameraId;
|
|
@@ -212,25 +220,33 @@ export class AlertManager {
|
|
|
212
220
|
try {
|
|
213
221
|
// Try new multiple notifiers setting first
|
|
214
222
|
const notifiers = this.storage.getItem('defaultNotifiers');
|
|
223
|
+
this.console.log(`[Notifiers] Raw storage value: ${notifiers}`);
|
|
224
|
+
|
|
215
225
|
if (notifiers) {
|
|
216
226
|
// Could be JSON array or comma-separated string
|
|
217
227
|
try {
|
|
218
228
|
const parsed = JSON.parse(notifiers);
|
|
219
229
|
if (Array.isArray(parsed)) {
|
|
230
|
+
this.console.log(`[Notifiers] Parsed JSON array: ${parsed.join(', ')}`);
|
|
220
231
|
return parsed;
|
|
221
232
|
}
|
|
222
233
|
} catch {
|
|
223
234
|
// Not JSON, might be comma-separated or single value
|
|
224
235
|
if (notifiers.includes(',')) {
|
|
225
|
-
|
|
236
|
+
const result = notifiers.split(',').map(s => s.trim()).filter(Boolean);
|
|
237
|
+
this.console.log(`[Notifiers] Parsed comma-separated: ${result.join(', ')}`);
|
|
238
|
+
return result;
|
|
226
239
|
}
|
|
240
|
+
this.console.log(`[Notifiers] Single value: ${notifiers}`);
|
|
227
241
|
return [notifiers];
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
244
|
// Fallback to old single notifier setting
|
|
231
245
|
const defaultNotifier = this.storage.getItem('defaultNotifier');
|
|
246
|
+
this.console.log(`[Notifiers] Fallback single notifier: ${defaultNotifier || 'NONE'}`);
|
|
232
247
|
return defaultNotifier ? [defaultNotifier] : [];
|
|
233
|
-
} catch {
|
|
248
|
+
} catch (e) {
|
|
249
|
+
this.console.error(`[Notifiers] Error reading notifiers:`, e);
|
|
234
250
|
return [];
|
|
235
251
|
}
|
|
236
252
|
}
|
|
@@ -466,7 +466,7 @@ export class SpatialReasoningEngine {
|
|
|
466
466
|
id: deviceId,
|
|
467
467
|
name: device.name || deviceId,
|
|
468
468
|
providerType: providerTypeEnum,
|
|
469
|
-
lastUsed:
|
|
469
|
+
lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
|
|
470
470
|
errorCount: 0,
|
|
471
471
|
});
|
|
472
472
|
this.console.log(`[LLM] Using configured LLM: ${device.name}`);
|
|
@@ -496,7 +496,7 @@ export class SpatialReasoningEngine {
|
|
|
496
496
|
id,
|
|
497
497
|
name: device.name || id,
|
|
498
498
|
providerType: providerTypeEnum,
|
|
499
|
-
lastUsed:
|
|
499
|
+
lastUsed: Date.now() - 60000, // Initialize to 1 minute ago so all LLMs start equal
|
|
500
500
|
errorCount: 0,
|
|
501
501
|
});
|
|
502
502
|
|
|
@@ -328,15 +328,24 @@ export class TrackingEngine {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
/** Check and record LLM call - returns false if rate limited */
|
|
331
|
-
private tryLlmCall(): boolean {
|
|
331
|
+
private tryLlmCall(silent: boolean = false): boolean {
|
|
332
332
|
if (!this.isLlmCallAllowed()) {
|
|
333
|
-
|
|
333
|
+
// Only log once per rate limit window, not every call
|
|
334
|
+
if (!silent && !this.rateLimitLogged) {
|
|
335
|
+
const remaining = Math.ceil((this.config.llmDebounceInterval || 30000) - (Date.now() - this.lastLlmCallTime)) / 1000;
|
|
336
|
+
this.console.log(`[LLM] Rate limited, ${remaining.toFixed(0)}s until next call allowed`);
|
|
337
|
+
this.rateLimitLogged = true;
|
|
338
|
+
}
|
|
334
339
|
return false;
|
|
335
340
|
}
|
|
341
|
+
this.rateLimitLogged = false;
|
|
336
342
|
this.recordLlmCall();
|
|
337
343
|
return true;
|
|
338
344
|
}
|
|
339
345
|
|
|
346
|
+
/** Track if we've already logged rate limit message */
|
|
347
|
+
private rateLimitLogged: boolean = false;
|
|
348
|
+
|
|
340
349
|
/** Get spatial reasoning result for movement (uses RAG + LLM) with debouncing and fallback */
|
|
341
350
|
private async getSpatialDescription(
|
|
342
351
|
tracked: TrackedObject,
|
|
@@ -620,32 +629,34 @@ export class TrackingEngine {
|
|
|
620
629
|
cameraId: string,
|
|
621
630
|
eventType: 'entry' | 'exit' | 'movement' = 'entry'
|
|
622
631
|
): Promise<void> {
|
|
632
|
+
// Skip if we already have a recent snapshot for this object (within 5 seconds)
|
|
633
|
+
const existingSnapshot = this.snapshotCache.get(globalId);
|
|
634
|
+
if (existingSnapshot && eventType !== 'exit') {
|
|
635
|
+
// For entry/movement, we can reuse existing snapshot
|
|
636
|
+
// For exit, we want a fresh snapshot while they're still visible
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
623
640
|
try {
|
|
624
641
|
const camera = systemManager.getDeviceById<Camera>(cameraId);
|
|
625
642
|
if (camera?.interfaces?.includes(ScryptedInterface.Camera)) {
|
|
626
643
|
const mediaObject = await camera.takePicture();
|
|
627
644
|
if (mediaObject) {
|
|
628
645
|
this.snapshotCache.set(globalId, mediaObject);
|
|
629
|
-
this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
|
|
630
646
|
|
|
631
647
|
// Start LLM analysis immediately in parallel (don't await) - but respect rate limits
|
|
632
648
|
const tracked = this.state.getObject(globalId);
|
|
633
649
|
if (tracked && this.config.useLlmDescriptions && this.tryLlmCall()) {
|
|
634
|
-
this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
|
|
635
650
|
const descriptionPromise = eventType === 'exit'
|
|
636
651
|
? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
|
|
637
652
|
: this.spatialReasoning.generateEntryDescription(tracked, cameraId, mediaObject);
|
|
638
653
|
|
|
639
654
|
this.pendingDescriptions.set(globalId, descriptionPromise);
|
|
640
655
|
|
|
641
|
-
// Log when complete (
|
|
642
|
-
descriptionPromise.
|
|
643
|
-
this.console.log(`[LLM Prefetch] ${eventType} analysis ready for ${globalId.slice(0, 8)}: "${result.description.substring(0, 40)}..."`);
|
|
644
|
-
}).catch(e => {
|
|
656
|
+
// Log when complete (but don't spam logs)
|
|
657
|
+
descriptionPromise.catch(e => {
|
|
645
658
|
this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
|
|
646
659
|
});
|
|
647
|
-
} else if (tracked && this.config.useLlmDescriptions) {
|
|
648
|
-
this.console.log(`[LLM Prefetch] Skipped for ${globalId.slice(0, 8)} - rate limited`);
|
|
649
660
|
}
|
|
650
661
|
}
|
|
651
662
|
}
|
package/src/main.ts
CHANGED
|
@@ -130,7 +130,7 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
|
|
|
130
130
|
llmDebounceInterval: {
|
|
131
131
|
title: 'LLM Rate Limit (seconds)',
|
|
132
132
|
type: 'number',
|
|
133
|
-
defaultValue:
|
|
133
|
+
defaultValue: 5,
|
|
134
134
|
description: 'Minimum time between LLM calls to prevent API rate limiting. Increase if you get rate limit errors. (0 = no limit)',
|
|
135
135
|
group: 'AI & Spatial Reasoning',
|
|
136
136
|
},
|