Преглед на файлове

new survellance feature

Dr-Swopt преди 4 дни
родител
ревизия
dc5c309367

+ 0 - 0
models/.gitkeep


+ 591 - 18
package-lock.json

@@ -12,13 +12,19 @@
         "@nestjs/common": "^11.0.1",
         "@nestjs/core": "^11.0.1",
         "@nestjs/platform-express": "^11.0.1",
+        "@nestjs/platform-socket.io": "^11.1.19",
         "@nestjs/typeorm": "^11.0.1",
+        "@nestjs/websockets": "^11.1.19",
+        "@xenova/transformers": "^2.17.2",
         "class-transformer": "^0.5.1",
         "class-validator": "^0.15.1",
+        "find-process": "^2.1.1",
         "onnxruntime-node": "^1.24.3",
+        "pidusage": "^4.0.1",
         "reflect-metadata": "^0.2.2",
         "rxjs": "^7.8.1",
         "sharp": "^0.34.5",
+        "socket.io": "^4.8.3",
         "sqlite3": "^5.1.7",
         "typeorm": "^0.3.28"
       },
@@ -930,6 +936,15 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/@huggingface/jinja": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz",
+      "integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@humanfs/core": {
       "version": "0.19.1",
       "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -2924,6 +2939,25 @@
         "@nestjs/core": "^11.0.0"
       }
     },
+    "node_modules/@nestjs/platform-socket.io": {
+      "version": "11.1.19",
+      "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.19.tgz",
+      "integrity": "sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==",
+      "license": "MIT",
+      "dependencies": {
+        "socket.io": "4.8.3",
+        "tslib": "2.8.1"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nest"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^11.0.0",
+        "@nestjs/websockets": "^11.0.0",
+        "rxjs": "^7.1.0"
+      }
+    },
     "node_modules/@nestjs/schematics": {
       "version": "11.0.10",
       "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.10.tgz",
@@ -3076,6 +3110,29 @@
         "typeorm": "^0.3.0 || ^1.0.0-dev"
       }
     },
+    "node_modules/@nestjs/websockets": {
+      "version": "11.1.19",
+      "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.19.tgz",
+      "integrity": "sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==",
+      "license": "MIT",
+      "dependencies": {
+        "iterare": "1.2.1",
+        "object-hash": "3.0.0",
+        "tslib": "2.8.1"
+      },
+      "peerDependencies": {
+        "@nestjs/common": "^11.0.0",
+        "@nestjs/core": "^11.0.0",
+        "@nestjs/platform-socket.io": "^11.0.0",
+        "reflect-metadata": "^0.1.12 || ^0.2.0",
+        "rxjs": "^7.1.0"
+      },
+      "peerDependenciesMeta": {
+        "@nestjs/platform-socket.io": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@noble/hashes": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
@@ -3202,6 +3259,70 @@
         "url": "https://opencollective.com/pkgr"
       }
     },
+    "node_modules/@protobufjs/aspromise": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/base64": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/codegen": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/eventemitter": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.1",
+        "@protobufjs/inquire": "^1.1.0"
+      }
+    },
+    "node_modules/@protobufjs/float": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/inquire": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/path": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/pool": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@protobufjs/utf8": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/@sinclair/typebox": {
       "version": "0.27.10",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
@@ -3242,6 +3363,12 @@
         "@sinonjs/commons": "^3.0.0"
       }
     },
+    "node_modules/@socket.io/component-emitter": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+      "license": "MIT"
+    },
     "node_modules/@sqltools/formatter": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
@@ -3734,6 +3861,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/cors": {
+      "version": "2.8.19",
+      "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+      "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/eslint": {
       "version": "9.6.1",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -3857,6 +3993,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/long": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+      "license": "MIT"
+    },
     "node_modules/@types/methods": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -3878,7 +4020,6 @@
       "version": "22.19.17",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
       "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
-      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "undici-types": "~6.21.0"
@@ -3956,6 +4097,15 @@
       "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
       "license": "MIT"
     },
+    "node_modules/@types/ws": {
+      "version": "8.18.1",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+      "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/yargs": {
       "version": "17.0.35",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
@@ -4416,6 +4566,85 @@
         "@xtuc/long": "4.2.2"
       }
     },
+    "node_modules/@xenova/transformers": {
+      "version": "2.17.2",
+      "resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz",
+      "integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@huggingface/jinja": "^0.2.2",
+        "onnxruntime-web": "1.14.0",
+        "sharp": "^0.32.0"
+      },
+      "optionalDependencies": {
+        "onnxruntime-node": "1.14.0"
+      }
+    },
+    "node_modules/@xenova/transformers/node_modules/node-addon-api": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
+      "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
+      "license": "MIT"
+    },
+    "node_modules/@xenova/transformers/node_modules/onnxruntime-common": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
+      "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/@xenova/transformers/node_modules/onnxruntime-node": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz",
+      "integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==",
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32",
+        "darwin",
+        "linux"
+      ],
+      "dependencies": {
+        "onnxruntime-common": "~1.14.0"
+      }
+    },
+    "node_modules/@xenova/transformers/node_modules/sharp": {
+      "version": "0.32.6",
+      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
+      "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "color": "^4.2.3",
+        "detect-libc": "^2.0.2",
+        "node-addon-api": "^6.1.0",
+        "prebuild-install": "^7.1.1",
+        "semver": "^7.5.4",
+        "simple-get": "^4.0.1",
+        "tar-fs": "^3.0.4",
+        "tunnel-agent": "^0.6.0"
+      },
+      "engines": {
+        "node": ">=14.15.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@xenova/transformers/node_modules/tar-fs": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
+      "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
+      "license": "MIT",
+      "dependencies": {
+        "pump": "^3.0.0",
+        "tar-stream": "^3.1.5"
+      },
+      "optionalDependencies": {
+        "bare-fs": "^4.0.1",
+        "bare-path": "^3.0.0"
+      }
+    },
     "node_modules/@xhmikosr/archive-type": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz",
@@ -5200,7 +5429,6 @@
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
       "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
-      "dev": true,
       "license": "Apache-2.0",
       "peerDependencies": {
         "react-native-b4a": "*"
@@ -5347,7 +5575,6 @@
       "version": "2.8.2",
       "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
       "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
-      "dev": true,
       "license": "Apache-2.0",
       "peerDependencies": {
         "bare-abort-controller": "*"
@@ -5362,7 +5589,6 @@
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.6.0.tgz",
       "integrity": "sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "bare-events": "^2.5.4",
@@ -5387,7 +5613,6 @@
       "version": "3.8.7",
       "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz",
       "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==",
-      "dev": true,
       "license": "Apache-2.0",
       "engines": {
         "bare": ">=1.14.0"
@@ -5397,7 +5622,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
       "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "bare-os": "^3.0.1"
@@ -5407,7 +5631,6 @@
       "version": "2.12.0",
       "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.12.0.tgz",
       "integrity": "sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "streamx": "^2.25.0",
@@ -5434,7 +5657,6 @@
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
       "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "bare-path": "^3.0.0"
@@ -5460,6 +5682,15 @@
       ],
       "license": "MIT"
     },
+    "node_modules/base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+      "license": "MIT",
+      "engines": {
+        "node": "^4.5.0 || >= 5.9"
+      }
+    },
     "node_modules/baseline-browser-mapping": {
       "version": "2.10.16",
       "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz",
@@ -5906,7 +6137,6 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.1.0",
@@ -6132,6 +6362,19 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/color": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+      "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1",
+        "color-string": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=12.5.0"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -6150,6 +6393,16 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "license": "MIT"
     },
+    "node_modules/color-string": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "^1.0.0",
+        "simple-swizzle": "^0.2.2"
+      }
+    },
     "node_modules/color-support": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
@@ -6720,6 +6973,79 @@
         "once": "^1.4.0"
       }
     },
+    "node_modules/engine.io": {
+      "version": "6.6.6",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.6.tgz",
+      "integrity": "sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/cors": "^2.8.12",
+        "@types/node": ">=10.0.0",
+        "@types/ws": "^8.5.12",
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "~0.7.2",
+        "cors": "~2.8.5",
+        "debug": "~4.4.1",
+        "engine.io-parser": "~5.2.1",
+        "ws": "~8.18.3"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/engine.io-parser": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/engine.io/node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/engine.io/node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/enhanced-resolve": {
       "version": "5.20.1",
       "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
@@ -7085,7 +7411,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
       "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "bare-events": "^2.7.0"
@@ -7258,7 +7583,6 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
       "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/fast-glob": {
@@ -7473,6 +7797,29 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/find-process": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/find-process/-/find-process-2.1.1.tgz",
+      "integrity": "sha512-SrQDx3QhlmHM90iqn9rdjCQcw/T+WlpOkHFsjoRgB+zTpDfltNA1VSNYeYELwhUTJy12UFxqjWhmhOrJc+o4sA==",
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "~4.1.2",
+        "commander": "^14.0.3",
+        "loglevel": "^1.9.2"
+      },
+      "bin": {
+        "find-process": "dist/cjs/bin/find-process.js"
+      }
+    },
+    "node_modules/find-process/node_modules/commander": {
+      "version": "14.0.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
+      "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=20"
+      }
+    },
     "node_modules/find-up": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -7520,6 +7867,12 @@
         "node": ">=16"
       }
     },
+    "node_modules/flatbuffers": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
+      "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==",
+      "license": "SEE LICENSE IN LICENSE.txt"
+    },
     "node_modules/flatted": {
       "version": "3.4.2",
       "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
@@ -8042,6 +8395,12 @@
       "devOptional": true,
       "license": "ISC"
     },
+    "node_modules/guid-typescript": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
+      "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
+      "license": "ISC"
+    },
     "node_modules/handlebars": {
       "version": "4.7.9",
       "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
@@ -8078,7 +8437,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -9595,6 +9953,25 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/loglevel": {
+      "version": "1.9.2",
+      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
+      "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      },
+      "funding": {
+        "type": "tidelift",
+        "url": "https://tidelift.com/funding/github/npm/loglevel"
+      }
+    },
+    "node_modules/long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+      "license": "Apache-2.0"
+    },
     "node_modules/lowercase-keys": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
@@ -10433,6 +10810,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.13.4",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -10491,6 +10877,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/onnx-proto": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz",
+      "integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==",
+      "license": "MIT",
+      "dependencies": {
+        "protobufjs": "^6.8.8"
+      }
+    },
     "node_modules/onnxruntime-common": {
       "version": "1.24.3",
       "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
@@ -10514,6 +10909,26 @@
         "onnxruntime-common": "1.24.3"
       }
     },
+    "node_modules/onnxruntime-web": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz",
+      "integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==",
+      "license": "MIT",
+      "dependencies": {
+        "flatbuffers": "^1.12.0",
+        "guid-typescript": "^1.0.9",
+        "long": "^4.0.0",
+        "onnx-proto": "^4.0.4",
+        "onnxruntime-common": "~1.14.0",
+        "platform": "^1.3.6"
+      }
+    },
+    "node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
+      "version": "1.14.0",
+      "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
+      "integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==",
+      "license": "MIT"
+    },
     "node_modules/optionator": {
       "version": "0.9.4",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -10781,6 +11196,18 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/pidusage": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-4.0.1.tgz",
+      "integrity": "sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "^5.2.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/pirates": {
       "version": "4.0.7",
       "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
@@ -10870,6 +11297,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/platform": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+      "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+      "license": "MIT"
+    },
     "node_modules/pluralize": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
@@ -11018,6 +11451,32 @@
         "node": ">= 6"
       }
     },
+    "node_modules/protobufjs": {
+      "version": "6.11.5",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.5.tgz",
+      "integrity": "sha512-OKjVH3hDoXdIZ/s5MLv8O2X0s+wOxGfV7ar6WFSKGaSAxi/6gYn3px5POS4vi+mc/0zCOdL7Jkwrj0oT1Yst2A==",
+      "hasInstallScript": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.2",
+        "@protobufjs/base64": "^1.1.2",
+        "@protobufjs/codegen": "^2.0.4",
+        "@protobufjs/eventemitter": "^1.1.0",
+        "@protobufjs/fetch": "^1.1.0",
+        "@protobufjs/float": "^1.0.2",
+        "@protobufjs/inquire": "^1.1.0",
+        "@protobufjs/path": "^1.1.2",
+        "@protobufjs/pool": "^1.1.0",
+        "@protobufjs/utf8": "^1.1.0",
+        "@types/long": "^4.0.1",
+        "@types/node": ">=13.7.0",
+        "long": "^4.0.0"
+      },
+      "bin": {
+        "pbjs": "bin/pbjs",
+        "pbts": "bin/pbts"
+      }
+    },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -11897,6 +12356,21 @@
         "simple-concat": "^1.0.0"
       }
     },
+    "node_modules/simple-swizzle": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
+      "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-arrayish": "^0.3.1"
+      }
+    },
+    "node_modules/simple-swizzle/node_modules/is-arrayish": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
+      "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
+      "license": "MIT"
+    },
     "node_modules/sisteransi": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -11925,6 +12399,90 @@
         "npm": ">= 3.0.0"
       }
     },
+    "node_modules/socket.io": {
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
+      "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.4",
+        "base64id": "~2.0.0",
+        "cors": "~2.8.5",
+        "debug": "~4.4.1",
+        "engine.io": "~6.6.0",
+        "socket.io-adapter": "~2.5.2",
+        "socket.io-parser": "~4.2.4"
+      },
+      "engines": {
+        "node": ">=10.2.0"
+      }
+    },
+    "node_modules/socket.io-adapter": {
+      "version": "2.5.6",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
+      "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "~4.4.1",
+        "ws": "~8.18.3"
+      }
+    },
+    "node_modules/socket.io-parser": {
+      "version": "4.2.6",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
+      "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
+      "license": "MIT",
+      "dependencies": {
+        "@socket.io/component-emitter": "~3.1.0",
+        "debug": "~4.4.1"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
+    "node_modules/socket.io/node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/socket.io/node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/socks": {
       "version": "2.8.7",
       "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
@@ -12136,7 +12694,6 @@
       "version": "2.25.0",
       "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
       "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "events-universal": "^1.0.0",
@@ -12321,7 +12878,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "has-flag": "^4.0.0"
@@ -12439,7 +12995,6 @@
       "version": "3.1.8",
       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
       "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "b4a": "^1.6.4",
@@ -12467,7 +13022,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
       "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "streamx": "^2.12.5"
@@ -12680,7 +13234,6 @@
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
       "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "b4a": "^1.6.4"
@@ -13341,7 +13894,6 @@
       "version": "6.21.0",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
       "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
-      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/unique-filename": {
@@ -13844,6 +14396,27 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/ws": {
+      "version": "8.18.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+      "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

+ 6 - 0
package.json

@@ -23,13 +23,19 @@
     "@nestjs/common": "^11.0.1",
     "@nestjs/core": "^11.0.1",
     "@nestjs/platform-express": "^11.0.1",
+    "@nestjs/platform-socket.io": "^11.1.19",
     "@nestjs/typeorm": "^11.0.1",
+    "@nestjs/websockets": "^11.1.19",
+    "@xenova/transformers": "^2.17.2",
     "class-transformer": "^0.5.1",
     "class-validator": "^0.15.1",
+    "find-process": "^2.1.1",
     "onnxruntime-node": "^1.24.3",
+    "pidusage": "^4.0.1",
     "reflect-metadata": "^0.2.2",
     "rxjs": "^7.8.1",
     "sharp": "^0.34.5",
+    "socket.io": "^4.8.3",
     "sqlite3": "^5.1.7",
     "typeorm": "^0.3.28"
   },

BIN
palm_history.db


+ 2 - 0
src/app.module.ts

@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 import { AppController } from './app.controller';
 import { AppService } from './app.service';
 import { PalmOilModule } from './palm-oil/palm-oil.module';
+import { SurveillanceModule } from './surveillance/surveillance.module';
 
 @Module({
   imports: [
@@ -13,6 +14,7 @@ import { PalmOilModule } from './palm-oil/palm-oil.module';
       synchronize: true, // Auto-create tables (use only for development)
     }),
     PalmOilModule,
+    SurveillanceModule,  // Lego 09 — boots PID polling on startup
   ],
   controllers: [AppController],
   providers: [AppService],

+ 12 - 16
src/palm-oil/palm-oil.controller.ts

@@ -1,7 +1,16 @@
-import { Controller, Post, Get, UseInterceptors, UploadedFile, Res, Param } from '@nestjs/common';
-import { FileInterceptor } from '@nestjs/platform-express';
+/**
+ * Lego 13 — Codebase Mutation: REST analyze endpoint stripped.
+ *
+ * POST /palm-oil/analyze has been replaced by the VisionGateway (Socket.io).
+ * Angular must no longer upload images via multipart HTTP.
+ * It must emit vision:stream with a raw Base64 string instead (Lego 11).
+ *
+ * Read-only REST endpoints (history, archive) are preserved — they serve
+ * the Angular history tab and do not conflict with the Hoarding architecture.
+ */
+
+import { Controller, Get, Res, Param } from '@nestjs/common';
 import { PalmOilService } from './palm-oil.service';
-import { AnalysisResponse } from './interfaces/palm-analysis.interface';
 import { Response } from 'express';
 import * as fs from 'fs';
 
@@ -9,18 +18,6 @@ import * as fs from 'fs';
 export class PalmOilController {
   constructor(private readonly palmOilService: PalmOilService) { }
 
-  @Post('analyze')
-  @UseInterceptors(FileInterceptor('image'))
-  async analyze(@UploadedFile() file: Express.Multer.File): Promise<AnalysisResponse> {
-    if (!file) {
-      throw new Error('No image uploaded');
-    }
-    let res = await this.palmOilService.analyzeImage(file.buffer, file.originalname)
-    // console.log(res)
-    // console.log(res.detections)
-    return res;
-  }
-
   @Get('history')
   async getHistory() {
     return this.palmOilService.getHistory();
@@ -33,7 +30,6 @@ export class PalmOilController {
       return res.status(404).send('Image not found');
     }
 
-    // Set the correct header and stream the file
     res.setHeader('Content-Type', 'image/jpeg');
     const fileStream = fs.createReadStream(record.image_path);
     fileStream.pipe(res);

+ 3 - 1
src/palm-oil/palm-oil.module.ts

@@ -3,12 +3,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 import { PalmOilController } from './palm-oil.controller';
 import { PalmOilService } from './palm-oil.service';
 import { ScannerProvider } from './providers/scanner.provider';
+import { VisionGateway } from './vision.gateway';
 import { History } from './entities/history.entity';
 
 @Module({
   imports: [TypeOrmModule.forFeature([History])],
   controllers: [PalmOilController],
-  providers: [PalmOilService, ScannerProvider],
+  // Lego 13 — VisionGateway replaces the REST analyze endpoint
+  providers: [PalmOilService, ScannerProvider, VisionGateway],
   exports: [PalmOilService, ScannerProvider],
 })
 export class PalmOilModule {}

+ 1 - 1
src/palm-oil/palm-oil.service.ts

@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm';
 import { ScannerProvider } from './providers/scanner.provider';
-import * as sharp from 'sharp';
+import sharp from 'sharp';
 import { performance } from 'perf_hooks';
 import { AnalysisResponse, IndustrialSummary } from './interfaces/palm-analysis.interface';
 import { History } from './entities/history.entity';

+ 127 - 0
src/palm-oil/vision.gateway.ts

@@ -0,0 +1,127 @@
+/**
+ * Lego 02  — Vision Inference over WebSocket (YOLOv8 + ONNX)
+ * Lego 11  — Socket Event Schema: vision:analyze → vision:result
+ * Lego 13  — Codebase Mutation: REST analyze endpoint replaced by WS Gateway
+ *
+ * CONTRACT (Lego 11 hard rule):
+ *   Angular MUST send raw, uncompressed Base64 strings on vision:analyze.
+ *   Binary compression and WebRTC are strictly forbidden.
+ *   The gateway deliberately skips all buffer optimisation to maximise
+ *   I/O and CPU overhead on the socket bus.
+ *
+ * ORDERING (Lego 12):
+ *   SQLite write (via PalmOilService.analyzeImage) is fully awaited
+ *   BEFORE vision:result is emitted to the client — intentional blocking.
+ */
+
+import {
+  WebSocketGateway,
+  WebSocketServer,
+  SubscribeMessage,
+  OnGatewayInit,
+  OnGatewayConnection,
+  OnGatewayDisconnect,
+  MessageBody,
+  ConnectedSocket,
+} from '@nestjs/websockets';
+import { Logger } from '@nestjs/common';
+import { Server, Socket } from 'socket.io';
+import { PalmOilService } from './palm-oil.service';
+import { AnalysisResponse } from './interfaces/palm-analysis.interface';
+
+// ─── Payload shape Angular sends on vision:analyze ────────────────────────────
+interface VisionStreamPayload {
+  /** Raw, uncompressed Base64 string — data URI prefix is stripped server-side */
+  frame: string;
+  /** Optional: lets the UI tag which camera source the frame came from */
+  sourceLabel?: string;
+}
+
+@WebSocketGateway({
+  cors: { origin: '*' },
+  namespace: '/vision',
+})
+export class VisionGateway
+  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
+{
+  @WebSocketServer()
+  private server!: Server;
+
+  private readonly logger = new Logger(VisionGateway.name);
+
+  constructor(private readonly palmOilService: PalmOilService) {}
+
+  // ─── Lifecycle ───────────────────────────────────────────────────────────────
+
+  afterInit() {
+    this.logger.log('🔌 VisionGateway initialized on /vision namespace');
+  }
+
+  handleConnection(client: Socket) {
+    this.logger.log(`📡 Vision client connected: ${client.id}`);
+  }
+
+  handleDisconnect(client: Socket) {
+    this.logger.log(`🔌 Vision client disconnected: ${client.id}`);
+  }
+
+  // ─── vision:analyze handler ─────────────────────────────────────────────────
+
+  /**
+   * Lego 11 — vision:analyze
+   *
+   * Receives a raw Base64 image frame from Angular.
+   * NO compression negotiation. NO WebRTC. NO binary frames.
+   * The full uncompressed Base64 string travels the socket bus every tick.
+   *
+   * Sequence (Lego 12 ordering guarantee):
+   *   1. Decode Base64 → Buffer
+   *   2. Run ONNX inference (ScannerProvider — untouched per Lego 13)
+   *   3. Synchronous SQLite persist (inside analyzeImage, awaited fully)
+   *   4. ONLY THEN emit vision:result back to the client
+   */
+  @SubscribeMessage('vision:analyze')
+  async handleVisionStream(
+    @MessageBody() payload: VisionStreamPayload,
+    @ConnectedSocket() client: Socket,
+  ) {
+    if (!payload?.frame) {
+      client.emit('vision:error', { message: 'No frame data received' });
+      return;
+    }
+
+    // Lego 11 — High-Tax payload audit: log raw size to prove I/O overhead
+    this.logger.log(
+      `📦 vision:analyze from ${client.id} — payload size: ${payload.frame.length} chars (~${(payload.frame.length / 1024).toFixed(1)} KB)`,
+    );
+
+    // Strip the data URI prefix if Angular included it
+    // e.g. "data:image/jpeg;base64,/9j/4AAQ..." → "/9j/4AAQ..."
+    const rawBase64 = payload.frame.replace(/^data:image\/\w+;base64,/, '');
+
+    // Decode to Buffer — this is deliberately the HEAVY path:
+    // Base64 is ~33% larger than binary; we absorb that overhead intentionally.
+    const imageBuffer = Buffer.from(rawBase64, 'base64');
+
+    const sourceLabel = payload.sourceLabel ?? 'socket-frame';
+
+    // ── STEP 3 happens inside analyzeImage ──────────────────────────────────
+    // PalmOilService.analyzeImage():
+    //   → runs ONNX preprocess / inference / postprocess (Lego 02)
+    //   → awaits historyRepository.save() — SQLite write blocks here (Lego 12)
+    //   → only then returns the result
+    // We await the full chain before emitting so the DB write is guaranteed
+    // to complete first. This is the intentional I/O bottleneck.
+    let result: AnalysisResponse;
+    try {
+      result = await this.palmOilService.analyzeImage(imageBuffer, sourceLabel);
+    } catch (err: any) {
+      this.logger.error(`❌ Inference failed for ${client.id}: ${err.message}`);
+      client.emit('vision:error', { message: err.message });
+      return;
+    }
+
+    // ── STEP 4 — emit AFTER the SQLite write is confirmed ────────────────────
+    client.emit('vision:result', result);
+  }
+}

+ 84 - 0
src/surveillance/surveillance.gateway.ts

@@ -0,0 +1,84 @@
+/**
+ * Lego 09 — Surveillance Gateway
+ * Lego 11 — Socket Event Schema: monitor:subscribe / monitor:data
+ *
+ * Registers the Socket.io namespace on port 3000 (shared server).
+ * On client subscribe → sends the latest snapshot immediately.
+ * Every 500ms tick → SurveillanceService calls back → we broadcast to all.
+ */
+
+import {
+  WebSocketGateway,
+  WebSocketServer,
+  SubscribeMessage,
+  OnGatewayInit,
+  OnGatewayConnection,
+  OnGatewayDisconnect,
+} from '@nestjs/websockets';
+import { Logger, OnModuleInit } from '@nestjs/common';
+import { Server, Socket } from 'socket.io';
+import { SurveillanceService, MonitorPayload } from './surveillance.service';
+
+@WebSocketGateway({
+  cors: { origin: '*' },   // Angular dev server on any port
+  namespace: '/monitor',
+})
+export class SurveillanceGateway
+  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, OnModuleInit
+{
+  @WebSocketServer()
+  private server: Server;
+
+  private readonly logger = new Logger(SurveillanceGateway.name);
+
+  constructor(private readonly surveillanceService: SurveillanceService) {}
+
+  // ─── Lifecycle ─────────────────────────────────────────────────────────────
+
+  onModuleInit() {
+    // Wire the service callback → broadcasts to all connected clients
+    this.surveillanceService.registerMetricsCallback((metrics: MonitorPayload[]) => {
+      this.broadcast(metrics);
+    });
+  }
+
+  afterInit(server: Server) {
+    this.logger.log('🔌 SurveillanceGateway initialized on /monitor namespace');
+  }
+
+  handleConnection(client: Socket) {
+    this.logger.log(`📡 Client connected: ${client.id}`);
+    // Immediately push the current snapshot so the UI isn't blank on load
+    const snapshot = this.surveillanceService.getLatestMetrics();
+    if (snapshot.length > 0) {
+      client.emit('monitor:data', snapshot);
+    }
+  }
+
+  handleDisconnect(client: Socket) {
+    this.logger.log(`🔌 Client disconnected: ${client.id}`);
+  }
+
+  // ─── Event Handlers ────────────────────────────────────────────────────────
+
+  /**
+   * Lego 11 — monitor:subscribe
+   * UI emits this to start (or re-confirm) the resource tracking stream.
+   * We acknowledge and immediately push the latest snapshot.
+   */
+  @SubscribeMessage('monitor:subscribe')
+  handleSubscribe(client: Socket) {
+    this.logger.log(`🟢 monitor:subscribe from ${client.id}`);
+    const snapshot = this.surveillanceService.getLatestMetrics();
+    client.emit('monitor:data', snapshot);
+    return { event: 'monitor:subscribed', data: { ok: true } };
+  }
+
+  // ─── Broadcast ─────────────────────────────────────────────────────────────
+
+  private broadcast(metrics: MonitorPayload[]) {
+    if (this.server) {
+      this.server.emit('monitor:data', metrics);
+    }
+  }
+}

+ 9 - 0
src/surveillance/surveillance.module.ts

@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { SurveillanceService } from './surveillance.service';
+import { SurveillanceGateway } from './surveillance.gateway';
+
+@Module({
+  providers: [SurveillanceService, SurveillanceGateway],
+  exports: [SurveillanceService],
+})
+export class SurveillanceModule {}

+ 156 - 0
src/surveillance/surveillance.service.ts

@@ -0,0 +1,156 @@
+/**
+ * Lego 09 — Surveillance Engine (PID Polling & Socket Stream)
+ *
+ * Discovers the PIDs for n8n (node), ollama_llama_server, and the NestJS
+ * process itself. Polls every 500ms via pidusage and exposes the live
+ * metrics payload so the Gateway can broadcast monitor:data to all clients.
+ */
+
+import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
+import * as pidusage from 'pidusage';
+import find from 'find-process';
+
+export interface MonitorPayload {
+  service: string;
+  pid: number;
+  cpu: number;
+  memory: number;
+  timestamp: Date;
+}
+
+@Injectable()
+export class SurveillanceService implements OnModuleInit, OnModuleDestroy {
+  private readonly logger = new Logger(SurveillanceService.name);
+
+  // Live metrics — the Gateway reads these on every tick
+  private _latestMetrics: MonitorPayload[] = [];
+
+  // Callback registered by the Gateway so it receives every poll result
+  private onMetricsUpdate: ((metrics: MonitorPayload[]) => void) | null = null;
+
+  private pollInterval: NodeJS.Timeout | null = null;
+
+  // Tracked PIDs: resolved once and re-used (re-discovered when null)
+  private pidMap: Record<string, number | null> = {
+    NestJS: process.pid,  // Always known immediately
+    n8n: null,
+    Ollama: null,
+  };
+
+  // ─── Lifecycle ─────────────────────────────────────────────────────────────
+
+  async onModuleInit() {
+    this.logger.log('🟢 SurveillanceService booting — starting 500ms PID poll loop');
+    await this.discoverPids();
+    this.pollInterval = setInterval(() => this.tick(), 500);
+  }
+
+  onModuleDestroy() {
+    if (this.pollInterval) clearInterval(this.pollInterval);
+    this.logger.log('🔴 SurveillanceService stopped');
+  }
+
+  // ─── Public API ────────────────────────────────────────────────────────────
+
+  registerMetricsCallback(cb: (metrics: MonitorPayload[]) => void) {
+    this.onMetricsUpdate = cb;
+  }
+
+  getLatestMetrics(): MonitorPayload[] {
+    return this._latestMetrics;
+  }
+
+  // ─── PID Discovery ─────────────────────────────────────────────────────────
+
+  /**
+   * Uses find-process to locate n8n and Ollama PIDs.
+   * Called once at boot and retried on each tick for any service still null.
+   */
+  private async discoverPids() {
+    // --- n8n: a Node.js process whose command line contains "n8n" ---
+    if (!this.pidMap['n8n']) {
+      try {
+        const nodeProcs = await find('name', 'node', true);
+        const n8nProc = nodeProcs.find(
+          (p: any) => p.cmd && p.cmd.toLowerCase().includes('n8n'),
+        );
+        if (n8nProc) {
+          this.pidMap['n8n'] = n8nProc.pid;
+          this.logger.log(`🔍 n8n PID discovered: ${n8nProc.pid}`);
+        }
+      } catch (e) {
+        // n8n not running yet — will retry next tick
+      }
+    }
+
+    // --- Ollama: match the server child process by executable name ---
+    if (!this.pidMap['Ollama']) {
+      try {
+        // On Windows the process is "ollama_llama_server.exe"; on Linux "ollama"
+        const ollamaNames = ['ollama_llama_server', 'ollama'];
+        for (const name of ollamaNames) {
+          const procs = await find('name', name, true);
+          if (procs.length > 0) {
+            this.pidMap['Ollama'] = procs[0].pid;
+            this.logger.log(`🔍 Ollama PID discovered: ${procs[0].pid} (${name})`);
+            break;
+          }
+        }
+      } catch (e) {
+        // Ollama not running yet — will retry next tick
+      }
+    }
+  }
+
+  // ─── Poll Tick ─────────────────────────────────────────────────────────────
+
+  private async tick() {
+    // Retry PID discovery for any services still unknown
+    const hasMissingPids = Object.values(this.pidMap).some((v) => v === null);
+    if (hasMissingPids) await this.discoverPids();
+
+    const activePids = Object.entries(this.pidMap).filter(
+      ([, pid]) => pid !== null,
+    ) as [string, number][];
+
+    if (activePids.length === 0) return;
+
+    const pidsToQuery = activePids.map(([, pid]) => pid);
+
+    let stats: Record<number, pidusage.Status>;
+    try {
+      stats = await pidusage(pidsToQuery);
+    } catch (e) {
+      // A process may have died — invalidate its PID so it gets re-discovered
+      this.invalidateDeadPids(e);
+      return;
+    }
+
+    const metrics: MonitorPayload[] = activePids.map(([service, pid]) => ({
+      service,
+      pid,
+      cpu: parseFloat((stats[pid]?.cpu ?? 0).toFixed(2)),
+      memory: stats[pid]?.memory ?? 0,   // bytes — let the UI format it
+      timestamp: new Date(),
+    }));
+
+    this._latestMetrics = metrics;
+
+    if (this.onMetricsUpdate) {
+      this.onMetricsUpdate(metrics);
+    }
+  }
+
+  // ─── Helpers ───────────────────────────────────────────────────────────────
+
+  private invalidateDeadPids(error: any) {
+    // pidusage throws with the bad PID in the message — reset it so we re-search
+    const msg = String(error);
+    for (const [service, pid] of Object.entries(this.pidMap)) {
+      if (pid !== null && msg.includes(String(pid)) && service !== 'NestJS') {
+        this.logger.warn(`⚠️ PID ${pid} (${service}) appears dead — resetting`);
+        this.pidMap[service] = null;
+      }
+    }
+  }
+}

+ 1 - 1
test/app.e2e-spec.ts

@@ -1,6 +1,6 @@
 import { Test, TestingModule } from '@nestjs/testing';
 import { INestApplication } from '@nestjs/common';
-import * as request from 'supertest';
+import request from 'supertest';
 import { App } from 'supertest/types';
 import { AppModule } from './../src/app.module';
 

+ 1 - 1
tsconfig.json

@@ -6,10 +6,10 @@
     "emitDecoratorMetadata": true,
     "experimentalDecorators": true,
     "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
     "target": "ES2023",
     "sourceMap": true,
     "outDir": "./dist",
-    "baseUrl": "./",
     "incremental": true,
     "skipLibCheck": true,
     "strictNullChecks": true,