diff --git a/interface/web/.gitignore b/interface/web/.gitignore old mode 100644 new mode 100755 diff --git a/interface/web/.nvmrc b/interface/web/.nvmrc old mode 100644 new mode 100755 diff --git a/interface/web/README.md b/interface/web/README.md old mode 100644 new mode 100755 diff --git a/interface/web/eslint.config.js b/interface/web/eslint.config.js old mode 100644 new mode 100755 diff --git a/interface/web/index.html b/interface/web/index.html old mode 100644 new mode 100755 diff --git a/interface/web/package-lock.json b/interface/web/package-lock.json old mode 100644 new mode 100755 index dfe520c..9bd5101 --- a/interface/web/package-lock.json +++ b/interface/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "web", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "web", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { "react": "^19.2.4", "react-dom": "^19.2.4" @@ -24,13 +24,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -39,9 +39,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -49,21 +49,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -80,14 +80,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -97,14 +97,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -114,9 +114,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -124,29 +124,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -156,9 +156,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -166,9 +166,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -176,9 +176,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -186,27 +186,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -216,33 +216,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -250,23 +250,23 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -276,9 +276,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -557,14 +557,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -576,9 +576,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.124.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", - "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -586,9 +586,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -603,9 +603,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -620,9 +620,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -637,9 +637,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -654,9 +654,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -671,9 +671,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -688,9 +688,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -705,9 +705,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -722,9 +722,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -739,9 +739,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -756,9 +756,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -773,9 +773,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -790,9 +790,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -800,18 +800,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -826,9 +826,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -850,9 +850,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -991,9 +991,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", - "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1059,9 +1059,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001788", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", - "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, "funding": [ { @@ -1188,9 +1188,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.336", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.336.tgz", - "integrity": "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==", + "version": "1.5.375", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", + "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", "dev": true, "license": "ISC" }, @@ -1644,10 +1644,20 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2042,9 +2052,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -2068,11 +2078,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/optionator": { "version": "0.9.4", @@ -2178,9 +2191,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -2198,7 +2211,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2258,14 +2271,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -2274,27 +2287,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", - "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -2374,9 +2387,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -2453,17 +2466,17 @@ } }, "node_modules/vite": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", - "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -2479,7 +2492,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/interface/web/package.json b/interface/web/package.json old mode 100644 new mode 100755 index 244254d..67cf7f4 --- a/interface/web/package.json +++ b/interface/web/package.json @@ -1,7 +1,7 @@ { "name": "web", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/interface/web/public/favicon.svg b/interface/web/public/favicon.svg old mode 100644 new mode 100755 diff --git a/interface/web/public/icons.svg b/interface/web/public/icons.svg old mode 100644 new mode 100755 diff --git a/interface/web/public/n small.png b/interface/web/public/n small.png old mode 100644 new mode 100755 diff --git a/interface/web/src/App.css b/interface/web/src/App.css old mode 100644 new mode 100755 diff --git a/interface/web/src/App.jsx b/interface/web/src/App.jsx old mode 100644 new mode 100755 index dc76e46..b3b6b75 --- a/interface/web/src/App.jsx +++ b/interface/web/src/App.jsx @@ -19,10 +19,12 @@ function timeAgo(epochSeconds) { function App() { const [currentPage, setCurrentPage] = useState("chatbot"); const [status, setStatus] = useState("Checking Synapse..."); + const [version, setVersion] = useState(""); const [ollamaStatus, setOllamaStatus] = useState("checking"); const [showStatusTooltip, setShowStatusTooltip] = useState(false); const [activeConversationId, setActiveConversationId] = useState(() => crypto.randomUUID()); + const [isModelPulling, setIsModelPulling] = useState(false); const [conversations, setConversations] = useState([]); const [search, setSearch] = useState(""); const [hoveredConvId, setHoveredConvId] = useState(null); @@ -33,6 +35,7 @@ function App() { .then(r => r.json()) .then(d => { setStatus(d.status ?? "Unknown"); + setVersion(d.version ?? ""); setOllamaStatus(d.ollama ?? "unavailable"); }) .catch(() => { @@ -91,6 +94,25 @@ function App() { setCurrentPage("chatbot"); }; + const renameConversation = async (e, conv) => { + e.stopPropagation(); + const current = conv.title || conv.preview || ""; + const next = window.prompt("Rename conversation:", current); + if (next == null) return; + const title = next.trim(); + if (!title || title === current) return; + try { + const response = await fetch(`${API_BASE}/conversations/${conv.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title }), + }); + if (response.ok) loadConversations(search); + } catch (error) { + console.error("Failed to rename conversation:", error); + } + }; + const deleteConversation = async (e, conversationId) => { e.stopPropagation(); if (!window.confirm("Delete this conversation?")) return; @@ -108,9 +130,9 @@ function App() { }; const navItems = [ - { key: "chatbot", label: "πŸ’¬ Chatbot" }, - { key: "playbook", label: "πŸ“– Playbook" }, - { key: "models", label: "πŸ€– Models" }, + { key: "chatbot", label: "πŸ’¬ Chat" }, + { key: "playbook", label: "πŸ“– Playbooks" }, + { key: "models", label: "πŸ€– Models", badge: isModelPulling }, { key: "memory", label: "🧠 Memory" }, { key: "settings", label: "βš™οΈ Settings" }, ]; @@ -120,6 +142,7 @@ function App() { {/* Sidebar */} {/* Main Content */} -
- {currentPage === "chatbot" ? ( +
+ {/* Models stays mounted so active downloads survive page navigation */} +
+ +
+ {currentPage === "chatbot" && ( loadConversations(search)} /> - ) - : currentPage === "playbook" ? - : currentPage === "models" ? - : currentPage === "memory" ? - : } + )} + {currentPage === "playbook" && } + {currentPage === "memory" && } + {currentPage === "settings" && }
); diff --git a/interface/web/src/Chatbot.jsx b/interface/web/src/Chatbot.jsx old mode 100644 new mode 100755 index d3c498c..1477b84 --- a/interface/web/src/Chatbot.jsx +++ b/interface/web/src/Chatbot.jsx @@ -3,7 +3,7 @@ import { useState, useRef, useEffect } from "react"; import { API_BASE } from "./config"; import { Markdown } from "./Markdown"; -export function Chatbot({ status, conversationId, setConversationId, onConversationChanged }) { +export function Chatbot({ conversationId, setConversationId, onConversationChanged }) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); @@ -67,7 +67,7 @@ export function Chatbot({ status, conversationId, setConversationId, onConversat headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model }), }); - } catch {} + } catch { /* persisting the model choice is best-effort */ } }; const startNewChat = () => { @@ -145,7 +145,18 @@ export function Chatbot({ status, conversationId, setConversationId, onConversat if (!payload.trim()) { pendingEventType = null; continue; } if (pendingEventType === "meta") { - try { setLastStats(JSON.parse(payload)); } catch { /* ignore */ } + try { + const stats = JSON.parse(payload); + setLastStats(stats); + // Tag this message with the model that answered + if (stats.model) { + setMessages(prev => { + const updated = [...prev]; + updated[assistantIndex] = { ...updated[assistantIndex], model: stats.model }; + return updated; + }); + } + } catch { /* ignore */ } pendingEventType = null; continue; } @@ -158,6 +169,18 @@ export function Chatbot({ status, conversationId, setConversationId, onConversat pendingEventType = null; continue; } + if (pendingEventType === "done") { + // Answer is complete; re-enable input while the backend finishes + // slow post-processing (title, memory) on the still-open stream. + setLoading(false); + pendingEventType = null; + continue; + } + if (pendingEventType === "title") { + if (onConversationChanged) onConversationChanged(); + pendingEventType = null; + continue; + } if (pendingEventType === "error") { try { const err = JSON.parse(payload); @@ -171,11 +194,14 @@ export function Chatbot({ status, conversationId, setConversationId, onConversat } pendingEventType = null; + let token = payload; + try { token = JSON.parse(payload); } catch { /* plain text fallback */ } + setMessages(prev => { const updated = [...prev]; updated[assistantIndex] = { ...updated[assistantIndex], - content: (updated[assistantIndex].content || "") + payload, + content: (updated[assistantIndex].content || "") + token, }; return updated; }); @@ -368,17 +394,19 @@ export function Chatbot({ status, conversationId, setConversationId, onConversat >
{msg.role === "user" ? {msg.content} - : msg.content + : msg.content ? : Thinking... } diff --git a/interface/web/src/Markdown.jsx b/interface/web/src/Markdown.jsx old mode 100644 new mode 100755 index 8b41241..127f018 --- a/interface/web/src/Markdown.jsx +++ b/interface/web/src/Markdown.jsx @@ -1,47 +1,122 @@ +import { useState } from "react"; + +// Parse content into an array of {type, value, lang, streaming} blocks. +// Handles: +// - ```lang\ncode``` (well-formed) +// - ```code``` (no language, no newline β€” model formatting bug) +// - ```lang\ncode (unclosed β€” streaming in progress) +function parseBlocks(content) { + const blocks = []; + let i = 0; + + while (i < content.length) { + const fenceStart = content.indexOf("```", i); + + if (fenceStart === -1) { + if (i < content.length) blocks.push({ type: "text", value: content.slice(i) }); + break; + } + + if (fenceStart > i) blocks.push({ type: "text", value: content.slice(i, fenceStart) }); + + let j = fenceStart + 3; + let lang = ""; + + // Language specifier is valid only when word-chars are followed by a newline. + // If there's no newline (e.g. ```pythonprint(...)) treat everything as code. + const langMatch = content.slice(j).match(/^(\w+)(\r?\n)/); + if (langMatch) { + lang = langMatch[1]; + j += langMatch[0].length; + } else if (/^\r?\n/.test(content.slice(j))) { + j += content[j] === "\r" ? 2 : 1; + } + // else: no newline β€” leave j as-is, lang stays "" + + const closeIdx = content.indexOf("```", j); + if (closeIdx === -1) { + blocks.push({ type: "code", lang, value: content.slice(j), streaming: true }); + i = content.length; + } else { + blocks.push({ type: "code", lang, value: content.slice(j, closeIdx).replace(/\n$/, ""), streaming: false }); + i = closeIdx + 3; + } + } + + return blocks; +} + export function Markdown({ content }) { if (!content) return null; - - const blocks = []; - const codeBlockRe = /```(\w*)\n?([\s\S]*?)```/g; - let last = 0; - let match; - - while ((match = codeBlockRe.exec(content)) !== null) { - if (match.index > last) { - blocks.push({ type: "text", value: content.slice(last, match.index) }); - } - blocks.push({ type: "code", lang: match[1], value: match[2] }); - last = match.index + match[0].length; - } - if (last < content.length) { - blocks.push({ type: "text", value: content.slice(last) }); - } - + const blocks = parseBlocks(content); return (
{blocks.map((block, i) => - block.type === "code" ? ( -
-            {block.value.trimEnd()}
-          
- ) : ( - - ) + block.type === "code" + ? + : )}
); } +function CodeBlock({ lang, value, streaming }) { + const [copied, setCopied] = useState(false); + + const copy = () => { + navigator.clipboard.writeText(value.trimEnd()).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }); + }; + + return ( +
+
+ + {lang || "code"} + {streaming && ●} + + {!streaming && ( + + )} +
+
+        {value.trimEnd()}
+      
+
+ ); +} + function TextBlock({ text }) { const lines = text.split("\n"); const elements = []; @@ -50,7 +125,6 @@ function TextBlock({ text }) { while (i < lines.length) { const line = lines[i]; - // Heading const hMatch = line.match(/^(#{1,3})\s+(.+)/); if (hMatch) { const level = hMatch[1].length; @@ -60,11 +134,9 @@ function TextBlock({ text }) { {inlineMarkdown(hMatch[2])}
); - i++; - continue; + i++; continue; } - // Unordered list if (/^[-*]\s+/.test(line)) { const items = []; while (i < lines.length && /^[-*]\s+/.test(lines[i])) { @@ -75,7 +147,6 @@ function TextBlock({ text }) { continue; } - // Ordered list if (/^\d+\.\s+/.test(line)) { const items = []; while (i < lines.length && /^\d+\.\s+/.test(lines[i])) { @@ -86,21 +157,16 @@ function TextBlock({ text }) { continue; } - // Horizontal rule if (/^---+$/.test(line.trim())) { elements.push(
); - i++; - continue; + i++; continue; } - // Empty line β†’ small gap if (line.trim() === "") { elements.push(
); - i++; - continue; + i++; continue; } - // Regular paragraph line elements.push(
{inlineMarkdown(line)}
); i++; } @@ -116,7 +182,6 @@ function inlineMarkdown(text) { while ((m = re.exec(text)) !== null) { if (m.index > last) parts.push(text.slice(last, m.index)); - if (m[2] !== undefined) { parts.push({m[2]}); } else if (m[3] !== undefined) { diff --git a/interface/web/src/Memory.jsx b/interface/web/src/Memory.jsx old mode 100644 new mode 100755 index e683684..946da2c --- a/interface/web/src/Memory.jsx +++ b/interface/web/src/Memory.jsx @@ -13,6 +13,28 @@ function groupBySection(items) { return map; } +function indentLevelOf(text) { + const m = text.match(/^( *)/); + return Math.floor((m ? m[0].length : 0) / 2); +} + +// Collapse a section's items into top-level "groups": each group is a parent +// (indent 0) plus its consecutive indented children. +function computeGroups(sectionItems) { + const groups = []; + const groupIndexById = {}; + for (const item of sectionItems) { + const lvl = indentLevelOf(item.text); + if (lvl === 0 || groups.length === 0) { + groups.push([item]); + } else { + groups[groups.length - 1].push(item); + } + groupIndexById[item.id] = groups.length - 1; + } + return { groups, groupIndexById }; +} + export function Memory() { const [items, setItems] = useState([]); const [editing, setEditing] = useState(null); // { id, section, text } or null @@ -20,6 +42,8 @@ export function Memory() { const [newSection, setNewSection] = useState(""); const [showNewSection, setShowNewSection] = useState(false); const [message, setMessage] = useState(""); + const [draggingId, setDraggingId] = useState(null); + const [dragOver, setDragOver] = useState(null); // { id, position: "before"|"after" } const loadItems = useCallback(async () => { try { @@ -36,22 +60,30 @@ export function Memory() { const flash = (msg) => { setMessage(msg); setTimeout(() => setMessage(""), 3000); }; const saveNew = async (section, text) => { - if (!text.trim()) return; - const res = await fetch(`${API_BASE}/memory`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ section, text: text.trim() }), - }); - if (res.ok) { loadItems(); setAdding(null); flash("Saved."); } + // Split on newlines so a pasted list becomes one memory per line (leading + // spaces kept for nesting). Direct write β€” no LLM curator, saves every line. + const lines = text.split("\n").map(l => l.replace(/\s+$/, "")).filter(l => l.trim()); + if (!lines.length) return; + let ok = 0; + for (const line of lines) { + const res = await fetch(`${API_BASE}/memory`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ section, text: line }), + }); + if (res.ok) ok++; + } + if (ok) { loadItems(); setAdding(null); flash(ok === 1 ? "Saved." : `Saved ${ok} items.`); } else flash("Save failed."); }; const saveEdit = async (id, section, text) => { - if (!text.trim()) return; + const cleaned = text.replace(/\s+$/, ""); + if (!cleaned.trim()) return; const res = await fetch(`${API_BASE}/memory/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ section, text: text.trim() }), + body: JSON.stringify({ section, text: cleaned }), }); if (res.ok) { loadItems(); setEditing(null); flash("Updated."); } else flash("Update failed."); @@ -64,6 +96,154 @@ export function Memory() { else flash("Delete failed."); }; + const reorderSection = async (section, ids) => { + try { + const res = await fetch(`${API_BASE}/memory/reorder`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ section, ids }), + }); + const result = await res.json(); + if (!res.ok || result.ok === false) { + throw new Error("Memory reorder failed"); + } + loadItems(); + } catch (err) { + setMessage(err.message); + } + }; + + const moveItem = (id, direction) => { + const item = items.find(i => i.id === id); + if (!item) return; + const section = item.section || "General"; + const sectionItems = items.filter(i => (i.section || "General") === section); + const { groups, groupIndexById } = computeGroups(sectionItems); + const lvl = indentLevelOf(item.text); + + if (lvl === 0) { + // Swap whole group with previous/next group + const gIdx = groupIndexById[id]; + const tIdx = direction === "up" ? gIdx - 1 : gIdx + 1; + if (tIdx < 0 || tIdx >= groups.length) return; + const newGroups = [...groups]; + [newGroups[gIdx], newGroups[tIdx]] = [newGroups[tIdx], newGroups[gIdx]]; + reorderSection(section, newGroups.flat().map(i => i.id)); + } else { + // Swap child with previous/next sibling within same group; never past the parent + const gIdx = groupIndexById[id]; + const group = groups[gIdx]; + const localIdx = group.findIndex(i => i.id === id); + const tLocal = direction === "up" ? localIdx - 1 : localIdx + 1; + if (tLocal <= 0 || tLocal >= group.length) return; // tLocal === 0 is the parent + const newGroup = [...group]; + [newGroup[localIdx], newGroup[tLocal]] = [newGroup[tLocal], newGroup[localIdx]]; + const newGroups = [...groups]; + newGroups[gIdx] = newGroup; + reorderSection(section, newGroups.flat().map(i => i.id)); + } + }; + + const onItemDragStart = (e, item) => { + setDraggingId(item.id); + e.dataTransfer.effectAllowed = "move"; + try { e.dataTransfer.setData("text/plain", item.id); } catch { /* some browsers restrict */ } + }; + + const onItemDragOver = (e, targetItem, section) => { + if (!draggingId || draggingId === targetItem.id) return; + const dragged = items.find(i => i.id === draggingId); + if (!dragged || (dragged.section || "General") !== section) return; + + const sectionItems = items.filter(i => (i.section || "General") === section); + const { groups, groupIndexById } = computeGroups(sectionItems); + const draggedLevel = indentLevelOf(dragged.text); + const targetGroupIdx = groupIndexById[targetItem.id]; + const draggedGroupIdx = groupIndexById[dragged.id]; + const rect = e.currentTarget.getBoundingClientRect(); + const inUpperHalf = e.clientY < rect.top + rect.height / 2; + + if (draggedLevel === 0) { + // Top-level drag: snap to group boundaries; ignore hover over own group + if (targetGroupIdx === draggedGroupIdx) { + if (dragOver) setDragOver(null); + return; + } + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + const targetGroup = groups[targetGroupIdx]; + const isTargetParent = targetItem.id === targetGroup[0].id; + const position = (isTargetParent && inUpperHalf) ? "before" : "after"; + if (dragOver?.kind !== "group" || dragOver?.section !== section || dragOver?.groupIdx !== targetGroupIdx || dragOver?.position !== position) { + setDragOver({ kind: "group", section, groupIdx: targetGroupIdx, position }); + } + } else { + // Child drag: confined to its own parent group + if (targetGroupIdx !== draggedGroupIdx) { + if (dragOver) setDragOver(null); + return; + } + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + const targetGroup = groups[targetGroupIdx]; + const isTargetParent = targetItem.id === targetGroup[0].id; + let position = inUpperHalf ? "before" : "after"; + if (isTargetParent) position = "after"; // child can't go before its parent + if (dragOver?.kind !== "item" || dragOver?.section !== section || dragOver?.id !== targetItem.id || dragOver?.position !== position) { + setDragOver({ kind: "item", section, id: targetItem.id, position }); + } + } + }; + + const onItemDrop = (e, targetItem, section) => { + e.preventDefault(); + const dragged = items.find(i => i.id === draggingId); + if (!dragged || dragged.id === targetItem.id || (dragged.section || "General") !== section || !dragOver) { + setDraggingId(null); + setDragOver(null); + return; + } + const sectionItems = items.filter(i => (i.section || "General") === section); + const { groups, groupIndexById } = computeGroups(sectionItems); + const draggedLevel = indentLevelOf(dragged.text); + + let block, anchorId, after; + if (draggedLevel === 0 && dragOver.kind === "group") { + block = groups[groupIndexById[dragged.id]]; + const targetGroup = groups[dragOver.groupIdx]; + anchorId = dragOver.position === "before" ? targetGroup[0].id : targetGroup[targetGroup.length - 1].id; + after = dragOver.position === "after"; + } else if (draggedLevel > 0 && dragOver.kind === "item") { + block = [dragged]; + anchorId = dragOver.id; + after = dragOver.position === "after"; + } else { + setDraggingId(null); + setDragOver(null); + return; + } + + const blockIds = new Set(block.map(b => b.id)); + const remainder = sectionItems.filter(i => !blockIds.has(i.id)); + let insertIdx = remainder.findIndex(i => i.id === anchorId); + if (insertIdx < 0) { + setDraggingId(null); + setDragOver(null); + return; + } + if (after) insertIdx += 1; + const newOrder = [...remainder.slice(0, insertIdx), ...block, ...remainder.slice(insertIdx)]; + + setDraggingId(null); + setDragOver(null); + reorderSection(section, newOrder.map(i => i.id)); + }; + + const onItemDragEnd = () => { + setDraggingId(null); + setDragOver(null); + }; + const addSection = () => { const sec = newSection.trim(); if (!sec) return; @@ -98,7 +278,7 @@ export function Memory() { }); return ( -
+

Memory

@@ -148,9 +328,49 @@ export function Memory() {
{/* Bullet items */} + {(() => { + const sectionItems = grouped[section] || []; + const { groups, groupIndexById } = computeGroups(sectionItems); + return (
- {(grouped[section] || []).map(item => ( -
+ {sectionItems.map((item) => { + const indentMatch = item.text.match(/^( *)(.*)$/s); + const indentLevel = indentMatch ? Math.floor(indentMatch[1].length / 2) : 0; + const displayText = indentMatch ? indentMatch[2] : item.text; + const gIdx = groupIndexById[item.id]; + const group = groups[gIdx]; + const localIdx = group.findIndex(i => i.id === item.id); + const isParentRow = localIdx === 0; + const isLastInGroup = localIdx === group.length - 1; + const arrowUpDisabled = indentLevel === 0 ? gIdx === 0 : localIdx <= 1; + const arrowDownDisabled = indentLevel === 0 ? gIdx === groups.length - 1 : isLastInGroup; + const isDragging = draggingId === item.id; + let showLineBefore = false, showLineAfter = false; + if (dragOver && dragOver.section === section) { + if (dragOver.kind === "group" && dragOver.groupIdx === gIdx) { + if (dragOver.position === "before" && isParentRow) showLineBefore = true; + if (dragOver.position === "after" && isLastInGroup) showLineAfter = true; + } else if (dragOver.kind === "item" && dragOver.id === item.id) { + if (dragOver.position === "before") showLineBefore = true; + else showLineAfter = true; + } + } + return ( +
onItemDragStart(e, item)} + onDragOver={(e) => onItemDragOver(e, item, section)} + onDrop={(e) => onItemDrop(e, item, section)} + onDragEnd={onItemDragEnd} + style={{ + marginBottom: "0.4rem", + paddingLeft: `${indentLevel * 1.5}rem`, + borderTop: showLineBefore ? "2px solid #4db8ff" : "2px solid transparent", + borderBottom: showLineAfter ? "2px solid #4db8ff" : "2px solid transparent", + opacity: isDragging ? 0.4 : 1, + }} + > {editing?.id === item.id ? ( ) : ( -
+
setEditing({ id: item.id, section, text: item.text })} + title="Drag to reorder Β· double-click to edit" + style={{ display: "flex", alignItems: "flex-start", gap: "0.5rem", cursor: "grab" }} + > - - {item.text} + {displayText}
e.currentTarget.style.opacity = 1} onMouseLeave={e => e.currentTarget.style.opacity = 0.4} > + onClick={() => moveItem(item.id, "up")} + disabled={arrowUpDisabled} + title="Move up" + style={{ ...btnStyle("#333"), fontSize: "0.72rem", padding: "0.2rem 0.4rem", opacity: arrowUpDisabled ? 0.3 : 1, cursor: arrowUpDisabled ? "default" : "pointer" }} + >β–² +
)}
- ))} + ); + })} {/* Inline add form */} {adding?.section === section && ( @@ -191,6 +424,8 @@ export function Memory() { /> )}
+ ); + })()}
))} @@ -203,19 +438,34 @@ export function Memory() { ); } +function handleIndentKey(e, setText) { + if (e.key !== "Tab") return false; + e.preventDefault(); + if (e.shiftKey) { + setText(t => t.startsWith(" ") ? t.slice(2) : t); + } else { + setText(t => " " + t); + } + return true; +} + function AddRow({ onSave, onCancel, inputStyle, btnStyle }) { const [text, setText] = useState(""); return (
- - setText(e.target.value)} - onKeyDown={e => { if (e.key === "Enter") onSave(text); if (e.key === "Escape") onCancel(); }} - style={{ ...inputStyle }} + onKeyDown={e => { + if (handleIndentKey(e, setText)) return; + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSave(text); } + if (e.key === "Escape") onCancel(); + }} + style={{ ...inputStyle, resize: "vertical", fontFamily: "inherit", minHeight: "2.1rem" }} /> @@ -233,7 +483,11 @@ function EditRow({ initial, onSave, onCancel, inputStyle, btnStyle }) { type="text" value={text} onChange={e => setText(e.target.value)} - onKeyDown={e => { if (e.key === "Enter") onSave(text); if (e.key === "Escape") onCancel(); }} + onKeyDown={e => { + if (handleIndentKey(e, setText)) return; + if (e.key === "Enter") onSave(text); + if (e.key === "Escape") onCancel(); + }} style={{ ...inputStyle }} /> diff --git a/interface/web/src/Models.jsx b/interface/web/src/Models.jsx old mode 100644 new mode 100755 index aeaa7c3..b66ef7e --- a/interface/web/src/Models.jsx +++ b/interface/web/src/Models.jsx @@ -1,28 +1,25 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { API_BASE } from "./config"; -export function Models({ status }) { +export function Models({ onPullStateChange }) { const [models, setModels] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [pulling, setPulling] = useState(false); const [modelName, setModelName] = useState(""); const [pullProgress, setPullProgress] = useState(""); - const [ollamaStatus, setOllamaStatus] = useState("checking"); - const checkOllamaStatus = async () => { + const checkOllamaStatus = useCallback(async () => { try { const response = await fetch(`${API_BASE}/ollama/status`); const data = await response.json(); - setOllamaStatus(data.status); return data.status === "running"; } catch { - setOllamaStatus("unavailable"); return false; } - }; + }, []); - const loadModels = async () => { + const loadModels = useCallback(async () => { setLoading(true); setError(""); try { @@ -35,14 +32,15 @@ export function Models({ status }) { const response = await fetch(`${API_BASE}/models/details`); if (!response.ok) throw new Error("Failed to fetch models"); const data = await response.json(); - setModels(data.models || []); + // Embedding models (nomic-embed-text) are infrastructure β€” hide them. + setModels((data.models || []).filter((m) => !m.name.toLowerCase().includes("embed"))); } catch (err) { setError(`Error loading models: ${err.message}`); setModels([]); } finally { setLoading(false); } - }; + }, [checkOllamaStatus]); const pullModel = async () => { if (!modelName.trim()) { @@ -51,6 +49,7 @@ export function Models({ status }) { } setPulling(true); + onPullStateChange?.(true); setError(""); setPullProgress(""); @@ -99,6 +98,7 @@ export function Models({ status }) { setError(`Error pulling model: ${err.message}`); } finally { setPulling(false); + onPullStateChange?.(false); } }; @@ -123,8 +123,9 @@ export function Models({ status }) { useEffect(() => { loadModels(); const interval = setInterval(loadModels, 30000); + return () => clearInterval(interval); - }, []); + }, [loadModels]); return (
@@ -214,7 +215,7 @@ export function Models({ status }) { )}
- {/* Models List */} + {/* Local Models List */}

Available Models ({models.length}) diff --git a/interface/web/src/Playbook.jsx b/interface/web/src/Playbook.jsx old mode 100644 new mode 100755 index 2e37aae..8706a07 --- a/interface/web/src/Playbook.jsx +++ b/interface/web/src/Playbook.jsx @@ -1,7 +1,7 @@ import { useEffect, useState, useCallback, useRef } from "react"; import { API_BASE } from "./config"; -export function Playbook({ status }) { +export function Playbook() { const [playbooks, setPlaybooks] = useState([]); const [selectedId, setSelectedId] = useState(null); const [form, setForm] = useState({ title: "", goal: "", instructions: "", tags: "" }); @@ -29,6 +29,7 @@ export function Playbook({ status }) { } }, []); + // eslint-disable-next-line react-hooks/set-state-in-effect -- async fetch on mount; setPlaybooks runs after await, not a synchronous cascading render useEffect(() => { loadPlaybooks(); }, [loadPlaybooks]); const safeString = (v) => (v == null ? "" : String(v)); @@ -184,7 +185,6 @@ export function Playbook({ status }) { // Optimistic UI update β€” reorder within filtered set, keep unfiltered playbooks intact setPlaybooks(prev => { const filteredSet = new Set(currentOrder); - const unfiltered = prev.filter(p => !filteredSet.has(String(p.id))); const filteredMap = Object.fromEntries(prev.filter(p => filteredSet.has(String(p.id))).map(p => [String(p.id), p])); const reorderedFiltered = newFilteredOrder.map(id => filteredMap[id]).filter(Boolean); // Merge: place unfiltered items back in their original relative positions diff --git a/interface/web/src/Settings.jsx b/interface/web/src/Settings.jsx old mode 100644 new mode 100755 index 7016b55..fafebe7 --- a/interface/web/src/Settings.jsx +++ b/interface/web/src/Settings.jsx @@ -6,6 +6,10 @@ const DEFAULTS = { temperature: 0.7, system_prompt: "", timeout: 120, + gpu_offload: -1, // -1 = Auto; 0–100 = percent of layers forced onto the GPU + memory_model: "mistral:latest", // curator model for memory extraction + memory_gpu_offload: 0, // curator offload; 0 = all CPU/RAM (safe on small GPUs) + memory_merge_threshold: 0.80, // 0 = off; else update near-duplicate facts in place }; export function Settings() { @@ -13,11 +17,25 @@ export function Settings() { const [saved, setSaved] = useState(false); const [error, setError] = useState(""); + // Icon branding state + const [iconApps, setIconApps] = useState([]); + const [iconFilter, setIconFilter] = useState(""); + const [iconFrac, setIconFrac] = useState(0.60); + const [iconRing, setIconRing] = useState(false); + const [selectedApps, setSelectedApps] = useState([]); + const [brandQueue, setBrandQueue] = useState([]); + const [isBranding, setIsBranding] = useState(false); + useEffect(() => { fetch(`${API_BASE}/settings`) .then(r => r.ok ? r.json() : null) .then(d => { if (d) setForm(f => ({ ...f, ...d })); }) .catch(() => {}); + + fetch(`${API_BASE}/icons/apps`) + .then(r => r.ok ? r.json() : null) + .then(d => { if (d) setIconApps(d.apps || []); }) + .catch(() => {}); }, []); const update = (key, value) => setForm(f => ({ ...f, [key]: value })); @@ -41,6 +59,73 @@ export function Settings() { const reset = () => setForm(DEFAULTS); + // Icon branding logic + const toggleAppSelect = (app) => { + setSelectedApps(prev => { + const exists = prev.find(a => a.icon_name === app.icon_name); + if (exists) return prev.filter(a => a.icon_name !== app.icon_name); + return [...prev, app]; + }); + }; + + const removeFromQueue = (icon_name) => { + setSelectedApps(prev => prev.filter(a => a.icon_name !== icon_name)); + }; + + const brandAll = async () => { + if (!selectedApps.length || isBranding) return; + setIsBranding(true); + const queue = selectedApps.map(a => ({ ...a, status: "pending" })); + setBrandQueue(queue); + + for (let i = 0; i < queue.length; i++) { + const app = queue[i]; + setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "running" } : a)); + try { + const r = await fetch(`${API_BASE}/icons/brand`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + src_path: app.icon_path, + output_name: app.icon_name, + frac: iconFrac, + nexus_ring: iconRing, + reload: false, + }), + }); + if (!r.ok) throw new Error(await r.text()); + setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "done" } : a)); + } catch (e) { + setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "error", err: e.message } : a)); + } + } + + // Apply cache once after all are done + await fetch(`${API_BASE}/icons/apply`, { method: "POST" }).catch(() => {}); + + // Mark already_branded in the app list + setIconApps(prev => prev.map(a => { + if (selectedApps.find(s => s.icon_name === a.icon_name)) { + return { ...a, already_branded: true }; + } + return a; + })); + + setIsBranding(false); + }; + + const filteredApps = iconApps.filter(a => + a.name.toLowerCase().includes(iconFilter.toLowerCase()) + ); + + const statusIcon = (status) => { + if (status === "pending") return β—‹; + if (status === "running") return ⟳; + if (status === "done") return βœ“; + if (status === "error") return βœ—; + return null; + }; + const inputStyle = { width: "100%", padding: "0.6rem 0.75rem", @@ -69,8 +154,10 @@ export function Settings() { marginBottom: "1rem", }; + const displayQueue = isBranding ? brandQueue : selectedApps; + return ( -
+

Settings

{/* Generation */} @@ -99,6 +186,140 @@ export function Settings() { 2.0 β€” creative
+ +
+ + update("gpu_offload", parseInt(e.target.value))} + style={{ width: "100%", accentColor: "#b040c0", opacity: form.gpu_offload < 0 ? 0.4 : 1 }} + /> +
+ 0% β€” all CPU + 100% β€” all GPU +
+ +

+ Lower values push more of the model onto the CPU β€” slower, but frees VRAM and reduces GPU load. Layer count is resolved per model. +

+
+

+ + {/* Memory Curator */} +
+

Memory Curator

+

+ The model that reviews each exchange and saves new facts to memory, in the background after every reply. +

+ +
+ + update("memory_model", e.target.value)} + placeholder="mistral:latest (blank = same model as chat)" + style={inputStyle} + /> +

+ A dedicated model like mistral:latest extracts facts more reliably than a small chat model. +

+
+ +
+ + update("memory_gpu_offload", parseInt(e.target.value))} + style={{ width: "100%", accentColor: "#b040c0", opacity: form.memory_gpu_offload < 0 ? 0.4 : 1 }} + /> +
+ 0% β€” all CPU/RAM + 100% β€” all GPU +
+ +

+ Keep at 0% (all CPU/RAM) on a small GPU so the curator doesn't evict the chat model β€” on a ~4 GB GPU that thrash makes extraction time out and silently save nothing. Raise it only if you have spare VRAM. +

+
+ +
+ +

+ When a newly extracted fact closely matches an existing one, update that entry instead of adding a duplicate. Uses embeddings. +

+ {form.memory_merge_threshold > 0 && ( + <> + + update("memory_merge_threshold", parseFloat(e.target.value))} + style={{ width: "100%", accentColor: "#b040c0" }} + /> +
+ 0.60 β€” merges more + 0.98 β€” only near-identical +
+ + )} +
{/* Behaviour */} @@ -143,6 +364,165 @@ export function Settings() {
+ {/* Icon Branding */} +
+

App Icon Branding

+

+ Select apps on the left to composite their icon onto the NexusOS tile. +

+ + {/* Controls row */} +
+
+ + setIconFrac(parseFloat(e.target.value))} + style={{ width: "100%", accentColor: "#b040c0" }} + /> +
+ +
+ + {/* Shuttle */} +
+ {/* Left: app list */} +
+ setIconFilter(e.target.value)} + style={{ ...inputStyle, padding: "0.4rem 0.6rem", fontSize: "0.82rem" }} + /> +
+ {filteredApps.length === 0 && ( +
+ {iconApps.length === 0 ? "Loading…" : "No apps match"} +
+ )} + {filteredApps.map(app => { + const isSelected = !!selectedApps.find(a => a.icon_name === app.icon_name); + return ( +
!isBranding && !isSelected && app.icon_path && toggleAppSelect(app)} + style={{ + display: "flex", + alignItems: "center", + gap: "0.5rem", + padding: "0.35rem 0.6rem", + cursor: isBranding || isSelected || !app.icon_path ? "default" : "pointer", + background: isSelected ? "rgba(176,64,192,0.15)" : "transparent", + borderLeft: isSelected ? "2px solid #b040c0" : "2px solid transparent", + opacity: !app.icon_path ? 0.35 : 1, + }} + onMouseEnter={e => { if (!isSelected && app.icon_path && !isBranding) e.currentTarget.style.background = "rgba(255,255,255,0.04)"; }} + onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = "transparent"; }} + > + {app.icon_path ? ( + { e.target.style.display = "none"; }} + /> + ) : ( +
+ )} + + {app.name} + + {app.already_branded && ( + βœ“ + )} +
+ ); + })} +
+
+ + {/* Right: queue */} +
+
+ {isBranding ? `Branding ${brandQueue.length} app${brandQueue.length !== 1 ? "s" : ""}…` : `${selectedApps.length} selected`} +
+
+ {displayQueue.length === 0 && ( +
+ Click apps to add β†’ +
+ )} + {displayQueue.map(app => ( +
+ {isBranding ? ( + + {statusIcon(app.status)} + + ) : ( + + )} + + {app.name} + +
+ ))} +
+
+
+ + {/* Brand button */} +
+ + {!isBranding && brandQueue.length > 0 && brandQueue.every(a => a.status === "done") && ( + Done β€” panel reloaded + )} +
+
+ {/* Actions */}