feat: remove chat section from dashboard
- Remove Chat from sidebar navigation - Remove /chat route from App.tsx - Delete ChatPage component, gateway.ts client lib - Delete backend chat routes and gateway-relay WebSocket code - No other features depended on removed code
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
"better-auth": "^1.4.17",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwindcss": "^4.1.18",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -259,16 +261,28 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.10.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.54.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA=="],
|
||||
@@ -289,6 +303,8 @@
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
@@ -301,6 +317,8 @@
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
@@ -317,12 +335,24 @@
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
@@ -335,12 +365,18 @@
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.279", "", {}, "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||
@@ -369,8 +405,12 @@
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
@@ -399,20 +439,38 @@
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
||||
|
||||
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
@@ -467,10 +525,100 @@
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
||||
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
|
||||
|
||||
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
||||
|
||||
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
|
||||
|
||||
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
|
||||
|
||||
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
|
||||
|
||||
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
|
||||
|
||||
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||
|
||||
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
|
||||
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
|
||||
|
||||
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
|
||||
|
||||
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
|
||||
|
||||
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
|
||||
|
||||
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
|
||||
|
||||
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
|
||||
|
||||
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
||||
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
|
||||
|
||||
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
|
||||
|
||||
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
|
||||
|
||||
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
|
||||
|
||||
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
||||
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
@@ -491,6 +639,8 @@
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
@@ -503,18 +653,30 @@
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||
|
||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||
|
||||
"react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="],
|
||||
|
||||
"react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="],
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"rollup": ["rollup@4.57.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.0", "@rollup/rollup-android-arm64": "4.57.0", "@rollup/rollup-darwin-arm64": "4.57.0", "@rollup/rollup-darwin-x64": "4.57.0", "@rollup/rollup-freebsd-arm64": "4.57.0", "@rollup/rollup-freebsd-x64": "4.57.0", "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", "@rollup/rollup-linux-arm-musleabihf": "4.57.0", "@rollup/rollup-linux-arm64-gnu": "4.57.0", "@rollup/rollup-linux-arm64-musl": "4.57.0", "@rollup/rollup-linux-loong64-gnu": "4.57.0", "@rollup/rollup-linux-loong64-musl": "4.57.0", "@rollup/rollup-linux-ppc64-gnu": "4.57.0", "@rollup/rollup-linux-ppc64-musl": "4.57.0", "@rollup/rollup-linux-riscv64-gnu": "4.57.0", "@rollup/rollup-linux-riscv64-musl": "4.57.0", "@rollup/rollup-linux-s390x-gnu": "4.57.0", "@rollup/rollup-linux-x64-gnu": "4.57.0", "@rollup/rollup-linux-x64-musl": "4.57.0", "@rollup/rollup-openbsd-x64": "4.57.0", "@rollup/rollup-openharmony-arm64": "4.57.0", "@rollup/rollup-win32-arm64-msvc": "4.57.0", "@rollup/rollup-win32-ia32-msvc": "4.57.0", "@rollup/rollup-win32-x64-gnu": "4.57.0", "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA=="],
|
||||
@@ -533,8 +695,16 @@
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||
@@ -543,6 +713,10 @@
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
@@ -553,10 +727,26 @@
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
@@ -571,6 +761,8 @@
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
@@ -593,6 +785,10 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useSession } from "./lib/auth-client";
|
||||
// Lazy-loaded pages for code splitting
|
||||
const DashboardPage = lazy(() => import("./pages/DashboardPage").then(m => ({ default: m.DashboardPage })));
|
||||
const QueuePage = lazy(() => import("./pages/QueuePage").then(m => ({ default: m.QueuePage })));
|
||||
const ChatPage = lazy(() => import("./pages/ChatPage").then(m => ({ default: m.ChatPage })));
|
||||
const ProjectsPage = lazy(() => import("./pages/ProjectsPage").then(m => ({ default: m.ProjectsPage })));
|
||||
const TaskPage = lazy(() => import("./pages/TaskPage").then(m => ({ default: m.TaskPage })));
|
||||
const ActivityPage = lazy(() => import("./pages/ActivityPage").then(m => ({ default: m.ActivityPage })));
|
||||
@@ -36,7 +35,6 @@ function AuthenticatedApp() {
|
||||
<Route path="/queue" element={<Suspense fallback={<PageLoader />}><QueuePage /></Suspense>} />
|
||||
<Route path="/task/:taskRef" element={<Suspense fallback={<PageLoader />}><TaskPage /></Suspense>} />
|
||||
<Route path="/projects" element={<Suspense fallback={<PageLoader />}><ProjectsPage /></Suspense>} />
|
||||
<Route path="/chat" element={<Suspense fallback={<PageLoader />}><ChatPage /></Suspense>} />
|
||||
<Route path="/activity" element={<Suspense fallback={<PageLoader />}><ActivityPage /></Suspense>} />
|
||||
<Route path="/admin" element={<Suspense fallback={<PageLoader />}><AdminPage /></Suspense>} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
|
||||
@@ -12,7 +12,6 @@ const navItems = [
|
||||
{ to: "/queue", label: "Queue", icon: "📋", badgeKey: "queue" },
|
||||
{ to: "/projects", label: "Projects", icon: "📁", badgeKey: null },
|
||||
{ to: "/activity", label: "Activity", icon: "📝", badgeKey: null },
|
||||
{ to: "/chat", label: "Chat", icon: "💬", badgeKey: null },
|
||||
] as const;
|
||||
|
||||
export function DashboardLayout() {
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
/**
|
||||
* Chat WebSocket client for Hammer Dashboard
|
||||
*
|
||||
* Connects to the dashboard backend's WebSocket relay (which proxies to the Clawdbot gateway).
|
||||
* Authentication is handled via BetterAuth session cookie.
|
||||
*/
|
||||
|
||||
type MessageHandler = (msg: any) => void;
|
||||
type StateHandler = (state: "connecting" | "connected" | "disconnected") => void;
|
||||
|
||||
let reqCounter = 0;
|
||||
function nextId() {
|
||||
return `r${++reqCounter}`;
|
||||
}
|
||||
|
||||
export class ChatClient {
|
||||
private ws: WebSocket | null = null;
|
||||
private state: "connecting" | "connected" | "disconnected" = "disconnected";
|
||||
private pendingRequests = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void }>();
|
||||
private eventHandlers = new Map<string, Set<MessageHandler>>();
|
||||
private stateHandlers = new Set<StateHandler>();
|
||||
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private shouldReconnect = true;
|
||||
|
||||
connect() {
|
||||
this.shouldReconnect = true;
|
||||
this._connect();
|
||||
}
|
||||
|
||||
private _connect() {
|
||||
if (this.ws) {
|
||||
try { this.ws.close(); } catch {}
|
||||
}
|
||||
|
||||
// Build WebSocket URL from current page origin
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
// Backend is at the same origin via nginx proxy on Dokploy
|
||||
const wsUrl = `${wsProtocol}//${window.location.host}/api/chat/ws`;
|
||||
|
||||
this.setState("connecting");
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
// Send auth message with session cookie
|
||||
this._send({
|
||||
type: "auth",
|
||||
cookie: document.cookie,
|
||||
});
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
this._handleMessage(msg);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse message:", e);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.setState("disconnected");
|
||||
if (this.shouldReconnect) {
|
||||
this.reconnectTimer = setTimeout(() => this._connect(), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = () => {
|
||||
// onclose handles reconnect
|
||||
};
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.shouldReconnect = false;
|
||||
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
||||
if (this.ws) {
|
||||
try { this.ws.close(); } catch {}
|
||||
}
|
||||
this.ws = null;
|
||||
this.setState("disconnected");
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
return this.state === "connected";
|
||||
}
|
||||
|
||||
getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
onStateChange(handler: StateHandler): () => void {
|
||||
this.stateHandlers.add(handler);
|
||||
return () => { this.stateHandlers.delete(handler); };
|
||||
}
|
||||
|
||||
on(event: string, handler: MessageHandler): () => void {
|
||||
if (!this.eventHandlers.has(event)) {
|
||||
this.eventHandlers.set(event, new Set());
|
||||
}
|
||||
this.eventHandlers.get(event)!.add(handler);
|
||||
return () => { this.eventHandlers.get(event)?.delete(handler); };
|
||||
}
|
||||
|
||||
async request(method: string, params?: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.ws || this.state !== "connected") {
|
||||
reject(new Error("Not connected"));
|
||||
return;
|
||||
}
|
||||
const id = nextId();
|
||||
this.pendingRequests.set(id, { resolve, reject });
|
||||
this._send({ type: method, id, ...params });
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.pendingRequests.has(id)) {
|
||||
this.pendingRequests.delete(id);
|
||||
reject(new Error("Request timeout"));
|
||||
}
|
||||
}, 120000);
|
||||
});
|
||||
}
|
||||
|
||||
// Chat methods
|
||||
async chatHistory(sessionKey: string, limit = 50) {
|
||||
return this.request("chat.history", { sessionKey, limit });
|
||||
}
|
||||
|
||||
async chatSend(sessionKey: string, message: string) {
|
||||
return this.request("chat.send", {
|
||||
sessionKey,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
async chatAbort(sessionKey: string) {
|
||||
return this.request("chat.abort", { sessionKey });
|
||||
}
|
||||
|
||||
async sessionsList(limit = 50) {
|
||||
return this.request("sessions.list", { limit });
|
||||
}
|
||||
|
||||
private setState(state: "connecting" | "connected" | "disconnected") {
|
||||
this.state = state;
|
||||
this.stateHandlers.forEach((h) => h(state));
|
||||
}
|
||||
|
||||
private _send(msg: any) {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
}
|
||||
|
||||
private _handleMessage(msg: any) {
|
||||
// Auth response
|
||||
if (msg.type === "auth_ok") {
|
||||
this.setState("connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "error" && this.state !== "connected") {
|
||||
console.error("Auth failed:", msg.error);
|
||||
this.shouldReconnect = false;
|
||||
this.ws?.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Request response
|
||||
if (msg.type === "res") {
|
||||
const pending = this.pendingRequests.get(msg.id);
|
||||
if (pending) {
|
||||
this.pendingRequests.delete(msg.id);
|
||||
if (msg.ok) {
|
||||
pending.resolve(msg.payload);
|
||||
} else {
|
||||
pending.reject(new Error(msg.error || "Request failed"));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Error for a specific request
|
||||
if (msg.type === "error" && msg.id) {
|
||||
const pending = this.pendingRequests.get(msg.id);
|
||||
if (pending) {
|
||||
this.pendingRequests.delete(msg.id);
|
||||
pending.reject(new Error(msg.error || "Request failed"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Gateway events (forwarded from backend)
|
||||
if (msg.type === "event") {
|
||||
const handlers = this.eventHandlers.get(msg.event);
|
||||
if (handlers) {
|
||||
handlers.forEach((h) => h(msg.payload));
|
||||
}
|
||||
const wildcardHandlers = this.eventHandlers.get("*");
|
||||
if (wildcardHandlers) {
|
||||
wildcardHandlers.forEach((h) => h({ event: msg.event, ...msg.payload }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton
|
||||
let _client: ChatClient | null = null;
|
||||
|
||||
export function getChatClient(): ChatClient {
|
||||
if (!_client) {
|
||||
_client = new ChatClient();
|
||||
}
|
||||
return _client;
|
||||
}
|
||||
@@ -1,751 +0,0 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { getChatClient, type ChatClient } from "../lib/gateway";
|
||||
|
||||
interface ChatMessage {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
interface ChatThread {
|
||||
sessionKey: string;
|
||||
name: string;
|
||||
lastMessage?: string;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
interface GatewaySession {
|
||||
sessionKey: string;
|
||||
kind?: string;
|
||||
channel?: string;
|
||||
lastActivity?: string;
|
||||
messageCount?: number;
|
||||
}
|
||||
|
||||
function ThreadList({
|
||||
threads,
|
||||
activeThread,
|
||||
onSelect,
|
||||
onCreate,
|
||||
onRename,
|
||||
onDelete,
|
||||
onClose,
|
||||
onBrowseSessions,
|
||||
gatewaySessions,
|
||||
loadingGatewaySessions,
|
||||
showGatewaySessions,
|
||||
onToggleGatewaySessions,
|
||||
}: {
|
||||
threads: ChatThread[];
|
||||
activeThread: string | null;
|
||||
onSelect: (key: string) => void;
|
||||
onCreate: () => void;
|
||||
onRename?: (key: string, name: string) => void;
|
||||
onDelete?: (key: string) => void;
|
||||
onClose?: () => void;
|
||||
onBrowseSessions?: () => void;
|
||||
gatewaySessions?: GatewaySession[];
|
||||
loadingGatewaySessions?: boolean;
|
||||
showGatewaySessions?: boolean;
|
||||
onToggleGatewaySessions?: () => void;
|
||||
}) {
|
||||
const [editingKey, setEditingKey] = useState<string | null>(null);
|
||||
const [editName, setEditName] = useState("");
|
||||
|
||||
const startRename = (key: string, currentName: string) => {
|
||||
setEditingKey(key);
|
||||
setEditName(currentName);
|
||||
};
|
||||
|
||||
const commitRename = () => {
|
||||
if (editingKey && editName.trim() && onRename) {
|
||||
onRename(editingKey, editName.trim());
|
||||
}
|
||||
setEditingKey(null);
|
||||
};
|
||||
|
||||
// Check if a session key exists in local threads already
|
||||
const localSessionKeys = new Set(threads.map(t => t.sessionKey));
|
||||
|
||||
return (
|
||||
<div className="w-full sm:w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col h-full">
|
||||
<div className="p-3 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-600 dark:text-gray-400">Threads</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={onCreate}
|
||||
className="text-xs bg-amber-500 text-white px-2.5 py-1 rounded-lg hover:bg-amber-600 transition font-medium"
|
||||
>
|
||||
+ New
|
||||
</button>
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="sm:hidden text-gray-400 hover:text-gray-600 p-1"
|
||||
aria-label="Close threads"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{/* Local threads */}
|
||||
{threads.length === 0 && !showGatewaySessions ? (
|
||||
<div className="p-4 text-sm text-gray-400 dark:text-gray-500 text-center">
|
||||
No threads yet
|
||||
</div>
|
||||
) : (
|
||||
threads.map((thread) => (
|
||||
<div
|
||||
key={thread.sessionKey}
|
||||
className={`group relative w-full text-left px-3 py-3 border-b border-gray-50 dark:border-gray-800 transition cursor-pointer ${
|
||||
activeThread === thread.sessionKey
|
||||
? "bg-amber-50 dark:bg-amber-900/20 border-l-2 border-l-amber-500"
|
||||
: "hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
}`}
|
||||
onClick={() => onSelect(thread.sessionKey)}
|
||||
>
|
||||
{editingKey === thread.sessionKey ? (
|
||||
<input
|
||||
autoFocus
|
||||
className="text-sm font-medium text-gray-800 dark:text-gray-200 w-full bg-white dark:bg-gray-800 border border-amber-300 dark:border-amber-700 rounded px-1 py-0.5 outline-none"
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
onBlur={commitRename}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") commitRename();
|
||||
if (e.key === "Escape") setEditingKey(null);
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="text-sm font-medium text-gray-800 dark:text-gray-200 truncate pr-6"
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startRename(thread.sessionKey, thread.name);
|
||||
}}
|
||||
>
|
||||
{thread.name}
|
||||
</div>
|
||||
)}
|
||||
{thread.lastMessage && (
|
||||
<div className="text-xs text-gray-400 truncate mt-0.5">
|
||||
{thread.lastMessage}
|
||||
</div>
|
||||
)}
|
||||
{onDelete && editingKey !== thread.sessionKey && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(thread.sessionKey);
|
||||
}}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 text-gray-300 hover:text-red-400 transition p-1"
|
||||
aria-label="Delete thread"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
{/* Gateway sessions browser */}
|
||||
<div className="border-t border-gray-200 dark:border-gray-800">
|
||||
<button
|
||||
onClick={() => {
|
||||
onToggleGatewaySessions?.();
|
||||
if (!showGatewaySessions) onBrowseSessions?.();
|
||||
}}
|
||||
className="w-full px-3 py-2.5 text-xs font-semibold text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 transition flex items-center justify-between"
|
||||
>
|
||||
<span>🔌 Gateway Sessions</span>
|
||||
<svg
|
||||
className={`w-3.5 h-3.5 transition-transform ${showGatewaySessions ? "rotate-180" : ""}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
{showGatewaySessions && (
|
||||
<div className="bg-gray-50 dark:bg-gray-800/50">
|
||||
{loadingGatewaySessions ? (
|
||||
<div className="px-3 py-3 text-xs text-gray-400 dark:text-gray-500 text-center">Loading sessions...</div>
|
||||
) : !gatewaySessions || gatewaySessions.length === 0 ? (
|
||||
<div className="px-3 py-3 text-xs text-gray-400 dark:text-gray-500 text-center">No sessions found</div>
|
||||
) : (
|
||||
gatewaySessions.filter(s => !localSessionKeys.has(s.sessionKey)).map((session) => (
|
||||
<div
|
||||
key={session.sessionKey}
|
||||
className="px-3 py-2.5 border-b border-gray-100 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer transition"
|
||||
onClick={() => onSelect(session.sessionKey)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs">
|
||||
{session.channel === "telegram" ? "📱" : session.kind === "cron" ? "⏰" : "💬"}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-gray-700 dark:text-gray-300 truncate">
|
||||
{session.sessionKey}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
{session.channel && (
|
||||
<span className="text-[10px] text-gray-400">{session.channel}</span>
|
||||
)}
|
||||
{session.kind && (
|
||||
<span className="text-[10px] text-gray-400">{session.kind}</span>
|
||||
)}
|
||||
{session.messageCount != null && (
|
||||
<span className="text-[10px] text-gray-400">{session.messageCount} msgs</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatTimestamp(ts?: number): string {
|
||||
if (!ts) return "";
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
|
||||
function MessageBubble({ msg }: { msg: ChatMessage }) {
|
||||
const [showTime, setShowTime] = useState(false);
|
||||
const isUser = msg.role === "user";
|
||||
const isSystem = msg.role === "system";
|
||||
|
||||
if (isSystem) {
|
||||
return (
|
||||
<div className="text-center my-2">
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 bg-gray-100 dark:bg-gray-800 px-3 py-1 rounded-full">
|
||||
{msg.content}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex ${isUser ? "justify-end" : "justify-start"} mb-3 group`}
|
||||
onClick={() => setShowTime(!showTime)}
|
||||
>
|
||||
{!isUser && (
|
||||
<div className="w-8 h-8 bg-amber-500 rounded-full flex items-center justify-center text-sm mr-2 shrink-0 mt-1">
|
||||
🔨
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className={`max-w-[75vw] sm:max-w-[60vw] rounded-2xl px-4 py-2.5 ${
|
||||
isUser
|
||||
? "bg-blue-500 text-white rounded-br-md"
|
||||
: "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-bl-md"
|
||||
}`}
|
||||
>
|
||||
{isUser ? (
|
||||
<p className="text-sm whitespace-pre-wrap leading-relaxed">{msg.content}</p>
|
||||
) : (
|
||||
<div className="text-sm leading-relaxed prose prose-sm prose-gray max-w-none [&_pre]:bg-gray-800 [&_pre]:text-gray-100 [&_pre]:rounded-lg [&_pre]:p-3 [&_pre]:overflow-x-auto [&_pre]:text-xs [&_code]:bg-gray-200 [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-xs [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_p]:mb-2 [&_p:last-child]:mb-0 [&_ul]:mb-2 [&_ol]:mb-2 [&_li]:mb-0.5 [&_h1]:text-base [&_h2]:text-sm [&_h3]:text-sm [&_a]:text-amber-600 [&_a]:underline [&_blockquote]:border-l-2 [&_blockquote]:border-gray-300 [&_blockquote]:pl-3 [&_blockquote]:text-gray-500">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{msg.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showTime && msg.timestamp && (
|
||||
<span className={`text-[10px] text-gray-400 mt-0.5 ${isUser ? "text-right" : "text-left"}`}>
|
||||
{formatTimestamp(msg.timestamp)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThinkingIndicator() {
|
||||
return (
|
||||
<div className="flex justify-start mb-3">
|
||||
<div className="w-8 h-8 bg-amber-500 rounded-full flex items-center justify-center text-sm mr-2 shrink-0 mt-1">
|
||||
🔨
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-800 rounded-2xl rounded-bl-md px-4 py-3">
|
||||
<div className="flex gap-1.5">
|
||||
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "0ms" }} />
|
||||
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "150ms" }} />
|
||||
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: "300ms" }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatArea({
|
||||
messages,
|
||||
loading,
|
||||
streaming,
|
||||
thinking,
|
||||
streamText,
|
||||
onSend,
|
||||
onAbort,
|
||||
connectionState,
|
||||
}: {
|
||||
messages: ChatMessage[];
|
||||
loading: boolean;
|
||||
streaming: boolean;
|
||||
thinking: boolean;
|
||||
streamText: string;
|
||||
onSend: (msg: string) => void;
|
||||
onAbort: () => void;
|
||||
connectionState: "connecting" | "connected" | "disconnected";
|
||||
}) {
|
||||
const [input, setInput] = useState("");
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages, streamText, thinking]);
|
||||
|
||||
// Auto-resize textarea
|
||||
const autoResize = useCallback(() => {
|
||||
const el = inputRef.current;
|
||||
if (!el) return;
|
||||
el.style.height = "42px";
|
||||
el.style.height = Math.min(el.scrollHeight, 128) + "px";
|
||||
}, []);
|
||||
|
||||
const handleSend = () => {
|
||||
const text = input.trim();
|
||||
if (!text || connectionState !== "connected") return;
|
||||
onSend(text);
|
||||
setInput("");
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
const connected = connectionState === "connected";
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
{/* Messages */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
{loading ? (
|
||||
<div className="text-center text-gray-400 dark:text-gray-500 py-12">Loading messages...</div>
|
||||
) : messages.length === 0 ? (
|
||||
<div className="text-center text-gray-400 dark:text-gray-500 py-12">
|
||||
<span className="text-4xl block mb-3">🔨</span>
|
||||
<p className="text-sm">Send a message to start chatting with Hammer</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{messages.map((msg, i) => (
|
||||
<MessageBubble key={i} msg={msg} />
|
||||
))}
|
||||
{streaming && streamText && (
|
||||
<MessageBubble msg={{ role: "assistant", content: streamText }} />
|
||||
)}
|
||||
{thinking && !streaming && <ThinkingIndicator />}
|
||||
</>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-3">
|
||||
{connectionState === "disconnected" && (
|
||||
<div className="text-xs text-red-500 mb-2 flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-red-500 rounded-full" />
|
||||
Disconnected — reconnecting...
|
||||
</div>
|
||||
)}
|
||||
{connectionState === "connecting" && (
|
||||
<div className="text-xs text-amber-500 mb-2 flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-amber-500 rounded-full animate-pulse" />
|
||||
Connecting...
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2 items-end">
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => { setInput(e.target.value); autoResize(); }}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={connected ? "Type a message..." : "Connecting..."}
|
||||
disabled={!connected}
|
||||
rows={1}
|
||||
className="flex-1 resize-none rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-amber-200 dark:focus:ring-amber-800 focus:border-amber-300 dark:focus:border-amber-700 disabled:opacity-50 max-h-32 placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
||||
style={{ minHeight: "42px" }}
|
||||
/>
|
||||
{streaming ? (
|
||||
<button
|
||||
onClick={onAbort}
|
||||
className="px-4 py-2.5 bg-red-500 text-white rounded-xl text-sm font-medium hover:bg-red-600 transition shrink-0"
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={!input.trim() || !connected}
|
||||
className="px-4 py-2.5 bg-amber-500 text-white rounded-xl text-sm font-medium hover:bg-amber-600 transition disabled:opacity-50 disabled:cursor-not-allowed shrink-0"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatPage() {
|
||||
const [client] = useState<ChatClient>(() => getChatClient());
|
||||
const [connectionState, setConnectionState] = useState<"connecting" | "connected" | "disconnected">("disconnected");
|
||||
const [threads, setThreads] = useState<ChatThread[]>(() => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem("hammer-chat-threads") || "[]");
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const [activeThread, setActiveThread] = useState<string | null>(null);
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [streaming, setStreaming] = useState(false);
|
||||
const [thinking, setThinking] = useState(false);
|
||||
const [streamText, setStreamText] = useState("");
|
||||
const [showThreads, setShowThreads] = useState(false);
|
||||
const [gatewaySessions, setGatewaySessions] = useState<GatewaySession[]>([]);
|
||||
const [loadingGatewaySessions, setLoadingGatewaySessions] = useState(false);
|
||||
const [showGatewaySessions, setShowGatewaySessions] = useState(false);
|
||||
|
||||
// Persist threads to localStorage
|
||||
useEffect(() => {
|
||||
localStorage.setItem("hammer-chat-threads", JSON.stringify(threads));
|
||||
}, [threads]);
|
||||
|
||||
// Connect client
|
||||
useEffect(() => {
|
||||
client.connect();
|
||||
const unsub = client.onStateChange(setConnectionState);
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, [client]);
|
||||
|
||||
// Listen for chat events (streaming responses)
|
||||
useEffect(() => {
|
||||
const unsub = client.on("chat", (payload: any) => {
|
||||
if (payload.sessionKey !== activeThread) return;
|
||||
|
||||
if (payload.state === "delta" && payload.message?.content) {
|
||||
setThinking(false);
|
||||
setStreaming(true);
|
||||
const textParts = payload.message.content
|
||||
.filter((c: any) => c.type === "text")
|
||||
.map((c: any) => c.text)
|
||||
.join("");
|
||||
if (textParts) {
|
||||
setStreamText((prev) => prev + textParts);
|
||||
}
|
||||
} else if (payload.state === "final") {
|
||||
setThinking(false);
|
||||
if (payload.message?.content) {
|
||||
const text = payload.message.content
|
||||
.filter((c: any) => c.type === "text")
|
||||
.map((c: any) => c.text)
|
||||
.join("");
|
||||
if (text) {
|
||||
setMessages((prev) => [...prev, { role: "assistant", content: text, timestamp: Date.now() }]);
|
||||
setThreads((prev) =>
|
||||
prev.map((t) =>
|
||||
t.sessionKey === activeThread
|
||||
? { ...t, lastMessage: text.slice(0, 100), updatedAt: Date.now() }
|
||||
: t
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
setStreaming(false);
|
||||
setStreamText("");
|
||||
} else if (payload.state === "aborted" || payload.state === "error") {
|
||||
setThinking(false);
|
||||
setStreaming(false);
|
||||
if (streamText) {
|
||||
setMessages((prev) => [...prev, { role: "assistant", content: streamText }]);
|
||||
}
|
||||
setStreamText("");
|
||||
}
|
||||
});
|
||||
return unsub;
|
||||
}, [client, activeThread, streamText]);
|
||||
|
||||
// Load messages when thread changes
|
||||
const loadMessages = useCallback(
|
||||
async (sessionKey: string) => {
|
||||
setLoading(true);
|
||||
setMessages([]);
|
||||
try {
|
||||
const result = await client.chatHistory(sessionKey);
|
||||
if (result?.messages) {
|
||||
const msgs: ChatMessage[] = result.messages
|
||||
.filter((m: any) => m.role === "user" || m.role === "assistant")
|
||||
.map((m: any) => ({
|
||||
role: m.role as "user" | "assistant",
|
||||
content:
|
||||
typeof m.content === "string"
|
||||
? m.content
|
||||
: Array.isArray(m.content)
|
||||
? m.content
|
||||
.filter((c: any) => c.type === "text")
|
||||
.map((c: any) => c.text)
|
||||
.join("")
|
||||
: "",
|
||||
}))
|
||||
.filter((m: ChatMessage) => m.content);
|
||||
setMessages(msgs);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load chat history:", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeThread && connectionState === "connected") {
|
||||
loadMessages(activeThread);
|
||||
}
|
||||
}, [activeThread, connectionState, loadMessages]);
|
||||
|
||||
const handleBrowseSessions = useCallback(async () => {
|
||||
if (!client.isConnected()) return;
|
||||
setLoadingGatewaySessions(true);
|
||||
try {
|
||||
const result = await client.sessionsList(50);
|
||||
if (result?.sessions) {
|
||||
setGatewaySessions(result.sessions.map((s: any) => ({
|
||||
sessionKey: s.sessionKey || s.key,
|
||||
kind: s.kind,
|
||||
channel: s.channel,
|
||||
lastActivity: s.lastActivity,
|
||||
messageCount: s.messageCount,
|
||||
})));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load sessions:", e);
|
||||
} finally {
|
||||
setLoadingGatewaySessions(false);
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
const handleCreateThread = () => {
|
||||
const id = `dash:chat:${Date.now()}`;
|
||||
const thread: ChatThread = {
|
||||
sessionKey: id,
|
||||
name: `Chat ${threads.length + 1}`,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
setThreads((prev) => [thread, ...prev]);
|
||||
setActiveThread(id);
|
||||
setMessages([]);
|
||||
};
|
||||
|
||||
const handleSelectThread = (sessionKey: string) => {
|
||||
// If it's a gateway session not in local threads, add it
|
||||
if (!threads.find(t => t.sessionKey === sessionKey)) {
|
||||
const gwSession = gatewaySessions.find(s => s.sessionKey === sessionKey);
|
||||
const thread: ChatThread = {
|
||||
sessionKey,
|
||||
name: gwSession?.channel
|
||||
? `${gwSession.channel} (${sessionKey.slice(0, 12)}...)`
|
||||
: sessionKey.length > 20
|
||||
? `${sessionKey.slice(0, 20)}...`
|
||||
: sessionKey,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
setThreads((prev) => [thread, ...prev]);
|
||||
}
|
||||
setActiveThread(sessionKey);
|
||||
};
|
||||
|
||||
const handleRenameThread = (key: string, name: string) => {
|
||||
setThreads((prev) => prev.map((t) => (t.sessionKey === key ? { ...t, name } : t)));
|
||||
};
|
||||
|
||||
const handleDeleteThread = (key: string) => {
|
||||
setThreads((prev) => prev.filter((t) => t.sessionKey !== key));
|
||||
if (activeThread === key) {
|
||||
const remaining = threads.filter((t) => t.sessionKey !== key);
|
||||
setActiveThread(remaining.length > 0 ? remaining[0].sessionKey : null);
|
||||
setMessages([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = async (text: string) => {
|
||||
if (!activeThread) return;
|
||||
|
||||
setMessages((prev) => [...prev, { role: "user", content: text, timestamp: Date.now() }]);
|
||||
setThinking(true);
|
||||
setThreads((prev) =>
|
||||
prev.map((t) =>
|
||||
t.sessionKey === activeThread
|
||||
? { ...t, lastMessage: text.slice(0, 100), updatedAt: Date.now() }
|
||||
: t
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await client.chatSend(activeThread, text);
|
||||
} catch (e) {
|
||||
console.error("Failed to send:", e);
|
||||
setThinking(false);
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ role: "system", content: "Failed to send message. Please try again." },
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAbort = async () => {
|
||||
if (!activeThread) return;
|
||||
try {
|
||||
await client.chatAbort(activeThread);
|
||||
} catch (e) {
|
||||
console.error("Failed to abort:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-create first thread if none exist
|
||||
useEffect(() => {
|
||||
if (threads.length === 0) {
|
||||
handleCreateThread();
|
||||
} else if (!activeThread) {
|
||||
setActiveThread(threads[0].sessionKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-[calc(100vh-3.5rem)] md:h-screen flex flex-col">
|
||||
{/* Page Header */}
|
||||
<header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 sticky top-0 z-30">
|
||||
<div className="px-4 sm:px-6 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setShowThreads(!showThreads)}
|
||||
className="sm:hidden p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition"
|
||||
aria-label="Toggle threads"
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h8m-8 6h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 className="text-lg font-bold text-gray-900 dark:text-gray-100">Chat</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
connectionState === "connected"
|
||||
? "bg-green-500"
|
||||
: connectionState === "connecting"
|
||||
? "bg-amber-500 animate-pulse"
|
||||
: "bg-red-500"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-xs text-gray-400">
|
||||
{connectionState === "connected" ? "Connected" : connectionState === "connecting" ? "Connecting..." : "Disconnected"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Chat body */}
|
||||
<div className="flex-1 flex overflow-hidden relative">
|
||||
{/* Thread list */}
|
||||
<div className={`
|
||||
absolute inset-0 z-20 sm:relative sm:inset-auto sm:z-auto
|
||||
${showThreads ? "block" : "hidden"} sm:block
|
||||
`}>
|
||||
{showThreads && (
|
||||
<div
|
||||
className="absolute inset-0 bg-black/30 sm:hidden"
|
||||
onClick={() => setShowThreads(false)}
|
||||
/>
|
||||
)}
|
||||
<div className="relative z-10 h-full">
|
||||
<ThreadList
|
||||
threads={threads}
|
||||
activeThread={activeThread}
|
||||
onSelect={(key) => {
|
||||
handleSelectThread(key);
|
||||
setShowThreads(false);
|
||||
}}
|
||||
onCreate={() => {
|
||||
handleCreateThread();
|
||||
setShowThreads(false);
|
||||
}}
|
||||
onRename={handleRenameThread}
|
||||
onDelete={handleDeleteThread}
|
||||
onClose={() => setShowThreads(false)}
|
||||
onBrowseSessions={handleBrowseSessions}
|
||||
gatewaySessions={gatewaySessions}
|
||||
loadingGatewaySessions={loadingGatewaySessions}
|
||||
showGatewaySessions={showGatewaySessions}
|
||||
onToggleGatewaySessions={() => setShowGatewaySessions(!showGatewaySessions)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{activeThread ? (
|
||||
<ChatArea
|
||||
messages={messages}
|
||||
loading={loading}
|
||||
streaming={streaming}
|
||||
thinking={thinking}
|
||||
streamText={streamText}
|
||||
onSend={handleSend}
|
||||
onAbort={handleAbort}
|
||||
connectionState={connectionState}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-400 dark:text-gray-500 p-4 text-center">
|
||||
<div>
|
||||
<span className="text-3xl block mb-2">💬</span>
|
||||
<p>Select or create a thread</p>
|
||||
<button
|
||||
onClick={() => setShowThreads(true)}
|
||||
className="sm:hidden mt-3 text-sm text-amber-500 font-medium"
|
||||
>
|
||||
View Threads →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user