scrypted-detection-trainer 0.1.6 β†’ 0.1.7

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.
@@ -1,2 +1,2 @@
1
- (()=>{var e={562(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),t.sdk=t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,i(n(192),t);const o=n(192);n(339);class a extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=t.sdk.deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=t.sdk.deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=t.sdk.deviceManager.getDeviceConsole(this.nativeId)),this._console}async createMediaObject(e,n){return t.sdk.mediaManager.createMediaObject(e,n,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:t.sdk.deviceManager.getMixinConsole(e.sourceId,this.nativeId)}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=t.sdk.deviceManager.getDeviceState(this.nativeId):this._deviceState=t.sdk.deviceManager.getDeviceState())}onDeviceEvent(e,n){return t.sdk.deviceManager.onDeviceEvent(this.nativeId,e,n)}}t.ScryptedDeviceBase=a;class s extends o.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.nativeId=t.sdk.systemManager.getDeviceById(this.id).nativeId,this.mixinProviderNativeId=e.mixinProviderNativeId,this._deviceState.__rpcproxy_traps_all_properties&&"string"==typeof this._deviceState.id&&(this._deviceState=t.sdk.deviceManager.createDeviceState(this._deviceState.id,this._deviceState.setState))}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,n=this.id+(e?":"+e:"");this._storage=t.sdk.deviceManager.getMixinStorage(n,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(t.sdk.deviceManager.getMixinConsole?this._console=t.sdk.deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=t.sdk.deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}async createMediaObject(e,n){return t.sdk.mediaManager.createMediaObject(e,n,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:t.sdk.deviceManager.getMixinConsole(e.sourceId,this.mixinProviderNativeId)}onDeviceEvent(e,n){return t.sdk.deviceManager.onMixinEvent(this.id,this,e,n)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=s,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState?.[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState?this._deviceState[e]=t:console.warn("device state is unavailable. the device must be discovered with deviceManager.onDeviceDiscovered or deviceManager.onDevicesChanged before the state can be set.")}}for(const n of Object.values(o.ScryptedInterfaceProperty))n!==o.ScryptedInterfaceProperty.nativeId&&(Object.defineProperty(a.prototype,n,{set:t(n),get:e(n)}),Object.defineProperty(s.prototype,n,{set:t(n),get:e(n)}))}(),t.sdk={};try{let e=!1;try{process.env.SCRYPTED_SDK_ES_MODULE||process.env.SCRYPTED_SDK_MODULE;const r=process.env.SCRYPTED_SDK_CJS_MODULE||process.env.SCRYPTED_SDK_MODULE;if(r)if("undefined"!=typeof require){const n=require(process.env.SCRYPTED_SDK_MODULE);Object.assign(t.sdk,n.getScryptedStatic()),e=!0}else{const i=n(891)(r);Object.assign(t.sdk,i.getScryptedStatic()),e=!0}}catch(e){throw console.warn("failed to load sdk module",e),e}if(!e){let e;try{e=pluginRuntimeAPI}catch(e){}Object.assign(t.sdk,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI,...e})}try{t.sdk.systemManager.setScryptedInterfaceDescriptors?.(o.TYPES_VERSION,o.ScryptedInterfaceDescriptors)?.catch(()=>{})}catch(e){}}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/client instead",e)}t.default=t.sdk},891(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=891,e.exports=t},192(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ScryptedMimeTypes=t.ScryptedInterface=t.MediaPlayerState=t.SecuritySystemObstruction=t.SecuritySystemMode=t.AirQuality=t.AirPurifierMode=t.AirPurifierStatus=t.ChargeState=t.LockState=t.PanTiltZoomMovement=t.ThermostatMode=t.TemperatureUnit=t.FanMode=t.HumidityMode=t.ScryptedDeviceType=t.ScryptedInterfaceDescriptors=t.ScryptedInterfaceMethod=t.ScryptedInterfaceProperty=t.DeviceBase=t.TYPES_VERSION=void 0,t.TYPES_VERSION="0.3.116";var n,r,i,o,a,s,d,c,l,p,m,u,g,v,h,y,S,b;t.DeviceBase=class{},function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.nativeId="nativeId",e.pluginId="pluginId",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.scryptedRuntimeArguments="scryptedRuntimeArguments",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.buttons="buttons",e.sensors="sensors",e.running="running",e.paused="paused",e.docked="docked",e.temperatureSetting="temperatureSetting",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.audioVolumes="audioVolumes",e.recordingActive="recordingActive",e.ptzCapabilities="ptzCapabilities",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.chargeState="chargeState",e.online="online",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.converters="converters",e.binaryState="binaryState",e.tampered="tampered",e.sleeping="sleeping",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.securitySystemState="securitySystemState",e.pm10Density="pm10Density",e.pm25Density="pm25Density",e.vocDensity="vocDensity",e.noxDensity="noxDensity",e.co2ppm="co2ppm",e.airQuality="airQuality",e.airPurifierState="airPurifierState",e.filterChangeIndication="filterChangeIndication",e.filterLifeLevel="filterLifeLevel",e.humiditySetting="humiditySetting",e.fan="fan",e.applicationInfo="applicationInfo",e.systemDevice="systemDevice"}(n||(t.ScryptedInterfaceProperty=n={})),function(e){e.listen="listen",e.probe="probe",e.setMixins="setMixins",e.setName="setName",e.setRoom="setRoom",e.setType="setType",e.getPluginJson="getPluginJson",e.turnOff="turnOff",e.turnOn="turnOn",e.setBrightness="setBrightness",e.getTemperatureMaxK="getTemperatureMaxK",e.getTemperatureMinK="getTemperatureMinK",e.setColorTemperature="setColorTemperature",e.setRgb="setRgb",e.setHsv="setHsv",e.pressButton="pressButton",e.sendNotification="sendNotification",e.start="start",e.stop="stop",e.pause="pause",e.resume="resume",e.dock="dock",e.setTemperature="setTemperature",e.setTemperatureUnit="setTemperatureUnit",e.getPictureOptions="getPictureOptions",e.takePicture="takePicture",e.getAudioStream="getAudioStream",e.setAudioVolumes="setAudioVolumes",e.startDisplay="startDisplay",e.stopDisplay="stopDisplay",e.getVideoStream="getVideoStream",e.getVideoStreamOptions="getVideoStreamOptions",e.getPrivacyMasks="getPrivacyMasks",e.setPrivacyMasks="setPrivacyMasks",e.getVideoTextOverlays="getVideoTextOverlays",e.setVideoTextOverlay="setVideoTextOverlay",e.getRecordingStream="getRecordingStream",e.getRecordingStreamCurrentTime="getRecordingStreamCurrentTime",e.getRecordingStreamOptions="getRecordingStreamOptions",e.getRecordingStreamThumbnail="getRecordingStreamThumbnail",e.deleteRecordingStream="deleteRecordingStream",e.setRecordingActive="setRecordingActive",e.ptzCommand="ptzCommand",e.getRecordedEvents="getRecordedEvents",e.getVideoClip="getVideoClip",e.getVideoClips="getVideoClips",e.getVideoClipThumbnail="getVideoClipThumbnail",e.removeVideoClips="removeVideoClips",e.setVideoStreamOptions="setVideoStreamOptions",e.startIntercom="startIntercom",e.stopIntercom="stopIntercom",e.lock="lock",e.unlock="unlock",e.addPassword="addPassword",e.getPasswords="getPasswords",e.removePassword="removePassword",e.activate="activate",e.deactivate="deactivate",e.isReversible="isReversible",e.closeEntry="closeEntry",e.openEntry="openEntry",e.getDevice="getDevice",e.releaseDevice="releaseDevice",e.adoptDevice="adoptDevice",e.discoverDevices="discoverDevices",e.createDevice="createDevice",e.getCreateDeviceSettings="getCreateDeviceSettings",e.reboot="reboot",e.getRefreshFrequency="getRefreshFrequency",e.refresh="refresh",e.getMediaStatus="getMediaStatus",e.load="load",e.seek="seek",e.skipNext="skipNext",e.skipPrevious="skipPrevious",e.convert="convert",e.convertMedia="convertMedia",e.getSettings="getSettings",e.putSetting="putSetting",e.armSecuritySystem="armSecuritySystem",e.disarmSecuritySystem="disarmSecuritySystem",e.setAirPurifierState="setAirPurifierState",e.getReadmeMarkdown="getReadmeMarkdown",e.getOauthUrl="getOauthUrl",e.onOauthCallback="onOauthCallback",e.canMixin="canMixin",e.getMixin="getMixin",e.releaseMixin="releaseMixin",e.onRequest="onRequest",e.onConnection="onConnection",e.onPush="onPush",e.run="run",e.eval="eval",e.loadScripts="loadScripts",e.saveScript="saveScript",e.forkInterface="forkInterface",e.trackObjects="trackObjects",e.getDetectionInput="getDetectionInput",e.getObjectTypes="getObjectTypes",e.detectObjects="detectObjects",e.generateObjectDetections="generateObjectDetections",e.getDetectionModel="getDetectionModel",e.setHumidity="setHumidity",e.setFan="setFan",e.startRTCSignalingSession="startRTCSignalingSession",e.createRTCSignalingSession="createRTCSignalingSession",e.getScryptedUserAccessControl="getScryptedUserAccessControl",e.generateVideoFrames="generateVideoFrames",e.connectStream="connectStream",e.getTTYSettings="getTTYSettings"}(r||(t.ScryptedInterfaceMethod=r={})),t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setMixins","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","nativeId","pluginId","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},ScryptedPluginRuntime:{name:"ScryptedPluginRuntime",methods:[],properties:["scryptedRuntimeArguments"]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Buttons:{name:"Buttons",methods:[],properties:["buttons"]},PressButtons:{name:"PressButtons",methods:["pressButton"],properties:[]},Sensors:{name:"Sensors",methods:[],properties:["sensors"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setTemperature"],properties:["temperatureSetting"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},Microphone:{name:"Microphone",methods:["getAudioStream"],properties:[]},AudioVolumeControl:{name:"AudioVolumeControl",methods:["setAudioVolumes"],properties:["audioVolumes"]},Display:{name:"Display",methods:["startDisplay","stopDisplay"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraMask:{name:"VideoCameraMask",methods:["getPrivacyMasks","setPrivacyMasks"],properties:[]},VideoTextOverlays:{name:"VideoTextOverlays",methods:["getVideoTextOverlays","setVideoTextOverlay"],properties:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamCurrentTime","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:["recordingActive"]},VideoRecorderManagement:{name:"VideoRecorderManagement",methods:["deleteRecordingStream","setRecordingActive"],properties:[]},PanTiltZoom:{name:"PanTiltZoom",methods:["ptzCommand"],properties:["ptzCapabilities"]},EventRecorder:{name:"EventRecorder",methods:["getRecordedEvents"],properties:[]},VideoClips:{name:"VideoClips",methods:["getVideoClip","getVideoClips","getVideoClipThumbnail","removeVideoClips"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice","releaseDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["adoptDevice","discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Charger:{name:"Charger",methods:[],properties:["chargeState"]},Reboot:{name:"Reboot",methods:["reboot"],properties:[]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},MediaConverter:{name:"MediaConverter",methods:["convertMedia"],properties:["converters"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},TamperSensor:{name:"TamperSensor",methods:[],properties:["tampered"]},Sleep:{name:"Sleep",methods:[],properties:["sleeping"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},SecuritySystem:{name:"SecuritySystem",methods:["armSecuritySystem","disarmSecuritySystem"],properties:["securitySystemState"]},PM10Sensor:{name:"PM10Sensor",methods:[],properties:["pm10Density"]},PM25Sensor:{name:"PM25Sensor",methods:[],properties:["pm25Density"]},VOCSensor:{name:"VOCSensor",methods:[],properties:["vocDensity"]},NOXSensor:{name:"NOXSensor",methods:[],properties:["noxDensity"]},CO2Sensor:{name:"CO2Sensor",methods:[],properties:["co2ppm"]},AirQualitySensor:{name:"AirQualitySensor",methods:[],properties:["airQuality"]},AirPurifier:{name:"AirPurifier",methods:["setAirPurifierState"],properties:["airPurifierState"]},FilterMaintenance:{name:"FilterMaintenance",methods:[],properties:["filterChangeIndication","filterLifeLevel"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ClusterForkInterface:{name:"ClusterForkInterface",methods:["forkInterface"],properties:[]},ObjectTracker:{name:"ObjectTracker",methods:["trackObjects"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","generateObjectDetections","getDetectionModel"],properties:[]},ObjectDetectionPreview:{name:"ObjectDetectionPreview",methods:[],properties:[]},ObjectDetectionGenerator:{name:"ObjectDetectionGenerator",methods:[],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]},LauncherApplication:{name:"LauncherApplication",methods:[],properties:["applicationInfo"]},ScryptedUser:{name:"ScryptedUser",methods:["getScryptedUserAccessControl"],properties:[]},VideoFrameGenerator:{name:"VideoFrameGenerator",methods:["generateVideoFrames"],properties:[]},StreamService:{name:"StreamService",methods:["connectStream"],properties:[]},TTY:{name:"TTY",methods:[],properties:[]},TTYSettings:{name:"TTYSettings",methods:["getTTYSettings"],properties:[]},ScryptedSystemDevice:{name:"ScryptedSystemDevice",methods:[],properties:["systemDevice"]},ScryptedDeviceCreator:{name:"ScryptedDeviceCreator",methods:[],properties:[]},ScryptedSettings:{name:"ScryptedSettings",methods:[],properties:[]}},function(e){e.Builtin="Builtin",e.Internal="Internal",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.SmartDisplay="SmartDisplay",e.Speaker="Speaker",e.SmartSpeaker="SmartSpeaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.SecuritySystem="SecuritySystem",e.WindowCovering="WindowCovering",e.Siren="Siren",e.AirPurifier="AirPurifier",e.Internet="Internet",e.Network="Network",e.Bridge="Bridge",e.Unknown="Unknown"}(i||(t.ScryptedDeviceType=i={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(o||(t.HumidityMode=o={})),function(e){e.Auto="Auto",e.Manual="Manual"}(a||(t.FanMode=a={})),function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(d||(t.ThermostatMode=d={})),function(e){e.Absolute="Absolute",e.Relative="Relative",e.Continuous="Continuous",e.Preset="Preset",e.Home="Home"}(c||(t.PanTiltZoomMovement=c={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(l||(t.LockState=l={})),function(e){e.Trickle="trickle",e.Charging="charging",e.NotCharging="not-charging"}(p||(t.ChargeState=p={})),function(e){e.Inactive="Inactive",e.Idle="Idle",e.Active="Active",e.ActiveNightMode="ActiveNightMode"}(m||(t.AirPurifierStatus=m={})),function(e){e.Manual="Manual",e.Automatic="Automatic"}(u||(t.AirPurifierMode=u={})),function(e){e.Unknown="Unknown",e.Excellent="Excellent",e.Good="Good",e.Fair="Fair",e.Inferior="Inferior",e.Poor="Poor"}(g||(t.AirQuality=g={})),function(e){e.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(v||(t.SecuritySystemMode=v={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(h||(t.SecuritySystemObstruction=h={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(y||(t.MediaPlayerState=y={})),function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.ScryptedPluginRuntime="ScryptedPluginRuntime",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Buttons="Buttons",e.PressButtons="PressButtons",e.Sensors="Sensors",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.Microphone="Microphone",e.AudioVolumeControl="AudioVolumeControl",e.Display="Display",e.VideoCamera="VideoCamera",e.VideoCameraMask="VideoCameraMask",e.VideoTextOverlays="VideoTextOverlays",e.VideoRecorder="VideoRecorder",e.VideoRecorderManagement="VideoRecorderManagement",e.PanTiltZoom="PanTiltZoom",e.EventRecorder="EventRecorder",e.VideoClips="VideoClips",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Charger="Charger",e.Reboot="Reboot",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.BufferConverter="BufferConverter",e.MediaConverter="MediaConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.TamperSensor="TamperSensor",e.Sleep="Sleep",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.SecuritySystem="SecuritySystem",e.PM10Sensor="PM10Sensor",e.PM25Sensor="PM25Sensor",e.VOCSensor="VOCSensor",e.NOXSensor="NOXSensor",e.CO2Sensor="CO2Sensor",e.AirQualitySensor="AirQualitySensor",e.AirPurifier="AirPurifier",e.FilterMaintenance="FilterMaintenance",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ClusterForkInterface="ClusterForkInterface",e.ObjectTracker="ObjectTracker",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.ObjectDetectionPreview="ObjectDetectionPreview",e.ObjectDetectionGenerator="ObjectDetectionGenerator",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient",e.LauncherApplication="LauncherApplication",e.ScryptedUser="ScryptedUser",e.VideoFrameGenerator="VideoFrameGenerator",e.StreamService="StreamService",e.TTY="TTY",e.TTYSettings="TTYSettings",e.ScryptedSystemDevice="ScryptedSystemDevice",e.ScryptedDeviceCreator="ScryptedDeviceCreator",e.ScryptedSettings="ScryptedSettings"}(S||(t.ScryptedInterface=S={})),function(e){e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.ServerId="text/x-server-id",e.PushEndpoint="text/x-push-endpoint",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaStreamUrl="text/x-media-url",e.MediaObject="x-scrypted/x-scrypted-media-object",e.RequestMediaObject="x-scrypted/x-scrypted-request-media-object",e.RequestMediaStream="x-scrypted/x-scrypted-request-stream",e.MediaStreamFeedback="x-scrypted/x-media-stream-feedback",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.FFmpegTranscodeStream="x-scrypted/x-ffmpeg-transcode-stream",e.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.RTCSignalingSession="x-scrypted/x-scrypted-rtc-signaling-session",e.RTCConnectionManagement="x-scrypted/x-scrypted-rtc-connection-management",e.Image="x-scrypted/x-scrypted-image"}(b||(t.ScryptedMimeTypes=b={}))},927(e,t,n){"use strict";var r,i=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),a=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),a=0;a<n.length;a++)"default"!==n[a]&&i(t,e,n[a]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0});const s=a(n(562)),{systemManager:d,deviceManager:c,mediaManager:l}=s.default,p=new Set(["person","cat","dog","animal","bird","face","vehicle","car","truck","bus","motorcycle","bicycle","plate","package"]),m=["disabled","1 per minute","1 per 10 seconds","every detection"],u={disabled:1/0,"1 per minute":6e4,"1 per 10 seconds":1e4,"every detection":0};class g extends s.ScryptedDeviceBase{constructor(e){super(e),this.lastCapture=new Map,this.captures=new Map,this.images=new Map,this.listeners=[],this.loadState(),this.registerListeners()}loadState(){try{const e=this.storage.getItem("captures");if(e){const t=JSON.parse(e);for(const e of t)this.captures.set(e.id,e);this.console.log(`Loaded ${this.captures.size} captures from storage.`)}}catch(e){this.console.warn("Could not load captures from storage:",e)}for(const[e]of this.captures){const t=this.storage.getItem(`img:${e}`);t&&this.images.set(e,Buffer.from(t,"base64"))}}saveCaptures(){this.storage.setItem("captures",JSON.stringify([...this.captures.values()]))}saveImage(e,t){this.storage.setItem(`img:${e}`,t.toString("base64"))}deleteCapture(e){this.storage.getItem(`img:${e}`)&&this.storage.removeItem(`img:${e}`),this.captures.delete(e),this.images.delete(e),this.saveCaptures()}async getSettings(){const e=Object.keys(d.getSystemState()).map(e=>d.getDeviceById(e)).filter(e=>e&&(e.type===s.ScryptedDeviceType.Camera||e.type===s.ScryptedDeviceType.Doorbell)&&e.interfaces?.includes(s.ScryptedInterface.ObjectDetector)),t=await s.default.endpointManager.getLocalEndpoint(void 0,{public:!0}).catch(()=>"/endpoint/scrypted-detection-trainer/public/"),n=[{key:"info",title:"Detection Trainer",description:`${this.captures.size} captures stored (${[...this.captures.values()].filter(e=>!e.reviewed).length} pending review, ${[...this.captures.values()].filter(e=>e.reviewed&&"discard"!==e.label).length} labeled).`,readonly:!0,value:""},{key:"open_ui",title:"Review UI",description:"Open the detection review and labeling interface.",type:"html",readonly:!0,value:`<a href="${t}" target="_blank" style="display:inline-block;padding:8px 16px;background:#1a4d8a;color:#fff;border-radius:6px;text-decoration:none;font-size:13px;">Open Review UI β†—</a>`}];for(const t of e){const e=`rate:${t.id}`;n.push({key:e,title:t.name,group:"Capture Rate per Camera",description:"How often to capture detections from this camera.",value:this.storage.getItem(e)||"1 per minute",choices:[...m]})}return n}async putSetting(e,t){"open_ui"!==e&&"ui_link"!==e&&"info"!==e&&(this.storage.setItem(e,t),e.startsWith("rate:")&&this.registerListeners())}registerListeners(){for(const e of this.listeners)e();this.listeners=[];const e=Object.keys(d.getSystemState()).map(e=>d.getDeviceById(e)).filter(e=>e&&(e.type===s.ScryptedDeviceType.Camera||e.type===s.ScryptedDeviceType.Doorbell)&&e.interfaces?.includes(s.ScryptedInterface.ObjectDetector));for(const t of e){const e=`rate:${t.id}`,n=this.storage.getItem(e)||"1 per minute";if("disabled"===n)continue;const r=t.listen(s.ScryptedInterface.ObjectDetector,async(e,r,i)=>{await this.onDetection(t.id,t.name,i,u[n])});this.listeners.push(()=>r.removeListener())}this.console.log(`Listening to ${this.listeners.length} camera(s).`)}async onDetection(e,t,n,r){if(!n?.detections?.length||!n.inputDimensions)return;const i=Date.now();if(i-(this.lastCapture.get(e)||0)<r)return;const o=n.detections.filter(e=>e.className&&p.has(e.className.toLowerCase())&&e.boundingBox);if(!o.length)return;const a=o.sort((e,t)=>(t.score||0)-(e.score||0))[0];if(this.captures.size>=2e3){const e=[...this.captures.values()].filter(e=>!e.reviewed).sort((e,t)=>e.timestamp-t.timestamp)[0];if(!e)return;this.deleteCapture(e.id)}let s;this.lastCapture.set(e,i);try{if(n.detectionId){const t=d.getDeviceById(e),r=await t.getDetectionInput(n.detectionId);s=await l.convertMediaObjectToBuffer(r,"image/jpeg")}}catch(e){this.console.warn(`Could not get detection image for ${t}:`,e)}if(!s)return;const c=`${i}-${Math.random().toString(36).slice(2,8)}`,m={id:c,cameraId:e,cameraName:t,timestamp:i,detectedClass:a.className,score:a.score||0,boundingBox:a.boundingBox,inputDimensions:n.inputDimensions,detectionId:n.detectionId,reviewed:!1};this.captures.set(c,m),this.images.set(c,s),this.saveImage(c,s),this.saveCaptures(),this.console.log(`Captured ${a.className} (${Math.round(100*(a.score||0))}%) from ${t} [${this.captures.size} total]`)}async onRequest(e,t){const n=new URL(e.url,"http://localhost").pathname.replace(e.rootPath,"");if(n.startsWith("/img/")){const e=n.slice(5),r=this.images.get(e);return r?t.send(r,{headers:{"Content-Type":"image/jpeg","Cache-Control":"max-age=3600"}}):t.send("Not found",{code:404})}if("/api/label"===n&&e.body){const n=e.body,r=JSON.parse("string"==typeof n?n:Buffer.isBuffer(n)?n.toString():String(n)),i=this.captures.get(r.id);return i?(i.label=r.label,i.reviewed=!0,"discard"===r.label?this.deleteCapture(r.id):(this.captures.set(r.id,i),this.saveCaptures()),t.send(JSON.stringify({ok:!0}),{headers:{"Content-Type":"application/json"}})):t.send("Not found",{code:404})}if("/api/pending"===n){const e=[...this.captures.values()].filter(e=>!e.reviewed).sort((e,t)=>t.timestamp-e.timestamp).slice(0,50);return t.send(JSON.stringify(e),{headers:{"Content-Type":"application/json"}})}if("/api/stats"===n){const e=[...this.captures.values()],n={total:e.length,pending:e.filter(e=>!e.reviewed).length,labeled:e.filter(e=>e.reviewed&&"discard"!==e.label).length,byLabel:{},byCamera:{},byDetectedClass:{}};for(const t of e)t.label&&(n.byLabel[t.label]=(n.byLabel[t.label]||0)+1),n.byCamera[t.cameraName]=(n.byCamera[t.cameraName]||0)+1,n.byDetectedClass[t.detectedClass]=(n.byDetectedClass[t.detectedClass]||0)+1;return t.send(JSON.stringify(n),{headers:{"Content-Type":"application/json"}})}if("/api/export"===n){const e=[...this.captures.values()].filter(e=>e.reviewed&&e.label&&"discard"!==e.label);if(!e.length)return t.send(JSON.stringify({error:"No labeled data yet"}),{headers:{"Content-Type":"application/json"},code:400});const n={person:0,animal:1,face:2,vehicle:3,plate:4,package:5,discard:-1},r=[];for(const t of e){const e=this.images.get(t.id);if(!e)continue;const i=`${t.id}`;r.push({filename:`images/${i}.jpg`,content:e.toString("base64"),encoding:"base64"});const[o,a,s,d]=t.boundingBox,[c,l]=t.inputDimensions,p=(o+s/2)/c,m=(a+d/2)/l,u=s/c,g=d/l,v=`${n[t.label]} ${p.toFixed(6)} ${m.toFixed(6)} ${u.toFixed(6)} ${g.toFixed(6)}\n`;r.push({filename:`labels/${i}.txt`,content:v,encoding:"utf8"})}const i=["path: dataset","train: images","val: images","","nc: 6","names: ['person', 'animal', 'face', 'vehicle', 'plate', 'package']","","# Generated by Scrypted Detection Trainer",`# ${e.length} labeled samples`].join("\n");return r.push({filename:"data.yaml",content:i,encoding:"utf8"}),t.send(JSON.stringify({files:r,count:e.length}),{headers:{"Content-Type":"application/json"}})}if("/"===n||""===n||"/index.html"===n)return t.send(this.renderUI(),{headers:{"Content-Type":"text/html"}});t.send("Not found",{code:404})}renderUI(){return'<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Detection Trainer</title>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"><\/script>\n<style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', sans-serif; background: #0f0f0f; color: #e8e8e8; min-height: 100vh; }\n header { background: #1a1a1a; border-bottom: 1px solid #333; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }\n header h1 { font-size: 18px; font-weight: 600; color: #fff; }\n .stats { display: flex; gap: 20px; font-size: 13px; color: #aaa; }\n .stat span { color: #fff; font-weight: 600; }\n .container { max-width: 1000px; margin: 0 auto; padding: 24px; }\n .card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 12px; overflow: hidden; margin-bottom: 24px; }\n .card-header { padding: 16px 20px; border-bottom: 1px solid #2a2a2a; display: flex; align-items: center; justify-content: space-between; }\n .card-header h2 { font-size: 15px; font-weight: 600; }\n .badge { background: #333; color: #aaa; font-size: 12px; padding: 2px 8px; border-radius: 20px; }\n .badge.orange { background: #3d2a00; color: #f90; }\n .badge.green { background: #0d2d0d; color: #4c4; }\n\n /* Detection card */\n .detection { display: grid; grid-template-columns: 200px 1fr; gap: 0; border-bottom: 1px solid #222; }\n .detection:last-child { border-bottom: none; }\n .detection-img { position: relative; background: #111; display: flex; align-items: center; justify-content: center; min-height: 150px; }\n .detection-img img { width: 100%; height: 150px; object-fit: cover; display: block; }\n .detection-class { position: absolute; top: 6px; left: 6px; background: rgba(0,0,0,0.7); color: #fff; font-size: 11px; padding: 2px 6px; border-radius: 4px; }\n .detection-info { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }\n .detection-meta { font-size: 12px; color: #888; display: flex; flex-wrap: wrap; gap: 10px; }\n .detection-meta strong { color: #ccc; }\n .label-buttons { display: flex; flex-wrap: wrap; gap: 8px; }\n .label-btn { padding: 7px 14px; border-radius: 8px; border: 1px solid #444; background: #222; color: #ccc; cursor: pointer; font-size: 13px; transition: all .15s; }\n .label-btn:hover { border-color: #666; background: #2a2a2a; color: #fff; }\n .label-btn.person { border-color: #2a6; color: #4d9; }\n .label-btn.person:hover { background: #0d2a1a; }\n .label-btn.animal { border-color: #a63; color: #d85; }\n .label-btn.animal:hover { background: #2a1a0d; }\n .label-btn.face { border-color: #49c; color: #6be; }\n .label-btn.face:hover { background: #0d1a2a; }\n .label-btn.vehicle { border-color: #76b; color: #99d; }\n .label-btn.vehicle:hover { background: #1a1a2a; }\n .label-btn.discard { border-color: #622; color: #a44; }\n .label-btn.discard:hover { background: #2a0d0d; }\n .detection.labeled { opacity: 0.4; pointer-events: none; }\n .labeled-tag { font-size: 11px; color: #4d9; background: #0d2a1a; border: 1px solid #2a6; padding: 2px 8px; border-radius: 4px; }\n\n /* Empty state */\n .empty { padding: 48px; text-align: center; color: #555; }\n .empty .icon { font-size: 48px; margin-bottom: 12px; }\n\n /* Export section */\n .export-btn { padding: 10px 20px; background: #1a4d8a; border: none; border-radius: 8px; color: #fff; cursor: pointer; font-size: 14px; font-weight: 500; }\n .export-btn:hover { background: #1e5ca0; }\n .export-btn:disabled { background: #333; color: #666; cursor: not-allowed; }\n .export-info { font-size: 13px; color: #888; padding: 12px 20px; }\n\n /* Progress bar */\n .progress { height: 4px; background: #222; border-radius: 2px; overflow: hidden; margin-top: 8px; }\n .progress-bar { height: 100%; background: #1a6; border-radius: 2px; transition: width .3s; }\n\n .toast { position: fixed; bottom: 24px; right: 24px; background: #1a3; color: #fff; padding: 10px 18px; border-radius: 8px; font-size: 13px; opacity: 0; transition: opacity .3s; pointer-events: none; }\n .toast.show { opacity: 1; }\n\n .tab-bar { display: flex; gap: 2px; padding: 12px 20px 0; border-bottom: 1px solid #2a2a2a; }\n .tab { padding: 8px 14px; font-size: 13px; color: #888; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }\n .tab.active { color: #fff; border-bottom-color: #4a9; }\n .tab-content { padding: 20px; }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n .breakdown-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }\n .breakdown-item { background: #222; border-radius: 8px; padding: 12px; }\n .breakdown-item .label { font-size: 12px; color: #888; margin-bottom: 4px; }\n .breakdown-item .value { font-size: 20px; font-weight: 600; color: #fff; }\n</style>\n</head>\n<body>\n<header>\n <h1>🎯 Detection Trainer</h1>\n <div class="stats">\n <div>Pending <span id="stat-pending">β€”</span></div>\n <div>Labeled <span id="stat-labeled">β€”</span></div>\n <div>Total <span id="stat-total">β€”</span></div>\n </div>\n</header>\n<div class="container">\n\n <div class="card">\n <div class="tab-bar">\n <div class="tab active" onclick="showTab(\'review\')">Review</div>\n <div class="tab" onclick="showTab(\'stats\')">Stats</div>\n <div class="tab" onclick="showTab(\'export\')">Export Dataset</div>\n </div>\n\n \x3c!-- Review tab --\x3e\n <div class="tab-panel active" id="tab-review">\n <div id="detections-list"></div>\n </div>\n\n \x3c!-- Stats tab --\x3e\n <div class="tab-panel" id="tab-stats">\n <div class="tab-content">\n <p style="font-size:13px;color:#888;margin-bottom:16px;">Breakdown of captured and labeled detections.</p>\n <h3 style="font-size:13px;color:#aaa;margin-bottom:10px;">By Detected Class (what the model said)</h3>\n <div class="breakdown-grid" id="stats-detected"></div>\n <h3 style="font-size:13px;color:#aaa;margin:20px 0 10px;">By Corrected Label (what you said)</h3>\n <div class="breakdown-grid" id="stats-label"></div>\n <h3 style="font-size:13px;color:#aaa;margin:20px 0 10px;">By Camera</h3>\n <div class="breakdown-grid" id="stats-camera"></div>\n </div>\n </div>\n\n \x3c!-- Export tab --\x3e\n <div class="tab-panel" id="tab-export">\n <div class="tab-content">\n <p style="font-size:13px;color:#888;margin-bottom:16px;">\n Exports a YOLO-format dataset (images + labels + data.yaml) as a downloadable bundle.\n Only labeled detections are included. Review more detections first to build a larger dataset.\n </p>\n <div id="export-stats" class="export-info">Loading…</div>\n <div style="display:flex;gap:12px;align-items:center;margin-top:12px;">\n <button class="export-btn" id="export-btn" onclick="exportDataset()">Download Dataset</button>\n <span id="export-status" style="font-size:13px;color:#888;"></span>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div class="toast" id="toast"></div>\n\n<script>\nconst BASE = location.pathname.endsWith(\'/\') ? location.pathname.slice(0, -1) : location.pathname;\nlet pending = [];\nlet labeledCount = 0;\n\nfunction imgError(img) {\n img.parentElement.innerHTML = \'<div style="padding:20px;color:#555;font-size:12px;text-align:center">No image</div>\';\n}\n\nfunction showTab(name) {\n document.querySelectorAll(\'.tab\').forEach((t, i) => {\n const names = [\'review\', \'stats\', \'export\'];\n t.classList.toggle(\'active\', names[i] === name);\n });\n document.querySelectorAll(\'.tab-panel\').forEach(p => p.classList.remove(\'active\'));\n document.getElementById(\'tab-\' + name).classList.add(\'active\');\n if (name === \'stats\') loadStats();\n if (name === \'export\') loadExportInfo();\n}\n\nfunction toast(msg, color=\'#1a3\') {\n const el = document.getElementById(\'toast\');\n el.textContent = msg;\n el.style.background = color;\n el.classList.add(\'show\');\n setTimeout(() => el.classList.remove(\'show\'), 2500);\n}\n\nasync function loadPending() {\n try {\n const res = await fetch(BASE + \'/api/pending\');\n pending = await res.json();\n\n const statsRes = await fetch(BASE + \'/api/stats\');\n const stats = await statsRes.json();\n document.getElementById(\'stat-pending\').textContent = stats.pending;\n document.getElementById(\'stat-labeled\').textContent = stats.labeled;\n document.getElementById(\'stat-total\').textContent = stats.total;\n\n const list = document.getElementById(\'detections-list\');\n if (!pending.length) {\n list.innerHTML = \'<div class="empty"><div class="icon">βœ…</div><div>No pending detections to review.<br><span style="font-size:12px;color:#444">Captures will appear here as cameras detect objects.</span></div></div>\';\n return;\n }\n\n list.innerHTML = pending.map(r => {\n const date = new Date(r.timestamp).toLocaleString();\n const score = Math.round(r.score * 100);\n return `\n <div class="detection" id="det-${r.id}">\n <div class="detection-img">\n <img src="${BASE}/img/${r.id}" alt="${r.detectedClass}" loading="lazy" onerror="imgError(this)">\n <div class="detection-class">${r.detectedClass} ${score}%</div>\n </div>\n <div class="detection-info">\n <div class="detection-meta">\n <div><strong>${r.cameraName}</strong></div>\n <div>${date}</div>\n <div>Box: ${r.boundingBox.map(v => Math.round(v)).join(\', \')}</div>\n </div>\n <div style="font-size:12px;color:#888;">What is this actually?</div>\n <div class="label-buttons">\n <button class="label-btn person" onclick="label(\'${r.id}\', \'person\')">πŸ‘€ Person</button>\n <button class="label-btn animal" onclick="label(\'${r.id}\', \'animal\')">🐾 Animal</button>\n <button class="label-btn face" onclick="label(\'${r.id}\', \'face\')">πŸ˜€ Face</button>\n <button class="label-btn vehicle" onclick="label(\'${r.id}\', \'vehicle\')">πŸš— Vehicle</button>\n <button class="label-btn" onclick="label(\'${r.id}\', \'plate\')">πŸ”’ Plate</button>\n <button class="label-btn" onclick="label(\'${r.id}\', \'package\')">πŸ“¦ Package</button>\n <button class="label-btn discard" onclick="label(\'${r.id}\', \'discard\')">πŸ—‘ Discard</button>\n </div>\n </div>\n </div>`;\n }).join(\'\');\n } catch(e) {\n console.error(\'loadPending error\', e);\n const list = document.getElementById(\'detections-list\');\n if (list) list.innerHTML = \'<div class="empty"><div style="color:#a44">Error loading captures: \' + e.message + \'</div></div>\';\n }\n}\n\nasync function label(id, labelVal) {\n const el = document.getElementById(\'det-\' + id);\n if (el) {\n el.classList.add(\'labeled\');\n const btns = el.querySelectorAll(\'.label-btn\');\n btns.forEach(b => b.disabled = true);\n }\n await fetch(BASE + \'/api/label\', {\n method: \'POST\',\n headers: { \'Content-Type\': \'application/json\' },\n body: JSON.stringify({ id, label: labelVal }),\n });\n toast(labelVal === \'discard\' ? \'Discarded\' : \'Labeled: \' + labelVal, labelVal === \'discard\' ? \'#633\' : \'#1a6\');\n setTimeout(() => {\n if (el) el.remove();\n loadPending();\n }, 600);\n}\n\nasync function loadStats() {\n const res = await fetch(BASE + \'/api/stats\');\n const stats = await res.json();\n\n const renderBreakdown = (obj, container) => {\n const el = document.getElementById(container);\n const entries = Object.entries(obj).sort((a, b) => b[1] - a[1]);\n el.innerHTML = entries.length\n ? entries.map(([k, v]) => `<div class="breakdown-item"><div class="label">${k}</div><div class="value">${v}</div></div>`).join(\'\')\n : \'<div style="color:#555;font-size:13px;">None yet</div>\';\n };\n\n renderBreakdown(stats.byDetectedClass, \'stats-detected\');\n renderBreakdown(stats.byLabel, \'stats-label\');\n renderBreakdown(stats.byCamera, \'stats-camera\');\n}\n\nasync function loadExportInfo() {\n const res = await fetch(BASE + \'/api/stats\');\n const stats = await res.json();\n document.getElementById(\'export-stats\').textContent =\n `${stats.labeled} labeled samples ready for export across ${Object.keys(stats.byCamera).length} camera(s).`;\n}\n\nasync function exportDataset() {\n const btn = document.getElementById(\'export-btn\');\n const status = document.getElementById(\'export-status\');\n btn.disabled = true;\n status.textContent = \'Fetching data…\';\n\n try {\n const res = await fetch(BASE + \'/api/export\');\n if (!res.ok) { status.textContent = \'Nothing to export yet.\'; btn.disabled = false; return; }\n const data = await res.json();\n if (data.error) { status.textContent = data.error; btn.disabled = false; return; }\n\n status.textContent = \'Building zip…\';\n\n const zip = new JSZip();\n for (const f of data.files) {\n if (f.encoding === \'base64\') {\n zip.file(f.filename, f.content, { base64: true });\n } else {\n zip.file(f.filename, f.content);\n }\n }\n\n const blob = await zip.generateAsync({ type: \'blob\', compression: \'DEFLATE\' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\'a\');\n a.href = url;\n a.download = \'scrypted_dataset_\' + new Date().toISOString().slice(0,10) + \'.zip\';\n a.click();\n URL.revokeObjectURL(url);\n status.textContent = `Downloaded ${data.count} samples.`;\n toast(\'Dataset downloaded!\');\n } catch (e) {\n status.textContent = \'Export failed: \' + e.message;\n }\n btn.disabled = false;\n}\n\n// Initial load\nloadPending();\n// Auto-refresh pending every 30s\nsetInterval(loadPending, 30_000);\n<\/script>\n</body>\n</html>'}}t.default=g},339(e){"use strict";e.exports=require("module")}},t={};function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var r=n(927),i=exports="undefined"==typeof exports?{}:exports;for(var o in r)i[o]=r[o];r.__esModule&&Object.defineProperty(i,"__esModule",{value:!0})})();
1
+ (()=>{var e={562(e,t,n){"use strict";var i=this&&this.__createBinding||(Object.create?function(e,t,n,i){void 0===i&&(i=n);var r=Object.getOwnPropertyDescriptor(t,n);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,i,r)}:function(e,t,n,i){void 0===i&&(i=n),e[i]=t[n]}),r=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||i(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),t.sdk=t.MixinDeviceBase=t.ScryptedDeviceBase=void 0,r(n(192),t);const o=n(192);n(339);class a extends o.DeviceBase{constructor(e){super(),this.nativeId=e}get storage(){return this._storage||(this._storage=t.sdk.deviceManager.getDeviceStorage(this.nativeId)),this._storage}get log(){return this._log||(this._log=t.sdk.deviceManager.getDeviceLogger(this.nativeId)),this._log}get console(){return this._console||(this._console=t.sdk.deviceManager.getDeviceConsole(this.nativeId)),this._console}async createMediaObject(e,n){return t.sdk.mediaManager.createMediaObject(e,n,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:t.sdk.deviceManager.getMixinConsole(e.sourceId,this.nativeId)}_lazyLoadDeviceState(){this._deviceState||(this.nativeId?this._deviceState=t.sdk.deviceManager.getDeviceState(this.nativeId):this._deviceState=t.sdk.deviceManager.getDeviceState())}onDeviceEvent(e,n){return t.sdk.deviceManager.onDeviceEvent(this.nativeId,e,n)}}t.ScryptedDeviceBase=a;class s extends o.DeviceBase{constructor(e){super(),this._listeners=new Set,this.mixinDevice=e.mixinDevice,this.mixinDeviceInterfaces=e.mixinDeviceInterfaces,this.mixinStorageSuffix=e.mixinStorageSuffix,this._deviceState=e.mixinDeviceState,this.nativeId=t.sdk.systemManager.getDeviceById(this.id).nativeId,this.mixinProviderNativeId=e.mixinProviderNativeId,this._deviceState.__rpcproxy_traps_all_properties&&"string"==typeof this._deviceState.id&&(this._deviceState=t.sdk.deviceManager.createDeviceState(this._deviceState.id,this._deviceState.setState))}get storage(){if(!this._storage){const e=this.mixinStorageSuffix,n=this.id+(e?":"+e:"");this._storage=t.sdk.deviceManager.getMixinStorage(n,this.mixinProviderNativeId)}return this._storage}get console(){return this._console||(t.sdk.deviceManager.getMixinConsole?this._console=t.sdk.deviceManager.getMixinConsole(this.id,this.mixinProviderNativeId):this._console=t.sdk.deviceManager.getDeviceConsole(this.mixinProviderNativeId)),this._console}async createMediaObject(e,n){return t.sdk.mediaManager.createMediaObject(e,n,{sourceId:this.id})}getMediaObjectConsole(e){return"string"!=typeof e.sourceId?this.console:t.sdk.deviceManager.getMixinConsole(e.sourceId,this.mixinProviderNativeId)}onDeviceEvent(e,n){return t.sdk.deviceManager.onMixinEvent(this.id,this,e,n)}_lazyLoadDeviceState(){}manageListener(e){this._listeners.add(e)}release(){for(const e of this._listeners)e.removeListener()}}t.MixinDeviceBase=s,function(){function e(e){return function(){return this._lazyLoadDeviceState(),this._deviceState?.[e]}}function t(e){return function(t){this._lazyLoadDeviceState(),this._deviceState?this._deviceState[e]=t:console.warn("device state is unavailable. the device must be discovered with deviceManager.onDeviceDiscovered or deviceManager.onDevicesChanged before the state can be set.")}}for(const n of Object.values(o.ScryptedInterfaceProperty))n!==o.ScryptedInterfaceProperty.nativeId&&(Object.defineProperty(a.prototype,n,{set:t(n),get:e(n)}),Object.defineProperty(s.prototype,n,{set:t(n),get:e(n)}))}(),t.sdk={};try{let e=!1;try{process.env.SCRYPTED_SDK_ES_MODULE||process.env.SCRYPTED_SDK_MODULE;const i=process.env.SCRYPTED_SDK_CJS_MODULE||process.env.SCRYPTED_SDK_MODULE;if(i)if("undefined"!=typeof require){const n=require(process.env.SCRYPTED_SDK_MODULE);Object.assign(t.sdk,n.getScryptedStatic()),e=!0}else{const r=n(891)(i);Object.assign(t.sdk,r.getScryptedStatic()),e=!0}}catch(e){throw console.warn("failed to load sdk module",e),e}if(!e){let e;try{e=pluginRuntimeAPI}catch(e){}Object.assign(t.sdk,{log:deviceManager.getDeviceLogger(void 0),deviceManager,endpointManager,mediaManager,systemManager,pluginHostAPI,...e})}try{t.sdk.systemManager.setScryptedInterfaceDescriptors?.(o.TYPES_VERSION,o.ScryptedInterfaceDescriptors)?.catch(()=>{})}catch(e){}}catch(e){console.error("sdk initialization error, import @scrypted/types or use @scrypted/client instead",e)}t.default=t.sdk},891(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=891,e.exports=t},192(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ScryptedMimeTypes=t.ScryptedInterface=t.MediaPlayerState=t.SecuritySystemObstruction=t.SecuritySystemMode=t.AirQuality=t.AirPurifierMode=t.AirPurifierStatus=t.ChargeState=t.LockState=t.PanTiltZoomMovement=t.ThermostatMode=t.TemperatureUnit=t.FanMode=t.HumidityMode=t.ScryptedDeviceType=t.ScryptedInterfaceDescriptors=t.ScryptedInterfaceMethod=t.ScryptedInterfaceProperty=t.DeviceBase=t.TYPES_VERSION=void 0,t.TYPES_VERSION="0.3.116";var n,i,r,o,a,s,c,d,l,p,m,g,u,h,v,b,y,f;t.DeviceBase=class{},function(e){e.id="id",e.info="info",e.interfaces="interfaces",e.mixins="mixins",e.name="name",e.nativeId="nativeId",e.pluginId="pluginId",e.providedInterfaces="providedInterfaces",e.providedName="providedName",e.providedRoom="providedRoom",e.providedType="providedType",e.providerId="providerId",e.room="room",e.type="type",e.scryptedRuntimeArguments="scryptedRuntimeArguments",e.on="on",e.brightness="brightness",e.colorTemperature="colorTemperature",e.rgb="rgb",e.hsv="hsv",e.buttons="buttons",e.sensors="sensors",e.running="running",e.paused="paused",e.docked="docked",e.temperatureSetting="temperatureSetting",e.temperature="temperature",e.temperatureUnit="temperatureUnit",e.humidity="humidity",e.audioVolumes="audioVolumes",e.recordingActive="recordingActive",e.ptzCapabilities="ptzCapabilities",e.lockState="lockState",e.entryOpen="entryOpen",e.batteryLevel="batteryLevel",e.chargeState="chargeState",e.online="online",e.fromMimeType="fromMimeType",e.toMimeType="toMimeType",e.converters="converters",e.binaryState="binaryState",e.tampered="tampered",e.sleeping="sleeping",e.powerDetected="powerDetected",e.audioDetected="audioDetected",e.motionDetected="motionDetected",e.ambientLight="ambientLight",e.occupied="occupied",e.flooded="flooded",e.ultraviolet="ultraviolet",e.luminance="luminance",e.position="position",e.securitySystemState="securitySystemState",e.pm10Density="pm10Density",e.pm25Density="pm25Density",e.vocDensity="vocDensity",e.noxDensity="noxDensity",e.co2ppm="co2ppm",e.airQuality="airQuality",e.airPurifierState="airPurifierState",e.filterChangeIndication="filterChangeIndication",e.filterLifeLevel="filterLifeLevel",e.humiditySetting="humiditySetting",e.fan="fan",e.applicationInfo="applicationInfo",e.systemDevice="systemDevice"}(n||(t.ScryptedInterfaceProperty=n={})),function(e){e.listen="listen",e.probe="probe",e.setMixins="setMixins",e.setName="setName",e.setRoom="setRoom",e.setType="setType",e.getPluginJson="getPluginJson",e.turnOff="turnOff",e.turnOn="turnOn",e.setBrightness="setBrightness",e.getTemperatureMaxK="getTemperatureMaxK",e.getTemperatureMinK="getTemperatureMinK",e.setColorTemperature="setColorTemperature",e.setRgb="setRgb",e.setHsv="setHsv",e.pressButton="pressButton",e.sendNotification="sendNotification",e.start="start",e.stop="stop",e.pause="pause",e.resume="resume",e.dock="dock",e.setTemperature="setTemperature",e.setTemperatureUnit="setTemperatureUnit",e.getPictureOptions="getPictureOptions",e.takePicture="takePicture",e.getAudioStream="getAudioStream",e.setAudioVolumes="setAudioVolumes",e.startDisplay="startDisplay",e.stopDisplay="stopDisplay",e.getVideoStream="getVideoStream",e.getVideoStreamOptions="getVideoStreamOptions",e.getPrivacyMasks="getPrivacyMasks",e.setPrivacyMasks="setPrivacyMasks",e.getVideoTextOverlays="getVideoTextOverlays",e.setVideoTextOverlay="setVideoTextOverlay",e.getRecordingStream="getRecordingStream",e.getRecordingStreamCurrentTime="getRecordingStreamCurrentTime",e.getRecordingStreamOptions="getRecordingStreamOptions",e.getRecordingStreamThumbnail="getRecordingStreamThumbnail",e.deleteRecordingStream="deleteRecordingStream",e.setRecordingActive="setRecordingActive",e.ptzCommand="ptzCommand",e.getRecordedEvents="getRecordedEvents",e.getVideoClip="getVideoClip",e.getVideoClips="getVideoClips",e.getVideoClipThumbnail="getVideoClipThumbnail",e.removeVideoClips="removeVideoClips",e.setVideoStreamOptions="setVideoStreamOptions",e.startIntercom="startIntercom",e.stopIntercom="stopIntercom",e.lock="lock",e.unlock="unlock",e.addPassword="addPassword",e.getPasswords="getPasswords",e.removePassword="removePassword",e.activate="activate",e.deactivate="deactivate",e.isReversible="isReversible",e.closeEntry="closeEntry",e.openEntry="openEntry",e.getDevice="getDevice",e.releaseDevice="releaseDevice",e.adoptDevice="adoptDevice",e.discoverDevices="discoverDevices",e.createDevice="createDevice",e.getCreateDeviceSettings="getCreateDeviceSettings",e.reboot="reboot",e.getRefreshFrequency="getRefreshFrequency",e.refresh="refresh",e.getMediaStatus="getMediaStatus",e.load="load",e.seek="seek",e.skipNext="skipNext",e.skipPrevious="skipPrevious",e.convert="convert",e.convertMedia="convertMedia",e.getSettings="getSettings",e.putSetting="putSetting",e.armSecuritySystem="armSecuritySystem",e.disarmSecuritySystem="disarmSecuritySystem",e.setAirPurifierState="setAirPurifierState",e.getReadmeMarkdown="getReadmeMarkdown",e.getOauthUrl="getOauthUrl",e.onOauthCallback="onOauthCallback",e.canMixin="canMixin",e.getMixin="getMixin",e.releaseMixin="releaseMixin",e.onRequest="onRequest",e.onConnection="onConnection",e.onPush="onPush",e.run="run",e.eval="eval",e.loadScripts="loadScripts",e.saveScript="saveScript",e.forkInterface="forkInterface",e.trackObjects="trackObjects",e.getDetectionInput="getDetectionInput",e.getObjectTypes="getObjectTypes",e.detectObjects="detectObjects",e.generateObjectDetections="generateObjectDetections",e.getDetectionModel="getDetectionModel",e.setHumidity="setHumidity",e.setFan="setFan",e.startRTCSignalingSession="startRTCSignalingSession",e.createRTCSignalingSession="createRTCSignalingSession",e.getScryptedUserAccessControl="getScryptedUserAccessControl",e.generateVideoFrames="generateVideoFrames",e.connectStream="connectStream",e.getTTYSettings="getTTYSettings"}(i||(t.ScryptedInterfaceMethod=i={})),t.ScryptedInterfaceDescriptors={ScryptedDevice:{name:"ScryptedDevice",methods:["listen","probe","setMixins","setName","setRoom","setType"],properties:["id","info","interfaces","mixins","name","nativeId","pluginId","providedInterfaces","providedName","providedRoom","providedType","providerId","room","type"]},ScryptedPlugin:{name:"ScryptedPlugin",methods:["getPluginJson"],properties:[]},ScryptedPluginRuntime:{name:"ScryptedPluginRuntime",methods:[],properties:["scryptedRuntimeArguments"]},OnOff:{name:"OnOff",methods:["turnOff","turnOn"],properties:["on"]},Brightness:{name:"Brightness",methods:["setBrightness"],properties:["brightness"]},ColorSettingTemperature:{name:"ColorSettingTemperature",methods:["getTemperatureMaxK","getTemperatureMinK","setColorTemperature"],properties:["colorTemperature"]},ColorSettingRgb:{name:"ColorSettingRgb",methods:["setRgb"],properties:["rgb"]},ColorSettingHsv:{name:"ColorSettingHsv",methods:["setHsv"],properties:["hsv"]},Buttons:{name:"Buttons",methods:[],properties:["buttons"]},PressButtons:{name:"PressButtons",methods:["pressButton"],properties:[]},Sensors:{name:"Sensors",methods:[],properties:["sensors"]},Notifier:{name:"Notifier",methods:["sendNotification"],properties:[]},StartStop:{name:"StartStop",methods:["start","stop"],properties:["running"]},Pause:{name:"Pause",methods:["pause","resume"],properties:["paused"]},Dock:{name:"Dock",methods:["dock"],properties:["docked"]},TemperatureSetting:{name:"TemperatureSetting",methods:["setTemperature"],properties:["temperatureSetting"]},Thermometer:{name:"Thermometer",methods:["setTemperatureUnit"],properties:["temperature","temperatureUnit"]},HumiditySensor:{name:"HumiditySensor",methods:[],properties:["humidity"]},Camera:{name:"Camera",methods:["getPictureOptions","takePicture"],properties:[]},Microphone:{name:"Microphone",methods:["getAudioStream"],properties:[]},AudioVolumeControl:{name:"AudioVolumeControl",methods:["setAudioVolumes"],properties:["audioVolumes"]},Display:{name:"Display",methods:["startDisplay","stopDisplay"],properties:[]},VideoCamera:{name:"VideoCamera",methods:["getVideoStream","getVideoStreamOptions"],properties:[]},VideoCameraMask:{name:"VideoCameraMask",methods:["getPrivacyMasks","setPrivacyMasks"],properties:[]},VideoTextOverlays:{name:"VideoTextOverlays",methods:["getVideoTextOverlays","setVideoTextOverlay"],properties:[]},VideoRecorder:{name:"VideoRecorder",methods:["getRecordingStream","getRecordingStreamCurrentTime","getRecordingStreamOptions","getRecordingStreamThumbnail"],properties:["recordingActive"]},VideoRecorderManagement:{name:"VideoRecorderManagement",methods:["deleteRecordingStream","setRecordingActive"],properties:[]},PanTiltZoom:{name:"PanTiltZoom",methods:["ptzCommand"],properties:["ptzCapabilities"]},EventRecorder:{name:"EventRecorder",methods:["getRecordedEvents"],properties:[]},VideoClips:{name:"VideoClips",methods:["getVideoClip","getVideoClips","getVideoClipThumbnail","removeVideoClips"],properties:[]},VideoCameraConfiguration:{name:"VideoCameraConfiguration",methods:["setVideoStreamOptions"],properties:[]},Intercom:{name:"Intercom",methods:["startIntercom","stopIntercom"],properties:[]},Lock:{name:"Lock",methods:["lock","unlock"],properties:["lockState"]},PasswordStore:{name:"PasswordStore",methods:["addPassword","getPasswords","removePassword"],properties:[]},Scene:{name:"Scene",methods:["activate","deactivate","isReversible"],properties:[]},Entry:{name:"Entry",methods:["closeEntry","openEntry"],properties:[]},EntrySensor:{name:"EntrySensor",methods:[],properties:["entryOpen"]},DeviceProvider:{name:"DeviceProvider",methods:["getDevice","releaseDevice"],properties:[]},DeviceDiscovery:{name:"DeviceDiscovery",methods:["adoptDevice","discoverDevices"],properties:[]},DeviceCreator:{name:"DeviceCreator",methods:["createDevice","getCreateDeviceSettings"],properties:[]},Battery:{name:"Battery",methods:[],properties:["batteryLevel"]},Charger:{name:"Charger",methods:[],properties:["chargeState"]},Reboot:{name:"Reboot",methods:["reboot"],properties:[]},Refresh:{name:"Refresh",methods:["getRefreshFrequency","refresh"],properties:[]},MediaPlayer:{name:"MediaPlayer",methods:["getMediaStatus","load","seek","skipNext","skipPrevious"],properties:[]},Online:{name:"Online",methods:[],properties:["online"]},BufferConverter:{name:"BufferConverter",methods:["convert"],properties:["fromMimeType","toMimeType"]},MediaConverter:{name:"MediaConverter",methods:["convertMedia"],properties:["converters"]},Settings:{name:"Settings",methods:["getSettings","putSetting"],properties:[]},BinarySensor:{name:"BinarySensor",methods:[],properties:["binaryState"]},TamperSensor:{name:"TamperSensor",methods:[],properties:["tampered"]},Sleep:{name:"Sleep",methods:[],properties:["sleeping"]},PowerSensor:{name:"PowerSensor",methods:[],properties:["powerDetected"]},AudioSensor:{name:"AudioSensor",methods:[],properties:["audioDetected"]},MotionSensor:{name:"MotionSensor",methods:[],properties:["motionDetected"]},AmbientLightSensor:{name:"AmbientLightSensor",methods:[],properties:["ambientLight"]},OccupancySensor:{name:"OccupancySensor",methods:[],properties:["occupied"]},FloodSensor:{name:"FloodSensor",methods:[],properties:["flooded"]},UltravioletSensor:{name:"UltravioletSensor",methods:[],properties:["ultraviolet"]},LuminanceSensor:{name:"LuminanceSensor",methods:[],properties:["luminance"]},PositionSensor:{name:"PositionSensor",methods:[],properties:["position"]},SecuritySystem:{name:"SecuritySystem",methods:["armSecuritySystem","disarmSecuritySystem"],properties:["securitySystemState"]},PM10Sensor:{name:"PM10Sensor",methods:[],properties:["pm10Density"]},PM25Sensor:{name:"PM25Sensor",methods:[],properties:["pm25Density"]},VOCSensor:{name:"VOCSensor",methods:[],properties:["vocDensity"]},NOXSensor:{name:"NOXSensor",methods:[],properties:["noxDensity"]},CO2Sensor:{name:"CO2Sensor",methods:[],properties:["co2ppm"]},AirQualitySensor:{name:"AirQualitySensor",methods:[],properties:["airQuality"]},AirPurifier:{name:"AirPurifier",methods:["setAirPurifierState"],properties:["airPurifierState"]},FilterMaintenance:{name:"FilterMaintenance",methods:[],properties:["filterChangeIndication","filterLifeLevel"]},Readme:{name:"Readme",methods:["getReadmeMarkdown"],properties:[]},OauthClient:{name:"OauthClient",methods:["getOauthUrl","onOauthCallback"],properties:[]},MixinProvider:{name:"MixinProvider",methods:["canMixin","getMixin","releaseMixin"],properties:[]},HttpRequestHandler:{name:"HttpRequestHandler",methods:["onRequest"],properties:[]},EngineIOHandler:{name:"EngineIOHandler",methods:["onConnection"],properties:[]},PushHandler:{name:"PushHandler",methods:["onPush"],properties:[]},Program:{name:"Program",methods:["run"],properties:[]},Scriptable:{name:"Scriptable",methods:["eval","loadScripts","saveScript"],properties:[]},ClusterForkInterface:{name:"ClusterForkInterface",methods:["forkInterface"],properties:[]},ObjectTracker:{name:"ObjectTracker",methods:["trackObjects"],properties:[]},ObjectDetector:{name:"ObjectDetector",methods:["getDetectionInput","getObjectTypes"],properties:[]},ObjectDetection:{name:"ObjectDetection",methods:["detectObjects","generateObjectDetections","getDetectionModel"],properties:[]},ObjectDetectionPreview:{name:"ObjectDetectionPreview",methods:[],properties:[]},ObjectDetectionGenerator:{name:"ObjectDetectionGenerator",methods:[],properties:[]},HumiditySetting:{name:"HumiditySetting",methods:["setHumidity"],properties:["humiditySetting"]},Fan:{name:"Fan",methods:["setFan"],properties:["fan"]},RTCSignalingChannel:{name:"RTCSignalingChannel",methods:["startRTCSignalingSession"],properties:[]},RTCSignalingClient:{name:"RTCSignalingClient",methods:["createRTCSignalingSession"],properties:[]},LauncherApplication:{name:"LauncherApplication",methods:[],properties:["applicationInfo"]},ScryptedUser:{name:"ScryptedUser",methods:["getScryptedUserAccessControl"],properties:[]},VideoFrameGenerator:{name:"VideoFrameGenerator",methods:["generateVideoFrames"],properties:[]},StreamService:{name:"StreamService",methods:["connectStream"],properties:[]},TTY:{name:"TTY",methods:[],properties:[]},TTYSettings:{name:"TTYSettings",methods:["getTTYSettings"],properties:[]},ScryptedSystemDevice:{name:"ScryptedSystemDevice",methods:[],properties:["systemDevice"]},ScryptedDeviceCreator:{name:"ScryptedDeviceCreator",methods:[],properties:[]},ScryptedSettings:{name:"ScryptedSettings",methods:[],properties:[]}},function(e){e.Builtin="Builtin",e.Internal="Internal",e.Camera="Camera",e.Fan="Fan",e.Light="Light",e.Switch="Switch",e.Outlet="Outlet",e.Sensor="Sensor",e.Scene="Scene",e.Program="Program",e.Automation="Automation",e.Vacuum="Vacuum",e.Notifier="Notifier",e.Thermostat="Thermostat",e.Lock="Lock",e.PasswordControl="PasswordControl",e.Display="Display",e.SmartDisplay="SmartDisplay",e.Speaker="Speaker",e.SmartSpeaker="SmartSpeaker",e.Event="Event",e.Entry="Entry",e.Garage="Garage",e.DeviceProvider="DeviceProvider",e.DataSource="DataSource",e.API="API",e.Doorbell="Doorbell",e.Irrigation="Irrigation",e.Valve="Valve",e.Person="Person",e.SecuritySystem="SecuritySystem",e.WindowCovering="WindowCovering",e.Siren="Siren",e.AirPurifier="AirPurifier",e.Internet="Internet",e.Network="Network",e.Bridge="Bridge",e.Unknown="Unknown"}(r||(t.ScryptedDeviceType=r={})),function(e){e.Humidify="Humidify",e.Dehumidify="Dehumidify",e.Auto="Auto",e.Off="Off"}(o||(t.HumidityMode=o={})),function(e){e.Auto="Auto",e.Manual="Manual"}(a||(t.FanMode=a={})),function(e){e.C="C",e.F="F"}(s||(t.TemperatureUnit=s={})),function(e){e.Off="Off",e.Cool="Cool",e.Heat="Heat",e.HeatCool="HeatCool",e.Auto="Auto",e.FanOnly="FanOnly",e.Purifier="Purifier",e.Eco="Eco",e.Dry="Dry",e.On="On"}(c||(t.ThermostatMode=c={})),function(e){e.Absolute="Absolute",e.Relative="Relative",e.Continuous="Continuous",e.Preset="Preset",e.Home="Home"}(d||(t.PanTiltZoomMovement=d={})),function(e){e.Locked="Locked",e.Unlocked="Unlocked",e.Jammed="Jammed"}(l||(t.LockState=l={})),function(e){e.Trickle="trickle",e.Charging="charging",e.NotCharging="not-charging"}(p||(t.ChargeState=p={})),function(e){e.Inactive="Inactive",e.Idle="Idle",e.Active="Active",e.ActiveNightMode="ActiveNightMode"}(m||(t.AirPurifierStatus=m={})),function(e){e.Manual="Manual",e.Automatic="Automatic"}(g||(t.AirPurifierMode=g={})),function(e){e.Unknown="Unknown",e.Excellent="Excellent",e.Good="Good",e.Fair="Fair",e.Inferior="Inferior",e.Poor="Poor"}(u||(t.AirQuality=u={})),function(e){e.Disarmed="Disarmed",e.HomeArmed="HomeArmed",e.AwayArmed="AwayArmed",e.NightArmed="NightArmed"}(h||(t.SecuritySystemMode=h={})),function(e){e.Sensor="Sensor",e.Occupied="Occupied",e.Time="Time",e.Error="Error"}(v||(t.SecuritySystemObstruction=v={})),function(e){e.Idle="Idle",e.Playing="Playing",e.Paused="Paused",e.Buffering="Buffering"}(b||(t.MediaPlayerState=b={})),function(e){e.ScryptedDevice="ScryptedDevice",e.ScryptedPlugin="ScryptedPlugin",e.ScryptedPluginRuntime="ScryptedPluginRuntime",e.OnOff="OnOff",e.Brightness="Brightness",e.ColorSettingTemperature="ColorSettingTemperature",e.ColorSettingRgb="ColorSettingRgb",e.ColorSettingHsv="ColorSettingHsv",e.Buttons="Buttons",e.PressButtons="PressButtons",e.Sensors="Sensors",e.Notifier="Notifier",e.StartStop="StartStop",e.Pause="Pause",e.Dock="Dock",e.TemperatureSetting="TemperatureSetting",e.Thermometer="Thermometer",e.HumiditySensor="HumiditySensor",e.Camera="Camera",e.Microphone="Microphone",e.AudioVolumeControl="AudioVolumeControl",e.Display="Display",e.VideoCamera="VideoCamera",e.VideoCameraMask="VideoCameraMask",e.VideoTextOverlays="VideoTextOverlays",e.VideoRecorder="VideoRecorder",e.VideoRecorderManagement="VideoRecorderManagement",e.PanTiltZoom="PanTiltZoom",e.EventRecorder="EventRecorder",e.VideoClips="VideoClips",e.VideoCameraConfiguration="VideoCameraConfiguration",e.Intercom="Intercom",e.Lock="Lock",e.PasswordStore="PasswordStore",e.Scene="Scene",e.Entry="Entry",e.EntrySensor="EntrySensor",e.DeviceProvider="DeviceProvider",e.DeviceDiscovery="DeviceDiscovery",e.DeviceCreator="DeviceCreator",e.Battery="Battery",e.Charger="Charger",e.Reboot="Reboot",e.Refresh="Refresh",e.MediaPlayer="MediaPlayer",e.Online="Online",e.BufferConverter="BufferConverter",e.MediaConverter="MediaConverter",e.Settings="Settings",e.BinarySensor="BinarySensor",e.TamperSensor="TamperSensor",e.Sleep="Sleep",e.PowerSensor="PowerSensor",e.AudioSensor="AudioSensor",e.MotionSensor="MotionSensor",e.AmbientLightSensor="AmbientLightSensor",e.OccupancySensor="OccupancySensor",e.FloodSensor="FloodSensor",e.UltravioletSensor="UltravioletSensor",e.LuminanceSensor="LuminanceSensor",e.PositionSensor="PositionSensor",e.SecuritySystem="SecuritySystem",e.PM10Sensor="PM10Sensor",e.PM25Sensor="PM25Sensor",e.VOCSensor="VOCSensor",e.NOXSensor="NOXSensor",e.CO2Sensor="CO2Sensor",e.AirQualitySensor="AirQualitySensor",e.AirPurifier="AirPurifier",e.FilterMaintenance="FilterMaintenance",e.Readme="Readme",e.OauthClient="OauthClient",e.MixinProvider="MixinProvider",e.HttpRequestHandler="HttpRequestHandler",e.EngineIOHandler="EngineIOHandler",e.PushHandler="PushHandler",e.Program="Program",e.Scriptable="Scriptable",e.ClusterForkInterface="ClusterForkInterface",e.ObjectTracker="ObjectTracker",e.ObjectDetector="ObjectDetector",e.ObjectDetection="ObjectDetection",e.ObjectDetectionPreview="ObjectDetectionPreview",e.ObjectDetectionGenerator="ObjectDetectionGenerator",e.HumiditySetting="HumiditySetting",e.Fan="Fan",e.RTCSignalingChannel="RTCSignalingChannel",e.RTCSignalingClient="RTCSignalingClient",e.LauncherApplication="LauncherApplication",e.ScryptedUser="ScryptedUser",e.VideoFrameGenerator="VideoFrameGenerator",e.StreamService="StreamService",e.TTY="TTY",e.TTYSettings="TTYSettings",e.ScryptedSystemDevice="ScryptedSystemDevice",e.ScryptedDeviceCreator="ScryptedDeviceCreator",e.ScryptedSettings="ScryptedSettings"}(y||(t.ScryptedInterface=y={})),function(e){e.Url="text/x-uri",e.InsecureLocalUrl="text/x-insecure-local-uri",e.LocalUrl="text/x-local-uri",e.ServerId="text/x-server-id",e.PushEndpoint="text/x-push-endpoint",e.SchemePrefix="x-scrypted/x-scrypted-scheme-",e.MediaStreamUrl="text/x-media-url",e.MediaObject="x-scrypted/x-scrypted-media-object",e.RequestMediaObject="x-scrypted/x-scrypted-request-media-object",e.RequestMediaStream="x-scrypted/x-scrypted-request-stream",e.MediaStreamFeedback="x-scrypted/x-media-stream-feedback",e.FFmpegInput="x-scrypted/x-ffmpeg-input",e.FFmpegTranscodeStream="x-scrypted/x-ffmpeg-transcode-stream",e.RTCSignalingChannel="x-scrypted/x-scrypted-rtc-signaling-channel",e.RTCSignalingSession="x-scrypted/x-scrypted-rtc-signaling-session",e.RTCConnectionManagement="x-scrypted/x-scrypted-rtc-connection-management",e.Image="x-scrypted/x-scrypted-image"}(f||(t.ScryptedMimeTypes=f={}))},927(e,t,n){"use strict";var i,r=this&&this.__createBinding||(Object.create?function(e,t,n,i){void 0===i&&(i=n);var r=Object.getOwnPropertyDescriptor(t,n);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,i,r)}:function(e,t,n,i){void 0===i&&(i=n),e[i]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),a=this&&this.__importStar||(i=function(e){return i=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},i(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=i(e),a=0;a<n.length;a++)"default"!==n[a]&&r(t,e,n[a]);return o(t,e),t});Object.defineProperty(t,"__esModule",{value:!0});const s=a(n(562)),{systemManager:c,deviceManager:d,mediaManager:l}=s.default,p=new Set(["person","cat","dog","animal","bird","face","vehicle","car","truck","bus","motorcycle","bicycle","plate","package"]),m=["disabled","1 per minute","1 per 10 seconds","every detection"],g={disabled:1/0,"1 per minute":6e4,"1 per 10 seconds":1e4,"every detection":0};class u extends s.ScryptedDeviceBase{constructor(e){super(e),this.lastCapture=new Map,this.captures=new Map,this.images=new Map,this.listeners=[],this.loadState(),this.registerListeners()}loadState(){try{const e=this.storage.getItem("captures");if(e){const t=JSON.parse(e);for(const e of t)this.captures.set(e.id,e);this.console.log(`Loaded ${this.captures.size} captures from storage.`)}}catch(e){this.console.warn("Could not load captures from storage:",e)}for(const[e]of this.captures){const t=this.storage.getItem(`img:${e}`);t&&this.images.set(e,Buffer.from(t,"base64"))}}saveCaptures(){this.storage.setItem("captures",JSON.stringify([...this.captures.values()]))}saveImage(e,t){this.storage.setItem(`img:${e}`,t.toString("base64"))}deleteCapture(e){this.storage.getItem(`img:${e}`)&&this.storage.removeItem(`img:${e}`),this.captures.delete(e),this.images.delete(e),this.saveCaptures()}async getSettings(){const e=Object.keys(c.getSystemState()).map(e=>c.getDeviceById(e)).filter(e=>e&&(e.type===s.ScryptedDeviceType.Camera||e.type===s.ScryptedDeviceType.Doorbell)&&e.interfaces?.includes(s.ScryptedInterface.ObjectDetector));let t;try{t=`${await s.default.endpointManager.getAuthenticatedPath()}endpoint/scrypted-detection-trainer/public/`}catch{t="/endpoint/scrypted-detection-trainer/public/"}const n=[{key:"info",title:"Detection Trainer",description:`${this.captures.size} captures stored (${[...this.captures.values()].filter(e=>!e.reviewed).length} pending review, ${[...this.captures.values()].filter(e=>e.reviewed&&"discard"!==e.label).length} labeled).`,readonly:!0,value:""},{key:"open_ui",title:"Review UI",description:"Open the detection review and labeling interface.",type:"html",readonly:!0,value:`<a href="${t}" target="_blank" style="display:inline-block;padding:8px 16px;background:#1a4d8a;color:#fff;border-radius:6px;text-decoration:none;font-size:13px;">Open Review UI β†—</a>`}];for(const t of e){const e=`rate:${t.id}`;n.push({key:e,title:t.name,group:"Capture Rate per Camera",description:"How often to capture detections from this camera.",value:this.storage.getItem(e)||"1 per minute",choices:[...m]})}return n}async putSetting(e,t){"open_ui"!==e&&"ui_link"!==e&&"info"!==e&&(this.storage.setItem(e,t),e.startsWith("rate:")&&this.registerListeners())}registerListeners(){for(const e of this.listeners)e();this.listeners=[];const e=Object.keys(c.getSystemState()).map(e=>c.getDeviceById(e)).filter(e=>e&&(e.type===s.ScryptedDeviceType.Camera||e.type===s.ScryptedDeviceType.Doorbell)&&e.interfaces?.includes(s.ScryptedInterface.ObjectDetector));for(const t of e){const e=`rate:${t.id}`,n=this.storage.getItem(e)||"1 per minute";if("disabled"===n)continue;const i=t.listen(s.ScryptedInterface.ObjectDetector,async(e,i,r)=>{await this.onDetection(t.id,t.name,r,g[n])});this.listeners.push(()=>i.removeListener())}this.console.log(`Listening to ${this.listeners.length} camera(s).`)}async onDetection(e,t,n,i){if(!n?.detections?.length||!n.inputDimensions)return;const r=Date.now();if(r-(this.lastCapture.get(e)||0)<i)return;const o=n.detections.filter(e=>e.className&&p.has(e.className.toLowerCase())&&e.boundingBox);if(!o.length)return;const a=o.sort((e,t)=>(t.score||0)-(e.score||0))[0];if(this.captures.size>=2e3){const e=[...this.captures.values()].filter(e=>!e.reviewed).sort((e,t)=>e.timestamp-t.timestamp)[0];if(!e)return;this.deleteCapture(e.id)}let s;this.lastCapture.set(e,r);try{if(n.detectionId){const t=c.getDeviceById(e),i=await t.getDetectionInput(n.detectionId);s=await l.convertMediaObjectToBuffer(i,"image/jpeg")}}catch(e){this.console.warn(`Could not get detection image for ${t}:`,e)}if(!s)return;const d=`${r}-${Math.random().toString(36).slice(2,8)}`,m={id:d,cameraId:e,cameraName:t,timestamp:r,detectedClass:a.className,score:a.score||0,boundingBox:a.boundingBox,inputDimensions:n.inputDimensions,detectionId:n.detectionId,reviewed:!1};this.captures.set(d,m),this.images.set(d,s),this.saveImage(d,s),this.saveCaptures(),this.console.log(`Captured ${a.className} (${Math.round(100*(a.score||0))}%) from ${t} [${this.captures.size} total]`)}async onRequest(e,t){if(!e.username)return void t.send("Unauthorized",{code:401,headers:{"WWW-Authenticate":'Basic realm="Detection Trainer"'}});const n=new URL(e.url,"http://localhost").pathname.replace(e.rootPath,"");if(n.startsWith("/img/")){const e=n.slice(5),i=this.images.get(e);return i?t.send(i,{headers:{"Content-Type":"image/jpeg","Cache-Control":"max-age=3600"}}):t.send("Not found",{code:404})}if("/api/label"===n&&e.body){const n=e.body,i=JSON.parse("string"==typeof n?n:Buffer.isBuffer(n)?n.toString():String(n)),r=this.captures.get(i.id);return r?(r.label=i.label,r.reviewed=!0,"discard"===i.label?this.deleteCapture(i.id):(this.captures.set(i.id,r),this.saveCaptures()),t.send(JSON.stringify({ok:!0}),{headers:{"Content-Type":"application/json"}})):t.send("Not found",{code:404})}if("/api/pending"===n){const e=[...this.captures.values()].filter(e=>!e.reviewed).sort((e,t)=>t.timestamp-e.timestamp).slice(0,50);return t.send(JSON.stringify(e),{headers:{"Content-Type":"application/json"}})}if("/api/stats"===n){const e=[...this.captures.values()],n={total:e.length,pending:e.filter(e=>!e.reviewed).length,labeled:e.filter(e=>e.reviewed&&"discard"!==e.label).length,byLabel:{},byCamera:{},byDetectedClass:{}};for(const t of e)t.label&&(n.byLabel[t.label]=(n.byLabel[t.label]||0)+1),n.byCamera[t.cameraName]=(n.byCamera[t.cameraName]||0)+1,n.byDetectedClass[t.detectedClass]=(n.byDetectedClass[t.detectedClass]||0)+1;return t.send(JSON.stringify(n),{headers:{"Content-Type":"application/json"}})}if("/api/export"===n){const e=[...this.captures.values()].filter(e=>e.reviewed&&e.label&&"discard"!==e.label);if(!e.length)return t.send(JSON.stringify({error:"No labeled data yet"}),{headers:{"Content-Type":"application/json"},code:400});const n={person:0,animal:1,face:2,vehicle:3,plate:4,package:5,discard:-1},i=[];for(const t of e){const e=this.images.get(t.id);if(!e)continue;const r=`${t.id}`;i.push({filename:`images/${r}.jpg`,content:e.toString("base64"),encoding:"base64"});const[o,a,s,c]=t.boundingBox,[d,l]=t.inputDimensions,p=(o+s/2)/d,m=(a+c/2)/l,g=s/d,u=c/l,h=`${n[t.label]} ${p.toFixed(6)} ${m.toFixed(6)} ${g.toFixed(6)} ${u.toFixed(6)}\n`;i.push({filename:`labels/${r}.txt`,content:h,encoding:"utf8"})}const r=["path: dataset","train: images","val: images","","nc: 6","names: ['person', 'animal', 'face', 'vehicle', 'plate', 'package']","","# Generated by Scrypted Detection Trainer",`# ${e.length} labeled samples`].join("\n");return i.push({filename:"data.yaml",content:r,encoding:"utf8"}),t.send(JSON.stringify({files:i,count:e.length}),{headers:{"Content-Type":"application/json"}})}if("/"===n||""===n||"/index.html"===n)return t.send(this.renderUI(),{headers:{"Content-Type":"text/html"}});t.send("Not found",{code:404})}renderUI(){return"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Detection Trainer</title>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js\"><\/script>\n<style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f0f0f; color: #e8e8e8; min-height: 100vh; }\n header { background: #1a1a1a; border-bottom: 1px solid #333; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }\n header h1 { font-size: 18px; font-weight: 600; color: #fff; }\n .stats { display: flex; gap: 20px; font-size: 13px; color: #aaa; }\n .stat span { color: #fff; font-weight: 600; }\n .container { max-width: 1000px; margin: 0 auto; padding: 24px; }\n .card { background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 12px; overflow: hidden; margin-bottom: 24px; }\n .card-header { padding: 16px 20px; border-bottom: 1px solid #2a2a2a; display: flex; align-items: center; justify-content: space-between; }\n .card-header h2 { font-size: 15px; font-weight: 600; }\n .badge { background: #333; color: #aaa; font-size: 12px; padding: 2px 8px; border-radius: 20px; }\n .badge.orange { background: #3d2a00; color: #f90; }\n .badge.green { background: #0d2d0d; color: #4c4; }\n\n /* Detection card */\n .detection { display: grid; grid-template-columns: 420px 1fr; gap: 0; border-bottom: 1px solid #222; }\n .detection:last-child { border-bottom: none; }\n .detection-imgs { display: flex; gap: 8px; padding: 10px; background: #111; align-items: center; }\n .img-panel { display: flex; flex-direction: column; align-items: center; gap: 4px; }\n .img-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: .5px; }\n .det-canvas { border-radius: 6px; display: block; }\n .det-class-badge { display: inline-block; background: #3d2a00; color: #f90; font-size: 11px; padding: 2px 8px; border-radius: 4px; }\n .detection-info { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }\n .detection-meta { font-size: 12px; color: #888; display: flex; flex-wrap: wrap; gap: 10px; }\n .detection-meta strong { color: #ccc; }\n .label-buttons { display: flex; flex-wrap: wrap; gap: 8px; }\n .label-btn { padding: 7px 14px; border-radius: 8px; border: 1px solid #444; background: #222; color: #ccc; cursor: pointer; font-size: 13px; transition: all .15s; }\n .label-btn:hover { border-color: #666; background: #2a2a2a; color: #fff; }\n .label-btn.person { border-color: #2a6; color: #4d9; }\n .label-btn.person:hover { background: #0d2a1a; }\n .label-btn.animal { border-color: #a63; color: #d85; }\n .label-btn.animal:hover { background: #2a1a0d; }\n .label-btn.face { border-color: #49c; color: #6be; }\n .label-btn.face:hover { background: #0d1a2a; }\n .label-btn.vehicle { border-color: #76b; color: #99d; }\n .label-btn.vehicle:hover { background: #1a1a2a; }\n .label-btn.discard { border-color: #622; color: #a44; }\n .label-btn.discard:hover { background: #2a0d0d; }\n .detection.labeled { opacity: 0.4; pointer-events: none; }\n .labeled-tag { font-size: 11px; color: #4d9; background: #0d2a1a; border: 1px solid #2a6; padding: 2px 8px; border-radius: 4px; }\n\n /* Empty state */\n .empty { padding: 48px; text-align: center; color: #555; }\n .empty .icon { font-size: 48px; margin-bottom: 12px; }\n\n /* Export section */\n .export-btn { padding: 10px 20px; background: #1a4d8a; border: none; border-radius: 8px; color: #fff; cursor: pointer; font-size: 14px; font-weight: 500; }\n .export-btn:hover { background: #1e5ca0; }\n .export-btn:disabled { background: #333; color: #666; cursor: not-allowed; }\n .export-info { font-size: 13px; color: #888; padding: 12px 20px; }\n\n /* Progress bar */\n .progress { height: 4px; background: #222; border-radius: 2px; overflow: hidden; margin-top: 8px; }\n .progress-bar { height: 100%; background: #1a6; border-radius: 2px; transition: width .3s; }\n\n .toast { position: fixed; bottom: 24px; right: 24px; background: #1a3; color: #fff; padding: 10px 18px; border-radius: 8px; font-size: 13px; opacity: 0; transition: opacity .3s; pointer-events: none; }\n .toast.show { opacity: 1; }\n\n .tab-bar { display: flex; gap: 2px; padding: 12px 20px 0; border-bottom: 1px solid #2a2a2a; }\n .tab { padding: 8px 14px; font-size: 13px; color: #888; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }\n .tab.active { color: #fff; border-bottom-color: #4a9; }\n .tab-content { padding: 20px; }\n .tab-panel { display: none; }\n .tab-panel.active { display: block; }\n\n .breakdown-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }\n .breakdown-item { background: #222; border-radius: 8px; padding: 12px; }\n .breakdown-item .label { font-size: 12px; color: #888; margin-bottom: 4px; }\n .breakdown-item .value { font-size: 20px; font-weight: 600; color: #fff; }\n\n /* Lightbox */\n .lightbox { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.92); z-index: 1000; align-items: center; justify-content: center; flex-direction: column; gap: 12px; }\n .lightbox.open { display: flex; }\n .lightbox canvas { max-width: 92vw; max-height: 82vh; border-radius: 8px; cursor: zoom-out; }\n .lightbox-meta { color: #aaa; font-size: 13px; text-align: center; }\n .lightbox-close { position: absolute; top: 16px; right: 20px; font-size: 28px; color: #888; cursor: pointer; line-height: 1; }\n .lightbox-close:hover { color: #fff; }\n .det-canvas { cursor: zoom-in; }\n</style>\n</head>\n<body>\n<header>\n <h1>🎯 Detection Trainer</h1>\n <div class=\"stats\">\n <div>Pending <span id=\"stat-pending\">β€”</span></div>\n <div>Labeled <span id=\"stat-labeled\">β€”</span></div>\n <div>Total <span id=\"stat-total\">β€”</span></div>\n </div>\n</header>\n<div class=\"container\">\n\n <div class=\"card\">\n <div class=\"tab-bar\">\n <div class=\"tab active\" onclick=\"showTab('review')\">Review</div>\n <div class=\"tab\" onclick=\"showTab('stats')\">Stats</div>\n <div class=\"tab\" onclick=\"showTab('export')\">Export Dataset</div>\n </div>\n\n \x3c!-- Review tab --\x3e\n <div class=\"tab-panel active\" id=\"tab-review\">\n <div id=\"detections-list\"></div>\n </div>\n\n \x3c!-- Stats tab --\x3e\n <div class=\"tab-panel\" id=\"tab-stats\">\n <div class=\"tab-content\">\n <p style=\"font-size:13px;color:#888;margin-bottom:16px;\">Breakdown of captured and labeled detections.</p>\n <h3 style=\"font-size:13px;color:#aaa;margin-bottom:10px;\">By Detected Class (what the model said)</h3>\n <div class=\"breakdown-grid\" id=\"stats-detected\"></div>\n <h3 style=\"font-size:13px;color:#aaa;margin:20px 0 10px;\">By Corrected Label (what you said)</h3>\n <div class=\"breakdown-grid\" id=\"stats-label\"></div>\n <h3 style=\"font-size:13px;color:#aaa;margin:20px 0 10px;\">By Camera</h3>\n <div class=\"breakdown-grid\" id=\"stats-camera\"></div>\n </div>\n </div>\n\n \x3c!-- Export tab --\x3e\n <div class=\"tab-panel\" id=\"tab-export\">\n <div class=\"tab-content\">\n <p style=\"font-size:13px;color:#888;margin-bottom:16px;\">\n Exports a YOLO-format dataset (images + labels + data.yaml) as a downloadable bundle.\n Only labeled detections are included. Review more detections first to build a larger dataset.\n </p>\n <div id=\"export-stats\" class=\"export-info\">Loading…</div>\n <div style=\"display:flex;gap:12px;align-items:center;margin-top:12px;\">\n <button class=\"export-btn\" id=\"export-btn\" onclick=\"exportDataset()\">Download Dataset</button>\n <span id=\"export-status\" style=\"font-size:13px;color:#888;\"></span>\n </div>\n </div>\n </div>\n </div>\n</div>\n\n<div class=\"lightbox\" id=\"lightbox\" onclick=\"closeLightbox()\">\n <div class=\"lightbox-close\" onclick=\"closeLightbox()\">βœ•</div>\n <canvas id=\"lightbox-canvas\"></canvas>\n <div class=\"lightbox-meta\" id=\"lightbox-meta\"></div>\n</div>\n\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>\nconst BASE = location.pathname.endsWith('/') ? location.pathname.slice(0, -1) : location.pathname;\nlet pending = [];\nlet labeledCount = 0;\n\nfunction imgError(img) {\n img.parentElement.innerHTML = '<div style=\"padding:20px;color:#555;font-size:12px;text-align:center\">No image</div>';\n}\n\nfunction drawDetection(img, r) {\n const [bx, by, bw, bh] = r.boundingBox;\n const [iw, ih] = r.inputDimensions;\n\n // --- Full frame canvas with box overlay ---\n const fullCanvas = document.getElementById('canvas-full-' + r.id);\n if (fullCanvas) {\n const cw = fullCanvas.width, ch = fullCanvas.height;\n const scale = Math.min(cw / iw, ch / ih);\n const dw = iw * scale, dh = ih * scale;\n const ox = (cw - dw) / 2, oy = (ch - dh) / 2;\n const ctx = fullCanvas.getContext('2d');\n ctx.fillStyle = '#111';\n ctx.fillRect(0, 0, cw, ch);\n ctx.drawImage(img, ox, oy, dw, dh);\n // Draw bounding box\n const rx = ox + bx * scale, ry = oy + by * scale;\n const rw = bw * scale, rh = bh * scale;\n ctx.strokeStyle = '#f90';\n ctx.lineWidth = 2;\n ctx.strokeRect(rx, ry, rw, rh);\n // Label badge\n ctx.fillStyle = 'rgba(255,153,0,0.85)';\n ctx.fillRect(rx, ry - 18, rw, 18);\n ctx.fillStyle = '#000';\n ctx.font = 'bold 11px sans-serif';\n ctx.fillText(r.detectedClass + ' ' + Math.round(r.score * 100) + '%', rx + 3, ry - 4);\n }\n\n // --- Crop canvas ---\n const cropCanvas = document.getElementById('canvas-crop-' + r.id);\n if (cropCanvas) {\n const cc = cropCanvas.width, ch2 = cropCanvas.height;\n const ctx2 = cropCanvas.getContext('2d');\n ctx2.fillStyle = '#111';\n ctx2.fillRect(0, 0, cc, ch2);\n // Add padding around the crop\n const pad = Math.min(bw, bh) * 0.15;\n const sx = Math.max(0, bx - pad), sy = Math.max(0, by - pad);\n const sw = Math.min(iw - sx, bw + pad * 2), sh = Math.min(ih - sy, bh + pad * 2);\n const scale2 = Math.min(cc / sw, ch2 / sh);\n const dw2 = sw * scale2, dh2 = sh * scale2;\n const ox2 = (cc - dw2) / 2, oy2 = (ch2 - dh2) / 2;\n ctx2.drawImage(img, sx, sy, sw, sh, ox2, oy2, dw2, dh2);\n // Thin box outline on crop\n ctx2.strokeStyle = '#f90';\n ctx2.lineWidth = 1.5;\n const rx2 = ox2 + pad * scale2, ry2 = oy2 + pad * scale2;\n ctx2.strokeRect(rx2, ry2, bw * scale2, bh * scale2);\n }\n}\n\n// Cache loaded images so lightbox reuses them\nconst imgCache = new Map();\n\nfunction openLightbox(r) {\n const img = imgCache.get(r.id);\n if (!img) return;\n\n const lb = document.getElementById('lightbox');\n const lbCanvas = document.getElementById('lightbox-canvas');\n\n // Size canvas to image, capped at viewport\n const maxW = window.innerWidth * 0.9;\n const maxH = window.innerHeight * 0.8;\n const [iw, ih] = r.inputDimensions;\n const scale = Math.min(maxW / iw, maxH / ih, 1);\n lbCanvas.width = Math.round(iw * scale);\n lbCanvas.height = Math.round(ih * scale);\n\n const ctx = lbCanvas.getContext('2d');\n ctx.drawImage(img, 0, 0, lbCanvas.width, lbCanvas.height);\n\n // Draw all bounding boxes for this detection (primary + others in same event if available)\n const boxes = r.allDetections || [{ boundingBox: r.boundingBox, detectedClass: r.detectedClass, score: r.score }];\n const colors = ['#f90', '#4af', '#f44', '#4f4', '#f4f', '#ff4'];\n boxes.forEach((d, i) => {\n const [bx, by, bw, bh] = d.boundingBox;\n const color = colors[i % colors.length];\n const rx = bx * scale, ry = by * scale, rw = bw * scale, rh = bh * scale;\n ctx.strokeStyle = color;\n ctx.lineWidth = 2;\n ctx.strokeRect(rx, ry, rw, rh);\n const label = d.detectedClass + ' ' + Math.round((d.score || 0) * 100) + '%';\n const textW = ctx.measureText(label).width + 8;\n ctx.fillStyle = color;\n ctx.globalAlpha = 0.85;\n ctx.fillRect(rx, Math.max(0, ry - 20), textW, 20);\n ctx.globalAlpha = 1;\n ctx.fillStyle = '#000';\n ctx.font = 'bold 12px sans-serif';\n ctx.fillText(label, rx + 4, Math.max(14, ry - 4));\n });\n\n document.getElementById('lightbox-meta').textContent =\n r.cameraName + ' Β· ' + new Date(r.timestamp).toLocaleString() + ' Β· ' + iw + 'Γ—' + ih;\n lb.classList.add('open');\n document.addEventListener('keydown', lbKeyHandler);\n}\n\nfunction closeLightbox() {\n document.getElementById('lightbox').classList.remove('open');\n document.removeEventListener('keydown', lbKeyHandler);\n}\n\nfunction lbKeyHandler(e) {\n if (e.key === 'Escape') closeLightbox();\n}\n\nfunction showTab(name) {\n document.querySelectorAll('.tab').forEach((t, i) => {\n const names = ['review', 'stats', 'export'];\n t.classList.toggle('active', names[i] === name);\n });\n document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));\n document.getElementById('tab-' + name).classList.add('active');\n if (name === 'stats') loadStats();\n if (name === 'export') loadExportInfo();\n}\n\nfunction toast(msg, color='#1a3') {\n const el = document.getElementById('toast');\n el.textContent = msg;\n el.style.background = color;\n el.classList.add('show');\n setTimeout(() => el.classList.remove('show'), 2500);\n}\n\nasync function loadPending() {\n try {\n const res = await fetch(BASE + '/api/pending');\n pending = await res.json();\n\n const statsRes = await fetch(BASE + '/api/stats');\n const stats = await statsRes.json();\n document.getElementById('stat-pending').textContent = stats.pending;\n document.getElementById('stat-labeled').textContent = stats.labeled;\n document.getElementById('stat-total').textContent = stats.total;\n\n const list = document.getElementById('detections-list');\n if (!pending.length) {\n list.innerHTML = '<div class=\"empty\"><div class=\"icon\">βœ…</div><div>No pending detections to review.<br><span style=\"font-size:12px;color:#444\">Captures will appear here as cameras detect objects.</span></div></div>';\n return;\n }\n\n list.innerHTML = pending.map(r => {\n const date = new Date(r.timestamp).toLocaleString();\n const score = Math.round(r.score * 100);\n const bb = r.boundingBox;\n const dim = r.inputDimensions;\n return `\n <div class=\"detection\" id=\"det-${r.id}\">\n <div class=\"detection-imgs\">\n <div class=\"img-panel\">\n <div class=\"img-label\">Full frame</div>\n <canvas id=\"canvas-full-${r.id}\" class=\"det-canvas\" width=\"240\" height=\"160\"></canvas>\n </div>\n <div class=\"img-panel\">\n <div class=\"img-label\">Crop</div>\n <canvas id=\"canvas-crop-${r.id}\" class=\"det-canvas\" width=\"160\" height=\"160\"></canvas>\n </div>\n </div>\n <div class=\"detection-info\">\n <div class=\"detection-meta\">\n <div><strong>${r.cameraName}</strong></div>\n <div>${date}</div>\n <div class=\"det-class-badge\">${r.detectedClass} ${score}%</div>\n </div>\n <div style=\"font-size:12px;color:#888;\">What is this actually?</div>\n <div class=\"label-buttons\">\n <button class=\"label-btn person\" onclick=\"label('${r.id}', 'person')\">πŸ‘€ Person</button>\n <button class=\"label-btn animal\" onclick=\"label('${r.id}', 'animal')\">🐾 Animal</button>\n <button class=\"label-btn face\" onclick=\"label('${r.id}', 'face')\">πŸ˜€ Face</button>\n <button class=\"label-btn vehicle\" onclick=\"label('${r.id}', 'vehicle')\">πŸš— Vehicle</button>\n <button class=\"label-btn\" onclick=\"label('${r.id}', 'plate')\">πŸ”’ Plate</button>\n <button class=\"label-btn\" onclick=\"label('${r.id}', 'package')\">πŸ“¦ Package</button>\n <button class=\"label-btn discard\" onclick=\"label('${r.id}', 'discard')\">πŸ—‘ Discard</button>\n </div>\n </div>\n </div>`;\n }).join('');\n\n // Draw bounding boxes and crops onto canvases after DOM is ready\n for (const r of pending) {\n const img = new Image();\n img.onload = () => {\n imgCache.set(r.id, img);\n drawDetection(img, r);\n // Wire up click to open lightbox\n const fullCanvas = document.getElementById('canvas-full-' + r.id);\n const cropCanvas = document.getElementById('canvas-crop-' + r.id);\n if (fullCanvas) fullCanvas.onclick = () => openLightbox(r);\n if (cropCanvas) cropCanvas.onclick = () => openLightbox(r);\n };\n img.onerror = () => {\n const c = document.getElementById('canvas-full-' + r.id);\n if (c) c.parentElement.innerHTML = '<div style=\"padding:10px;color:#555;font-size:11px\">No image</div>';\n };\n img.src = BASE + '/img/' + r.id;\n } } catch(e) {\n console.error('loadPending error', e);\n const list = document.getElementById('detections-list');\n if (list) list.innerHTML = '<div class=\"empty\"><div style=\"color:#a44\">Error loading captures: ' + e.message + '</div></div>';\n }\n}\n\nasync function label(id, labelVal) {\n const el = document.getElementById('det-' + id);\n if (el) {\n el.classList.add('labeled');\n const btns = el.querySelectorAll('.label-btn');\n btns.forEach(b => b.disabled = true);\n }\n await fetch(BASE + '/api/label', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id, label: labelVal }),\n });\n toast(labelVal === 'discard' ? 'Discarded' : 'Labeled: ' + labelVal, labelVal === 'discard' ? '#633' : '#1a6');\n setTimeout(() => {\n if (el) el.remove();\n loadPending();\n }, 600);\n}\n\nasync function loadStats() {\n const res = await fetch(BASE + '/api/stats');\n const stats = await res.json();\n\n const renderBreakdown = (obj, container) => {\n const el = document.getElementById(container);\n const entries = Object.entries(obj).sort((a, b) => b[1] - a[1]);\n el.innerHTML = entries.length\n ? entries.map(([k, v]) => `<div class=\"breakdown-item\"><div class=\"label\">${k}</div><div class=\"value\">${v}</div></div>`).join('')\n : '<div style=\"color:#555;font-size:13px;\">None yet</div>';\n };\n\n renderBreakdown(stats.byDetectedClass, 'stats-detected');\n renderBreakdown(stats.byLabel, 'stats-label');\n renderBreakdown(stats.byCamera, 'stats-camera');\n}\n\nasync function loadExportInfo() {\n const res = await fetch(BASE + '/api/stats');\n const stats = await res.json();\n document.getElementById('export-stats').textContent =\n `${stats.labeled} labeled samples ready for export across ${Object.keys(stats.byCamera).length} camera(s).`;\n}\n\nasync function exportDataset() {\n const btn = document.getElementById('export-btn');\n const status = document.getElementById('export-status');\n btn.disabled = true;\n status.textContent = 'Fetching data…';\n\n try {\n const res = await fetch(BASE + '/api/export');\n if (!res.ok) { status.textContent = 'Nothing to export yet.'; btn.disabled = false; return; }\n const data = await res.json();\n if (data.error) { status.textContent = data.error; btn.disabled = false; return; }\n\n status.textContent = 'Building zip…';\n\n const zip = new JSZip();\n for (const f of data.files) {\n if (f.encoding === 'base64') {\n zip.file(f.filename, f.content, { base64: true });\n } else {\n zip.file(f.filename, f.content);\n }\n }\n\n const blob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = 'scrypted_dataset_' + new Date().toISOString().slice(0,10) + '.zip';\n a.click();\n URL.revokeObjectURL(url);\n status.textContent = `Downloaded ${data.count} samples.`;\n toast('Dataset downloaded!');\n } catch (e) {\n status.textContent = 'Export failed: ' + e.message;\n }\n btn.disabled = false;\n}\n\n// Initial load\nloadPending();\n// Auto-refresh pending every 30s\nsetInterval(loadPending, 30_000);\n<\/script>\n</body>\n</html>"}}t.default=u},339(e){"use strict";e.exports=require("module")}},t={};function n(i){var r=t[i];if(void 0!==r)return r.exports;var o=t[i]={exports:{}};return e[i].call(o.exports,o,o.exports,n),o.exports}n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var i=n(927),r=exports="undefined"==typeof exports?{}:exports;for(var o in i)r[o]=i[o];i.__esModule&&Object.defineProperty(r,"__esModule",{value:!0})})();
2
2
  //# sourceMappingURL=main.nodejs.js.map