scrypted-detection-trainer 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +5 -3
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +6 -3
package/dist/main.nodejs.js
CHANGED
|
@@ -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=[{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:"ui_link",title:"Review UI",description:"Open the detection review and labeling interface.",readonly:!0,value:await s.default.endpointManager.getLocalEndpoint("scrypted-detection-trainer",{public:!0}).catch(()=>"/endpoint/scrypted-detection-trainer/public/")}];for(const n of e){const e=`rate:${n.id}`;t.push({key:e,title:n.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 t}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.replace(//$/, \'\');\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 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}\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 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.replace(//+$/, \'\');\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 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}\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})})();
|
|
2
2
|
//# sourceMappingURL=main.nodejs.js.map
|