adds eslint

This commit is contained in:
geoffsee
2025-06-24 17:29:52 -04:00
committed by Geoff Seemueller
parent 9698fc6f3b
commit 02c3253343
169 changed files with 4896 additions and 4804 deletions

41
.eslintignore Normal file
View File

@@ -0,0 +1,41 @@
# Dependencies
node_modules
.pnp
.pnp.js
# Build outputs
dist
build
out
.next
.nuxt
.cache
# Test coverage
coverage
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# TypeScript
*.d.ts

49
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,49 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
project: './tsconfig.json',
},
env: {
browser: true,
node: true,
es6: true,
},
globals: {
Bun: 'readonly',
},
plugins: ['@typescript-eslint', 'import', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'prettier',
],
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'import/order': [
'error',
{
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
},
],
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
moduleDirectory: ['node_modules', 'packages/*/node_modules'],
},
},
},
ignorePatterns: ['node_modules', 'dist', 'build', '*.d.ts', '*.min.js'],
};

47
.prettierignore Normal file
View File

@@ -0,0 +1,47 @@
# Dependencies
node_modules
.pnp
.pnp.js
# Build outputs
dist
build
out
.next
.nuxt
.cache
# Test coverage
coverage
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Package files
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lock
# Generated files
CHANGELOG.md

19
.prettierrc.cjs Normal file
View File

@@ -0,0 +1,19 @@
module.exports = {
semi: true,
singleQuote: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: 'avoid',
endOfLine: 'lf',
overrides: [
{
files: '*.{json,yml,yaml,md}',
options: {
tabWidth: 2,
},
},
],
};

331
bun.lock
View File

@@ -4,7 +4,14 @@
"": {
"devDependencies": {
"@types/bun": "latest",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"happy-dom": "^18.0.1",
"prettier": "^3.2.5",
},
"peerDependencies": {
"typescript": "^5",
@@ -65,6 +72,8 @@
"name": "@open-gsio/worker",
"dependencies": {
"@cloudflare/vite-plugin": "^1.3.1",
"@open-gsio/client": "workspace:*",
"@open-gsio/server": "workspace:*",
"vite": "6.3.5",
"wrangler": "^4.18.0",
},
@@ -435,8 +444,22 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.23.1", "", { "os": "win32", "cpu": "x64" }, "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
@@ -535,6 +558,8 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@pkgr/core": ["@pkgr/core@0.2.7", "", {}, "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg=="],
"@polka/url": ["@polka/url@1.0.0-next.28", "", {}, "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw=="],
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
@@ -589,6 +614,8 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.39.0", "", { "os": "win32", "cpu": "x64" }, "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
@@ -629,6 +656,8 @@
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],
"@types/lodash.mergewith": ["@types/lodash.mergewith@4.6.9", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-fgkoCAOF47K7sxrQ7Mlud2TH023itugZs2bUg8h/KzT+BnZNrR2jAOmaokbLunHNnobXVWOezAeNn/lZqwxkcw=="],
@@ -655,6 +684,22 @@
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.3.4", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug=="],
@@ -687,24 +732,38 @@
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
"array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
"array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
"as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
@@ -733,7 +792,7 @@
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
"brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -763,7 +822,7 @@
"chai": ["chai@5.2.0", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw=="],
"chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
@@ -831,6 +890,8 @@
"deep-equal": ["deep-equal@2.2.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", "which-typed-array": "^1.1.13" } }, "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
@@ -849,6 +910,10 @@
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
"dompurify": ["dompurify@3.2.5", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ=="],
@@ -885,6 +950,8 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
"esbuild": ["esbuild@0.23.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.23.1", "@esbuild/android-arm": "0.23.1", "@esbuild/android-arm64": "0.23.1", "@esbuild/android-x64": "0.23.1", "@esbuild/darwin-arm64": "0.23.1", "@esbuild/darwin-x64": "0.23.1", "@esbuild/freebsd-arm64": "0.23.1", "@esbuild/freebsd-x64": "0.23.1", "@esbuild/linux-arm": "0.23.1", "@esbuild/linux-arm64": "0.23.1", "@esbuild/linux-ia32": "0.23.1", "@esbuild/linux-loong64": "0.23.1", "@esbuild/linux-mips64el": "0.23.1", "@esbuild/linux-ppc64": "0.23.1", "@esbuild/linux-riscv64": "0.23.1", "@esbuild/linux-s390x": "0.23.1", "@esbuild/linux-x64": "0.23.1", "@esbuild/netbsd-x64": "0.23.1", "@esbuild/openbsd-arm64": "0.23.1", "@esbuild/openbsd-x64": "0.23.1", "@esbuild/sunos-x64": "0.23.1", "@esbuild/win32-arm64": "0.23.1", "@esbuild/win32-ia32": "0.23.1", "@esbuild/win32-x64": "0.23.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg=="],
@@ -893,6 +960,30 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
"eslint-config-prettier": ["eslint-config-prettier@9.1.0", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
"eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="],
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
"eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.0", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA=="],
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
@@ -907,10 +998,14 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
@@ -919,12 +1014,18 @@
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
"filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"focus-lock": ["focus-lock@1.3.6", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg=="],
@@ -973,18 +1074,22 @@
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"happy-dom": ["happy-dom@18.0.1", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
@@ -1023,8 +1128,12 @@
"idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="],
"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=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
@@ -1075,6 +1184,8 @@
"is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
@@ -1125,17 +1236,23 @@
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"jsdom": ["jsdom@24.1.3", "", { "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.12", "parse5": "^7.1.2", "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.4", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^2.11.2" }, "optionalPeers": ["canvas"] }, "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
@@ -1143,14 +1260,22 @@
"katex": ["katex@0.16.21", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="],
"lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="],
@@ -1207,7 +1332,9 @@
"miniflare": ["miniflare@4.20250525.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250525.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-F5XRDn9WqxUaHphUT8qwy5WXC/3UwbBRJTdjjP5uwHX82vypxIlHNyHziZnplPLhQa1kbSdIY7wfuP1XJyyYZw=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -1231,6 +1358,8 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
@@ -1249,6 +1378,12 @@
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
"object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
"object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -1257,8 +1392,14 @@
"openai": ["openai@5.0.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-Do6vxhbDv7cXhji/4ct1lrpZYMAOmjYbhyA9LJTuG7OfpbWMpuS+EIXkRT7R+XxpRB1OZhU/op4FU3p3uxU6gw=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
@@ -1267,6 +1408,8 @@
"parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -1291,6 +1434,12 @@
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.6.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw=="],
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
"pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
@@ -1375,6 +1524,8 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"rollup": ["rollup@4.39.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.39.0", "@rollup/rollup-android-arm64": "4.39.0", "@rollup/rollup-darwin-arm64": "4.39.0", "@rollup/rollup-darwin-x64": "4.39.0", "@rollup/rollup-freebsd-arm64": "4.39.0", "@rollup/rollup-freebsd-x64": "4.39.0", "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", "@rollup/rollup-linux-arm-musleabihf": "4.39.0", "@rollup/rollup-linux-arm64-gnu": "4.39.0", "@rollup/rollup-linux-arm64-musl": "4.39.0", "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-musl": "4.39.0", "@rollup/rollup-linux-s390x-gnu": "4.39.0", "@rollup/rollup-linux-x64-gnu": "4.39.0", "@rollup/rollup-linux-x64-musl": "4.39.0", "@rollup/rollup-win32-arm64-msvc": "4.39.0", "@rollup/rollup-win32-ia32-msvc": "4.39.0", "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g=="],
"rrweb-cssom": ["rrweb-cssom@0.7.1", "", {}, "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="],
@@ -1395,7 +1546,7 @@
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
@@ -1429,6 +1580,8 @@
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"smob": ["smob@1.5.0", "", {}, "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig=="],
"source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
@@ -1467,14 +1620,18 @@
"stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="],
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-comments": ["strip-comments@2.0.1", "", {}, "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="],
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -1483,6 +1640,8 @@
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"synckit": ["synckit@0.11.8", "", { "dependencies": { "@pkgr/core": "^0.2.4" } }, "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A=="],
"temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="],
"tempy": ["tempy@0.6.0", "", { "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" } }, "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw=="],
@@ -1491,6 +1650,8 @@
"test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="],
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@@ -1519,9 +1680,15 @@
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-fest": ["type-fest@0.16.0", "", {}, "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
@@ -1569,6 +1736,8 @@
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
@@ -1621,6 +1790,8 @@
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"workbox-background-sync": ["workbox-background-sync@7.3.0", "", { "dependencies": { "idb": "^7.0.1", "workbox-core": "7.3.0" } }, "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg=="],
"workbox-broadcast-update": ["workbox-broadcast-update@7.3.0", "", { "dependencies": { "workbox-core": "7.3.0" } }, "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA=="],
@@ -1673,6 +1844,8 @@
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
@@ -1681,24 +1854,20 @@
"@anthropic-ai/sdk/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
"@apideck/better-ajv-errors/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"@babel/core/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
"@babel/helper-define-polyfill-provider/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
@@ -1769,6 +1938,8 @@
"@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-computed-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-computed-properties/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
@@ -1879,23 +2050,25 @@
"@babel/preset-env/@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/preset-modules/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/template/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@cloudflare/vite-plugin/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"@open-gsio/client/@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@open-gsio/scripts/@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@open-gsio/scripts/@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
"@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
@@ -1905,19 +2078,23 @@
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@surma/rollup-plugin-off-main-thread/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"@testing-library/dom/aria-query": ["aria-query@5.1.3", "", { "dependencies": { "deep-equal": "^2.0.5" } }, "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ=="],
"@testing-library/dom/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
"@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"@types/node-fetch/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
"babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.27.3", "", {}, "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"babel-plugin-polyfill-corejs2/@babel/compat-data": ["@babel/compat-data@7.27.3", "", {}, "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw=="],
"bun-types/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
@@ -1925,6 +2102,16 @@
"data-urls/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
"framesync/tslib": ["tslib@2.4.0", "", {}, "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="],
@@ -1933,12 +2120,10 @@
"get-source/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"isomorphic-dompurify/jsdom": ["jsdom@26.0.0", "", { "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.1", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.16", "parse5": "^7.2.1", "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.1.0", "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw=="],
"jake/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"jake/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"jsdom/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
"jsonfile/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
@@ -1947,6 +2132,8 @@
"magicast/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"make-dir/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@@ -1967,18 +2154,26 @@
"regjsparser/jsesc": ["jsesc@3.0.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="],
"rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"tempy/type-fest": ["type-fest@0.16.0", "", {}, "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"test-exclude/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"vike/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"vike/sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="],
"vite/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
@@ -1987,6 +2182,8 @@
"workbox-build/@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="],
"workbox-build/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"workbox-build/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"workbox-build/pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="],
@@ -1999,12 +2196,14 @@
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
@@ -2017,18 +2216,20 @@
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.27.3", "", {}, "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw=="],
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
@@ -2041,6 +2242,8 @@
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2049,12 +2252,16 @@
"@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/helper-replace-supers/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-wrap-function/@babel/template/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2063,6 +2270,8 @@
"@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
"@babel/helper-wrap-function/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/helper-wrap-function/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
@@ -2073,6 +2282,8 @@
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2081,6 +2292,8 @@
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2089,6 +2302,8 @@
"@babel/plugin-transform-async-generator-functions/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-async-generator-functions/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
@@ -2097,8 +2312,6 @@
"@babel/plugin-transform-classes/@babel/helper-compilation-targets/@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/plugin-transform-classes/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2115,8 +2328,6 @@
"@babel/plugin-transform-function-name/@babel/helper-compilation-targets/@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/plugin-transform-function-name/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
@@ -2125,6 +2336,8 @@
"@babel/plugin-transform-function-name/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-function-name/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
@@ -2143,6 +2356,8 @@
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse": ["@babel/traverse@7.27.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.3", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ=="],
@@ -2151,15 +2366,15 @@
"@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/template/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@open-gsio/client/@types/bun/bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"@open-gsio/scripts/@types/bun/bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"@open-gsio/scripts/@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
"@rollup/plugin-babel/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
@@ -2171,16 +2386,20 @@
"@types/node-fetch/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"isomorphic-dompurify/jsdom/rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="],
"isomorphic-dompurify/jsdom/tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
"isomorphic-dompurify/jsdom/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
"jake/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"magicast/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
@@ -2189,6 +2408,10 @@
"parse-json/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"test-exclude/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
@@ -2241,7 +2464,7 @@
"workbox-build/@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"workbox-build/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"workbox-build/source-map/whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
@@ -2295,6 +2518,8 @@
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
@@ -2313,6 +2538,8 @@
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
@@ -2331,6 +2558,8 @@
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-modules-amd/@babel/helper-module-transforms/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="],
@@ -2341,6 +2570,8 @@
"@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@babel/plugin-transform-modules-systemjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
@@ -2355,9 +2586,9 @@
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="],
"@open-gsio/client/@types/bun/bun-types/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
"@open-gsio/scripts/@types/bun/bun-types/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
"@open-gsio/client/@types/bun/bun-types/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="],
"workbox-build/@rollup/plugin-replace/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
@@ -2365,8 +2596,6 @@
"workbox-build/@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"workbox-build/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"workbox-build/source-map/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
"workbox-build/source-map/whatwg-url/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
@@ -2386,7 +2615,5 @@
"@babel/plugin-transform-modules-umd/@babel/helper-module-transforms/@babel/traverse/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@open-gsio/client/@types/bun/bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@open-gsio/scripts/@types/bun/bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
}
}

View File

@@ -19,14 +19,24 @@
"deploy:secrets": "wrangler secret bulk secrets.json -c packages/cloudflare-workers/open-gsio/wrangler.jsonc",
"openai:local:mlx": "packages/scripts/start_inference_server.sh mlx-omni-server",
"openai:local:ollama": "packages/scripts/start_inference_server.sh ollama",
"openai:local:configure": "packages/scripts/configure_local_inference.sh"
"openai:local:configure": "packages/scripts/configure_local_inference.sh",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,md}\""
},
"devDependencies": {
"@types/bun": "latest",
"happy-dom": "^18.0.1"
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"happy-dom": "^18.0.1",
"prettier": "^3.2.5"
},
"peerDependencies": {
"typescript": "^5"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"
}
}

View File

@@ -1,2 +1,2 @@
// for future use
export {}
export {};

View File

@@ -1,17 +1,17 @@
#!/usr/bin/env bun
/* eslint-env node */
import fs from "fs";
import {parseArgs} from "util";
import fs from 'fs';
import { parseArgs } from 'util';
const {positionals} = parseArgs({
const { positionals } = parseArgs({
args: Bun.argv,
options: {},
strict: true,
allowPositionals: true,
});
const currentDate = new Date().toISOString().split("T")[0];
const currentDate = new Date().toISOString().split('T')[0];
const host = positionals[2];
@@ -25,12 +25,12 @@ Disallow: /assets
Sitemap: https://${host}/sitemap.xml
`;
const robotsTxtPath = "./public/robots.txt";
const robotsTxtPath = './public/robots.txt';
fs.writeFile(robotsTxtPath, robotsTxtTemplate, (err) => {
fs.writeFile(robotsTxtPath, robotsTxtTemplate, err => {
if (err) {
console.error("Error writing robots.txt:", err);
console.error('Error writing robots.txt:', err);
process.exit(1);
}
console.log("robots.txt created successfully:", currentDate);
console.log('robots.txt created successfully:', currentDate);
});

View File

@@ -1,17 +1,16 @@
#!/usr/bin/env bun
import fs from "fs";
import {parseArgs} from "util";
import fs from 'fs';
import { parseArgs } from 'util';
const {positionals} = parseArgs({
const { positionals } = parseArgs({
args: Bun.argv,
options: {},
strict: true,
allowPositionals: true,
});
const currentDate = new Date().toISOString().split("T")[0];
const currentDate = new Date().toISOString().split('T')[0];
const host = positionals[2];
@@ -30,12 +29,12 @@ const sitemapTemplate = `<?xml version="1.0" encoding="UTF-8"?>
</url>
</urlset>`;
const sitemapPath = "./public/sitemap.xml";
const sitemapPath = './public/sitemap.xml';
fs.writeFile(sitemapPath, sitemapTemplate, (err) => {
fs.writeFile(sitemapPath, sitemapTemplate, err => {
if (err) {
console.error("Error writing sitemap file:", err);
console.error('Error writing sitemap file:', err);
process.exit(1);
}
console.log("Sitemap updated successfully with current date:", currentDate);
console.log('Sitemap updated successfully with current date:', currentDate);
});

View File

@@ -1,4 +1,4 @@
import { renderPage } from "vike/server";
import { renderPage } from 'vike/server';
// This is what makes SSR possible. It is consumed by @open-gsio/server
export default renderPage;
export default renderPage;

View File

@@ -1,7 +1,8 @@
import React from "react";
import { IconButton } from "@chakra-ui/react";
import { LucideHammer } from "lucide-react";
import { toolbarButtonZIndex } from "./toolbar/Toolbar";
import { IconButton } from '@chakra-ui/react';
import { LucideHammer } from 'lucide-react';
import React from 'react';
import { toolbarButtonZIndex } from './toolbar/Toolbar';
export default function BuiltWithButton() {
return (
@@ -12,12 +13,12 @@ export default function BuiltWithButton() {
bg="transparent"
stroke="text.accent"
color="text.accent"
onClick={() => alert("Built by Geoff Seemueller")}
onClick={() => alert('Built by Geoff Seemueller')}
_hover={{
bg: "transparent",
bg: 'transparent',
svg: {
stroke: "accent.secondary",
transition: "stroke 0.3s ease-in-out",
stroke: 'accent.secondary',
transition: 'stroke 0.3s ease-in-out',
},
}}
zIndex={toolbarButtonZIndex}

View File

@@ -1,10 +1,12 @@
import { getColorThemes } from "../layout/theme/color-themes";
import { Center, IconButton, VStack } from "@chakra-ui/react";
import userOptionsStore from "../stores/UserOptionsStore";
import { Circle } from "lucide-react";
import { toolbarButtonZIndex } from "./toolbar/Toolbar";
import React from "react";
import { useIsMobile } from "./contexts/MobileContext";
import { Center, IconButton, VStack } from '@chakra-ui/react';
import { Circle } from 'lucide-react';
import React from 'react';
import { getColorThemes } from '../layout/theme/color-themes';
import userOptionsStore from '../stores/UserOptionsStore';
import { useIsMobile } from './contexts/MobileContext';
import { toolbarButtonZIndex } from './toolbar/Toolbar';
export function ThemeSelectionOptions() {
const children = [];
@@ -24,11 +26,11 @@ export function ThemeSelectionOptions() {
size={!isMobile ? 16 : 20}
stroke="transparent"
style={{
background: `conic-gradient(${theme.colors.background.primary.startsWith("#") ? theme.colors.background.primary : theme.colors.background.secondary} 0 50%, ${theme.colors.text.secondary} 50% 100%)`,
borderRadius: "50%",
boxShadow: "0 0 0.5px 0.25px #fff",
cursor: "pointer",
transition: "background 0.2s",
background: `conic-gradient(${theme.colors.background.primary.startsWith('#') ? theme.colors.background.primary : theme.colors.background.secondary} 0 50%, ${theme.colors.text.secondary} 50% 100%)`,
borderRadius: '50%',
boxShadow: '0 0 0.5px 0.25px #fff',
cursor: 'pointer',
transition: 'background 0.2s',
}}
/>
}
@@ -38,7 +40,7 @@ export function ThemeSelectionOptions() {
color="transparent"
_hover={{
svg: {
transition: "stroke 0.3s ease-in-out", // Smooth transition effect
transition: 'stroke 0.3s ease-in-out', // Smooth transition effect
},
}}
zIndex={toolbarButtonZIndex}
@@ -47,7 +49,7 @@ export function ThemeSelectionOptions() {
}
return (
<VStack align={!isMobile ? "end" : "start"} p={1.2}>
<VStack align={!isMobile ? 'end' : 'start'} p={1.2}>
<Center>{children}</Center>
</VStack>
);

View File

@@ -1,11 +1,9 @@
import { motion } from "framer-motion";
import { Box, Center, VStack } from "@chakra-ui/react";
import {
welcome_home_text,
welcome_home_tip,
} from "../static-data/welcome_home_text";
import {renderMarkdown} from "./markdown/MarkdownComponent";
import { Box, Center, VStack } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { welcome_home_text, welcome_home_tip } from '../static-data/welcome_home_text';
import { renderMarkdown } from './markdown/MarkdownComponent';
function WelcomeHomeMessage({ visible }) {
const containerVariants = {
@@ -45,33 +43,19 @@ function WelcomeHomeMessage({ visible }) {
<Center>
<VStack spacing={8} align="center" maxW="400px">
{/* Welcome Message */}
<Box
fontSize="sm"
fontStyle="italic"
textAlign="center"
color="text.secondary"
mt={4}
>
<Box fontSize="sm" fontStyle="italic" textAlign="center" color="text.secondary" mt={4}>
<motion.div
variants={containerVariants}
initial="hidden"
animate={visible ? "visible" : "hidden"}
animate={visible ? 'visible' : 'hidden'}
>
<Box userSelect={"none"}>
<motion.div variants={textVariants}>
{renderMarkdown(welcome_home_text)}
</motion.div>
<Box userSelect={'none'}>
<motion.div variants={textVariants}>{renderMarkdown(welcome_home_text)}</motion.div>
</Box>
</motion.div>
</Box>
<motion.div variants={textVariants}>
<Box
fontSize="sm"
fontStyle="italic"
textAlign="center"
color="text.secondary"
mt={1}
>
<Box fontSize="sm" fontStyle="italic" textAlign="center" color="text.secondary" mt={1}>
{renderMarkdown(welcome_home_tip)}
</Box>
</motion.div>

View File

@@ -1,37 +1,38 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeSelectionOptions } from '../ThemeSelection';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userOptionsStore from '../../stores/UserOptionsStore';
import * as MobileContext from '../contexts/MobileContext';
import { ThemeSelectionOptions } from '../ThemeSelection';
// Mock dependencies
vi.mock('../../layout/theme/color-themes', () => ({
getColorThemes: () => [
{
name: 'light',
colors: {
{
name: 'light',
colors: {
background: { primary: '#ffffff', secondary: '#f0f0f0' },
text: { secondary: '#333333' }
}
text: { secondary: '#333333' },
},
},
{
name: 'dark',
colors: {
{
name: 'dark',
colors: {
background: { primary: '#121212', secondary: '#1e1e1e' },
text: { secondary: '#e0e0e0' }
}
}
]
text: { secondary: '#e0e0e0' },
},
},
],
}));
vi.mock('../../stores/UserOptionsStore', () => ({
default: {
selectTheme: vi.fn()
}
selectTheme: vi.fn(),
},
}));
vi.mock('../toolbar/Toolbar', () => ({
toolbarButtonZIndex: 100
toolbarButtonZIndex: 100,
}));
describe('ThemeSelectionOptions', () => {
@@ -42,20 +43,20 @@ describe('ThemeSelectionOptions', () => {
it('renders theme options for desktop view', () => {
// Mock useIsMobile to return false (desktop view)
vi.spyOn(MobileContext, 'useIsMobile').mockReturnValue(false);
render(<ThemeSelectionOptions />);
// Should render 2 theme buttons (from our mock)
const buttons = screen.getAllByRole("button")
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(2);
});
it('renders theme options for mobile view', () => {
// Mock useIsMobile to return true (mobile view)
vi.spyOn(MobileContext, 'useIsMobile').mockReturnValue(true);
render(<ThemeSelectionOptions />);
// Should still render 2 theme buttons
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(2);
@@ -63,16 +64,16 @@ describe('ThemeSelectionOptions', () => {
it('calls selectTheme when a theme button is clicked', () => {
vi.spyOn(MobileContext, 'useIsMobile').mockReturnValue(false);
render(<ThemeSelectionOptions />);
const buttons = screen.getAllByRole('button');
fireEvent.click(buttons[0]); // Click the first theme button (light)
// Verify that selectTheme was called with the correct theme name
expect(userOptionsStore.selectTheme).toHaveBeenCalledWith('light');
fireEvent.click(buttons[1]); // Click the second theme button (dark)
expect(userOptionsStore.selectTheme).toHaveBeenCalledWith('dark');
});
});
});

View File

@@ -1,22 +1,23 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import WelcomeHomeMessage from '../WelcomeHome';
import { describe, it, expect } from 'vitest';
import { welcome_home_text, welcome_home_tip } from '../../static-data/welcome_home_text';
import { renderMarkdown } from '../markdown/MarkdownComponent';
import WelcomeHomeMessage from '../WelcomeHome';
// Mock the renderMarkdown function
vi.mock('../markdown/MarkdownComponent', () => ({
renderMarkdown: vi.fn((text) => `Rendered: ${text}`),
renderMarkdown: vi.fn(text => `Rendered: ${text}`),
}));
describe('WelcomeHomeMessage', () => {
it('renders correctly when visible', () => {
render(<WelcomeHomeMessage visible={true} />);
// Check if the rendered markdown content is in the document
expect(screen.getByText(`Rendered: ${welcome_home_text}`)).toBeInTheDocument();
expect(screen.getByText(`Rendered: ${welcome_home_tip}`)).toBeInTheDocument();
// Verify that renderMarkdown was called with the correct arguments
expect(renderMarkdown).toHaveBeenCalledWith(welcome_home_text);
expect(renderMarkdown).toHaveBeenCalledWith(welcome_home_tip);
@@ -24,17 +25,17 @@ describe('WelcomeHomeMessage', () => {
it('applies animation variants based on visible prop', () => {
const { rerender } = render(<WelcomeHomeMessage visible={true} />);
// When visible is true, the component should have the visible animation state
// Since we've mocked framer-motion, we can't directly test the animation state
// But we can verify that the component renders the content
expect(screen.getByText(`Rendered: ${welcome_home_text}`)).toBeInTheDocument();
// Re-render with visible=false
rerender(<WelcomeHomeMessage visible={false} />);
// Content should still be in the document even when not visible
// (since we've mocked the animations)
expect(screen.getByText(`Rendered: ${welcome_home_text}`)).toBeInTheDocument();
});
});
});

View File

@@ -1,14 +1,14 @@
import React from "react";
import { Grid, GridItem, Image, Text } from "@chakra-ui/react";
import { Grid, GridItem, Image, Text } from '@chakra-ui/react';
import React from 'react';
const fontSize = "md";
const fontSize = 'md';
function AboutComponent() {
return (
<Grid
templateColumns="1fr"
gap={4}
maxW={["100%", "100%", "100%"]}
maxW={['100%', '100%', '100%']}
mx="auto"
className="about-container"
>
@@ -17,22 +17,22 @@ function AboutComponent() {
src="/me.png"
alt="Geoff Seemueller"
borderRadius="full"
boxSize={["120px", "150px"]}
boxSize={['120px', '150px']}
objectFit="cover"
/>
</GridItem>
<GridItem
colSpan={1}
maxW={["100%", "100%", "container.md"]}
maxW={['100%', '100%', 'container.md']}
justifySelf="center"
minH={"100%"}
minH={'100%'}
>
<Grid templateColumns="1fr" gap={4} overflowY={"auto"}>
<Grid templateColumns="1fr" gap={4} overflowY={'auto'}>
<GridItem>
<Text fontSize={fontSize}>
If you're interested in collaborating on innovative projects that
push technological boundaries and create real value, I'd be keen
to connect and explore potential opportunities.
If you're interested in collaborating on innovative projects that push technological
boundaries and create real value, I'd be keen to connect and explore potential
opportunities.
</Text>
</GridItem>
</Grid>

View File

@@ -1,30 +1,26 @@
import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Box, Grid, GridItem } from "@chakra-ui/react";
import ChatMessages from "./messages/ChatMessages";
import ChatInput from "./input/ChatInput";
import chatStore from "../../stores/ClientChatStore";
import menuState from "../../stores/AppMenuStore";
import WelcomeHome from "../WelcomeHome";
import { Box, Grid, GridItem } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import menuState from '../../stores/AppMenuStore';
import chatStore from '../../stores/ClientChatStore';
import WelcomeHome from '../WelcomeHome';
import ChatInput from './input/ChatInput';
import ChatMessages from './messages/ChatMessages';
const Chat = observer(({ height, width }) => {
const scrollRef = useRef();
const [isAndroid, setIsAndroid] = useState(false);
useEffect(() => {
if (typeof window !== "undefined") {
if (typeof window !== 'undefined') {
setIsAndroid(/android/i.test(window.navigator.userAgent));
}
}, []);
return (
<Grid
templateRows="1fr auto"
templateColumns="1fr"
height={height}
width={width}
gap={0}
>
<Grid templateRows="1fr auto" templateColumns="1fr" height={height} width={width} gap={0}>
<GridItem alignSelf="center" hidden={!(chatStore.items.length < 1)}>
<WelcomeHome visible={chatStore.items.length < 1} />
</GridItem>
@@ -35,32 +31,17 @@ const Chat = observer(({ height, width }) => {
maxH="100%"
ref={scrollRef}
// If there are attachments, use "100px". Otherwise, use "128px" on Android, "73px" elsewhere.
pb={
isAndroid
? "128px"
: "73px"
}
pb={isAndroid ? '128px' : '73px'}
alignSelf="flex-end"
>
<ChatMessages scrollRef={scrollRef} />
</GridItem>
<GridItem
position="relative"
bg="background.primary"
zIndex={1000}
width="100%"
>
<Box
w="100%"
display="flex"
justifyContent="center"
mx="auto"
hidden={menuState.isOpen}
>
<GridItem position="relative" bg="background.primary" zIndex={1000} width="100%">
<Box w="100%" display="flex" justifyContent="center" mx="auto" hidden={menuState.isOpen}>
<ChatInput
input={chatStore.input}
setInput={(value) => chatStore.setInput(value)}
setInput={value => chatStore.setInput(value)}
handleSendMessage={chatStore.sendMessage}
isLoading={chatStore.isLoading}
/>

View File

@@ -1,16 +1,17 @@
import React from "react";
import { observer } from "mobx-react-lite";
import clientChatStore from "../../stores/ClientChatStore";
import { observer } from 'mobx-react-lite';
import React from 'react';
import clientChatStore from '../../stores/ClientChatStore';
export const IntermediateStepsComponent = observer(({ hidden }) => {
return (
<div hidden={hidden}>
{clientChatStore.intermediateSteps.map((step, index) => {
switch (step.kind) {
case "web-search": {
case 'web-search': {
return <WebSearchResult key={index} data={step.data} />;
}
case "tool-result":
case 'tool-result':
return <ToolResult key={index} data={step.data} />;
default:
return <GenericStep key={index} data={step.data} />;
@@ -45,7 +46,7 @@ export const GenericStep = ({ data }) => {
return (
<div className="generic-step">
<h3>Generic Step</h3>
<p>{data.description || "No additional information provided."}</p>
<p>{data.description || 'No additional information provided.'}</p>
</div>
);
};

View File

@@ -1,5 +1,3 @@
import React, { useRef } from "react";
import { observer } from "mobx-react-lite";
import {
Box,
Divider,
@@ -11,8 +9,10 @@ import {
Portal,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { ChevronRight } from "lucide-react";
} from '@chakra-ui/react';
import { ChevronRight } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import React, { useRef } from 'react';
const FlyoutSubMenu: React.FC<{
title: string;
@@ -23,15 +23,7 @@ const FlyoutSubMenu: React.FC<{
parentIsOpen: boolean;
setMenuState?: (state) => void;
}> = observer(
({
title,
flyoutMenuOptions,
onClose,
handleSelect,
isSelected,
parentIsOpen,
setMenuState,
}) => {
({ title, flyoutMenuOptions, onClose, handleSelect, isSelected, parentIsOpen, setMenuState }) => {
const { isOpen, onOpen, onClose: onSubMenuClose } = useDisclosure();
const menuRef = new useRef();
@@ -41,9 +33,9 @@ const FlyoutSubMenu: React.FC<{
placement="right-start"
isOpen={isOpen && parentIsOpen}
closeOnBlur={true}
lazyBehavior={"keepMounted"}
lazyBehavior={'keepMounted'}
isLazy={true}
onClose={(e) => {
onClose={e => {
onSubMenuClose();
}}
closeOnSelect={false}
@@ -54,12 +46,12 @@ const FlyoutSubMenu: React.FC<{
ref={menuRef}
bg="background.tertiary"
color="text.primary"
_hover={{ bg: "rgba(0, 0, 0, 0.05)" }}
_focus={{ bg: "rgba(0, 0, 0, 0.1)" }}
_hover={{ bg: 'rgba(0, 0, 0, 0.05)' }}
_focus={{ bg: 'rgba(0, 0, 0, 0.1)' }}
>
<HStack width={"100%"} justifyContent={"space-between"}>
<HStack width={'100%'} justifyContent={'space-between'}>
<Text>{title}</Text>
<ChevronRight size={"1rem"} />
<ChevronRight size={'1rem'} />
</HStack>
</MenuButton>
<Portal>
@@ -67,7 +59,7 @@ const FlyoutSubMenu: React.FC<{
key={title}
maxHeight={56}
overflowY="scroll"
visibility={"visible"}
visibility={'visible'}
minWidth="180px"
bg="background.tertiary"
boxShadow="lg"
@@ -77,43 +69,35 @@ const FlyoutSubMenu: React.FC<{
left="100%"
bottom={-10}
sx={{
"::-webkit-scrollbar": {
width: "8px",
'::-webkit-scrollbar': {
width: '8px',
},
"::-webkit-scrollbar-thumb": {
background: "background.primary",
borderRadius: "4px",
'::-webkit-scrollbar-thumb': {
background: 'background.primary',
borderRadius: '4px',
},
"::-webkit-scrollbar-track": {
background: "background.tertiary",
'::-webkit-scrollbar-track': {
background: 'background.tertiary',
},
}}
>
{flyoutMenuOptions.map((item, index) => (
<Box key={"itemflybox" + index}>
<Box key={'itemflybox' + index}>
<MenuItem
key={"itemfly" + index}
key={'itemfly' + index}
onClick={() => {
onSubMenuClose();
onClose();
handleSelect(item);
}}
bg={
isSelected(item)
? "background.secondary"
: "background.tertiary"
}
_hover={{ bg: "rgba(0, 0, 0, 0.05)" }}
_focus={{ bg: "rgba(0, 0, 0, 0.1)" }}
bg={isSelected(item) ? 'background.secondary' : 'background.tertiary'}
_hover={{ bg: 'rgba(0, 0, 0, 0.05)' }}
_focus={{ bg: 'rgba(0, 0, 0, 0.1)' }}
>
{item.name}
</MenuItem>
{index < flyoutMenuOptions.length - 1 && (
<Divider
key={item.name + "-divider"}
color="text.tertiary"
w={"100%"}
/>
<Divider key={item.name + '-divider'} color="text.tertiary" w={'100%'} />
)}
</Box>
))}

View File

@@ -1,197 +1,190 @@
import React, {useCallback, useEffect, useRef, useState} from "react";
import {
Box,
Button,
Divider,
Flex,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
Text,
useDisclosure,
useOutsideClick,
} from "@chakra-ui/react";
import {observer} from "mobx-react-lite";
import {ChevronDown, Copy, RefreshCcw, Settings} from "lucide-react";
import clientChatStore from "../../../stores/ClientChatStore";
import FlyoutSubMenu from "./FlyoutSubMenu";
import {useIsMobile} from "../../contexts/MobileContext";
import {useIsMobile as useIsMobileUserAgent} from "../../../hooks/_IsMobileHook";
import {formatConversationMarkdown} from "../lib/exportConversationAsMarkdown";
Box,
Button,
Divider,
Flex,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
Text,
useDisclosure,
useOutsideClick,
} from '@chakra-ui/react';
import { ChevronDown, Copy, RefreshCcw, Settings } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useIsMobile as useIsMobileUserAgent } from '../../../hooks/_IsMobileHook';
import clientChatStore from '../../../stores/ClientChatStore';
import { useIsMobile } from '../../contexts/MobileContext';
import { formatConversationMarkdown } from '../lib/exportConversationAsMarkdown';
import FlyoutSubMenu from './FlyoutSubMenu';
export const MsM_commonButtonStyles = {
bg: "transparent",
color: "text.primary",
borderRadius: "full",
padding: 2,
border: "none",
_hover: {bg: "rgba(255, 255, 255, 0.2)"},
_active: {bg: "rgba(255, 255, 255, 0.3)"},
_focus: {boxShadow: "none"},
bg: 'transparent',
color: 'text.primary',
borderRadius: 'full',
padding: 2,
border: 'none',
_hover: { bg: 'rgba(255, 255, 255, 0.2)' },
_active: { bg: 'rgba(255, 255, 255, 0.3)' },
_focus: { boxShadow: 'none' },
};
const InputMenu: React.FC<{ isDisabled?: boolean }> = observer(
({isDisabled}) => {
const isMobile = useIsMobile();
const isMobileUserAgent = useIsMobileUserAgent();
const {
isOpen,
onOpen,
onClose,
onToggle,
getDisclosureProps,
getButtonProps,
} = useDisclosure();
const InputMenu: React.FC<{ isDisabled?: boolean }> = observer(({ isDisabled }) => {
const isMobile = useIsMobile();
const isMobileUserAgent = useIsMobileUserAgent();
const { isOpen, onOpen, onClose, onToggle, getDisclosureProps, getButtonProps } = useDisclosure();
const [controlledOpen, setControlledOpen] = useState<boolean>(false);
const [supportedModels, setSupportedModels] = useState<any[]>([]);
const [controlledOpen, setControlledOpen] = useState<boolean>(false);
const [supportedModels, setSupportedModels] = useState<any[]>([]);
useEffect(() => {
setControlledOpen(isOpen);
}, [isOpen]);
useEffect(() => {
setControlledOpen(isOpen);
}, [isOpen]);
useEffect(() => {
fetch("/api/models").then(response => response.json()).then((models) => {
setSupportedModels(models);
}).catch((err) => {
console.error("Could not fetch models: ", err);
});
}, []);
useEffect(() => {
fetch('/api/models')
.then(response => response.json())
.then(models => {
setSupportedModels(models);
})
.catch(err => {
console.error('Could not fetch models: ', err);
});
}, []);
const handleClose = useCallback(() => {
onClose();
}, [isOpen]);
const handleClose = useCallback(() => {
onClose();
}, [isOpen]);
const handleCopyConversation = useCallback(() => {
navigator.clipboard
.writeText(formatConversationMarkdown(clientChatStore.items))
.then(() => {
window.alert('Conversation copied to clipboard. \n\nPaste it somewhere safe!');
onClose();
})
.catch(err => {
console.error('Could not copy text to clipboard: ', err);
window.alert('Failed to copy conversation. Please try again.');
});
}, [onClose]);
const handleCopyConversation = useCallback(() => {
navigator.clipboard
.writeText(formatConversationMarkdown(clientChatStore.items))
.then(() => {
window.alert(
"Conversation copied to clipboard. \n\nPaste it somewhere safe!",
);
onClose();
})
.catch((err) => {
console.error("Could not copy text to clipboard: ", err);
window.alert("Failed to copy conversation. Please try again.");
});
}, [onClose]);
async function selectModelFn({ name, value }) {
clientChatStore.setModel(value);
}
async function selectModelFn({name, value}) {
clientChatStore.setModel(value);
}
function isSelectedModelFn({ name, value }) {
return clientChatStore.model === value;
}
function isSelectedModelFn({name, value}) {
return clientChatStore.model === value;
}
const menuRef = useRef();
const [menuState, setMenuState] = useState();
const menuRef = useRef();
const [menuState, setMenuState] = useState();
useOutsideClick({
enabled: !isMobile && isOpen,
ref: menuRef,
handler: () => {
handleClose();
},
});
return (
<Menu
isOpen={controlledOpen}
onClose={onClose}
onOpen={onOpen}
autoSelect={false}
closeOnSelect={false}
closeOnBlur={isOpen && !isMobileUserAgent}
isLazy={true}
lazyBehavior={"unmount"}
>
{isMobile ? (
<MenuButton
as={IconButton}
bg="text.accent"
icon={<Settings size={20}/>}
isDisabled={isDisabled}
aria-label="Settings"
_hover={{bg: "rgba(255, 255, 255, 0.2)"}}
_focus={{boxShadow: "none"}}
{...MsM_commonButtonStyles}
/>
) : (
<MenuButton
as={Button}
rightIcon={<ChevronDown size={16}/>}
isDisabled={isDisabled}
variant="ghost"
display="flex"
justifyContent="space-between"
alignItems="center"
minW="auto"
{...MsM_commonButtonStyles}
>
<Text noOfLines={1} maxW="100px" fontSize="sm">
{clientChatStore.model}
</Text>
</MenuButton>
)}
<MenuList
bg="background.tertiary"
border="none"
borderRadius="md"
boxShadow="lg"
minW={"10rem"}
ref={menuRef}
>
<FlyoutSubMenu
title="Text Models"
flyoutMenuOptions={supportedModels.map((modelData) => ({
name: modelData.id.split('/').pop() || modelData.id,
value: modelData.id
}))}
onClose={onClose}
parentIsOpen={isOpen}
setMenuState={setMenuState}
handleSelect={selectModelFn}
isSelected={isSelectedModelFn}
/>
<Divider color="text.tertiary"/>
{/*Export conversation button*/}
<MenuItem
bg="background.tertiary"
color="text.primary"
onClick={handleCopyConversation}
_hover={{bg: "rgba(0, 0, 0, 0.05)"}}
_focus={{bg: "rgba(0, 0, 0, 0.1)"}}
>
<Flex align="center">
<Copy size="16px" style={{marginRight: "8px"}}/>
<Box>Export</Box>
</Flex>
</MenuItem>
{/*New conversation button*/}
<MenuItem
bg="background.tertiary"
color="text.primary"
onClick={() => {
clientChatStore.setActiveConversation("conversation:new");
onClose();
}}
_hover={{bg: "rgba(0, 0, 0, 0.05)"}}
_focus={{bg: "rgba(0, 0, 0, 0.1)"}}
>
<Flex align="center">
<RefreshCcw size="16px" style={{marginRight: "8px"}}/>
<Box>New</Box>
</Flex>
</MenuItem>
</MenuList>
</Menu>
);
useOutsideClick({
enabled: !isMobile && isOpen,
ref: menuRef,
handler: () => {
handleClose();
},
);
});
return (
<Menu
isOpen={controlledOpen}
onClose={onClose}
onOpen={onOpen}
autoSelect={false}
closeOnSelect={false}
closeOnBlur={isOpen && !isMobileUserAgent}
isLazy={true}
lazyBehavior={'unmount'}
>
{isMobile ? (
<MenuButton
as={IconButton}
bg="text.accent"
icon={<Settings size={20} />}
isDisabled={isDisabled}
aria-label="Settings"
_hover={{ bg: 'rgba(255, 255, 255, 0.2)' }}
_focus={{ boxShadow: 'none' }}
{...MsM_commonButtonStyles}
/>
) : (
<MenuButton
as={Button}
rightIcon={<ChevronDown size={16} />}
isDisabled={isDisabled}
variant="ghost"
display="flex"
justifyContent="space-between"
alignItems="center"
minW="auto"
{...MsM_commonButtonStyles}
>
<Text noOfLines={1} maxW="100px" fontSize="sm">
{clientChatStore.model}
</Text>
</MenuButton>
)}
<MenuList
bg="background.tertiary"
border="none"
borderRadius="md"
boxShadow="lg"
minW={'10rem'}
ref={menuRef}
>
<FlyoutSubMenu
title="Text Models"
flyoutMenuOptions={supportedModels.map(modelData => ({
name: modelData.id.split('/').pop() || modelData.id,
value: modelData.id,
}))}
onClose={onClose}
parentIsOpen={isOpen}
setMenuState={setMenuState}
handleSelect={selectModelFn}
isSelected={isSelectedModelFn}
/>
<Divider color="text.tertiary" />
{/*Export conversation button*/}
<MenuItem
bg="background.tertiary"
color="text.primary"
onClick={handleCopyConversation}
_hover={{ bg: 'rgba(0, 0, 0, 0.05)' }}
_focus={{ bg: 'rgba(0, 0, 0, 0.1)' }}
>
<Flex align="center">
<Copy size="16px" style={{ marginRight: '8px' }} />
<Box>Export</Box>
</Flex>
</MenuItem>
{/*New conversation button*/}
<MenuItem
bg="background.tertiary"
color="text.primary"
onClick={() => {
clientChatStore.setActiveConversation('conversation:new');
onClose();
}}
_hover={{ bg: 'rgba(0, 0, 0, 0.05)' }}
_focus={{ bg: 'rgba(0, 0, 0, 0.1)' }}
>
<Flex align="center">
<RefreshCcw size="16px" style={{ marginRight: '8px' }} />
<Box>New</Box>
</Flex>
</MenuItem>
</MenuList>
</Menu>
);
});
export default InputMenu;

View File

@@ -1,34 +1,28 @@
import React, { useEffect, useRef, useState } from "react";
import {
Box,
Button,
Grid,
GridItem,
useBreakpointValue,
} from "@chakra-ui/react";
import { observer } from "mobx-react-lite";
import chatStore from "../../../stores/ClientChatStore";
import InputMenu from "../input-menu/InputMenu";
import InputTextarea from "./ChatInputTextArea";
import SendButton from "./ChatInputSendButton";
import { useMaxWidth } from "../../../hooks/useMaxWidth";
import userOptionsStore from "../../../stores/UserOptionsStore";
import { Box, Button, Grid, GridItem, useBreakpointValue } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import { useMaxWidth } from '../../../hooks/useMaxWidth';
import chatStore from '../../../stores/ClientChatStore';
import userOptionsStore from '../../../stores/UserOptionsStore';
import InputMenu from '../input-menu/InputMenu';
import SendButton from './ChatInputSendButton';
import InputTextarea from './ChatInputTextArea';
const ChatInput = observer(() => {
const inputRef = useRef<HTMLTextAreaElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const maxWidth = useMaxWidth();
const [inputValue, setInputValue] = useState<string>("");
const [inputValue, setInputValue] = useState<string>('');
const [containerHeight, setContainerHeight] = useState(56);
const [containerBorderRadius, setContainerBorderRadius] = useState(9999);
const [shouldFollow, setShouldFollow] = useState<boolean>(
userOptionsStore.followModeEnabled,
);
const [shouldFollow, setShouldFollow] = useState<boolean>(userOptionsStore.followModeEnabled);
const [couldFollow, setCouldFollow] = useState<boolean>(chatStore.isLoading);
const [inputWidth, setInputWidth] = useState<string>("50%");
const [inputWidth, setInputWidth] = useState<string>('50%');
useEffect(() => {
setShouldFollow(chatStore.isLoading && userOptionsStore.followModeEnabled);
@@ -42,8 +36,8 @@ const ChatInput = observer(() => {
useEffect(() => {
if (containerRef.current) {
const observer = new ResizeObserver((entries) => {
for (let entry of entries) {
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const newHeight = entry.target.clientHeight;
setContainerHeight(newHeight);
@@ -63,20 +57,20 @@ const ChatInput = observer(() => {
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
chatStore.sendMessage();
}
};
const inputMaxWidth = useBreakpointValue(
{ base: "50rem", lg: "50rem", md: "80%", sm: "100vw" },
{ base: '50rem', lg: '50rem', md: '80%', sm: '100vw' },
{ ssr: true },
);
const inputMinWidth = useBreakpointValue({ lg: "40rem" }, { ssr: true });
const inputMinWidth = useBreakpointValue({ lg: '40rem' }, { ssr: true });
useEffect(() => {
setInputWidth("100%");
setInputWidth('100%');
}, [inputMaxWidth, inputMinWidth]);
return (
@@ -105,12 +99,12 @@ const ChatInput = observer(() => {
size="sm"
variant="ghost"
colorScheme="blue"
onClick={(_) => {
onClick={_ => {
userOptionsStore.toggleFollowMode();
}}
isDisabled={!chatStore.isLoading}
>
{shouldFollow ? "Disable Follow Mode" : "Enable Follow Mode"}
{shouldFollow ? 'Disable Follow Mode' : 'Enable Follow Mode'}
</Button>
</Box>
)}
@@ -123,7 +117,7 @@ const ChatInput = observer(() => {
gap={2}
alignItems="center"
style={{
transition: "border-radius 0.2s ease",
transition: 'border-radius 0.2s ease',
}}
>
<GridItem>

View File

@@ -1,9 +1,9 @@
import React from "react";
import { Button } from "@chakra-ui/react";
import clientChatStore from "../../../stores/ClientChatStore";
import { CirclePause, Send } from "lucide-react";
import { Button } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { CirclePause, Send } from 'lucide-react';
import React from 'react';
import { motion } from "framer-motion";
import clientChatStore from '../../../stores/ClientChatStore';
interface SendButtonProps {
isLoading: boolean;
@@ -13,25 +13,20 @@ interface SendButtonProps {
}
const SendButton: React.FC<SendButtonProps> = ({ onClick }) => {
const isDisabled =
clientChatStore.input.trim().length === 0 && !clientChatStore.isLoading;
const isDisabled = clientChatStore.input.trim().length === 0 && !clientChatStore.isLoading;
return (
<Button
onClick={(e) =>
clientChatStore.isLoading
? clientChatStore.stopIncomingMessage()
: onClick(e)
onClick={e =>
clientChatStore.isLoading ? clientChatStore.stopIncomingMessage() : onClick(e)
}
bg="transparent"
color={
clientChatStore.input.trim().length <= 1 ? "brand.700" : "text.primary"
}
color={clientChatStore.input.trim().length <= 1 ? 'brand.700' : 'text.primary'}
borderRadius="full"
p={2}
isDisabled={isDisabled}
_hover={{ bg: !isDisabled ? "rgba(255, 255, 255, 0.2)" : "inherit" }}
_active={{ bg: !isDisabled ? "rgba(255, 255, 255, 0.3)" : "inherit" }}
_focus={{ boxShadow: "none" }}
_hover={{ bg: !isDisabled ? 'rgba(255, 255, 255, 0.2)' : 'inherit' }}
_active={{ bg: !isDisabled ? 'rgba(255, 255, 255, 0.3)' : 'inherit' }}
_focus={{ boxShadow: 'none' }}
>
{clientChatStore.isLoading ? <MySpinner /> : <Send size={20} />}
</Button>
@@ -45,10 +40,10 @@ const MySpinner = ({ onClick }) => (
exit={{ opacity: 0, scale: 0.9 }}
transition={{
duration: 0.4,
ease: "easeInOut",
ease: 'easeInOut',
}}
>
<CirclePause color={"#F0F0F0"} size={24} onClick={onClick} />
<CirclePause color={'#F0F0F0'} size={24} onClick={onClick} />
</motion.div>
);

View File

@@ -1,7 +1,7 @@
import React, {useEffect, useRef, useState} from "react";
import {observer} from "mobx-react-lite";
import {Box, chakra, InputGroup,} from "@chakra-ui/react";
import AutoResize from "react-textarea-autosize";
import { Box, chakra, InputGroup } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import AutoResize from 'react-textarea-autosize';
const AutoResizeTextArea = chakra(AutoResize);
@@ -15,10 +15,7 @@ interface InputTextAreaProps {
const InputTextArea: React.FC<InputTextAreaProps> = observer(
({ inputRef, value, onChange, onKeyDown, isLoading }) => {
const [heightConstraint, setHeightConstraint] = useState<
number | undefined
>(10);
const [heightConstraint, setHeightConstraint] = useState<number | undefined>(10);
useEffect(() => {
if (value.length > 10) {
@@ -34,7 +31,6 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
display="flex"
flexDirection="column"
>
{/* Input Area */}
<InputGroup position="relative">
<AutoResizeTextArea
@@ -43,7 +39,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
value={value}
height={heightConstraint}
autoFocus
onChange={(e) => onChange(e.target.value)}
onChange={e => onChange(e.target.value)}
onKeyDown={onKeyDown}
p={2}
pr="8px"
@@ -53,19 +49,19 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
borderRadius="20px"
border="none"
placeholder="Free my mind..."
_placeholder={{ color: "gray.400" }}
_placeholder={{ color: 'gray.400' }}
_focus={{
outline: "none",
outline: 'none',
}}
disabled={isLoading}
minRows={1}
maxRows={12}
style={{
touchAction: "none",
resize: "none",
overflowY: "auto",
width: "100%",
transition: "height 0.2s ease-in-out",
touchAction: 'none',
resize: 'none',
overflowY: 'auto',
width: '100%',
transition: 'height 0.2s ease-in-out',
}}
/>
</InputGroup>

View File

@@ -1,9 +1,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import ChatInput from '../ChatInput';
import userOptionsStore from '../../../../stores/UserOptionsStore';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import chatStore from '../../../../stores/ClientChatStore';
import userOptionsStore from '../../../../stores/UserOptionsStore';
import ChatInput from '../ChatInput';
// Mock browser APIs
class MockResizeObserver {
@@ -85,7 +86,7 @@ vi.mock('./ChatInputTextArea', () => ({
aria-label="Chat input"
ref={inputRef}
value={value}
onChange={(e) => onChange(e.target.value)}
onChange={e => onChange(e.target.value)}
onKeyDown={onKeyDown}
disabled={isLoading}
/>

View File

@@ -8,16 +8,16 @@ const SUPPORTED_MODELS_GROUPS = {
groq: [
// "mixtral-8x7b-32768",
// "deepseek-r1-distill-llama-70b",
"meta-llama/llama-4-scout-17b-16e-instruct",
"gemma2-9b-it",
"mistral-saba-24b",
'meta-llama/llama-4-scout-17b-16e-instruct',
'gemma2-9b-it',
'mistral-saba-24b',
// "qwen-2.5-32b",
"llama-3.3-70b-versatile",
'llama-3.3-70b-versatile',
// "llama-3.3-70b-versatile"
// "llama-3.1-70b-versatile",
// "llama-3.3-70b-versatile"
],
cerebras: ["llama-3.3-70b"],
cerebras: ['llama-3.3-70b'],
claude: [
// "claude-3-5-sonnet-20241022",
// "claude-3-opus-20240229"
@@ -44,34 +44,34 @@ const SUPPORTED_MODELS_GROUPS = {
// "grok-beta"
],
cloudflareAI: [
"llama-3.2-3b-instruct", // max_tokens
"llama-3-8b-instruct", // max_tokens
"llama-3.1-8b-instruct-fast", // max_tokens
"deepseek-math-7b-instruct",
"deepseek-coder-6.7b-instruct-awq",
"hermes-2-pro-mistral-7b",
"openhermes-2.5-mistral-7b-awq",
"mistral-7b-instruct-v0.2",
"neural-chat-7b-v3-1-awq",
"openchat-3.5-0106",
'llama-3.2-3b-instruct', // max_tokens
'llama-3-8b-instruct', // max_tokens
'llama-3.1-8b-instruct-fast', // max_tokens
'deepseek-math-7b-instruct',
'deepseek-coder-6.7b-instruct-awq',
'hermes-2-pro-mistral-7b',
'openhermes-2.5-mistral-7b-awq',
'mistral-7b-instruct-v0.2',
'neural-chat-7b-v3-1-awq',
'openchat-3.5-0106',
// "gemma-7b-it",
],
};
export type SupportedModel =
| keyof typeof SUPPORTED_MODELS_GROUPS
| (typeof SUPPORTED_MODELS_GROUPS)[keyof typeof SUPPORTED_MODELS_GROUPS][number];
| keyof typeof SUPPORTED_MODELS_GROUPS
| (typeof SUPPORTED_MODELS_GROUPS)[keyof typeof SUPPORTED_MODELS_GROUPS][number];
export type ModelFamily = keyof typeof SUPPORTED_MODELS_GROUPS;
function getModelFamily(model: string): ModelFamily | undefined {
return Object.keys(SUPPORTED_MODELS_GROUPS)
.filter((family) => {
return SUPPORTED_MODELS_GROUPS[
family as keyof typeof SUPPORTED_MODELS_GROUPS
].includes(model.trim());
})
.at(0) as ModelFamily | undefined;
.filter(family => {
return SUPPORTED_MODELS_GROUPS[family as keyof typeof SUPPORTED_MODELS_GROUPS].includes(
model.trim(),
);
})
.at(0) as ModelFamily | undefined;
}
const SUPPORTED_MODELS = [

View File

@@ -1,30 +1,30 @@
import DOMPurify from "isomorphic-dompurify";
import DOMPurify from 'isomorphic-dompurify';
function domPurify(dirty: string) {
return DOMPurify.sanitize(dirty, {
USE_PROFILES: { html: true },
ALLOWED_TAGS: [
"b",
"i",
"u",
"a",
"p",
"span",
"div",
"table",
"thead",
"tbody",
"tr",
"td",
"th",
"ul",
"ol",
"li",
"code",
"pre",
'b',
'i',
'u',
'a',
'p',
'span',
'div',
'table',
'thead',
'tbody',
'tr',
'td',
'th',
'ul',
'ol',
'li',
'code',
'pre',
],
ALLOWED_ATTR: ["href", "src", "alt", "title", "class", "style"],
FORBID_TAGS: ["script", "iframe"],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'style'],
FORBID_TAGS: ['script', 'iframe'],
KEEP_CONTENT: true,
SAFE_FOR_TEMPLATES: true,
});

View File

@@ -1,18 +1,17 @@
// Function to generate a Markdown representation of the current conversation
import { type IMessage } from "../../../stores/ClientChatStore";
import { type Instance } from "mobx-state-tree";
import { type Instance } from 'mobx-state-tree';
export function formatConversationMarkdown(
messages: Instance<typeof IMessage>[],
): string {
import { type IMessage } from '../../../stores/ClientChatStore';
export function formatConversationMarkdown(messages: Instance<typeof IMessage>[]): string {
return messages
.map((message) => {
if (message.role === "user") {
.map(message => {
if (message.role === 'user') {
return `**You**: ${message.content}`;
} else if (message.role === "assistant") {
} else if (message.role === 'assistant') {
return `**Geoff's AI**: ${message.content}`;
}
return "";
return '';
})
.join("\n\n");
.join('\n\n');
}

View File

@@ -1,6 +1,6 @@
import React from "react";
import React from 'react';
import MessageMarkdownRenderer from "./MessageMarkdownRenderer";
import MessageMarkdownRenderer from './MessageMarkdownRenderer';
const ChatMessageContent = ({ content }) => {
return <MessageMarkdownRenderer markdown={content} />;

View File

@@ -1,9 +1,11 @@
import React from "react";
import {Box, Grid, GridItem} from "@chakra-ui/react";
import MessageBubble from "./MessageBubble";
import {observer} from "mobx-react-lite";
import chatStore from "../../../stores/ClientChatStore";
import {useIsMobile} from "../../contexts/MobileContext";
import { Box, Grid, GridItem } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React from 'react';
import chatStore from '../../../stores/ClientChatStore';
import { useIsMobile } from '../../contexts/MobileContext';
import MessageBubble from './MessageBubble';
interface ChatMessagesProps {
scrollRef: React.RefObject<HTMLDivElement>;
@@ -13,11 +15,7 @@ const ChatMessages: React.FC<ChatMessagesProps> = observer(({ scrollRef }) => {
const isMobile = useIsMobile();
return (
<Box
pt={isMobile ? 24 : undefined}
overflowY={"scroll"}
overflowX={"hidden"}
>
<Box pt={isMobile ? 24 : undefined} overflowY={'scroll'} overflowX={'hidden'}>
<Grid
fontFamily="Arial, sans-serif"
templateColumns="1fr"

View File

@@ -1,43 +1,43 @@
import React, { useEffect, useRef, useState } from "react";
import { Box, Flex, Text } from "@chakra-ui/react";
import MessageRenderer from "./ChatMessageContent";
import { observer } from "mobx-react-lite";
import MessageEditor from "./MessageEditorComponent";
import UserMessageTools from "./UserMessageTools";
import clientChatStore from "../../../stores/ClientChatStore";
import UserOptionsStore from "../../../stores/UserOptionsStore";
import MotionBox from "./MotionBox";
import { Box, Flex, Text } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import clientChatStore from '../../../stores/ClientChatStore';
import UserOptionsStore from '../../../stores/UserOptionsStore';
import MessageRenderer from './ChatMessageContent';
import MessageEditor from './MessageEditorComponent';
import MotionBox from './MotionBox';
import UserMessageTools from './UserMessageTools';
const LoadingDots = () => {
return (
<Flex>
{[0, 1, 2].map((i) => (
<MotionBox
key={i}
width="8px"
height="8px"
borderRadius="50%"
backgroundColor="text.primary"
margin="0 4px"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 1,
repeat: Infinity,
delay: i * 0.2,
}}
/>
))}
</Flex>
<Flex>
{[0, 1, 2].map(i => (
<MotionBox
key={i}
width="8px"
height="8px"
borderRadius="50%"
backgroundColor="text.primary"
margin="0 4px"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 1,
repeat: Infinity,
delay: i * 0.2,
}}
/>
))}
</Flex>
);
}
};
function renderMessage(msg: any) {
if (msg.role === "user") {
if (msg.role === 'user') {
return (
<Text as="p" fontSize="sm" lineHeight="short" color="text.primary">
{msg.content}
@@ -50,8 +50,8 @@ function renderMessage(msg: any) {
const MessageBubble = observer(({ msg, scrollRef }) => {
const [isEditing, setIsEditing] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const isUser = msg.role === "user";
const senderName = isUser ? "You" : "Geoff's AI";
const isUser = msg.role === 'user';
const senderName = isUser ? 'You' : "Geoff's AI";
const isLoading = !msg.content || !(msg.content.trim().length > 0);
const messageRef = useRef();
@@ -64,10 +64,15 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
};
useEffect(() => {
if (clientChatStore.items.length > 0 && clientChatStore.isLoading && UserOptionsStore.followModeEnabled) { // Refine condition
if (
clientChatStore.items.length > 0 &&
clientChatStore.isLoading &&
UserOptionsStore.followModeEnabled
) {
// Refine condition
scrollRef.current?.scrollTo({
top: scrollRef.current.scrollHeight,
behavior: "auto",
behavior: 'auto',
});
}
});
@@ -75,7 +80,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
return (
<Flex
flexDirection="column"
alignItems={isUser ? "flex-end" : "flex-start"}
alignItems={isUser ? 'flex-end' : 'flex-start'}
role="listitem"
flex={0}
aria-label={`Message from ${senderName}`}
@@ -85,19 +90,19 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
<Text
fontSize="xs"
color="text.tertiary"
textAlign={isUser ? "right" : "left"}
alignSelf={isUser ? "flex-end" : "flex-start"}
textAlign={isUser ? 'right' : 'left'}
alignSelf={isUser ? 'flex-end' : 'flex-start'}
mb={1}
>
{senderName}
</Text>
<MotionBox
minW={{ base: "99%", sm: "99%", lg: isUser ? "55%" : "60%" }}
maxW={{ base: "99%", sm: "99%", lg: isUser ? "65%" : "65%" }}
minW={{ base: '99%', sm: '99%', lg: isUser ? '55%' : '60%' }}
maxW={{ base: '99%', sm: '99%', lg: isUser ? '65%' : '65%' }}
p={3}
borderRadius="1.5em"
bg={isUser ? "#0A84FF" : "#3A3A3C"}
bg={isUser ? '#0A84FF' : '#3A3A3C'}
color="text.primary"
textAlign="left"
boxShadow="0 2px 4px rgba(0, 0, 0, 0.1)"
@@ -115,10 +120,10 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
whiteSpace="pre-wrap"
ref={messageRef}
sx={{
"pre, code": {
maxWidth: "100%",
whiteSpace: "pre-wrap",
overflowX: "auto",
'pre, code': {
maxWidth: '100%',
whiteSpace: 'pre-wrap',
overflowX: 'auto',
},
}}
>
@@ -139,9 +144,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
justifyContent="center"
alignItems="center"
>
{isHovered && !isEditing && (
<UserMessageTools message={msg} onEdit={handleEdit} />
)}
{isHovered && !isEditing && <UserMessageTools message={msg} onEdit={handleEdit} />}
</Box>
)}
</Flex>

View File

@@ -1,10 +1,11 @@
import React, {type KeyboardEvent, useEffect } from "react";
import { Box, Flex, IconButton, Textarea } from "@chakra-ui/react";
import { Check, X } from "lucide-react";
import { observer } from "mobx-react-lite";
import { type Instance } from "mobx-state-tree";
import Message from "../../../models/Message";
import messageEditorStore from "../../../stores/MessageEditorStore";
import { Box, Flex, IconButton, Textarea } from '@chakra-ui/react';
import { Check, X } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import { type Instance } from 'mobx-state-tree';
import React, { type KeyboardEvent, useEffect } from 'react';
import Message from '../../../models/Message';
import messageEditorStore from '../../../stores/MessageEditorStore';
interface MessageEditorProps {
message: Instance<typeof Message>;
@@ -30,15 +31,13 @@ const MessageEditor = observer(({ message, onCancel }: MessageEditorProps) => {
onCancel();
};
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
handleSave();
}
if (e.key === "Escape") {
if (e.key === 'Escape') {
e.preventDefault();
handleCancel();
}
@@ -48,14 +47,14 @@ const MessageEditor = observer(({ message, onCancel }: MessageEditorProps) => {
<Box width="100%">
<Textarea
value={messageEditorStore.editedContent}
onChange={(e) => messageEditorStore.setEditedContent(e.target.value)}
onChange={e => messageEditorStore.setEditedContent(e.target.value)}
onKeyDown={handleKeyDown}
minHeight="100px"
bg="transparent"
border="1px solid"
borderColor="whiteAlpha.300"
_hover={{ borderColor: "whiteAlpha.400" }}
_focus={{ borderColor: "brand.100", boxShadow: "none" }}
_hover={{ borderColor: 'whiteAlpha.400' }}
_focus={{ borderColor: 'brand.100', boxShadow: 'none' }}
resize="vertical"
color="text.primary"
/>
@@ -66,7 +65,7 @@ const MessageEditor = observer(({ message, onCancel }: MessageEditorProps) => {
onClick={handleCancel}
size="sm"
variant="ghost"
color={"accent.danger"}
color={'accent.danger'}
/>
<IconButton
aria-label="Save edit"
@@ -74,7 +73,7 @@ const MessageEditor = observer(({ message, onCancel }: MessageEditorProps) => {
onClick={handleSave}
size="sm"
variant="ghost"
color={"accent.confirm"}
color={'accent.confirm'}
/>
</Flex>
</Box>

View File

@@ -1,5 +1,3 @@
import React from "react";
import {
Box,
Code,
@@ -17,13 +15,15 @@ import {
Thead,
Tr,
useColorModeValue,
} from "@chakra-ui/react";
import { marked } from "marked";
import CodeBlock from "../../code/CodeBlock";
import ImageWithFallback from "../../markdown/ImageWithFallback";
import markedKatex from "marked-katex-extension";
import katex from "katex";
import domPurify from "../lib/domPurify";
} from '@chakra-ui/react';
import katex from 'katex';
import { marked } from 'marked';
import markedKatex from 'marked-katex-extension';
import React from 'react';
import CodeBlock from '../../code/CodeBlock';
import ImageWithFallback from '../../markdown/ImageWithFallback';
import domPurify from '../lib/domPurify';
try {
if (localStorage) {
@@ -34,11 +34,13 @@ try {
throwOnError: false,
strict: true,
colorIsTextColor: true,
errorColor: "red",
errorColor: 'red',
}),
);
}
} catch (_) {}
} catch (_) {
// Silently ignore errors in marked setup - fallback to default behavior
}
const MemoizedCodeBlock = React.memo(CodeBlock);
@@ -49,32 +51,29 @@ const MemoizedCodeBlock = React.memo(CodeBlock);
const getHeadingProps = (depth: number) => {
switch (depth) {
case 1:
return { as: "h1", size: "xl", mt: 4, mb: 2 };
return { as: 'h1', size: 'xl', mt: 4, mb: 2 };
case 2:
return { as: "h2", size: "lg", mt: 3, mb: 2 };
return { as: 'h2', size: 'lg', mt: 3, mb: 2 };
case 3:
return { as: "h3", size: "md", mt: 2, mb: 1 };
return { as: 'h3', size: 'md', mt: 2, mb: 1 };
case 4:
return { as: "h4", size: "sm", mt: 2, mb: 1 };
return { as: 'h4', size: 'sm', mt: 2, mb: 1 };
case 5:
return { as: "h5", size: "sm", mt: 2, mb: 1 };
return { as: 'h5', size: 'sm', mt: 2, mb: 1 };
case 6:
return { as: "h6", size: "xs", mt: 2, mb: 1 };
return { as: 'h6', size: 'xs', mt: 2, mb: 1 };
default:
return { as: `h${depth}`, size: "md", mt: 2, mb: 1 };
return { as: `h${depth}`, size: 'md', mt: 2, mb: 1 };
}
};
interface TableToken extends marked.Tokens.Table {
align: Array<"center" | "left" | "right" | null>;
align: Array<'center' | 'left' | 'right' | null>;
header: (string | marked.Tokens.TableCell)[];
rows: (string | marked.Tokens.TableCell)[][];
}
const CustomHeading: React.FC<{ text: string; depth: number }> = ({
text,
depth,
}) => {
const CustomHeading: React.FC<{ text: string; depth: number }> = ({ text, depth }) => {
const headingProps = getHeadingProps(depth);
return (
<Heading {...headingProps} wordBreak="break-word" maxWidth="100%">
@@ -83,9 +82,7 @@ const CustomHeading: React.FC<{ text: string; depth: number }> = ({
);
};
const CustomParagraph: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const CustomParagraph: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Text
as="p"
@@ -100,9 +97,7 @@ const CustomParagraph: React.FC<{ children: React.ReactNode }> = ({
);
};
const CustomBlockquote: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const CustomBlockquote: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Box
as="blockquote"
@@ -120,16 +115,9 @@ const CustomBlockquote: React.FC<{ children: React.ReactNode }> = ({
);
};
const CustomCodeBlock: React.FC<{ code: string; language?: string }> = ({
code,
language,
}) => {
const CustomCodeBlock: React.FC<{ code: string; language?: string }> = ({ code, language }) => {
return (
<MemoizedCodeBlock
language={language}
code={code}
onRenderComplete={() => Promise.resolve()}
/>
<MemoizedCodeBlock language={language} code={code} onRenderComplete={() => Promise.resolve()} />
);
};
@@ -141,10 +129,10 @@ const CustomList: React.FC<{
children: React.ReactNode;
}> = ({ ordered, start, children }) => {
const commonStyles = {
fontSize: "sm",
wordBreak: "break-word" as const,
maxWidth: "100%" as const,
stylePosition: "outside" as const,
fontSize: 'sm',
wordBreak: 'break-word' as const,
maxWidth: '100%' as const,
stylePosition: 'outside' as const,
mb: 2,
pl: 4,
};
@@ -166,16 +154,13 @@ const CustomListItem: React.FC<{
return <ListItem mb={1}>{children}</ListItem>;
};
const CustomKatex: React.FC<{ math: string; displayMode: boolean }> = ({
math,
displayMode,
}) => {
const CustomKatex: React.FC<{ math: string; displayMode: boolean }> = ({ math, displayMode }) => {
const renderedMath = katex.renderToString(math, { displayMode });
return (
<Box
as="span"
display={displayMode ? "block" : "inline"}
display={displayMode ? 'block' : 'inline'}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
@@ -188,23 +173,17 @@ const CustomKatex: React.FC<{ math: string; displayMode: boolean }> = ({
const CustomTable: React.FC<{
header: React.ReactNode[];
align: Array<"center" | "left" | "right" | null>;
align: Array<'center' | 'left' | 'right' | null>;
rows: React.ReactNode[][];
}> = ({ header, align, rows }) => {
return (
<Table
variant="simple"
size="sm"
my={4}
borderRadius="md"
overflow="hidden"
>
<Table variant="simple" size="sm" my={4} borderRadius="md" overflow="hidden">
<Thead bg="background.secondary">
<Tr>
{header.map((cell, i) => (
<Th
key={i}
textAlign={align[i] || "left"}
textAlign={align[i] || 'left'}
fontWeight="bold"
p={2}
minW={16}
@@ -219,12 +198,7 @@ const CustomTable: React.FC<{
{rows.map((row, rIndex) => (
<Tr key={rIndex}>
{row.map((cell, cIndex) => (
<Td
key={cIndex}
textAlign={align[cIndex] || "left"}
p={2}
wordBreak="break-word"
>
<Td key={cIndex} textAlign={align[cIndex] || 'left'} p={2} wordBreak="break-word">
{cell}
</Td>
))}
@@ -241,13 +215,7 @@ const CustomHtmlBlock: React.FC<{ content: string }> = ({ content }) => {
const CustomText: React.FC<{ text: React.ReactNode }> = ({ text }) => {
return (
<Text
fontSize="sm"
lineHeight="short"
wordBreak="break-word"
maxWidth="100%"
as="span"
>
<Text fontSize="sm" lineHeight="short" wordBreak="break-word" maxWidth="100%" as="span">
{text}
</Text>
);
@@ -262,13 +230,7 @@ const CustomStrong: React.FC<CustomStrongProps> = ({ children }) => {
const CustomEm: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Text
as="em"
fontStyle="italic"
lineHeight="short"
wordBreak="break-word"
display="inline"
>
<Text as="em" fontStyle="italic" lineHeight="short" wordBreak="break-word" display="inline">
{children}
</Text>
);
@@ -289,7 +251,7 @@ const CustomDel: React.FC<{ text: string }> = ({ text }) => {
};
const CustomCodeSpan: React.FC<{ code: string }> = ({ code }) => {
const bg = useColorModeValue("gray.100", "gray.800");
const bg = useColorModeValue('gray.100', 'gray.800');
return (
<Code
fontSize="sm"
@@ -312,13 +274,13 @@ const CustomMath: React.FC<{ math: string; displayMode?: boolean }> = ({
return (
<Box
as="span"
display={displayMode ? "block" : "inline"}
display={displayMode ? 'block' : 'inline'}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
overflow="auto"
maxWidth="100%"
className={`math ${displayMode ? "math-display" : "math-inline"}`}
className={`math ${displayMode ? 'math-display' : 'math-inline'}`}
>
{math}
</Box>
@@ -336,8 +298,8 @@ const CustomLink: React.FC<{
title={title}
isExternal
sx={{
"& span": {
color: "text.link",
'& span': {
color: 'text.link',
},
}}
maxWidth="100%"
@@ -379,46 +341,34 @@ function parseTokens(tokens: marked.Token[]): JSX.Element[] {
tokens.forEach((token, i) => {
switch (token.type) {
case "heading":
output.push(
<CustomHeading key={i} text={token.text} depth={token.depth} />,
);
case 'heading':
output.push(<CustomHeading key={i} text={token.text} depth={token.depth} />);
break;
case "paragraph": {
const parsedContent = token.tokens
? parseTokens(token.tokens)
: token.text;
case 'paragraph': {
const parsedContent = token.tokens ? parseTokens(token.tokens) : token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(
<CustomParagraph key={i}>{parsedContent}</CustomParagraph>,
);
blockquoteContent.push(<CustomParagraph key={i}>{parsedContent}</CustomParagraph>);
} else {
output.push(
<CustomParagraph key={i}>{parsedContent}</CustomParagraph>,
);
output.push(<CustomParagraph key={i}>{parsedContent}</CustomParagraph>);
}
break;
}
case "br":
case 'br':
output.push(<br key={i} />);
break;
case "escape": {
case 'escape': {
break;
}
case "blockquote_start":
case 'blockquote_start':
blockquoteContent = [];
break;
case "blockquote_end":
output.push(
<CustomBlockquote key={i}>
{parseTokens(blockquoteContent)}
</CustomBlockquote>,
);
case 'blockquote_end':
output.push(<CustomBlockquote key={i}>{parseTokens(blockquoteContent)}</CustomBlockquote>);
blockquoteContent = [];
break;
case "blockquote": {
case 'blockquote': {
output.push(
<CustomBlockquote key={i}>
{token.tokens ? parseTokens(token.tokens) : null}
@@ -426,44 +376,30 @@ function parseTokens(tokens: marked.Token[]): JSX.Element[] {
);
break;
}
case "math":
output.push(
<CustomMath key={i} math={(token as any).value} displayMode={true} />,
);
case 'math':
output.push(<CustomMath key={i} math={(token as any).value} displayMode={true} />);
break;
case "inlineMath":
output.push(
<CustomMath
key={i}
math={(token as any).value}
displayMode={false}
/>,
);
case 'inlineMath':
output.push(<CustomMath key={i} math={(token as any).value} displayMode={false} />);
break;
case "inlineKatex":
case "blockKatex": {
case 'inlineKatex':
case 'blockKatex': {
const katexToken = token as any;
output.push(
<CustomKatex
key={i}
math={katexToken.text}
displayMode={katexToken.displayMode}
/>,
<CustomKatex key={i} math={katexToken.text} displayMode={katexToken.displayMode} />,
);
break;
}
case "code":
output.push(
<CustomCodeBlock key={i} code={token.text} language={token.lang} />,
);
case 'code':
output.push(<CustomCodeBlock key={i} code={token.text} language={token.lang} />);
break;
case "hr":
case 'hr':
output.push(<CustomHr key={i} />);
break;
case "list": {
case 'list': {
const { ordered, start, items } = token;
const listItems = items.map((listItem, idx) => {
const nestedContent = parseTokens(listItem.tokens);
@@ -477,53 +413,43 @@ function parseTokens(tokens: marked.Token[]): JSX.Element[] {
);
break;
}
case "table": {
case 'table': {
const tableToken = token as TableToken;
output.push(
<CustomTable
key={i}
header={tableToken.header.map((cell) =>
typeof cell === "string" ? cell : parseTokens(cell.tokens || []),
header={tableToken.header.map(cell =>
typeof cell === 'string' ? cell : parseTokens(cell.tokens || []),
)}
align={tableToken.align}
rows={tableToken.rows.map((row) =>
row.map((cell) =>
typeof cell === "string"
? cell
: parseTokens(cell.tokens || []),
),
rows={tableToken.rows.map(row =>
row.map(cell => (typeof cell === 'string' ? cell : parseTokens(cell.tokens || []))),
)}
/>,
);
break;
}
case "html":
case 'html':
output.push(<CustomHtmlBlock key={i} content={token.text} />);
break;
case "def":
case "space":
case 'def':
case 'space':
break;
case "strong":
output.push(
<CustomStrong key={i}>
{parseTokens(token.tokens || [])}
</CustomStrong>,
);
case 'strong':
output.push(<CustomStrong key={i}>{parseTokens(token.tokens || [])}</CustomStrong>);
break;
case "em":
case 'em':
output.push(
<CustomEm key={i}>
{token.tokens ? parseTokens(token.tokens) : token.text}
</CustomEm>,
<CustomEm key={i}>{token.tokens ? parseTokens(token.tokens) : token.text}</CustomEm>,
);
break;
case "codespan":
case 'codespan':
output.push(<CustomCodeSpan key={i} code={token.text} />);
break;
case "link":
case 'link':
output.push(
<CustomLink key={i} href={token.href} title={token.title}>
{token.tokens ? parseTokens(token.tokens) : token.text}
@@ -531,33 +457,24 @@ function parseTokens(tokens: marked.Token[]): JSX.Element[] {
);
break;
case "image":
case 'image':
output.push(
<CustomImage
key={i}
href={token.href}
title={token.title}
text={token.text}
/>,
<CustomImage key={i} href={token.href} title={token.title} text={token.text} />,
);
break;
case "text": {
const parsedContent = token.tokens
? parseTokens(token.tokens)
: token.text;
case 'text': {
const parsedContent = token.tokens ? parseTokens(token.tokens) : token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(
<React.Fragment key={i}>{parsedContent}</React.Fragment>,
);
blockquoteContent.push(<React.Fragment key={i}>{parsedContent}</React.Fragment>);
} else {
output.push(<CustomText key={i} text={parsedContent} />);
}
break;
}
default:
console.warn("Unhandled token type:", token.type, token);
console.warn('Unhandled token type:', token.type, token);
}
});

View File

@@ -1,13 +1,12 @@
import React from "react";
import {renderMessageMarkdown} from "./MessageMarkdown";
import React from 'react';
import { renderMessageMarkdown } from './MessageMarkdown';
interface CustomMarkdownRendererProps {
markdown: string;
}
const MessageMarkdownRenderer: React.FC<CustomMarkdownRendererProps> = ({
markdown,
}) => {
const MessageMarkdownRenderer: React.FC<CustomMarkdownRendererProps> = ({ markdown }) => {
return <div>{renderMessageMarkdown(markdown)}</div>;
};

View File

@@ -1,4 +1,4 @@
import {motion} from "framer-motion";
import {Box} from "@chakra-ui/react";
import { Box } from '@chakra-ui/react';
import { motion } from 'framer-motion';
export default motion(Box);
export default motion(Box);

View File

@@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { IconButton } from "@chakra-ui/react";
import { Edit2Icon } from "lucide-react";
import { IconButton } from '@chakra-ui/react';
import { Edit2Icon } from 'lucide-react';
import { observer } from 'mobx-react-lite';
const UserMessageTools = observer(({ disabled = false, message, onEdit }) => (
<IconButton
@@ -8,26 +8,26 @@ const UserMessageTools = observer(({ disabled = false, message, onEdit }) => (
color="text.primary"
aria-label="Edit message"
title="Edit message"
icon={<Edit2Icon size={"1em"} />}
icon={<Edit2Icon size={'1em'} />}
onClick={() => onEdit(message)}
_active={{
bg: "transparent",
bg: 'transparent',
svg: {
stroke: "brand.100",
transition: "stroke 0.3s ease-in-out",
stroke: 'brand.100',
transition: 'stroke 0.3s ease-in-out',
},
}}
_hover={{
bg: "transparent",
bg: 'transparent',
svg: {
stroke: "accent.secondary",
transition: "stroke 0.3s ease-in-out",
stroke: 'accent.secondary',
transition: 'stroke 0.3s ease-in-out',
},
}}
variant="ghost"
size="sm"
isDisabled={disabled}
_focus={{ boxShadow: "none" }}
_focus={{ boxShadow: 'none' }}
/>
));

View File

@@ -1,14 +1,15 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import messageEditorStore from '../../../../stores/MessageEditorStore';
import MessageBubble from '../MessageBubble';
import messageEditorStore from "../../../../stores/MessageEditorStore";
// Mock browser APIs
class MockResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
observe() {}
unobserve() {}
disconnect() {}
}
// Add ResizeObserver to the global object
@@ -16,140 +17,140 @@ global.ResizeObserver = MockResizeObserver;
// Mock the Message model
vi.mock('../../../../models/Message', () => ({
default: {
// This is needed for the Instance<typeof Message> type
}
default: {
// This is needed for the Instance<typeof Message> type
},
}));
// Mock the stores
vi.mock('../../../../stores/ClientChatStore', () => ({
default: {
items: [],
isLoading: false,
editMessage: vi.fn().mockReturnValue(true)
}
default: {
items: [],
isLoading: false,
editMessage: vi.fn().mockReturnValue(true),
},
}));
vi.mock('../../../../stores/UserOptionsStore', () => ({
default: {
followModeEnabled: false,
setFollowModeEnabled: vi.fn()
}
default: {
followModeEnabled: false,
setFollowModeEnabled: vi.fn(),
},
}));
// Mock the MessageEditorStore
vi.mock('../../../../stores/MessageEditorStore', () => ({
default: {
editedContent: 'Test message',
setEditedContent: vi.fn(),
setMessage: vi.fn(),
onCancel: vi.fn(),
handleSave: vi.fn().mockImplementation(function() {
// Use the mocked messageEditorStore from the import
messageEditorStore.onCancel();
return Promise.resolve();
})
}
default: {
editedContent: 'Test message',
setEditedContent: vi.fn(),
setMessage: vi.fn(),
onCancel: vi.fn(),
handleSave: vi.fn().mockImplementation(function () {
// Use the mocked messageEditorStore from the import
messageEditorStore.onCancel();
return Promise.resolve();
}),
},
}));
// Mock the MessageRenderer component
vi.mock('../ChatMessageContent', () => ({
default: ({ content }) => <div data-testid="message-content">{content}</div>
default: ({ content }) => <div data-testid="message-content">{content}</div>,
}));
// Mock the UserMessageTools component
vi.mock('../UserMessageTools', () => ({
default: ({ message, onEdit }) => (
<button data-testid="edit-button" onClick={() => onEdit(message)}>
Edit
</button>
)
default: ({ message, onEdit }) => (
<button data-testid="edit-button" onClick={() => onEdit(message)}>
Edit
</button>
),
}));
vi.mock("../MotionBox", async (importOriginal) => {
const actual = await importOriginal()
vi.mock('../MotionBox', async importOriginal => {
const actual = await importOriginal();
return { default: {
...actual.default,
div: (props: any) => React.createElement('div', props, props.children),
motion: (props: any) => React.createElement('div', props, props.children),
}
}
return {
default: {
...actual.default,
div: (props: any) => React.createElement('div', props, props.children),
motion: (props: any) => React.createElement('div', props, props.children),
},
};
});
describe('MessageBubble', () => {
const mockScrollRef = { current: { scrollTo: vi.fn() } };
const mockUserMessage = {
role: 'user',
content: 'Test message'
};
const mockAssistantMessage = {
role: 'assistant',
content: 'Assistant response'
};
const mockScrollRef = { current: { scrollTo: vi.fn() } };
const mockUserMessage = {
role: 'user',
content: 'Test message',
};
const mockAssistantMessage = {
role: 'assistant',
content: 'Assistant response',
};
beforeEach(() => {
vi.clearAllMocks();
beforeEach(() => {
vi.clearAllMocks();
});
it('should render user message correctly', () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
expect(screen.getByText('You')).toBeInTheDocument();
expect(screen.getByText('Test message')).toBeInTheDocument();
});
it('should render assistant message correctly', () => {
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />);
expect(screen.getByText("Geoff's AI")).toBeInTheDocument();
expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
});
it('should show edit button on hover for user messages', async () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Simulate hover
fireEvent.mouseEnter(screen.getByRole('listitem'));
expect(screen.getByTestId('edit-button')).toBeInTheDocument();
});
it('should show editor when edit button is clicked', () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Simulate hover and click edit
fireEvent.mouseEnter(screen.getByRole('listitem'));
fireEvent.click(screen.getByTestId('edit-button'));
// Check if the textarea is rendered (part of MessageEditor)
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
it('should hide editor after message is edited and saved', async () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Show the editor
fireEvent.mouseEnter(screen.getByRole('listitem'));
fireEvent.click(screen.getByTestId('edit-button'));
// Verify editor is shown
expect(screen.getByRole('textbox')).toBeInTheDocument();
// Find and click the save button
const saveButton = screen.getByLabelText('Save edit');
fireEvent.click(saveButton);
// Wait for the editor to disappear
await waitFor(() => {
// Check that the editor is no longer visible
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
// And the message content is visible again
expect(screen.getByText('Test message')).toBeInTheDocument();
});
it('should render user message correctly', () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
expect(screen.getByText('You')).toBeInTheDocument();
expect(screen.getByText('Test message')).toBeInTheDocument();
});
it('should render assistant message correctly', () => {
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />);
expect(screen.getByText("Geoff's AI")).toBeInTheDocument();
expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
});
it('should show edit button on hover for user messages', async () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Simulate hover
fireEvent.mouseEnter(screen.getByRole('listitem'));
expect(screen.getByTestId('edit-button')).toBeInTheDocument();
});
it('should show editor when edit button is clicked', () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Simulate hover and click edit
fireEvent.mouseEnter(screen.getByRole('listitem'));
fireEvent.click(screen.getByTestId('edit-button'));
// Check if the textarea is rendered (part of MessageEditor)
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
it('should hide editor after message is edited and saved', async () => {
render(<MessageBubble msg={mockUserMessage} scrollRef={mockScrollRef} />);
// Show the editor
fireEvent.mouseEnter(screen.getByRole('listitem'));
fireEvent.click(screen.getByTestId('edit-button'));
// Verify editor is shown
expect(screen.getByRole('textbox')).toBeInTheDocument();
// Find and click the save button
const saveButton = screen.getByLabelText('Save edit');
fireEvent.click(saveButton);
// Wait for the editor to disappear
await waitFor(() => {
// Check that the editor is no longer visible
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
// And the message content is visible again
expect(screen.getByText('Test message')).toBeInTheDocument();
});
// Verify that handleSave was called
expect(messageEditorStore.handleSave).toHaveBeenCalled();
});
// Verify that handleSave was called
expect(messageEditorStore.handleSave).toHaveBeenCalled();
});
});

View File

@@ -1,27 +1,27 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import MessageEditor from '../MessageEditorComponent';
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Import the mocked stores
import clientChatStore from '../../../../stores/ClientChatStore';
import messageEditorStore from '../../../../stores/MessageEditorStore';
import MessageEditor from '../MessageEditorComponent';
// Mock the Message model
vi.mock('../../../../models/Message', () => {
return {
default: {
// This is needed for the Instance<typeof Message> type
}
},
};
});
// Mock fetch globally
globalThis.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({})
})
Promise.resolve({
ok: true,
json: () => Promise.resolve({}),
}),
);
// Mock the ClientChatStore
@@ -31,14 +31,14 @@ vi.mock('../../../../stores/ClientChatStore', () => {
removeAfter: vi.fn(),
sendMessage: vi.fn(),
setIsLoading: vi.fn(),
editMessage: vi.fn().mockReturnValue(true)
editMessage: vi.fn().mockReturnValue(true),
};
// Add the mockUserMessage to the items array
mockStore.items.indexOf = vi.fn().mockReturnValue(0);
return {
default: mockStore
default: mockStore,
};
});
@@ -48,25 +48,25 @@ vi.mock('../../../../stores/MessageEditorStore', () => {
editedContent: 'Test message', // Set initial value to match the test expectation
message: null,
setEditedContent: vi.fn(),
setMessage: vi.fn((message) => {
setMessage: vi.fn(message => {
mockStore.message = message;
mockStore.editedContent = message.content;
}),
onCancel: vi.fn(),
handleSave: vi.fn()
handleSave: vi.fn(),
};
return {
default: mockStore
default: mockStore,
};
});
describe('MessageEditor', () => {
// Create a message object with a setContent method
const mockUserMessage = {
content: 'Test message',
const mockUserMessage = {
content: 'Test message',
role: 'user',
setContent: vi.fn()
setContent: vi.fn(),
};
const mockOnCancel = vi.fn();
@@ -93,7 +93,7 @@ describe('MessageEditor', () => {
});
it('should call handleSave when save button is clicked', () => {
render(<MessageEditor message={mockUserMessage} onCancel={mockOnCancel}/>);
render(<MessageEditor message={mockUserMessage} onCancel={mockOnCancel} />);
const saveButton = screen.getByLabelText('Save edit');
fireEvent.click(saveButton);

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import { buildCodeHighlighter } from "./CodeHighlighter";
import React, { useState, useEffect, useCallback } from 'react';
import { buildCodeHighlighter } from './CodeHighlighter';
interface CodeBlockProps {
language: string;
@@ -9,23 +10,19 @@ interface CodeBlockProps {
const highlighter = buildCodeHighlighter();
const CodeBlock: React.FC<CodeBlockProps> = ({
language,
code,
onRenderComplete,
}) => {
const [html, setHtml] = useState<string>("");
const CodeBlock: React.FC<CodeBlockProps> = ({ language, code, onRenderComplete }) => {
const [html, setHtml] = useState<string>('');
const [loading, setLoading] = useState<boolean>(true);
const highlightCode = useCallback(async () => {
try {
const highlighted = (await highlighter).codeToHtml(code, {
lang: language,
theme: "github-dark",
theme: 'github-dark',
});
setHtml(highlighted);
} catch (error) {
console.error("Error highlighting code:", error);
console.error('Error highlighting code:', error);
setHtml(`<pre>${code}</pre>`);
} finally {
setLoading(false);
@@ -41,9 +38,9 @@ const CodeBlock: React.FC<CodeBlockProps> = ({
return (
<div
style={{
backgroundColor: "#24292e",
padding: "10px",
borderRadius: "1.5em",
backgroundColor: '#24292e',
padding: '10px',
borderRadius: '1.5em',
}}
>
Loading code...
@@ -55,12 +52,12 @@ const CodeBlock: React.FC<CodeBlockProps> = ({
<div
dangerouslySetInnerHTML={{ __html: html }}
style={{
transition: "none",
transition: 'none',
padding: 20,
backgroundColor: "#24292e",
overflowX: "auto",
borderRadius: ".37em",
fontSize: ".75rem",
backgroundColor: '#24292e',
overflowX: 'auto',
borderRadius: '.37em',
fontSize: '.75rem',
}}
/>
);

View File

@@ -1,5 +1,6 @@
import { createHighlighterCore } from "shiki";
import { createHighlighterCore } from 'shiki';
/* eslint-disable import/no-unresolved */
export async function buildCodeHighlighter() {
const [
githubDark,
@@ -23,26 +24,26 @@ export async function buildCodeHighlighter() {
zig,
wasm,
] = await Promise.all([
import("shiki/themes/github-dark.mjs"),
import("shiki/langs/html.mjs"),
import("shiki/langs/javascript.mjs"),
import("shiki/langs/jsx.mjs"),
import("shiki/langs/typescript.mjs"),
import("shiki/langs/tsx.mjs"),
import("shiki/langs/go.mjs"),
import("shiki/langs/rust.mjs"),
import("shiki/langs/python.mjs"),
import("shiki/langs/java.mjs"),
import("shiki/langs/kotlin.mjs"),
import("shiki/langs/shell.mjs"),
import("shiki/langs/sql.mjs"),
import("shiki/langs/yaml.mjs"),
import("shiki/langs/toml.mjs"),
import("shiki/langs/markdown.mjs"),
import("shiki/langs/json.mjs"),
import("shiki/langs/xml.mjs"),
import("shiki/langs/zig.mjs"),
import("shiki/wasm"),
import('shiki/themes/github-dark.mjs'),
import('shiki/langs/html.mjs'),
import('shiki/langs/javascript.mjs'),
import('shiki/langs/jsx.mjs'),
import('shiki/langs/typescript.mjs'),
import('shiki/langs/tsx.mjs'),
import('shiki/langs/go.mjs'),
import('shiki/langs/rust.mjs'),
import('shiki/langs/python.mjs'),
import('shiki/langs/java.mjs'),
import('shiki/langs/kotlin.mjs'),
import('shiki/langs/shell.mjs'),
import('shiki/langs/sql.mjs'),
import('shiki/langs/yaml.mjs'),
import('shiki/langs/toml.mjs'),
import('shiki/langs/markdown.mjs'),
import('shiki/langs/json.mjs'),
import('shiki/langs/xml.mjs'),
import('shiki/langs/zig.mjs'),
import('shiki/wasm'),
]);
// Create the highlighter instance with the loaded themes and languages

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
Alert,
AlertIcon,
@@ -9,40 +8,41 @@ import {
Link,
List,
ListItem,
} from "@chakra-ui/react";
import { MarkdownEditor } from "./MarkdownEditor";
import { Fragment, useState } from "react";
} from '@chakra-ui/react';
import React, { Fragment, useState } from 'react';
import { MarkdownEditor } from './MarkdownEditor';
function ConnectComponent() {
const [formData, setFormData] = useState({
markdown: "",
email: "",
firstname: "",
lastname: "",
markdown: '',
email: '',
firstname: '',
lastname: '',
});
const [isSubmitted, setIsSubmitted] = useState(false);
const [isError, setIsError] = useState(false);
const [validationError, setValidationError] = useState("");
const [validationError, setValidationError] = useState('');
const handleChange = (field: string) => (value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
setFormData(prev => ({ ...prev, [field]: value }));
setIsSubmitted(false);
setValidationError("");
setValidationError('');
};
const handleSubmitButton = async () => {
setValidationError("");
setValidationError('');
if (!formData.email || !formData.firstname || !formData.markdown) {
setValidationError("Please fill in all required fields.");
setValidationError('Please fill in all required fields.');
return;
}
try {
const response = await fetch("/api/contact", {
method: "POST",
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
@@ -51,10 +51,10 @@ function ConnectComponent() {
setIsSubmitted(true);
setIsError(false);
setFormData({
markdown: "",
email: "",
firstname: "",
lastname: "",
markdown: '',
email: '',
firstname: '',
lastname: '',
});
} else {
setIsError(true);
@@ -68,7 +68,7 @@ function ConnectComponent() {
<Fragment>
<List color="text.primary" mb={4}>
<ListItem>
Email:{" "}
Email:{' '}
<Link href="mailto:geoff@seemueller.io" color="teal.500">
geoff@seemueller.io
</Link>
@@ -79,14 +79,14 @@ function ConnectComponent() {
<Input
placeholder="First name *"
value={formData.firstname}
onChange={(e) => handleChange("firstname")(e.target.value)}
onChange={e => handleChange('firstname')(e.target.value)}
color="text.primary"
borderColor="text.primary"
/>
<Input
placeholder="Last name *"
value={formData.lastname}
onChange={(e) => handleChange("lastname")(e.target.value)}
onChange={e => handleChange('lastname')(e.target.value)}
color="text.primary"
borderColor="text.primary"
// bg="text.primary"
@@ -95,13 +95,13 @@ function ConnectComponent() {
<Input
placeholder="Email *"
value={formData.email}
onChange={(e) => handleChange("email")(e.target.value)}
onChange={e => handleChange('email')(e.target.value)}
mb={4}
borderColor="text.primary"
color="text.primary"
/>
<MarkdownEditor
onChange={handleChange("markdown")}
onChange={handleChange('markdown')}
markdown={formData.markdown}
placeholder="Your Message..."
/>
@@ -116,47 +116,32 @@ function ConnectComponent() {
mb={4}
float="right"
_hover={{
bg: "",
transform: "scale(1.05)",
bg: '',
transform: 'scale(1.05)',
}}
_active={{
bg: "gray.800",
transform: "scale(1)",
bg: 'gray.800',
transform: 'scale(1)',
}}
>
SEND
</Button>
<Box mt={12}>
{isSubmitted && (
<Alert
status="success"
borderRadius="md"
color="text.primary"
bg="green.500"
>
<Alert status="success" borderRadius="md" color="text.primary" bg="green.500">
<AlertIcon />
Message sent successfully!
</Alert>
)}
{isError && (
<Alert
status="error"
borderRadius="md"
color="text.primary"
bg="red.500"
>
<Alert status="error" borderRadius="md" color="text.primary" bg="red.500">
<AlertIcon />
There was an error sending your message. Please try again.
</Alert>
)}
{validationError && (
<Alert
status="warning"
borderRadius="md"
color="background.primary"
bg="yellow.500"
>
<Alert status="warning" borderRadius="md" color="background.primary" bg="yellow.500">
<AlertIcon />
{validationError}
</Alert>

View File

@@ -1,5 +1,5 @@
import React from "react";
import { Box, Textarea } from "@chakra-ui/react";
import { Box, Textarea } from '@chakra-ui/react';
import React from 'react';
export const MarkdownEditor = (props: {
placeholder: string;
@@ -11,7 +11,7 @@ export const MarkdownEditor = (props: {
<Textarea
value={props.markdown}
placeholder={props.placeholder}
onChange={(e) => props.onChange(e.target.value)}
onChange={e => props.onChange(e.target.value)}
width="100%"
minHeight="150px"
height="100%"

View File

@@ -1,13 +1,9 @@
import {
ChakraProvider,
cookieStorageManagerSSR,
localStorageManager,
} from "@chakra-ui/react";
import { ChakraProvider, cookieStorageManagerSSR, localStorageManager } from '@chakra-ui/react';
export function Chakra({ cookies, children, theme }) {
const colorModeManager =
typeof cookies === "string"
? cookieStorageManagerSSR("color_state", cookies)
typeof cookies === 'string'
? cookieStorageManagerSSR('color_state', cookies)
: localStorageManager;
return (

View File

@@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from "react";
import { useMediaQuery } from "@chakra-ui/react";
import { useMediaQuery } from '@chakra-ui/react';
import React, { createContext, useContext, useState, useEffect } from 'react';
// Create the context to provide mobile state
const MobileContext = createContext(false);
@@ -7,25 +7,20 @@ const MobileContext = createContext(false);
// Create a provider component to wrap your app
export const MobileProvider = ({ children }: { children: React.ReactNode }) => {
const [isMobile, setIsMobile] = useState(false);
const [isFallbackMobile] = useMediaQuery("(max-width: 768px)");
const [isFallbackMobile] = useMediaQuery('(max-width: 768px)');
useEffect(() => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
const mobile =
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
userAgent.toLowerCase(),
);
const mobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
userAgent.toLowerCase(),
);
setIsMobile(mobile);
}, []);
// Provide the combined mobile state globally
const mobileState = isMobile || isFallbackMobile;
return (
<MobileContext.Provider value={mobileState}>
{children}
</MobileContext.Provider>
);
return <MobileContext.Provider value={mobileState}>{children}</MobileContext.Provider>;
};
// Custom hook to use the mobile context in any component

View File

@@ -1,5 +1,5 @@
import React from "react";
import { Badge, Box, Flex, Heading, Image, Text } from "@chakra-ui/react";
import { Badge, Box, Flex, Heading, Image, Text } from '@chakra-ui/react';
import React from 'react';
function DemoCard({ icon, title, description, imageUrl, badge, onClick }) {
return (
@@ -9,15 +9,15 @@ function DemoCard({ icon, title, description, imageUrl, badge, onClick }) {
overflowY="hidden"
boxShadow="md"
transition="transform 0.2s"
_hover={{ transform: "scale(1.05)", cursor: "pointer" }}
_hover={{ transform: 'scale(1.05)', cursor: 'pointer' }}
color="text.primary"
onClick={onClick}
display="flex"
flexDirection="column"
minW={"12rem"}
maxW={"18rem"}
minH={"35rem"}
maxH={"20rem"}
minW={'12rem'}
maxW={'18rem'}
minH={'35rem'}
maxH={'20rem'}
>
{imageUrl && (
<Image
@@ -42,7 +42,7 @@ function DemoCard({ icon, title, description, imageUrl, badge, onClick }) {
</Flex>
{badge && (
<Box p={2}>
<Badge colorScheme={"teal"}>{badge}</Badge>
<Badge colorScheme={'teal'}>{badge}</Badge>
</Box>
)}
</Box>

View File

@@ -1,16 +1,12 @@
import React from "react";
import { SimpleGrid } from "@chakra-ui/react";
import { Rocket, Shield } from "lucide-react";
import DemoCard from "./DemoCard";
import { SimpleGrid } from '@chakra-ui/react';
import { Rocket, Shield } from 'lucide-react';
import React from 'react';
import DemoCard from './DemoCard';
function DemoComponent() {
return (
<SimpleGrid
columns={{ base: 1, sm: 1, lg: 2 }}
spacing={"7%"}
minH={"min-content"}
h={"100vh"}
>
<SimpleGrid columns={{ base: 1, sm: 1, lg: 2 }} spacing={'7%'} minH={'min-content'} h={'100vh'}>
<DemoCard
icon={<Rocket size={24} color="teal" />}
title="toak"
@@ -18,7 +14,7 @@ function DemoComponent() {
imageUrl="/code-tokenizer-md.jpg"
badge="npm"
onClick={() => {
window.open("https://github.com/seemueller-io/toak");
window.open('https://github.com/seemueller-io/toak');
}}
/>
<DemoCard
@@ -28,7 +24,7 @@ function DemoComponent() {
imageUrl="/rehoboam.png"
badge="APP"
onClick={() => {
window.open("https://rehoboam.seemueller.io");
window.open('https://rehoboam.seemueller.io');
}}
/>
</SimpleGrid>

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
Box,
Button,
@@ -14,9 +13,11 @@ import {
Textarea,
useToast,
VStack,
} from "@chakra-ui/react";
import { observer } from "mobx-react-lite";
import feedbackState from "../../stores/ClientFeedbackStore";
} from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React from 'react';
import feedbackState from '../../stores/ClientFeedbackStore';
const FeedbackModal = observer(({ isOpen, onClose, zIndex }) => {
const toast = useToast();
@@ -26,9 +27,9 @@ const FeedbackModal = observer(({ isOpen, onClose, zIndex }) => {
if (success) {
toast({
title: "Feedback Submitted",
description: "Thank you for your feedback!",
status: "success",
title: 'Feedback Submitted',
description: 'Thank you for your feedback!',
status: 'success',
duration: 3000,
isClosable: true,
});
@@ -40,9 +41,9 @@ const FeedbackModal = observer(({ isOpen, onClose, zIndex }) => {
}
toast({
title: "Submission Failed",
title: 'Submission Failed',
description: feedbackState.error,
status: "error",
status: 'error',
duration: 3000,
isClosable: true,
});
@@ -78,7 +79,7 @@ const FeedbackModal = observer(({ isOpen, onClose, zIndex }) => {
<Textarea
placeholder="Type your feedback here..."
value={feedbackState.input}
onChange={(e) => feedbackState.setInput(e.target.value)}
onChange={e => feedbackState.setInput(e.target.value)}
bg="gray.700"
color="white"
minHeight="120px"
@@ -89,7 +90,7 @@ const FeedbackModal = observer(({ isOpen, onClose, zIndex }) => {
bottom="2"
right="2"
fontSize="xs"
color={charactersRemaining < 50 ? "orange.300" : "gray.400"}
color={charactersRemaining < 50 ? 'orange.300' : 'gray.400'}
>
{charactersRemaining} characters remaining
</Text>

View File

@@ -1,14 +1,14 @@
import React from "react";
import { Box } from "@chakra-ui/react";
import { Box } from '@chakra-ui/react';
import React from 'react';
const TealDogecoinIcon = (props) => (
const TealDogecoinIcon = props => (
<Box
as="svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke={"currentColor"}
stroke={'currentColor'}
fill="currentColor"
boxSize={props.boxSize || "1em"}
boxSize={props.boxSize || '1em'}
{...props}
>
<path

View File

@@ -1,18 +1,14 @@
import React from "react";
import { Box, VStack } from "@chakra-ui/react";
import {renderMarkdown} from "../markdown/MarkdownComponent";
import { Box, VStack } from '@chakra-ui/react';
import React from 'react';
import { renderMarkdown } from '../markdown/MarkdownComponent';
function LegalDoc({ text }) {
return (
<Box maxWidth="800px" margin="0 auto">
<VStack spacing={6} align="stretch">
<Box
color="text.primary"
wordBreak="break-word"
whiteSpace="pre-wrap"
spacing={4}
>
{renderMarkdown(text)}
<Box color="text.primary" wordBreak="break-word" whiteSpace="pre-wrap" spacing={4}>
{renderMarkdown(text)}
</Box>
</VStack>
</Box>

View File

@@ -1,21 +1,16 @@
import React, { useState, useEffect } from "react";
import { Image, Box, Spinner, Text, Flex } from "@chakra-ui/react";
import { keyframes } from "@emotion/react";
import { Image, Box, Spinner, Text, Flex } from '@chakra-ui/react';
import { keyframes } from '@emotion/react';
import React, { useState, useEffect } from 'react';
const shimmer = keyframes`
0% { background-position: -100% 0; }
100% { background-position: 100% 0; }
`;
const ImageWithFallback = ({
alt,
src,
fallbackSrc = "/fallback.png",
...props
}) => {
const ImageWithFallback = ({ alt, src, fallbackSrc = '/fallback.png', ...props }) => {
const [isLoading, setIsLoading] = useState(true);
const [scrollPosition, setScrollPosition] = useState(0);
const isSlowLoadingSource = src.includes("text2image.seemueller.io");
const isSlowLoadingSource = src.includes('text2image.seemueller.io');
const handleImageLoad = () => setIsLoading(false);
const handleImageError = () => {
@@ -33,24 +28,17 @@ const ImageWithFallback = ({
setScrollPosition(scrolled);
};
window.addEventListener("scroll", handleScroll);
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener('scroll', handleScroll);
};
}, []);
const parallaxOffset = scrollPosition * 0.2;
return (
<Box
position="relative"
w="full"
maxW="full"
borderRadius="md"
my={2}
overflow="hidden"
>
<Box position="relative" w="full" maxW="full" borderRadius="md" my={2} overflow="hidden">
{isLoading && isSlowLoadingSource && (
<Flex
align="center"
@@ -76,7 +64,7 @@ const ImageWithFallback = ({
fallbackSrc={fallbackSrc}
onLoad={handleImageLoad}
onError={handleImageError}
display={isLoading ? "none" : "block"}
display={isLoading ? 'none' : 'block'}
transform={`translateY(${parallaxOffset}px)`}
transition="transform 0.1s ease-out"
{...props}

View File

@@ -1,576 +1,487 @@
import React from "react";
import {
Box,
Code,
Divider,
Heading,
Link,
List,
ListItem,
OrderedList,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useColorModeValue,
} from "@chakra-ui/react";
import {marked} from "marked";
Box,
Code,
Divider,
Heading,
Link,
List,
ListItem,
OrderedList,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useColorModeValue,
} from '@chakra-ui/react';
import katex from 'katex';
import { marked } from 'marked';
import markedKatex from 'marked-katex-extension';
import React from 'react';
import markedKatex from "marked-katex-extension";
import katex from "katex";
import CodeBlock from "../code/CodeBlock";
import ImageWithFallback from "./ImageWithFallback";
import CodeBlock from '../code/CodeBlock';
import ImageWithFallback from './ImageWithFallback';
try {
if (localStorage) {
marked.use(
markedKatex({
nonStandard: false,
displayMode: true,
throwOnError: false,
strict: true,
colorIsTextColor: true,
errorColor: "red",
}),
);
}
if (localStorage) {
marked.use(
markedKatex({
nonStandard: false,
displayMode: true,
throwOnError: false,
strict: true,
colorIsTextColor: true,
errorColor: 'red',
}),
);
}
} catch (_) {
// Silently ignore errors in marked setup - fallback to default behavior
}
const MemoizedCodeBlock = React.memo(CodeBlock);
const getHeadingProps = (depth: number) => {
switch (depth) {
case 1:
return {as: "h1", size: "xl", mt: 4, mb: 2};
case 2:
return {as: "h2", size: "lg", mt: 3, mb: 2};
case 3:
return {as: "h3", size: "md", mt: 2, mb: 1};
case 4:
return {as: "h4", size: "sm", mt: 2, mb: 1};
case 5:
return {as: "h5", size: "sm", mt: 2, mb: 1};
case 6:
return {as: "h6", size: "xs", mt: 2, mb: 1};
default:
return {as: `h${depth}`, size: "md", mt: 2, mb: 1};
}
switch (depth) {
case 1:
return { as: 'h1', size: 'xl', mt: 4, mb: 2 };
case 2:
return { as: 'h2', size: 'lg', mt: 3, mb: 2 };
case 3:
return { as: 'h3', size: 'md', mt: 2, mb: 1 };
case 4:
return { as: 'h4', size: 'sm', mt: 2, mb: 1 };
case 5:
return { as: 'h5', size: 'sm', mt: 2, mb: 1 };
case 6:
return { as: 'h6', size: 'xs', mt: 2, mb: 1 };
default:
return { as: `h${depth}`, size: 'md', mt: 2, mb: 1 };
}
};
interface TableToken extends marked.Tokens.Table {
align: Array<"center" | "left" | "right" | null>;
header: (string | marked.Tokens.TableCell)[];
rows: (string | marked.Tokens.TableCell)[][];
align: Array<'center' | 'left' | 'right' | null>;
header: (string | marked.Tokens.TableCell)[];
rows: (string | marked.Tokens.TableCell)[][];
}
const CustomHeading: React.FC<{ text: string; depth: number }> = ({
text,
depth,
}) => {
const headingProps = getHeadingProps(depth);
return (
<Heading
{...headingProps}
wordBreak="break-word"
maxWidth="100%"
color="text.accent"
>
{text}
</Heading>
);
const CustomHeading: React.FC<{ text: string; depth: number }> = ({ text, depth }) => {
const headingProps = getHeadingProps(depth);
return (
<Heading {...headingProps} wordBreak="break-word" maxWidth="100%" color="text.accent">
{text}
</Heading>
);
};
const CustomParagraph: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
return (
<Text
as="p"
fontSize="sm"
lineHeight="short"
wordBreak="break-word"
maxWidth="100%"
>
{children}
</Text>
);
const CustomParagraph: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Text as="p" fontSize="sm" lineHeight="short" wordBreak="break-word" maxWidth="100%">
{children}
</Text>
);
};
const CustomBlockquote: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
return (
<Box
as="blockquote"
borderLeft="4px solid"
borderColor="gray.200"
fontStyle="italic"
color="gray.600"
pl={4}
maxWidth="100%"
wordBreak="break-word"
mb={2}
>
{children}
</Box>
);
const CustomBlockquote: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Box
as="blockquote"
borderLeft="4px solid"
borderColor="gray.200"
fontStyle="italic"
color="gray.600"
pl={4}
maxWidth="100%"
wordBreak="break-word"
mb={2}
>
{children}
</Box>
);
};
const CustomCodeBlock: React.FC<{ code: string; language?: string }> = ({
code,
language,
}) => {
return (
<MemoizedCodeBlock
language={language}
code={code}
onRenderComplete={() => Promise.resolve()}
/>
);
const CustomCodeBlock: React.FC<{ code: string; language?: string }> = ({ code, language }) => {
return (
<MemoizedCodeBlock language={language} code={code} onRenderComplete={() => Promise.resolve()} />
);
};
const CustomHr: React.FC = () => <Divider my={4}/>;
const CustomHr: React.FC = () => <Divider my={4} />;
const CustomList: React.FC<{
ordered?: boolean;
start?: number;
children: React.ReactNode;
}> = ({ordered, start, children}) => {
const commonStyles = {
fontSize: "sm",
wordBreak: "break-word" as const,
maxWidth: "100%" as const,
stylePosition: "outside" as const,
mb: 2,
pl: 4,
};
ordered?: boolean;
start?: number;
children: React.ReactNode;
}> = ({ ordered, start, children }) => {
const commonStyles = {
fontSize: 'sm',
wordBreak: 'break-word' as const,
maxWidth: '100%' as const,
stylePosition: 'outside' as const,
mb: 2,
pl: 4,
};
return ordered ? (
<OrderedList start={start} {...commonStyles}>
{children}
</OrderedList>
) : (
<List styleType="disc" {...commonStyles}>
{children}
</List>
);
return ordered ? (
<OrderedList start={start} {...commonStyles}>
{children}
</OrderedList>
) : (
<List styleType="disc" {...commonStyles}>
{children}
</List>
);
};
const CustomListItem: React.FC<{
children: React.ReactNode;
}> = ({children}) => {
return <ListItem mb={1}>{children}</ListItem>;
children: React.ReactNode;
}> = ({ children }) => {
return <ListItem mb={1}>{children}</ListItem>;
};
const CustomKatex: React.FC<{ math: string; displayMode: boolean }> = ({
math,
displayMode,
}) => {
const renderedMath = katex.renderToString(math, {displayMode});
const CustomKatex: React.FC<{ math: string; displayMode: boolean }> = ({ math, displayMode }) => {
const renderedMath = katex.renderToString(math, { displayMode });
return (
<Box
as="span"
display={displayMode ? "block" : "inline"}
// bg={bg}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
overflow="auto"
maxWidth="100%"
dangerouslySetInnerHTML={{__html: renderedMath}}
/>
);
return (
<Box
as="span"
display={displayMode ? 'block' : 'inline'}
// bg={bg}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
overflow="auto"
maxWidth="100%"
dangerouslySetInnerHTML={{ __html: renderedMath }}
/>
);
};
const CustomTable: React.FC<{
header: React.ReactNode[];
align: Array<"center" | "left" | "right" | null>;
rows: React.ReactNode[][];
}> = ({header, align, rows}) => {
return (
<Table
variant="simple"
size="sm"
my={4}
borderRadius="md"
overflow="hidden"
>
<Thead bg="background.secondary">
<Tr>
{header.map((cell, i) => (
<Th
key={i}
textAlign={align[i] || "left"}
fontWeight="bold"
p={2}
minW={16}
wordBreak="break-word"
>
{cell}
</Th>
))}
</Tr>
</Thead>
<Tbody>
{rows.map((row, rIndex) => (
<Tr key={rIndex}>
{row.map((cell, cIndex) => (
<Td
key={cIndex}
textAlign={align[cIndex] || "left"}
p={2}
wordBreak="break-word"
>
{cell}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
);
header: React.ReactNode[];
align: Array<'center' | 'left' | 'right' | null>;
rows: React.ReactNode[][];
}> = ({ header, align, rows }) => {
return (
<Table variant="simple" size="sm" my={4} borderRadius="md" overflow="hidden">
<Thead bg="background.secondary">
<Tr>
{header.map((cell, i) => (
<Th
key={i}
textAlign={align[i] || 'left'}
fontWeight="bold"
p={2}
minW={16}
wordBreak="break-word"
>
{cell}
</Th>
))}
</Tr>
</Thead>
<Tbody>
{rows.map((row, rIndex) => (
<Tr key={rIndex}>
{row.map((cell, cIndex) => (
<Td key={cIndex} textAlign={align[cIndex] || 'left'} p={2} wordBreak="break-word">
{cell}
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
);
};
const CustomHtmlBlock: React.FC<{ content: string }> = ({content}) => {
return <Box as="span" display="inline" dangerouslySetInnerHTML={{__html: content}} mb={2}/>;
const CustomHtmlBlock: React.FC<{ content: string }> = ({ content }) => {
return <Box as="span" display="inline" dangerouslySetInnerHTML={{ __html: content }} mb={2} />;
};
const CustomText: React.FC<{ text: React.ReactNode }> = ({text}) => {
return (
<Text
fontSize="sm"
lineHeight="short"
color="text.accent"
wordBreak="break-word"
maxWidth="100%"
as="span"
>
{text}
</Text>
);
const CustomText: React.FC<{ text: React.ReactNode }> = ({ text }) => {
return (
<Text
fontSize="sm"
lineHeight="short"
color="text.accent"
wordBreak="break-word"
maxWidth="100%"
as="span"
>
{text}
</Text>
);
};
interface CustomStrongProps {
children: React.ReactNode;
children: React.ReactNode;
}
const CustomStrong: React.FC<CustomStrongProps> = ({children}) => {
return <Text as="strong">{children}</Text>;
const CustomStrong: React.FC<CustomStrongProps> = ({ children }) => {
return <Text as="strong">{children}</Text>;
};
const CustomEm: React.FC<{ children: React.ReactNode }> = ({children}) => {
return (
<Text
as="em"
fontStyle="italic"
lineHeight="short"
wordBreak="break-word"
display="inline"
>
{children}
</Text>
);
const CustomEm: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Text as="em" fontStyle="italic" lineHeight="short" wordBreak="break-word" display="inline">
{children}
</Text>
);
};
const CustomDel: React.FC<{ text: string }> = ({text}) => {
return (
<Text
as="del"
textDecoration="line-through"
lineHeight="short"
wordBreak="break-word"
display="inline"
>
{text}
</Text>
);
const CustomDel: React.FC<{ text: string }> = ({ text }) => {
return (
<Text
as="del"
textDecoration="line-through"
lineHeight="short"
wordBreak="break-word"
display="inline"
>
{text}
</Text>
);
};
const CustomCodeSpan: React.FC<{ code: string }> = ({code}) => {
const bg = useColorModeValue("gray.100", "gray.800");
return (
<Code
fontSize="sm"
bg={bg}
overflowX="clip"
borderRadius="md"
wordBreak="break-word"
maxWidth="100%"
p={0.5}
>
{code}
</Code>
);
const CustomCodeSpan: React.FC<{ code: string }> = ({ code }) => {
const bg = useColorModeValue('gray.100', 'gray.800');
return (
<Code
fontSize="sm"
bg={bg}
overflowX="clip"
borderRadius="md"
wordBreak="break-word"
maxWidth="100%"
p={0.5}
>
{code}
</Code>
);
};
const CustomMath: React.FC<{ math: string; displayMode?: boolean }> = ({
math,
displayMode = false,
}) => {
return (
<Box
as="span"
display={displayMode ? "block" : "inline"}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
overflow="auto"
maxWidth="100%"
className={`math ${displayMode ? "math-display" : "math-inline"}`}
>
{math}
</Box>
);
math,
displayMode = false,
}) => {
return (
<Box
as="span"
display={displayMode ? 'block' : 'inline'}
p={displayMode ? 4 : 1}
my={displayMode ? 4 : 0}
borderRadius="md"
overflow="auto"
maxWidth="100%"
className={`math ${displayMode ? 'math-display' : 'math-inline'}`}
>
{math}
</Box>
);
};
const CustomLink: React.FC<{
href: string;
title?: string;
children: React.ReactNode;
}> = ({href, title, children, ...props}) => {
return (
<Link
href={href}
title={title}
isExternal
sx={{
"& span": {
color: "text.link",
},
}}
maxWidth="100%"
color="teal.500"
wordBreak="break-word"
{...props}
>
{children}
</Link>
);
href: string;
title?: string;
children: React.ReactNode;
}> = ({ href, title, children, ...props }) => {
return (
<Link
href={href}
title={title}
isExternal
sx={{
'& span': {
color: 'text.link',
},
}}
maxWidth="100%"
color="teal.500"
wordBreak="break-word"
{...props}
>
{children}
</Link>
);
};
const CustomImage: React.FC<{ href: string; text: string; title?: string }> = ({
href,
text,
title,
}) => {
return (
<ImageWithFallback
src={href}
alt={text}
title={title}
maxW="100%"
width="auto"
height="auto"
my={2}
/>
);
href,
text,
title,
}) => {
return (
<ImageWithFallback
src={href}
alt={text}
title={title}
maxW="100%"
width="auto"
height="auto"
my={2}
/>
);
};
function parseTokens(tokens: marked.Token[]): JSX.Element[] {
const output: JSX.Element[] = [];
let blockquoteContent: JSX.Element[] = [];
const output: JSX.Element[] = [];
let blockquoteContent: JSX.Element[] = [];
tokens.forEach((token, i) => {
switch (token.type) {
case "heading":
output.push(
<CustomHeading key={i} text={token.text} depth={token.depth}/>,
);
break;
tokens.forEach((token, i) => {
switch (token.type) {
case 'heading':
output.push(<CustomHeading key={i} text={token.text} depth={token.depth} />);
break;
case "paragraph": {
const parsedContent = token.tokens
? parseTokens(token.tokens)
: token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(
<CustomParagraph key={i}>{parsedContent}</CustomParagraph>,
);
} else {
output.push(
<CustomParagraph key={i}>{parsedContent}</CustomParagraph>,
);
}
break;
}
case "br":
output.push(<br key={i}/>);
break;
case "escape": {
break;
}
case "blockquote_start":
blockquoteContent = [];
break;
case "blockquote_end":
output.push(
<CustomBlockquote key={i}>
{parseTokens(blockquoteContent)}
</CustomBlockquote>,
);
blockquoteContent = [];
break;
case "blockquote": {
output.push(
<CustomBlockquote key={i}>
{token.tokens ? parseTokens(token.tokens) : null}
</CustomBlockquote>,
);
break;
}
case "math":
output.push(
<CustomMath key={i} math={(token as any).value} displayMode={true}/>,
);
break;
case "inlineMath":
output.push(
<CustomMath
key={i}
math={(token as any).value}
displayMode={false}
/>,
);
break;
case "inlineKatex":
case "blockKatex": {
const katexToken = token as any;
output.push(
<CustomKatex
key={i}
math={katexToken.text}
displayMode={katexToken.displayMode}
/>,
);
break;
}
case "code":
output.push(
<CustomCodeBlock key={i} code={token.text} language={token.lang}/>,
);
break;
case "hr":
output.push(<CustomHr key={i}/>);
break;
case "list": {
const {ordered, start, items} = token;
const listItems = items.map((listItem, idx) => {
const nestedContent = parseTokens(listItem.tokens);
return <CustomListItem key={idx}>{nestedContent}</CustomListItem>;
});
output.push(
<CustomList key={i} ordered={ordered} start={start}>
{listItems}
</CustomList>,
);
break;
}
case "table": {
const tableToken = token as TableToken;
output.push(
<CustomTable
key={i}
header={tableToken.header.map((cell) =>
typeof cell === "string" ? cell : parseTokens(cell.tokens || []),
)}
align={tableToken.align}
rows={tableToken.rows.map((row) =>
row.map((cell) =>
typeof cell === "string"
? cell
: parseTokens(cell.tokens || []),
),
)}
/>,
);
break;
}
case "html":
output.push(<CustomHtmlBlock key={i} content={token.text}/>);
break;
case "def":
case "space":
break;
case "strong":
output.push(
<CustomStrong key={i}>
{parseTokens(token.tokens || [])}
</CustomStrong>,
);
break;
case "em":
output.push(
<CustomEm key={i}>
{token.tokens ? parseTokens(token.tokens) : token.text}
</CustomEm>,
);
break;
case "codespan":
output.push(<CustomCodeSpan key={i} code={token.text}/>);
break;
case "link":
output.push(
<CustomLink key={i} href={token.href} title={token.title}>
{token.tokens ? parseTokens(token.tokens) : token.text}
</CustomLink>,
);
break;
case "image":
output.push(
<CustomImage
key={i}
href={token.href}
title={token.title}
text={token.text}
/>,
);
break;
case "text": {
const parsedContent = token.tokens
? parseTokens(token.tokens)
: token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(
<React.Fragment key={i}>{parsedContent}</React.Fragment>,
);
} else {
output.push(<CustomText key={i} text={parsedContent}/>);
}
break;
}
default:
console.warn("Unhandled token type:", token.type, token);
case 'paragraph': {
const parsedContent = token.tokens ? parseTokens(token.tokens) : token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(<CustomParagraph key={i}>{parsedContent}</CustomParagraph>);
} else {
output.push(<CustomParagraph key={i}>{parsedContent}</CustomParagraph>);
}
});
break;
}
case 'br':
output.push(<br key={i} />);
break;
case 'escape': {
break;
}
case 'blockquote_start':
blockquoteContent = [];
break;
return output;
case 'blockquote_end':
output.push(<CustomBlockquote key={i}>{parseTokens(blockquoteContent)}</CustomBlockquote>);
blockquoteContent = [];
break;
case 'blockquote': {
output.push(
<CustomBlockquote key={i}>
{token.tokens ? parseTokens(token.tokens) : null}
</CustomBlockquote>,
);
break;
}
case 'math':
output.push(<CustomMath key={i} math={(token as any).value} displayMode={true} />);
break;
case 'inlineMath':
output.push(<CustomMath key={i} math={(token as any).value} displayMode={false} />);
break;
case 'inlineKatex':
case 'blockKatex': {
const katexToken = token as any;
output.push(
<CustomKatex key={i} math={katexToken.text} displayMode={katexToken.displayMode} />,
);
break;
}
case 'code':
output.push(<CustomCodeBlock key={i} code={token.text} language={token.lang} />);
break;
case 'hr':
output.push(<CustomHr key={i} />);
break;
case 'list': {
const { ordered, start, items } = token;
const listItems = items.map((listItem, idx) => {
const nestedContent = parseTokens(listItem.tokens);
return <CustomListItem key={idx}>{nestedContent}</CustomListItem>;
});
output.push(
<CustomList key={i} ordered={ordered} start={start}>
{listItems}
</CustomList>,
);
break;
}
case 'table': {
const tableToken = token as TableToken;
output.push(
<CustomTable
key={i}
header={tableToken.header.map(cell =>
typeof cell === 'string' ? cell : parseTokens(cell.tokens || []),
)}
align={tableToken.align}
rows={tableToken.rows.map(row =>
row.map(cell => (typeof cell === 'string' ? cell : parseTokens(cell.tokens || []))),
)}
/>,
);
break;
}
case 'html':
output.push(<CustomHtmlBlock key={i} content={token.text} />);
break;
case 'def':
case 'space':
break;
case 'strong':
output.push(<CustomStrong key={i}>{parseTokens(token.tokens || [])}</CustomStrong>);
break;
case 'em':
output.push(
<CustomEm key={i}>{token.tokens ? parseTokens(token.tokens) : token.text}</CustomEm>,
);
break;
case 'codespan':
output.push(<CustomCodeSpan key={i} code={token.text} />);
break;
case 'link':
output.push(
<CustomLink key={i} href={token.href} title={token.title}>
{token.tokens ? parseTokens(token.tokens) : token.text}
</CustomLink>,
);
break;
case 'image':
output.push(
<CustomImage key={i} href={token.href} title={token.title} text={token.text} />,
);
break;
case 'text': {
const parsedContent = token.tokens ? parseTokens(token.tokens) : token.text;
if (blockquoteContent.length > 0) {
blockquoteContent.push(<React.Fragment key={i}>{parsedContent}</React.Fragment>);
} else {
output.push(<CustomText key={i} text={parsedContent} />);
}
break;
}
default:
console.warn('Unhandled token type:', token.type, token);
}
});
return output;
}
export function renderMarkdown(markdown: string): JSX.Element[] {
marked.setOptions({
breaks: true,
gfm: true,
silent: false,
async: true,
});
marked.setOptions({
breaks: true,
gfm: true,
silent: false,
async: true,
});
const tokens = marked.lexer(markdown);
return parseTokens(tokens);
const tokens = marked.lexer(markdown);
return parseTokens(tokens);
}

View File

@@ -1,7 +1,8 @@
import React from "react";
import { IconButton } from "@chakra-ui/react";
import { Github } from "lucide-react";
import { toolbarButtonZIndex } from "./Toolbar";
import { IconButton } from '@chakra-ui/react';
import { Github } from 'lucide-react';
import React from 'react';
import { toolbarButtonZIndex } from './Toolbar';
export default function GithubButton() {
return (
@@ -16,10 +17,10 @@ export default function GithubButton() {
stroke="text.accent"
color="text.accent"
_hover={{
bg: "transparent",
bg: 'transparent',
svg: {
stroke: "accent.secondary",
transition: "stroke 0.3s ease-in-out",
stroke: 'accent.secondary',
transition: 'stroke 0.3s ease-in-out',
},
}}
title="GitHub"

View File

@@ -1,8 +1,9 @@
import React from "react";
import { IconButton, useDisclosure } from "@chakra-ui/react";
import { LucideHeart } from "lucide-react";
import { toolbarButtonZIndex } from "./Toolbar";
import SupportThisSiteModal from "./SupportThisSiteModal";
import { IconButton, useDisclosure } from '@chakra-ui/react';
import { LucideHeart } from 'lucide-react';
import React from 'react';
import SupportThisSiteModal from './SupportThisSiteModal';
import { toolbarButtonZIndex } from './Toolbar';
export default function SupportThisSiteButton() {
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -18,10 +19,10 @@ export default function SupportThisSiteButton() {
stroke="text.accent"
bg="transparent"
_hover={{
bg: "transparent",
bg: 'transparent',
svg: {
stroke: "accent.danger",
transition: "stroke 0.3s ease-in-out",
stroke: 'accent.danger',
transition: 'stroke 0.3s ease-in-out',
},
}}
title="Support"
@@ -29,9 +30,9 @@ export default function SupportThisSiteButton() {
zIndex={toolbarButtonZIndex}
sx={{
svg: {
stroke: "text.accent",
strokeWidth: "2px",
transition: "stroke 0.2s ease-in-out",
stroke: 'text.accent',
strokeWidth: '2px',
transition: 'stroke 0.2s ease-in-out',
},
}}
/>

View File

@@ -1,4 +1,3 @@
import React from "react";
import {
Box,
Button,
@@ -19,26 +18,26 @@ import {
useClipboard,
useToast,
VStack,
} from "@chakra-ui/react";
import { QRCodeCanvas } from "qrcode.react";
import { FaBitcoin, FaEthereum } from "react-icons/fa";
import { observer } from "mobx-react-lite";
import clientTransactionStore from "../../stores/ClientTransactionStore";
import DogecoinIcon from "../icons/DogecoinIcon";
} from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import { QRCodeCanvas } from 'qrcode.react';
import React from 'react';
import { FaBitcoin, FaEthereum } from 'react-icons/fa';
import clientTransactionStore from '../../stores/ClientTransactionStore';
import DogecoinIcon from '../icons/DogecoinIcon';
const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
const { hasCopied, onCopy } = useClipboard(
clientTransactionStore.depositAddress || "",
);
const { hasCopied, onCopy } = useClipboard(clientTransactionStore.depositAddress || '');
const toast = useToast();
const handleCopy = () => {
if (clientTransactionStore.depositAddress) {
onCopy();
toast({
title: "Address Copied!",
description: "Thank you for your support!",
status: "success",
title: 'Address Copied!',
description: 'Thank you for your support!',
status: 'success',
duration: 3000,
isClosable: true,
});
@@ -49,17 +48,17 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
try {
await clientTransactionStore.prepareTransaction();
toast({
title: "Success",
title: 'Success',
description: `Use your wallet app (Coinbase, ...ect) to send the selected asset to the provided address.`,
status: "success",
status: 'success',
duration: 6000,
isClosable: true,
});
} catch (error) {
toast({
title: "Transaction Failed",
description: "There was an issue preparing your transaction.",
status: "error",
title: 'Transaction Failed',
description: 'There was an issue preparing your transaction.',
status: 'error',
duration: 3000,
isClosable: true,
});
@@ -68,32 +67,23 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
const donationMethods = [
{
name: "Ethereum",
name: 'Ethereum',
icon: FaEthereum,
},
{
name: "Bitcoin",
name: 'Bitcoin',
icon: FaBitcoin,
},
{
name: "Dogecoin",
name: 'Dogecoin',
icon: DogecoinIcon,
},
];
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="md"
motionPreset="slideInBottom"
zIndex={zIndex}
>
<ModalOverlay
bg='bg.primary'
backdropFilter='blur(10px) hue-rotate(90deg)'
/>
<ModalContent bg="bg.primary" color="text.primary" >
<Modal isOpen={isOpen} onClose={onClose} size="md" motionPreset="slideInBottom" zIndex={zIndex}>
<ModalOverlay bg="bg.primary" backdropFilter="blur(10px) hue-rotate(90deg)" />
<ModalContent bg="bg.primary" color="text.primary">
<ModalHeader textAlign="center" mb={2}>
Support
</ModalHeader>
@@ -109,38 +99,38 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
// colorScheme="teal"
isFitted
>
<TabList mb={2} w={"20%"}>
{donationMethods.map((method) => (
<TabList mb={2} w={'20%'}>
{donationMethods.map(method => (
<Tab
p={4}
key={method.name}
color={"text.primary"}
bg={clientTransactionStore.selectedMethod=== method.name ? "bg.primary": "bg.secondary"}
color={'text.primary'}
bg={
clientTransactionStore.selectedMethod === method.name
? 'bg.primary'
: 'bg.secondary'
}
onClick={() => {
clientTransactionStore.setSelectedMethod(method.name);
}}
>
<Box p={1} w={"fit-content"} >
<method.icon />{" "}
<Box p={1} w={'fit-content'}>
<method.icon />{' '}
</Box>
{method.name}
</Tab>
))}
</TabList>
<TabPanels>
{donationMethods.map((method) => (
{donationMethods.map(method => (
<TabPanel key={method.name}>
{!clientTransactionStore.userConfirmed ? (
<VStack spacing={4}>
<Text>Enter your information:</Text>
<Input
placeholder="Your name"
value={
clientTransactionStore.donerId as string | undefined
}
onChange={(e) =>
clientTransactionStore.setDonerId(e.target.value)
}
value={clientTransactionStore.donerId as string | undefined}
onChange={e => clientTransactionStore.setDonerId(e.target.value)}
type="text"
bg="bg.secondary"
color="text.primary"
@@ -149,12 +139,8 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
<Text>Enter the amount you wish to donate:</Text>
<Input
placeholder="Enter amount"
value={
clientTransactionStore.amount as number | undefined
}
onChange={(e) =>
clientTransactionStore.setAmount(e.target.value)
}
value={clientTransactionStore.amount as number | undefined}
onChange={e => clientTransactionStore.setAmount(e.target.value)}
type="number"
bg="bg.secondary"
// color="white"
@@ -170,17 +156,9 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
</VStack>
) : (
<>
<Box
bg="white"
p={2}
borderRadius="lg"
mb={4}
w={"min-content"}
>
<Box bg="white" p={2} borderRadius="lg" mb={4} w={'min-content'}>
<QRCodeCanvas
value={
clientTransactionStore.depositAddress as string
}
value={clientTransactionStore.depositAddress as string}
size={180}
/>
</Box>
@@ -204,7 +182,7 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
// colorScheme="teal"
mb={4}
>
{hasCopied ? "Address Copied!" : "Copy Address"}
{hasCopied ? 'Address Copied!' : 'Copy Address'}
</Button>
<Text fontSize="md" fontWeight="bold">
Transaction ID: {clientTransactionStore.txId}
@@ -218,7 +196,7 @@ const SupportThisSiteModal = observer(({ isOpen, onClose, zIndex }) => {
</VStack>
</ModalBody>
<ModalFooter>
<Button variant="outline" mr={3} onClick={onClose} >
<Button variant="outline" mr={3} onClick={onClose}>
Close
</Button>
</ModalFooter>

View File

@@ -1,8 +1,10 @@
import React from "react";
import { Flex } from "@chakra-ui/react";
import SupportThisSiteButton from "./SupportThisSiteButton";
import GithubButton from "./GithubButton";
import BuiltWithButton from "../BuiltWithButton";
import { Flex } from '@chakra-ui/react';
import React from 'react';
import BuiltWithButton from '../BuiltWithButton';
import GithubButton from './GithubButton';
import SupportThisSiteButton from './SupportThisSiteButton';
const toolbarButtonZIndex = 901;
@@ -11,8 +13,8 @@ export { toolbarButtonZIndex };
function ToolBar({ isMobile }) {
return (
<Flex
direction={isMobile ? "row" : "column"}
alignItems={isMobile ? "flex-start" : "flex-end"}
direction={isMobile ? 'row' : 'column'}
alignItems={isMobile ? 'flex-start' : 'flex-end'}
pb={4}
>
<SupportThisSiteButton />

View File

@@ -1,17 +1,16 @@
import { useEffect, useState } from "react";
import { useMediaQuery } from "@chakra-ui/react";
import { useMediaQuery } from '@chakra-ui/react';
import { useEffect, useState } from 'react';
// Only use this when it is necessary to style responsively outside a MobileProvider.
export function useIsMobile() {
const [isMobile, setIsMobile] = useState(false);
const [isFallbackMobile] = useMediaQuery("(max-width: 768px)");
const [isFallbackMobile] = useMediaQuery('(max-width: 768px)');
useEffect(() => {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
const mobile =
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
userAgent.toLowerCase(),
);
const mobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
userAgent.toLowerCase(),
);
setIsMobile(mobile);
}, []);

View File

@@ -1,10 +1,11 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import usePageLoaded from '../usePageLoaded';
describe('usePageLoaded', () => {
const callback = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
// Reset event listeners
@@ -16,17 +17,17 @@ describe('usePageLoaded', () => {
// Mock document.readyState to be "complete"
Object.defineProperty(document, 'readyState', {
configurable: true,
get: () => 'complete'
get: () => 'complete',
});
const { result } = renderHook(() => usePageLoaded(callback));
// The hook should return true
expect(result.current).toBe(true);
// Callback should be called immediately
expect(callback).toHaveBeenCalledTimes(1);
// No event listener should be added
expect(window.addEventListener).not.toHaveBeenCalled();
});
@@ -35,17 +36,17 @@ describe('usePageLoaded', () => {
// Mock document.readyState to be "loading"
Object.defineProperty(document, 'readyState', {
configurable: true,
get: () => 'loading'
get: () => 'loading',
});
const { result } = renderHook(() => usePageLoaded(callback));
// The hook should return false initially
expect(result.current).toBe(false);
// Callback should not be called yet
expect(callback).not.toHaveBeenCalled();
// Event listener should be added
expect(window.addEventListener).toHaveBeenCalledWith('load', expect.any(Function));
});
@@ -54,14 +55,14 @@ describe('usePageLoaded', () => {
// Mock document.readyState to be "loading"
Object.defineProperty(document, 'readyState', {
configurable: true,
get: () => 'loading'
get: () => 'loading',
});
const { unmount } = renderHook(() => usePageLoaded(callback));
// Unmount the hook
unmount();
// Event listener should be removed
expect(window.removeEventListener).toHaveBeenCalledWith('load', expect.any(Function));
});
@@ -70,30 +71,33 @@ describe('usePageLoaded', () => {
// Mock document.readyState to be "loading"
Object.defineProperty(document, 'readyState', {
configurable: true,
get: () => 'loading'
get: () => 'loading',
});
// Capture the event handler
let loadHandler: Function;
vi.stubGlobal('addEventListener', vi.fn((event, handler) => {
if (event === 'load') {
loadHandler = handler;
}
}));
let loadHandler: () => void;
vi.stubGlobal(
'addEventListener',
vi.fn((event, handler) => {
if (event === 'load') {
loadHandler = handler;
}
}),
);
const { result } = renderHook(() => usePageLoaded(callback));
// Initially, isLoaded should be false
expect(result.current).toBe(false);
// Simulate the load event
loadHandler();
// Now the callback should have been called
expect(callback).toHaveBeenCalledTimes(1);
// And isLoaded should be updated to true
// Note: We need to use rerender or waitFor in a real test to see this update
// For simplicity, we're just testing the callback was called
});
});
});

View File

@@ -1,17 +1,18 @@
import { useState, useEffect } from "react";
import { useIsMobile } from "../components/contexts/MobileContext";
import { useState, useEffect } from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
export const useMaxWidth = () => {
const isMobile = useIsMobile();
const [maxWidth, setMaxWidth] = useState("600px");
const [maxWidth, setMaxWidth] = useState('600px');
const calculateMaxWidth = () => {
if (isMobile) {
setMaxWidth("800px");
setMaxWidth('800px');
} else if (window.innerWidth < 1024) {
setMaxWidth("500px");
setMaxWidth('500px');
} else {
setMaxWidth("800px");
setMaxWidth('800px');
}
};
@@ -22,10 +23,10 @@ export const useMaxWidth = () => {
calculateMaxWidth();
};
window.addEventListener("resize", handleResize);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener("resize", handleResize);
window.removeEventListener('resize', handleResize);
};
}, [isMobile]);

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState } from 'react';
const usePageLoaded = (callback: () => void) => {
const [isLoaded, setIsLoaded] = useState(false);
@@ -9,15 +9,15 @@ const usePageLoaded = (callback: () => void) => {
callback();
};
if (document.readyState === "complete") {
if (document.readyState === 'complete') {
// Page is already fully loaded
handlePageLoad();
} else {
// Wait for the page to load
window.addEventListener("load", handlePageLoad);
window.addEventListener('load', handlePageLoad);
}
return () => window.removeEventListener("load", handlePageLoad);
return () => window.removeEventListener('load', handlePageLoad);
}, [callback]);
return isLoaded;

View File

@@ -1,6 +1,7 @@
import { Flex } from "@chakra-ui/react";
import React from "react";
import { useIsMobile } from "../components/contexts/MobileContext";
import { Flex } from '@chakra-ui/react';
import React from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
function Content({ children }) {
const isMobile = useIsMobile();

View File

@@ -1,8 +1,9 @@
import React from "react";
import { Box, Heading, Text } from "@chakra-ui/react";
import { usePageContext } from "../renderer/usePageContext";
import Routes from "../renderer/routes";
import { useIsMobile } from "../components/contexts/MobileContext";
import { Box, Heading, Text } from '@chakra-ui/react';
import React from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
import Routes from '../renderer/routes';
import { usePageContext } from '../renderer/usePageContext';
export default function Hero() {
const pageContext = usePageContext();
@@ -12,12 +13,12 @@ export default function Hero() {
<Box p={2}>
<Box>
<Heading
textAlign={isMobile ? "left" : "right"}
textAlign={isMobile ? 'left' : 'right'}
minWidth="90px"
maxWidth={"220px"}
maxWidth={'220px'}
color="text.accent"
as="h3"
letterSpacing={"tight"}
letterSpacing={'tight'}
size="lg"
>
{Routes[normalizePath(pageContext.urlPathname)]?.heroLabel}
@@ -28,9 +29,9 @@ export default function Hero() {
isTruncated
maxWidth="100%"
whiteSpace="nowrap"
letterSpacing={"tight"}
letterSpacing={'tight'}
color="text.accent"
textAlign={isMobile ? "left" : "right"}
textAlign={isMobile ? 'left' : 'right'}
overflow="hidden"
>
{new Date().toLocaleDateString()}
@@ -39,9 +40,9 @@ export default function Hero() {
);
}
const normalizePath = (path) => {
if (!path) return "/";
if (path.length > 1 && path.endsWith("/")) {
const normalizePath = path => {
if (!path) return '/';
if (path.length > 1 && path.endsWith('/')) {
path = path.slice(0, -1);
}
return path.toLowerCase();

View File

@@ -1,16 +1,18 @@
import React, { useEffect, useState } from "react";
import { PageContextProvider } from "../renderer/usePageContext";
import { MobileProvider } from "../components/contexts/MobileContext";
import LayoutComponent from "./LayoutComponent";
import userOptionsStore from "../stores/UserOptionsStore";
import { observer } from "mobx-react-lite";
import { Chakra } from "../components/contexts/ChakraContext";
import { getTheme } from "./theme/color-themes";
import { observer } from 'mobx-react-lite';
import React, { useEffect, useState } from 'react';
import { Chakra } from '../components/contexts/ChakraContext';
import { MobileProvider } from '../components/contexts/MobileContext';
import { PageContextProvider } from '../renderer/usePageContext';
import userOptionsStore from '../stores/UserOptionsStore';
import LayoutComponent from './LayoutComponent';
import { getTheme } from './theme/color-themes';
export { Layout };
const Layout = observer(({ pageContext, children }) => {
const [activeTheme, setActiveTheme] = useState<string>("darknight");
const [activeTheme, setActiveTheme] = useState<string>('darknight');
useEffect(() => {
if (userOptionsStore.theme !== activeTheme) {
@@ -22,21 +24,23 @@ const Layout = observer(({ pageContext, children }) => {
if (pageContext?.headersOriginal) {
const headers = new Headers(pageContext.headersOriginal);
const cookies = headers.get("cookie");
const cookies = headers.get('cookie');
const userPreferencesCookie = cookies
?.split("; ")
.find((row) => row.startsWith("user_preferences="))
?.split("=")[1];
?.split('; ')
.find(row => row.startsWith('user_preferences='))
?.split('=')[1];
try {
const { theme: receivedTheme } = JSON.parse(
atob(userPreferencesCookie ?? "{}"),
);
const { theme: receivedTheme } = JSON.parse(atob(userPreferencesCookie ?? '{}'));
setActiveTheme(receivedTheme);
} catch (e) {}
} catch (e) {
// Ignore parsing errors for user preferences cookie
}
}
} catch (e) {}
} catch (e) {
// Ignore errors when accessing headers or cookies
}
return (
<React.StrictMode>

View File

@@ -1,10 +1,12 @@
import React from "react";
import { Grid, GridItem } from "@chakra-ui/react";
import Navigation from "./Navigation";
import Routes from "../renderer/routes";
import Hero from "./Hero";
import Content from "./Content";
import { useIsMobile } from "../components/contexts/MobileContext";
import { Grid, GridItem } from '@chakra-ui/react';
import React from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
import Routes from '../renderer/routes';
import Content from './Content';
import Hero from './Hero';
import Navigation from './Navigation';
export default function LayoutComponent({ children }) {
const isMobile = useIsMobile();
@@ -17,17 +19,17 @@ export default function LayoutComponent({ children }) {
"main"`
: `"nav main"`
}
gridTemplateRows={isMobile ? "auto 1fr" : "1fr"}
gridTemplateColumns={isMobile ? "1fr" : "auto 1fr"}
gridTemplateRows={isMobile ? 'auto 1fr' : '1fr'}
gridTemplateColumns={isMobile ? '1fr' : 'auto 1fr'}
minHeight="100vh"
gap="1"
>
<GridItem area={"nav"} hidden={false}>
<GridItem area={'nav'} hidden={false}>
<Navigation routeRegistry={Routes}>
<Hero />
</Navigation>
</GridItem>
<GridItem area={"main"}>
<GridItem area={'main'}>
<Content>{children}</Content>
</GridItem>
</Grid>

View File

@@ -1,16 +1,16 @@
import { Box } from "@chakra-ui/react";
import React from "react";
import { Box } from '@chakra-ui/react';
import React from 'react';
function NavItem({ path, children, color, onClick, as, cursor }) {
return (
<Box
as={as ?? "a"}
as={as ?? 'a'}
href={path}
mb={2}
cursor={cursor}
// ml={5}
mr={2}
color={color ?? "text.accent"}
color={color ?? 'text.accent'}
letterSpacing="normal"
display="block"
position="relative"
@@ -18,20 +18,20 @@ function NavItem({ path, children, color, onClick, as, cursor }) {
onClick={onClick}
_after={{
content: '""',
position: "absolute",
width: "100%",
height: "2px",
bottom: "0",
left: "0",
bg: "accent.secondary",
transform: "scaleX(0)",
transformOrigin: "right",
transition: "transform 0.3s ease-in-out",
position: 'absolute',
width: '100%',
height: '2px',
bottom: '0',
left: '0',
bg: 'accent.secondary',
transform: 'scaleX(0)',
transformOrigin: 'right',
transition: 'transform 0.3s ease-in-out',
}}
_hover={{
color: "tertiary.tertiary",
color: 'tertiary.tertiary',
_after: {
transform: "scaleX(1)",
transform: 'scaleX(1)',
},
}}
>

View File

@@ -1,40 +1,36 @@
import React, { useEffect } from "react";
import { observer } from "mobx-react-lite";
import {
Box,
Collapse,
Grid,
GridItem,
useBreakpointValue,
} from "@chakra-ui/react";
import { MenuIcon } from "lucide-react";
import Sidebar from "./Sidebar";
import NavItem from "./NavItem";
import menuState from "../stores/AppMenuStore";
import { usePageContext } from "../renderer/usePageContext";
import { useIsMobile } from "../components/contexts/MobileContext";
import { getTheme } from "./theme/color-themes";
import userOptionsStore from "../stores/UserOptionsStore";
import { Box, Collapse, Grid, GridItem, useBreakpointValue } from '@chakra-ui/react';
import { MenuIcon } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
import { usePageContext } from '../renderer/usePageContext';
import menuState from '../stores/AppMenuStore';
import userOptionsStore from '../stores/UserOptionsStore';
import NavItem from './NavItem';
import Sidebar from './Sidebar';
import { getTheme } from './theme/color-themes';
const Navigation = observer(({ children, routeRegistry }) => {
const isMobile = useIsMobile();
const pageContext = usePageContext();
const currentPath = pageContext.urlPathname || "/";
const currentPath = pageContext.urlPathname || '/';
const getTopValue = () => {
if (!isMobile) return undefined;
if (currentPath === "/") return 12;
if (currentPath === '/') return 12;
return 0;
};
const variant = useBreakpointValue(
{
base: "outline",
md: "solid",
base: 'outline',
md: 'solid',
},
{
fallback: "md",
fallback: 'md',
},
);
@@ -50,7 +46,7 @@ const Navigation = observer(({ children, routeRegistry }) => {
top={0}
left={0}
zIndex={1100}
width={isMobile ? "20%" : "100%"}
width={isMobile ? '20%' : '100%'}
hidden={!isMobile}
>
<Grid templateColumns="auto 1fr" alignItems="center">
@@ -85,10 +81,10 @@ const Navigation = observer(({ children, routeRegistry }) => {
as="nav"
templateColumns="1fr"
width="100%"
h={isMobile ? "100vh" : "100vh"}
h={isMobile ? '100vh' : '100vh'}
top={getTopValue()}
position={"relative"}
bg={"transparent"}
position={'relative'}
bg={'transparent'}
zIndex={1000}
gap={4}
p={isMobile ? 4 : 0}
@@ -97,8 +93,8 @@ const Navigation = observer(({ children, routeRegistry }) => {
<GridItem>
<Sidebar>
{Object.keys(routeRegistry)
.filter((p) => !routeRegistry[p].hideNav)
.map((path) => (
.filter(p => !routeRegistry[p].hideNav)
.map(path => (
<NavItem key={path} path={path}>
{routeRegistry[path].sidebarLabel}
</NavItem>
@@ -114,15 +110,11 @@ const Navigation = observer(({ children, routeRegistry }) => {
top="0"
left="0"
right="0"
height={menuState.isOpen ? "100vh" : "auto"}
height={menuState.isOpen ? '100vh' : 'auto'}
pointerEvents="none"
zIndex={900}
>
<Box
height="100%"
transition="all 0.3s"
opacity={menuState.isOpen ? 1 : 0}
/>
<Box height="100%" transition="all 0.3s" opacity={menuState.isOpen ? 1 : 0} />
</Box>
)}
</Grid>

View File

@@ -1,14 +1,16 @@
import React, { useState } from "react";
import { Box, Flex, VStack } from "@chakra-ui/react";
import NavItem from "./NavItem";
import ToolBar from "../components/toolbar/Toolbar";
import { useIsMobile } from "../components/contexts/MobileContext";
import FeedbackModal from "../components/feedback/FeedbackModal";
import { ThemeSelectionOptions } from "../components/ThemeSelection";
import { Box, Flex, VStack } from '@chakra-ui/react';
import React, { useState } from 'react';
import { useIsMobile } from '../components/contexts/MobileContext';
import FeedbackModal from '../components/feedback/FeedbackModal';
import { ThemeSelectionOptions } from '../components/ThemeSelection';
import ToolBar from '../components/toolbar/Toolbar';
import NavItem from './NavItem';
function LowerSidebarContainer({ children, isMobile, ...props }) {
const bottom = isMobile ? undefined : "6rem";
const position = isMobile ? "relative" : "absolute";
const bottom = isMobile ? undefined : '6rem';
const position = isMobile ? 'relative' : 'absolute';
return (
<Box width="100%" m={0.99} position={position} bottom={bottom} {...props}>
{children}
@@ -23,7 +25,7 @@ function Sidebar({ children: navLinks }) {
<SidebarContainer isMobile={isMobile}>
<VStack
spacing={6}
alignItems={isMobile ? "flex-start" : "flex-end"}
alignItems={isMobile ? 'flex-start' : 'flex-end'}
letterSpacing="tighter"
width="100%"
height="100%"
@@ -31,11 +33,11 @@ function Sidebar({ children: navLinks }) {
{navLinks}
<Box
alignItems={isMobile ? "flex-start" : "flex-end"}
alignItems={isMobile ? 'flex-start' : 'flex-end'}
bg="background.primary"
zIndex={1000}
width="100%"
fontSize={"x-small"}
fontSize={'x-small'}
>
<LowerSidebarContainer isMobile={isMobile}>
<ToolBar isMobile={isMobile} />
@@ -58,12 +60,12 @@ function RegulatoryItems({ isMobile }) {
return (
<>
<VStack alignItems={isMobile ? "flex-start" : "flex-end"} spacing={1}>
<VStack alignItems={isMobile ? 'flex-start' : 'flex-end'} spacing={1}>
<NavItem
color="text.tertiary"
as={"span"}
as={'span'}
path=""
cursor={"pointer"}
cursor={'pointer'}
onClick={openFeedbackModal}
>
Feedback
@@ -77,22 +79,14 @@ function RegulatoryItems({ isMobile }) {
</VStack>
{/* Feedback Modal */}
<FeedbackModal
isOpen={isFeedbackModalOpen}
onClose={closeFeedbackModal}
/>
<FeedbackModal isOpen={isFeedbackModalOpen} onClose={closeFeedbackModal} />
</>
);
}
function SidebarContainer({ children, isMobile }) {
return (
<Flex
mt={isMobile ? 28 : undefined}
position="relative"
height="100vh"
width="100%"
>
<Flex mt={isMobile ? 28 : undefined} position="relative" height="100vh" width="100%">
{children}
</Flex>
);

View File

@@ -1,25 +1,25 @@
import { extendTheme } from "@chakra-ui/react";
import { extendTheme } from '@chakra-ui/react';
const fonts = {
body: "monospace",
heading: "monospace",
body: 'monospace',
heading: 'monospace',
};
const styles = {
global: {
body: {
fontFamily: fonts.body,
bg: "background.primary",
color: "text.primary",
bg: 'background.primary',
color: 'text.primary',
margin: 0,
overflow: "hidden",
overflow: 'hidden',
},
html: {
overflow: "hidden",
overflow: 'hidden',
},
"::selection": {
backgroundColor: "accent.secondary",
color: "background.primary",
'::selection': {
backgroundColor: 'accent.secondary',
color: 'background.primary',
},
},
};
@@ -27,69 +27,69 @@ const styles = {
const components = {
Button: {
baseStyle: {
fontWeight: "bold",
borderRadius: "md", // Slightly rounded corners
fontWeight: 'bold',
borderRadius: 'md', // Slightly rounded corners
},
variants: {
solid: {
bg: "accent.primary",
color: "background.primary",
bg: 'accent.primary',
color: 'background.primary',
_hover: {
bg: "accent.primary",
color: "background.primary",
bg: 'accent.primary',
color: 'background.primary',
},
},
outline: {
borderColor: "accent.primary",
color: "text.primary",
borderColor: 'accent.primary',
color: 'text.primary',
_hover: {
bg: "accent.primary",
color: "background.primary",
bg: 'accent.primary',
color: 'background.primary',
},
},
ghost: {
color: "text.primary",
color: 'text.primary',
_hover: {
bg: "background.secondary",
bg: 'background.secondary',
},
},
},
},
Link: {
baseStyle: {
color: "accent.secondary",
color: 'accent.secondary',
_hover: {
color: "accent.primary",
textDecoration: "none",
color: 'accent.primary',
textDecoration: 'none',
},
},
},
Heading: {
baseStyle: {
color: "text.primary",
letterSpacing: "tight",
color: 'text.primary',
letterSpacing: 'tight',
},
sizes: {
"4xl": { fontSize: ["6xl", null, "7xl"], lineHeight: 1 },
"3xl": { fontSize: ["5xl", null, "6xl"], lineHeight: 1.2 },
"2xl": { fontSize: ["4xl", null, "5xl"] },
xl: { fontSize: ["3xl", null, "4xl"] },
lg: { fontSize: ["2xl", null, "3xl"] },
md: { fontSize: "xl" },
sm: { fontSize: "md" },
xs: { fontSize: "sm" },
'4xl': { fontSize: ['6xl', null, '7xl'], lineHeight: 1 },
'3xl': { fontSize: ['5xl', null, '6xl'], lineHeight: 1.2 },
'2xl': { fontSize: ['4xl', null, '5xl'] },
xl: { fontSize: ['3xl', null, '4xl'] },
lg: { fontSize: ['2xl', null, '3xl'] },
md: { fontSize: 'xl' },
sm: { fontSize: 'md' },
xs: { fontSize: 'sm' },
},
},
Text: {
baseStyle: {
color: "text.primary",
color: 'text.primary',
},
variants: {
secondary: {
color: "text.secondary",
color: 'text.secondary',
},
accent: {
color: "text.accent",
color: 'text.accent',
},
},
},
@@ -97,21 +97,21 @@ const components = {
variants: {
filled: {
field: {
bg: "background.secondary",
bg: 'background.secondary',
_hover: {
bg: "background.tertiary",
bg: 'background.tertiary',
},
_focus: {
bg: "background.tertiary",
borderColor: "accent.primary",
bg: 'background.tertiary',
borderColor: 'accent.primary',
},
},
},
},
},
CodeBlocks: {
baseStyle: (props) => ({
bg: "background.primary",
baseStyle: props => ({
bg: 'background.primary',
// color: 'text.primary',
}),
},
@@ -119,59 +119,59 @@ const components = {
const Base_theme = extendTheme({
config: {
cssVarPrefix: "wgs",
initialColorMode: "dark",
cssVarPrefix: 'wgs',
initialColorMode: 'dark',
useSystemColorMode: false,
},
fonts,
styles,
components,
letterSpacings: {
tighter: "-0.05em",
tight: "-0.025em",
normal: "0",
wide: "0.025em",
wider: "0.05em",
widest: "0.1em",
tighter: '-0.05em',
tight: '-0.025em',
normal: '0',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
space: {
px: "1px",
0.5: "0.125rem",
1: "0.25rem",
1.5: "0.375rem",
2: "0.5rem",
2.5: "0.625rem",
3: "0.75rem",
3.5: "0.875rem",
4: "1rem",
5: "1.25rem",
6: "1.5rem",
7: "1.75rem",
8: "2rem",
9: "2.25rem",
10: "2.5rem",
12: "3rem",
14: "3.5rem",
16: "4rem",
18: "4.5rem",
20: "5rem",
22: "5.5rem",
24: "6rem",
28: "7rem",
32: "8rem",
34: "8.5rem",
36: "9rem",
38: "9.5rem",
40: "10rem",
44: "11rem",
48: "12rem",
52: "13rem",
56: "14rem",
60: "15rem",
64: "16rem",
72: "18rem",
80: "20rem",
96: "24rem",
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
1.5: '0.375rem',
2: '0.5rem',
2.5: '0.625rem',
3: '0.75rem',
3.5: '0.875rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
18: '4.5rem',
20: '5rem',
22: '5.5rem',
24: '6rem',
28: '7rem',
32: '8rem',
34: '8.5rem',
36: '9rem',
38: '9.5rem',
40: '10rem',
44: '11rem',
48: '12rem',
52: '13rem',
56: '14rem',
60: '15rem',
64: '16rem',
72: '18rem',
80: '20rem',
96: '24rem',
},
});

View File

@@ -1,32 +1,32 @@
export default {
brand: {
900: "#21252b",
800: "#343a40",
750: "#495057",
700: "#525c65",
600: "#90ee90",
500: "#ffa07a",
400: "#e0e0e0",
300: "#ff69b4",
200: "#da70d6",
100: "#ffffff",
900: '#21252b',
800: '#343a40',
750: '#495057',
700: '#525c65',
600: '#90ee90',
500: '#ffa07a',
400: '#e0e0e0',
300: '#ff69b4',
200: '#da70d6',
100: '#ffffff',
},
background: {
primary: "#21252b",
secondary: "#343a40",
tertiary: "#495057",
primary: '#21252b',
secondary: '#343a40',
tertiary: '#495057',
},
text: {
primary: "#e0e0e0",
secondary: "#c0c0c0",
tertiary: "#a9a9a9",
accent: "#87cefa",
link: "#87cefa",
primary: '#e0e0e0',
secondary: '#c0c0c0',
tertiary: '#a9a9a9',
accent: '#87cefa',
link: '#87cefa',
},
accent: {
primary: "#90ee90",
secondary: "#ffa07a",
danger: "#ff69b4",
confirm: "#90ee90",
primary: '#90ee90',
secondary: '#ffa07a',
danger: '#ff69b4',
confirm: '#90ee90',
},
};

View File

@@ -1,32 +1,32 @@
export default {
brand: {
900: "#1E1E2E",
800: "#302D41",
750: "#332E41",
700: "#575268",
600: "#6E6C7E",
500: "#988BA2",
400: "#C3BAC6",
300: "#D9E0EE",
200: "#F5E0DC",
100: "#FAE3B0",
900: '#1E1E2E',
800: '#302D41',
750: '#332E41',
700: '#575268',
600: '#6E6C7E',
500: '#988BA2',
400: '#C3BAC6',
300: '#D9E0EE',
200: '#F5E0DC',
100: '#FAE3B0',
},
background: {
primary: "#1E1E2E",
secondary: "#302D41",
tertiary: "#575268",
primary: '#1E1E2E',
secondary: '#302D41',
tertiary: '#575268',
},
text: {
primary: "#D9E0EE",
secondary: "#C3BAC6",
tertiary: "#988BA2",
accent: "#F5E0DC",
link: "#96CDFB",
primary: '#D9E0EE',
secondary: '#C3BAC6',
tertiary: '#988BA2',
accent: '#F5E0DC',
link: '#96CDFB',
},
accent: {
primary: "#F5C2E7",
secondary: "#DDB6F2",
danger: "#F28FAD",
confirm: "#ABE9B3",
primary: '#F5C2E7',
secondary: '#DDB6F2',
danger: '#F28FAD',
confirm: '#ABE9B3',
},
};

View File

@@ -1,32 +1,32 @@
export default {
brand: {
900: "#000000",
800: "#333333",
750: "#2B2B2B",
700: "#666666",
600: "#999999",
500: "#CCCCCC",
400: "#FFFFFF",
300: "#F0F0F0",
200: "#F8F9FA",
100: "#FFFFFF",
900: '#000000',
800: '#333333',
750: '#2B2B2B',
700: '#666666',
600: '#999999',
500: '#CCCCCC',
400: '#FFFFFF',
300: '#F0F0F0',
200: '#F8F9FA',
100: '#FFFFFF',
},
background: {
primary: "#000000",
secondary: "#222222",
tertiary: "#333333",
primary: '#000000',
secondary: '#222222',
tertiary: '#333333',
},
text: {
primary: "#F0F0F0",
secondary: "#CCCCCC",
tertiary: "#999999",
accent: "#FFFFFF",
link: "#0d9488",
primary: '#F0F0F0',
secondary: '#CCCCCC',
tertiary: '#999999',
accent: '#FFFFFF',
link: '#0d9488',
},
accent: {
primary: "#FFFFFF",
secondary: "#c0c0c0",
danger: "#E53E3E",
confirm: "#00D26A",
primary: '#FFFFFF',
secondary: '#c0c0c0',
danger: '#E53E3E',
confirm: '#00D26A',
},
};

View File

@@ -1,40 +1,40 @@
export default {
brand: {
colors: {
900: "#2C2E43",
800: "#3D4162",
750: "#4F5285",
700: "#6076AC",
600: "#7693D6",
500: "#8DAFF0",
400: "#A3C7FF",
300: "#B9E0FF",
200: "#CDF4FE",
100: "#E1FEFF",
900: '#2C2E43',
800: '#3D4162',
750: '#4F5285',
700: '#6076AC',
600: '#7693D6',
500: '#8DAFF0',
400: '#A3C7FF',
300: '#B9E0FF',
200: '#CDF4FE',
100: '#E1FEFF',
},
},
background: {
primary: "linear-gradient(360deg, #15171C 100%, #353A47 100%)",
primary: 'linear-gradient(360deg, #15171C 100%, #353A47 100%)',
secondary: "#1B1F26",
tertiary: "#1E1E2E",
secondary: '#1B1F26',
tertiary: '#1E1E2E',
},
text: {
primary: "#f8f8f8",
secondary: "#3D4162",
tertiary: "#e5ebff",
accent: "#e6e6e6",
link: "aquamarine",
primary: '#f8f8f8',
secondary: '#3D4162',
tertiary: '#e5ebff',
accent: '#e6e6e6',
link: 'aquamarine',
},
accent: {
primary: "#127c91",
primary: '#127c91',
secondary: "#39b4bf",
secondary: '#39b4bf',
danger: "#E74C3C",
confirm: "#27AE60",
danger: '#E74C3C',
confirm: '#27AE60',
},
};

View File

@@ -1,35 +1,35 @@
export default {
brand: {
900: "#15171C",
800: "#1B1F26",
750: "#222731",
700: "#353A47",
600: "#535966",
500: "#747C88",
400: "#A0A4AC",
300: "#C6CBDC",
200: "#E6E9F0",
100: "#F3F4F8",
900: '#15171C',
800: '#1B1F26',
750: '#222731',
700: '#353A47',
600: '#535966',
500: '#747C88',
400: '#A0A4AC',
300: '#C6CBDC',
200: '#E6E9F0',
100: '#F3F4F8',
},
background: {
primary: "#15171C",
secondary: "#1B1F26",
tertiary: "#353A47",
primary: '#15171C',
secondary: '#1B1F26',
tertiary: '#353A47',
},
text: {
primary: "#ffffff",
secondary: "#A0A4AC",
tertiary: "#747C88",
accent: "#E6E9F0",
link: "#96CDFB",
primary: '#ffffff',
secondary: '#A0A4AC',
tertiary: '#747C88',
accent: '#E6E9F0',
link: '#96CDFB',
},
accent: {
primary: "#0095ff",
secondary: "#00acff",
danger: "#EA4D4D",
confirm: "#10CE8D",
primary: '#0095ff',
secondary: '#00acff',
danger: '#EA4D4D',
confirm: '#10CE8D',
},
};

View File

@@ -1,16 +1,18 @@
import { extendTheme } from "@chakra-ui/react";
import BaseTheme from "../base_theme";
import DarknightColors from "./Darknight";
import CapuchinColors from "./Capuchin";
import VsCodeColors from "./VsCode";
import OneDark from "./OneDark";
import { extendTheme } from '@chakra-ui/react';
import BaseTheme from '../base_theme';
import CapuchinColors from './Capuchin';
import DarknightColors from './Darknight';
import OneDark from './OneDark';
import VsCodeColors from './VsCode';
export function getColorThemes() {
return [
{ name: "darknight", colors: DarknightColors },
{ name: "onedark", colors: OneDark },
{ name: "capuchin", colors: CapuchinColors },
{ name: "vscode", colors: VsCodeColors },
{ name: 'darknight', colors: DarknightColors },
{ name: 'onedark', colors: OneDark },
{ name: 'capuchin', colors: CapuchinColors },
{ name: 'vscode', colors: VsCodeColors },
];
}
@@ -36,13 +38,13 @@ const onedark = extendTheme({
export function getTheme(theme: string) {
switch (theme) {
case "onedark":
case 'onedark':
return onedark;
case "darknight":
case 'darknight':
return darknight;
case "capuchin":
case 'capuchin':
return capuchin;
case "vscode":
case 'vscode':
return vsCode;
default:
return darknight;

View File

@@ -1,83 +1,80 @@
import { types, flow } from "mobx-state-tree";
/* eslint-disable no-irregular-whitespace */
import { types, flow } from 'mobx-state-tree';
// Utility to pause execution inside a flow
const sleep = (ms: number) => new Promise<void>((res) => setTimeout(res, ms));
const sleep = (ms: number) => new Promise<void>(res => setTimeout(res, ms));
// Simple function to generate a unique ID
export const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
return Date.now().toString(36) + Math.random().toString(36).substring(2);
};
// Utility for efficient batched content updates
let batchedContent = "";
let batchedContent = '';
let batchUpdateTimeout: NodeJS.Timeout | null = null;
const BATCH_UPDATE_DELAY = 50; // ms
export const batchContentUpdate = (message: any, content: string) => {
if (!content) return;
if (!content) return;
// Add the content to the batch
batchedContent += content;
// Add the content to the batch
batchedContent += content;
// If we already have a timeout scheduled, do nothing
if (batchUpdateTimeout) return;
// If we already have a timeout scheduled, do nothing
if (batchUpdateTimeout) return;
// Schedule a timeout to apply the batched content
batchUpdateTimeout = setTimeout(() => {
if (message && batchedContent) {
message.append(batchedContent);
batchedContent = "";
}
batchUpdateTimeout = null;
}, BATCH_UPDATE_DELAY);
// Schedule a timeout to apply the batched content
batchUpdateTimeout = setTimeout(() => {
if (message && batchedContent) {
message.append(batchedContent);
batchedContent = '';
}
batchUpdateTimeout = null;
}, BATCH_UPDATE_DELAY);
};
const Message = types
.model("Message", {
id: types.optional(types.identifier, generateId),
content: types.string,
role: types.enumeration(["user", "assistant"]),
})
// Runtimeonly flags that never persist or get serialized
.volatile(() => ({
/** Indicates that characters are still being streamed in */
isStreaming: false,
}))
.actions((self) => {
// Basic mutators ---------------------------------------------------------
const setContent = (newContent: string) => {
self.content = newContent;
};
.model('Message', {
id: types.optional(types.identifier, generateId),
content: types.string,
role: types.enumeration(['user', 'assistant']),
})
// Runtime-only flags that never persist or get serialized
.volatile(() => ({
/** Indicates that characters are still being streamed in */
isStreaming: false,
}))
.actions(self => {
// Basic mutators ---------------------------------------------------------
const setContent = (newContent: string) => {
self.content = newContent;
};
const append = (newContent: string) => {
self.content += newContent;
};
const append = (newContent: string) => {
self.content += newContent;
};
/**
* Stream content into the message for a smooth “typing” effect.
* @param newContent The full text to stream in.
* @param chunkSize How many characters to reveal per tick (default 3).
* @param delay Delay (ms) between ticks (default 20ms).
*/
const streamContent = flow(function* (
newContent: string,
chunkSize = 3,
delay = 20
) {
self.isStreaming = true;
let pointer = 0;
/**
* Stream content into the message for a smooth “typing” effect.
* @param newContent The full text to stream in.
* @param chunkSize How many characters to reveal per tick (default 3).
* @param delay Delay (ms) between ticks (default 20ms).
*/
const streamContent = flow(function* (newContent: string, chunkSize = 3, delay = 20) {
self.isStreaming = true;
let pointer = 0;
// Reveal the content chunkbychunk
while (pointer < newContent.length) {
append(newContent.slice(pointer, pointer + chunkSize));
pointer += chunkSize;
yield sleep(delay);
}
// Reveal the content chunkbychunk
while (pointer < newContent.length) {
append(newContent.slice(pointer, pointer + chunkSize));
pointer += chunkSize;
yield sleep(delay);
}
self.isStreaming = false; // finished
});
return { setContent, append, streamContent };
self.isStreaming = false; // finished
});
return { setContent, append, streamContent };
});
export default Message;

View File

@@ -1,4 +1,4 @@
// runs before anything else
import UserOptionsStore from "../stores/UserOptionsStore";
import UserOptionsStore from '../stores/UserOptionsStore';
UserOptionsStore.initialize();

View File

@@ -1,18 +1,19 @@
import Routes from "../renderer/routes";
import type { PageContextServer } from 'vike/types';
import Routes from '../renderer/routes';
export { data };
export type Data = Awaited<ReturnType<typeof data>>;
import type { PageContextServer } from "vike/types";
// sets the window title depending on the route
const data = async (pageContext: PageContextServer) => {
const getTitle = (path) => {
return Routes[normalizePath(path)]?.heroLabel || "";
const getTitle = path => {
return Routes[normalizePath(path)]?.heroLabel || '';
};
const normalizePath = (path) => {
if (!path) return "/";
if (path.length > 1 && path.endsWith("/")) {
const normalizePath = path => {
if (!path) return '/';
if (path.length > 1 && path.endsWith('/')) {
path = path.slice(0, -1);
}
return path.toLowerCase();

View File

@@ -1,6 +1,7 @@
// client error catcher
import { usePageContext } from "../../renderer/usePageContext";
import { Center, Text } from "@chakra-ui/react";
import { Center, Text } from '@chakra-ui/react';
import { usePageContext } from '../../renderer/usePageContext';
export { Page };
@@ -11,18 +12,16 @@ function Page() {
const { abortReason, abortStatusCode } = pageContext;
if (abortReason?.notAdmin) {
msg = "You cannot access this page because you aren't an administrator.";
} else if (typeof abortReason === "string") {
} else if (typeof abortReason === 'string') {
msg = abortReason;
} else if (abortStatusCode === 403) {
msg =
"You cannot access this page because you don't have enough privileges.";
msg = "You cannot access this page because you don't have enough privileges.";
} else if (abortStatusCode === 401) {
msg =
"You cannot access this page because you aren't logged in. Please log in.";
msg = "You cannot access this page because you aren't logged in. Please log in.";
} else {
msg = pageContext.is404
? "This page doesn't exist."
: "Something went wrong. Try again (later).";
: 'Something went wrong. Try again (later).';
}
return (
@@ -32,6 +31,7 @@ function Page() {
);
}
/* eslint-disable @typescript-eslint/no-namespace */
declare global {
namespace Vike {
interface PageContext {

View File

@@ -1,21 +1,16 @@
import React from "react";
import { Box, VStack } from "@chakra-ui/react";
import ConnectComponent from "../../components/connect/ConnectComponent";
import { Fragment } from "react";
import { useIsMobile } from "../../components/contexts/MobileContext";
import { Box, VStack } from '@chakra-ui/react';
import React, { Fragment } from 'react';
import ConnectComponent from '../../components/connect/ConnectComponent';
import { useIsMobile } from '../../components/contexts/MobileContext';
export default function ConnectPage() {
const isMobile = useIsMobile();
return (
<Fragment>
<VStack
minH={"100%"}
maxW={"40rem"}
align="start"
h={!isMobile ? "90vh" : "70vh"}
>
<Box maxW={"710px"} p={2} overflowY={"auto"} mt={isMobile ? 24 : 0}>
<VStack minH={'100%'} maxW={'40rem'} align="start" h={!isMobile ? '90vh' : '70vh'}>
<Box maxW={'710px'} p={2} overflowY={'auto'} mt={isMobile ? 24 : 0}>
<ConnectComponent />
</Box>
</VStack>

View File

@@ -1,13 +1,14 @@
import React, { useEffect } from "react";
import { Stack } from "@chakra-ui/react";
import Chat from "../../components/chat/Chat";
import clientChatStore from "../../stores/ClientChatStore";
import { Stack } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import Chat from '../../components/chat/Chat';
import clientChatStore from '../../stores/ClientChatStore';
// renders "/"
export default function IndexPage() {
useEffect(() => {
try {
const model = localStorage.getItem("recentModel");
const model = localStorage.getItem('recentModel');
clientChatStore.setModel(model as string);
} catch (_) {

View File

@@ -1,22 +1,24 @@
import React, { Fragment } from "react";
import { Box, VStack } from "@chakra-ui/react";
import PrivacyPolicy from "../../components/legal/LegalDoc";
import privacy_policy from "./privacy_policy";
import { useIsMobile } from "../../components/contexts/MobileContext";
import { Box, VStack } from '@chakra-ui/react';
import React, { Fragment } from 'react';
import { useIsMobile } from '../../components/contexts/MobileContext';
import PrivacyPolicy from '../../components/legal/LegalDoc';
import privacy_policy from './privacy_policy';
export default function Page() {
const isMobile = useIsMobile();
return (
<Fragment>
<VStack
width={"100%"}
align={"center"}
height={!isMobile ? "100%" : "100%"}
overflowX={"auto"}
width={'100%'}
align={'center'}
height={!isMobile ? '100%' : '100%'}
overflowX={'auto'}
>
<Box
overflowY={isMobile ? "scroll" : undefined}
maxH={!isMobile ? "70vh" : "89vh"}
overflowY={isMobile ? 'scroll' : undefined}
maxH={!isMobile ? '70vh' : '89vh'}
mt={isMobile ? 24 : undefined}
>
<PrivacyPolicy text={privacy_policy} />

View File

@@ -1,4 +1,4 @@
const privacyPolicyUpdateDate = new Date().toISOString().split("T")[0];
const privacyPolicyUpdateDate = new Date().toISOString().split('T')[0];
export default `
### Privacy Policy

View File

@@ -1,22 +1,24 @@
import React, { Fragment } from "react";
import { Box, VStack } from "@chakra-ui/react";
import TermsOfService from "../../components/legal/LegalDoc";
import terms_of_service from "./terms_of_service";
import { useIsMobile } from "../../components/contexts/MobileContext";
import { Box, VStack } from '@chakra-ui/react';
import React, { Fragment } from 'react';
import { useIsMobile } from '../../components/contexts/MobileContext';
import TermsOfService from '../../components/legal/LegalDoc';
import terms_of_service from './terms_of_service';
export default function Page() {
const isMobile = useIsMobile();
return (
<Fragment>
<VStack
width={"100%"}
align={"center"}
height={!isMobile ? "100%" : "100%"}
overflowX={"auto"}
width={'100%'}
align={'center'}
height={!isMobile ? '100%' : '100%'}
overflowX={'auto'}
>
<Box
overflowY={isMobile ? "scroll" : undefined}
maxH={!isMobile ? "70vh" : "89vh"}
overflowY={isMobile ? 'scroll' : undefined}
maxH={!isMobile ? '70vh' : '89vh'}
mt={isMobile ? 24 : undefined}
>
<TermsOfService text={terms_of_service} />

View File

@@ -1,4 +1,4 @@
const tosUpdateDate = new Date().toISOString().split("T")[0];
const tosUpdateDate = new Date().toISOString().split('T')[0];
export default `
### Terms of Service

View File

@@ -1,6 +1,6 @@
import type { Config } from "vike/types";
import type { Config } from 'vike/types';
// https://vike.dev/config
export default {
passToClient: ["pageProps", "urlPathname"],
passToClient: ['pageProps', 'urlPathname'],
} satisfies Config;

View File

@@ -1,16 +1,17 @@
export { onRenderClient };
import React from "react";
import { hydrateRoot } from "react-dom/client";
import { Layout } from "../layout/Layout";
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { Layout } from '../layout/Layout';
// See https://vike.dev/onRenderClient for usage details
async function onRenderClient(pageContext) {
const { Page, pageProps } = pageContext;
hydrateRoot(
document.getElementById("page-view"),
document.getElementById('page-view'),
<Layout pageContext={pageContext}>
<Page {...pageProps} />
</Layout>,
);
}
}

View File

@@ -1,15 +1,14 @@
import React from "react";
import React from 'react';
import { renderToStream } from 'react-streaming/server';
import { escapeInject } from 'vike/server';
import type { OnRenderHtmlAsync } from 'vike/types';
import { Layout } from '../layout/Layout';
export { onRenderHtml };
import { renderToStream } from "react-streaming/server";
import { escapeInject } from "vike/server";
import { Layout } from "../layout/Layout";
import type { OnRenderHtmlAsync } from "vike/types";
// See https://vike.dev/onRenderHtml for usage details
const onRenderHtml: OnRenderHtmlAsync = async (
pageContext,
): ReturnType<OnRenderHtmlAsync> => {
const onRenderHtml: OnRenderHtmlAsync = async (pageContext): ReturnType<OnRenderHtmlAsync> => {
const { Page, pageProps } = pageContext;
const page = (
@@ -20,9 +19,9 @@ const onRenderHtml: OnRenderHtmlAsync = async (
let ua;
try {
ua = pageContext.headers["user-agent"];
ua = pageContext.headers['user-agent'];
} catch (e) {
ua = "";
ua = '';
}
const res = escapeInject`<!DOCTYPE html>
@@ -43,7 +42,7 @@ window.ga_api = "/api/metrics";
<script src="/cfga.min.js" async></script>
</head>
<body>
<div id="page-view">${await renderToStream(page, {userAgent: ua})}</div>
<div id="page-view">${await renderToStream(page, { userAgent: ua })}</div>
</body>
</html>`;

View File

@@ -1,14 +1,14 @@
export default {
"/": { sidebarLabel: "Home", heroLabel: "gsio" },
"/connect": { sidebarLabel: "Connect", heroLabel: "connect" },
"/privacy-policy": {
sidebarLabel: "",
heroLabel: "privacy policy",
'/': { sidebarLabel: 'Home', heroLabel: 'gsio' },
'/connect': { sidebarLabel: 'Connect', heroLabel: 'connect' },
'/privacy-policy': {
sidebarLabel: '',
heroLabel: 'privacy policy',
hideNav: true,
},
"/terms-of-service": {
sidebarLabel: "",
heroLabel: "terms of service",
'/terms-of-service': {
sidebarLabel: '',
heroLabel: 'terms of service',
hideNav: true,
},
};

View File

@@ -4,12 +4,13 @@ type Page = (pageProps: PageProps) => React.ReactElement;
type PageProps = Record<string, unknown>;
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Vike {
interface PageContext {
Page: Page;
pageProps?: PageProps;
fetch?: typeof fetch;
env: import("@open-gsio/env");
env: import('@open-gsio/env');
}
}
}

View File

@@ -1,5 +1,5 @@
import { useContext, createContext, type ReactNode } from "react";
import type { PageContext } from "vike/types";
import { useContext, createContext, type ReactNode } from 'react';
import type { PageContext } from 'vike/types';
export { PageContextProvider };
export { usePageContext };

View File

@@ -1,43 +1,40 @@
export const resumeData = {
professionalSummary:
"Software engineer and DoD veteran with 10+ years of diverse experiences. " +
"Expertise in cloud technologies, DevOps practices, and full-stack development. " +
"Solid track record of leading high-stakes projects and teams in both military and civilian sectors.",
'Software engineer and DoD veteran with 10+ years of diverse experiences. ' +
'Expertise in cloud technologies, DevOps practices, and full-stack development. ' +
'Solid track record of leading high-stakes projects and teams in both military and civilian sectors.',
skills: [
"AI: Retrieval Augmented Generation, Meta, Mistral, OpenAI, Anthropic",
"Cloud: Cloudflare, Google GKE, Amazon AWS Lambda, PCF/TAS, TrueNAS",
"DEVOPS: Pulumi, Helm, Docker",
"CI/CD: Github Actions, Gitlab CI",
"Languages: Typescript, Javascript, Rust, Python, Java, Go",
"Databases: Durable Objects, Postgres, MySQL, Snowflake, Elasticsearch",
"Other: Isolate Compute, WASM Toolchains, Microservice Architectures, GraphQL",
'AI: Retrieval Augmented Generation, Meta, Mistral, OpenAI, Anthropic',
'Cloud: Cloudflare, Google GKE, Amazon AWS Lambda, PCF/TAS, TrueNAS',
'DEVOPS: Pulumi, Helm, Docker',
'CI/CD: Github Actions, Gitlab CI',
'Languages: Typescript, Javascript, Rust, Python, Java, Go',
'Databases: Durable Objects, Postgres, MySQL, Snowflake, Elasticsearch',
'Other: Isolate Compute, WASM Toolchains, Microservice Architectures, GraphQL',
],
experience: [
{
title: "Senior Software Engineer",
company: "Orbis Operations LLC",
timeline: "Mar 2022 - Aug 2024",
description:
"Architected and delivered AI products with a focus in national security.",
title: 'Senior Software Engineer',
company: 'Orbis Operations LLC',
timeline: 'Mar 2022 - Aug 2024',
description: 'Architected and delivered AI products with a focus in national security.',
},
{
title: "Software Engineer",
company: "JSOC",
timeline: "Jan 2020 - Mar 2022",
description:
"Spearheaded development of mission-critical data analytics systems.",
title: 'Software Engineer',
company: 'JSOC',
timeline: 'Jan 2020 - Mar 2022',
description: 'Spearheaded development of mission-critical data analytics systems.',
},
{
title: "U.S. Army Ranger",
company: "1st Battalion, 75th Ranger Regiment",
timeline: "Jul 2014 - Sep 2018",
description:
"Conducted technical, high-risk operations in diverse environments.",
title: 'U.S. Army Ranger',
company: '1st Battalion, 75th Ranger Regiment',
timeline: 'Jul 2014 - Sep 2018',
description: 'Conducted technical, high-risk operations in diverse environments.',
},
],
education: [
"Ranger Assessment and Selection Program, U.S. Army",
"Basic Leaders Course, U.S. Army",
"(In progress) BAS Computer Science",
'Ranger Assessment and Selection Program, U.S. Army',
'Basic Leaders Course, U.S. Army',
'(In progress) BAS Computer Science',
],
};

View File

@@ -4,34 +4,34 @@ Working with my partners, I offer a comprehensive skill set to address demanding
If you're facing difficult technical challenges and need reliable, efficient solutions, let's discuss how I can help.`,
offerings: [
{
title: "AI Integration and Development Services",
title: 'AI Integration and Development Services',
description:
"Leverage advanced AI technologies like Retrieval Augmented Generation to develop custom AI solutions. Expertise with platforms such as OpenAI, Meta, Mistral, and Anthropic ensures state-of-the-art AI integration tailored to your business needs.",
'Leverage advanced AI technologies like Retrieval Augmented Generation to develop custom AI solutions. Expertise with platforms such as OpenAI, Meta, Mistral, and Anthropic ensures state-of-the-art AI integration tailored to your business needs.',
},
{
title: "Cloud Infrastructure and DevOps Consulting",
title: 'Cloud Infrastructure and DevOps Consulting',
description:
"Provide comprehensive cloud solutions using platforms like Google GKE, AWS Lambda, and Cloudflare. Implement DevOps best practices with tools like Pulumi, Helm, and Docker to streamline your development pipeline and enhance scalability.",
'Provide comprehensive cloud solutions using platforms like Google GKE, AWS Lambda, and Cloudflare. Implement DevOps best practices with tools like Pulumi, Helm, and Docker to streamline your development pipeline and enhance scalability.',
},
{
title: "Full-Stack Development Services",
title: 'Full-Stack Development Services',
description:
"Offer full-stack development expertise across multiple languages including TypeScript, Rust, Python, Java, and Go. Build robust, high-performance applications that meet your specific requirements.",
'Offer full-stack development expertise across multiple languages including TypeScript, Rust, Python, Java, and Go. Build robust, high-performance applications that meet your specific requirements.',
},
{
title: "Data Analytics and Visualization Solutions",
title: 'Data Analytics and Visualization Solutions',
description:
"Develop mission-critical data analytics systems to unlock valuable insights. Utilize databases like PostgreSQL, MySQL, Snowflake, and Elasticsearch for efficient data management and retrieval.",
'Develop mission-critical data analytics systems to unlock valuable insights. Utilize databases like PostgreSQL, MySQL, Snowflake, and Elasticsearch for efficient data management and retrieval.',
},
{
title: "Real-Time Communication Systems",
title: 'Real-Time Communication Systems',
description:
"Design and implement robust real-time communication systems, drawing from experience in developing solutions for special operations forces. Ensure secure, reliable, and efficient communication channels for your organization.",
'Design and implement robust real-time communication systems, drawing from experience in developing solutions for special operations forces. Ensure secure, reliable, and efficient communication channels for your organization.',
},
{
title: "Technical Leadership and Project Management",
title: 'Technical Leadership and Project Management',
description:
"Provide leadership in high-stakes projects, guiding teams through complex technical challenges. Combine military discipline with technical expertise to deliver projects on time and within scope.",
'Provide leadership in high-stakes projects, guiding teams through complex technical challenges. Combine military discipline with technical expertise to deliver projects on time and within scope.',
},
],
};

View File

@@ -1,10 +1,10 @@
import { types } from "mobx-state-tree";
import { types } from 'mobx-state-tree';
const AppMenuStateModel = types
.model("AppMenuState", {
.model('AppMenuState', {
isOpen: types.optional(types.boolean, false),
})
.actions((self) => ({
.actions(self => ({
openMenu() {
self.isOpen = true;
},

View File

@@ -1,15 +1,16 @@
// ---------------------------
// stores/ClientChatStore.ts (root)
// ---------------------------
import { types, type Instance } from "mobx-state-tree";
import { MessagesStore } from "./MessagesStore";
import { UIStore } from "./UIStore";
import { ModelStore } from "./ModelStore";
import { StreamStore } from "./StreamStore";
import { types, type Instance } from 'mobx-state-tree';
import { MessagesStore } from './MessagesStore';
import { ModelStore } from './ModelStore';
import { StreamStore } from './StreamStore';
import { UIStore } from './UIStore';
export const ClientChatStore = types
.compose(MessagesStore, UIStore, ModelStore, StreamStore)
.named("ClientChatStore");
.compose(MessagesStore, UIStore, ModelStore, StreamStore)
.named('ClientChatStore');
const clientChatStore = ClientChatStore.create();

View File

@@ -1,44 +1,44 @@
import { types, flow } from "mobx-state-tree";
import { types, flow } from 'mobx-state-tree';
const ClientFeedbackStore = types
.model("ClientFeedbackStore", {
input: types.optional(types.string, ""),
.model('ClientFeedbackStore', {
input: types.optional(types.string, ''),
isLoading: types.optional(types.boolean, false),
isSubmitted: types.optional(types.boolean, false),
error: types.optional(types.string, ""),
error: types.optional(types.string, ''),
})
.actions((self) => {
const setError = (error) => {
.actions(self => {
const setError = error => {
self.error = error;
};
const setInput = (value) => {
const setInput = value => {
self.input = value;
if (self.error) {
setError("");
setError('');
}
};
const reset = () => {
self.input = "";
self.input = '';
self.isLoading = false;
self.isSubmitted = false;
self.error = "";
self.error = '';
};
const validateInput = () => {
if (!self.input.trim()) {
setError("Feedback cannot be empty.");
setError('Feedback cannot be empty.');
return false;
}
if (self.input.length > 500) {
setError("Feedback cannot exceed 500 characters.");
setError('Feedback cannot exceed 500 characters.');
return false;
}
setError("");
setError('');
return true;
};
@@ -50,10 +50,10 @@ const ClientFeedbackStore = types
self.isLoading = true;
try {
const response = yield fetch("/api/feedback", {
method: "POST",
const response = yield fetch('/api/feedback', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify({ feedback: self.input }),
});
@@ -63,13 +63,11 @@ const ClientFeedbackStore = types
}
self.isSubmitted = true;
self.input = "";
self.input = '';
return true;
} catch (error) {
console.error(error);
setError(
error.message || "An error occurred while submitting feedback.",
);
setError(error.message || 'An error occurred while submitting feedback.');
return false;
} finally {
self.isLoading = false;

View File

@@ -1,15 +1,15 @@
import { types, flow } from "mobx-state-tree";
import { types, flow } from 'mobx-state-tree';
const ClientTransactionStore = types
.model("ClientTransactionStore", {
.model('ClientTransactionStore', {
selectedMethod: types.string,
depositAddress: types.maybeNull(types.string),
amount: types.optional(types.string, ""),
donerId: types.optional(types.string, ""),
amount: types.optional(types.string, ''),
donerId: types.optional(types.string, ''),
userConfirmed: types.optional(types.boolean, false),
txId: types.optional(types.string, ""),
txId: types.optional(types.string, ''),
})
.actions((self) => ({
.actions(self => ({
setSelectedMethod(method: string) {
self.selectedMethod = method;
self.userConfirmed = false;
@@ -30,47 +30,45 @@ const ClientTransactionStore = types
self.depositAddress = address;
},
resetTransaction() {
self.txId = "";
self.txId = '';
self.depositAddress = null;
self.userConfirmed = false;
},
prepareTransaction: flow(function* () {
if (!self.amount || !self.donerId || parseInt(self.amount) <= 0) {
throw new Error("Invalid donation data");
throw new Error('Invalid donation data');
}
const currency = self.selectedMethod.toLowerCase();
try {
const response = yield fetch("/api/tx", {
method: "POST",
body: ["PREPARE_TX", self.donerId, currency, self.amount]
.join(",")
.trim(),
const response = yield fetch('/api/tx', {
method: 'POST',
body: ['PREPARE_TX', self.donerId, currency, self.amount].join(',').trim(),
});
if (!response.ok) throw new Error("Failed to prepare transaction");
if (!response.ok) throw new Error('Failed to prepare transaction');
const txData = yield response.json();
let finalDepositAddress = txData.depositAddress;
if (currency === "ethereum") {
finalDepositAddress = "0x" + finalDepositAddress;
if (currency === 'ethereum') {
finalDepositAddress = '0x' + finalDepositAddress;
}
self.setTransactionId(txData.txKey);
self.setDepositAddress(finalDepositAddress);
self.confirmUser();
} catch (error) {
console.error("Transaction preparation failed:", error);
console.error('Transaction preparation failed:', error);
throw error;
}
}),
}));
export default ClientTransactionStore.create({
selectedMethod: "Ethereum",
selectedMethod: 'Ethereum',
depositAddress: null,
amount: "",
donerId: "",
amount: '',
donerId: '',
userConfirmed: false,
txId: "",
txId: '',
});

View File

@@ -1,169 +1,171 @@
import {types, type Instance} from "mobx-state-tree";
import clientChatStore from "./ClientChatStore";
import UserOptionsStore from "./UserOptionsStore";
import Message, { batchContentUpdate } from "../models/Message";
import {MessagesStore} from "./MessagesStore";
import { types, type Instance } from 'mobx-state-tree';
import Message, { batchContentUpdate } from '../models/Message';
import clientChatStore from './ClientChatStore';
import { MessagesStore } from './MessagesStore';
import UserOptionsStore from './UserOptionsStore';
export const MessageEditorStore = types
.compose(
MessagesStore,
types.model("MessageEditorStore", {
editedContent: types.optional(types.string, ""),
messageId: types.optional(types.string, "")
})
)
.views((self) => ({
getMessage() {
// Find the message in clientChatStore by ID
if (!self.messageId) return null;
.compose(
MessagesStore,
types.model('MessageEditorStore', {
editedContent: types.optional(types.string, ''),
messageId: types.optional(types.string, ''),
}),
)
.views(self => ({
getMessage() {
// Find the message in clientChatStore by ID
if (!self.messageId) return null;
const message = clientChatStore.items.find(item => item.id === self.messageId);
return message || null;
const message = clientChatStore.items.find(item => item.id === self.messageId);
return message || null;
},
}))
.actions(self => ({
setEditedContent(content: string) {
self.editedContent = content;
},
setMessage(message: Instance<typeof Message>) {
// Handle messages that might not have an id property (for testing)
self.messageId = message.id || '';
self.editedContent = message.content;
},
onCancel() {
self.messageId = '';
self.editedContent = '';
},
handleSave: async () => {
// Get the message using the ID
const message = self.getMessage();
// Check if message reference is still valid
if (!message) {
// Message reference is no longer valid, just cancel the edit
self.onCancel();
return;
}
// Store the content we want to update
const contentToUpdate = self.editedContent;
try {
// Use the editMessage function from MessagesStore
const success = clientChatStore.editMessage(message, contentToUpdate);
if (!success) {
// Message not found in the items array, just cancel
self.onCancel();
return;
}
}))
.actions((self) => ({
setEditedContent(content: string) {
self.editedContent = content;
},
setMessage(message: Instance<typeof Message>) {
// Handle messages that might not have an id property (for testing)
self.messageId = message.id || "";
self.editedContent = message.content;
},
onCancel() {
self.messageId = "";
self.editedContent = "";
},
handleSave: async () => {
// Get the message using the ID
const message = self.getMessage();
// Check if message reference is still valid
if (!message) {
// Message reference is no longer valid, just cancel the edit
self.onCancel();
return;
}
// Set follow mode and loading state
UserOptionsStore.setFollowModeEnabled(true);
clientChatStore.setIsLoading(true);
// Store the content we want to update
const contentToUpdate = self.editedContent;
try {
// Add a small delay before adding the assistant message (for better UX)
await new Promise(r => setTimeout(r, 500));
// Add an empty assistant message to clientChatStore's items
clientChatStore.add(Message.create({ content: '', role: 'assistant' }));
// Use clientChatStore for the API call since it has the model property
const payload = { messages: clientChatStore.items.slice(), model: clientChatStore.model };
// Make API call
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (response.status === 429) {
clientChatStore.appendLast('\n\nError: Too many requests • please slow down.');
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
clientChatStore.appendLast('\n\nError: Something went wrong.');
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
const { streamUrl } = await response.json();
// Use the StreamStore's functionality to handle the event source
const eventSource = new EventSource(streamUrl);
// Set up event handlers using a more efficient approach
const handleMessage = event => {
try {
// Use the editMessage function from MessagesStore
const success = clientChatStore.editMessage(message, contentToUpdate);
if (!success) {
// Message not found in the items array, just cancel
self.onCancel();
return;
}
const parsed = JSON.parse(event.data);
// Set follow mode and loading state
UserOptionsStore.setFollowModeEnabled(true);
clientChatStore.setIsLoading(true);
try {
// Add a small delay before adding the assistant message (for better UX)
await new Promise((r) => setTimeout(r, 500));
// Add an empty assistant message to clientChatStore's items
clientChatStore.add(Message.create({content: "", role: "assistant"}));
// Use clientChatStore for the API call since it has the model property
const payload = {messages: clientChatStore.items.slice(), model: clientChatStore.model};
// Make API call
const response = await fetch("/api/chat", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(payload),
});
if (response.status === 429) {
clientChatStore.appendLast("\n\nError: Too many requests • please slow down.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
clientChatStore.appendLast("\n\nError: Something went wrong.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
const {streamUrl} = await response.json();
// Use the StreamStore's functionality to handle the event source
const eventSource = new EventSource(streamUrl);
// Set up event handlers using a more efficient approach
const handleMessage = (event) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.type === "error") {
// Append error message instead of replacing content
clientChatStore.appendLast("\n\nError: " + parsed.error);
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
return;
}
// Get the last message to use its streamContent method
const lastMessage = clientChatStore.items[clientChatStore.items.length - 1];
if (parsed.type === "chat" && parsed.data.choices[0]?.finish_reason === "stop") {
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
clientChatStore.appendLast(content);
}
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
return;
}
if (parsed.type === "chat") {
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error("stream parse error", err);
}
};
const handleError = () => {
clientChatStore.appendLast("\n\nError: Connection lost.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
};
eventSource.onmessage = handleMessage;
eventSource.onerror = handleError;
} catch (err) {
console.error("sendMessage", err);
clientChatStore.appendLast("\n\nError: Sorry • network error.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
}
} catch (err) {
console.error("Error in handleSave:", err);
// If any error occurs, just cancel the edit
self.onCancel();
if (parsed.type === 'error') {
// Append error message instead of replacing content
clientChatStore.appendLast('\n\nError: ' + parsed.error);
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
return;
}
}
// Always clean up at the end
self.onCancel();
// Get the last message to use its streamContent method
const lastMessage = clientChatStore.items[clientChatStore.items.length - 1];
if (parsed.type === 'chat' && parsed.data.choices[0]?.finish_reason === 'stop') {
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? '';
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
clientChatStore.appendLast(content);
}
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
return;
}
if (parsed.type === 'chat') {
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? '';
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error('stream parse error', err);
}
};
const handleError = () => {
clientChatStore.appendLast('\n\nError: Connection lost.');
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
};
eventSource.onmessage = handleMessage;
eventSource.onerror = handleError;
} catch (err) {
console.error('sendMessage', err);
clientChatStore.appendLast('\n\nError: Sorry • network error.');
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
}
}));
} catch (err) {
console.error('Error in handleSave:', err);
// If any error occurs, just cancel the edit
self.onCancel();
return;
}
// Always clean up at the end
self.onCancel();
},
}));
const messageEditorStore = MessageEditorStore.create();

View File

@@ -1,51 +1,52 @@
// ---------------------------
// stores/MessagesStore.ts
// ---------------------------
import { type Instance, types } from "mobx-state-tree";
import Message from "../models/Message";
import { type Instance, types } from 'mobx-state-tree';
import Message from '../models/Message';
export const MessagesStore = types
.model("MessagesStore", {
items: types.optional(types.array(Message), []),
})
.actions((self) => ({
add(message: Instance<typeof Message>) {
self.items.push(message);
},
updateLast(content: string) {
if (self.items.length) {
self.items[self.items.length - 1].content = content;
}
},
appendLast(content: string) {
if (self.items.length) {
self.items[self.items.length - 1].content += content;
}
},
removeAfter(index: number) {
if (index >= 0 && index < self.items.length) {
self.items.splice(index + 1);
}
},
reset() {
self.items.clear();
},
editMessage(message: Instance<typeof Message>, newContent: string) {
// Find the index of the message in the items array
const messageIndex = self.items.map(i => i.id).indexOf(message.id);
if (messageIndex === -1) {
// Message not found in the items array
return false;
}
.model('MessagesStore', {
items: types.optional(types.array(Message), []),
})
.actions(self => ({
add(message: Instance<typeof Message>) {
self.items.push(message);
},
updateLast(content: string) {
if (self.items.length) {
self.items[self.items.length - 1].content = content;
}
},
appendLast(content: string) {
if (self.items.length) {
self.items[self.items.length - 1].content += content;
}
},
removeAfter(index: number) {
if (index >= 0 && index < self.items.length) {
self.items.splice(index + 1);
}
},
reset() {
self.items.clear();
},
editMessage(message: Instance<typeof Message>, newContent: string) {
// Find the index of the message in the items array
const messageIndex = self.items.map(i => i.id).indexOf(message.id);
if (messageIndex === -1) {
// Message not found in the items array
return false;
}
// Update the message content
message.setContent(newContent);
// Update the message content
message.setContent(newContent);
// Remove all messages after the edited message
self.removeAfter(messageIndex);
// Remove all messages after the edited message
self.removeAfter(messageIndex);
return true;
},
}));
return true;
},
}));
export interface IMessagesStore extends Instance<typeof MessagesStore> {}

View File

@@ -1,35 +1,33 @@
// ---------------------------
// stores/ModelStore.ts
// ---------------------------
import { type Instance, types } from "mobx-state-tree";
import { type Instance, types } from 'mobx-state-tree';
export const ModelStore = types
.model("ModelStore", {
model: types.optional(
types.string,
"meta-llama/llama-4-scout-17b-16e-instruct",
),
imageModel: types.optional(types.string, "black-forest-labs/flux-1.1-pro"),
supportedModels: types.optional(types.array(types.string), []),
})
.actions((self) => ({
setModel(value: string) {
self.model = value;
try {
localStorage.setItem("recentModel", value);
} catch (_) {}
},
setImageModel(value: string) {
self.imageModel = value;
},
setSupportedModels(list: string[]) {
self.supportedModels = list;
if (!list.includes(self.model)) {
// fall back to last entry (arbitrary but predictable)
self.model = list[list.length - 1] ?? self.model;
}
},
}));
.model('ModelStore', {
model: types.optional(types.string, 'meta-llama/llama-4-scout-17b-16e-instruct'),
imageModel: types.optional(types.string, 'black-forest-labs/flux-1.1-pro'),
supportedModels: types.optional(types.array(types.string), []),
})
.actions(self => ({
setModel(value: string) {
self.model = value;
try {
localStorage.setItem('recentModel', value);
} catch (_) {
// Silently ignore localStorage errors (e.g., in environments where it's not available)
}
},
setImageModel(value: string) {
self.imageModel = value;
},
setSupportedModels(list: string[]) {
self.supportedModels = list;
if (!list.includes(self.model)) {
// fall back to last entry (arbitrary but predictable)
self.model = list[list.length - 1] ?? self.model;
}
},
}));
export interface IModelStore extends Instance<typeof ModelStore> {}

View File

@@ -1,5 +1,5 @@
import type {IMessagesStore} from "./MessagesStore.ts";
import type {IUIStore} from "./UIStore.ts";
import type {IModelStore} from "./ModelStore.ts";
import type { IMessagesStore } from './MessagesStore.ts';
import type { IModelStore } from './ModelStore.ts';
import type { IUIStore } from './UIStore.ts';
export type RootDeps = IMessagesStore & IUIStore & IModelStore;
export type RootDeps = IMessagesStore & IUIStore & IModelStore;

View File

@@ -1,155 +1,155 @@
import {flow, getParent, type Instance, types} from "mobx-state-tree";
import UserOptionsStore from "./UserOptionsStore";
import Message, { batchContentUpdate } from "../models/Message";
import type {RootDeps} from "./RootDeps.ts";
import { flow, getParent, type Instance, types } from 'mobx-state-tree';
import Message, { batchContentUpdate } from '../models/Message';
import type { RootDeps } from './RootDeps.ts';
import UserOptionsStore from './UserOptionsStore';
export const StreamStore = types
.model("StreamStore", {
streamId: types.optional(types.string, ""),
})
.volatile(() => ({
eventSource: undefined as unknown as EventSource,
}))
.actions((self: any) => { // ← annotate `self` so it isnt implicitly `any`
let root: RootDeps;
try {
root = getParent<RootDeps>(self);
} catch {
root = self as any;
}
.model('StreamStore', {
streamId: types.optional(types.string, ''),
})
.volatile(() => ({
eventSource: undefined as unknown as EventSource,
}))
.actions((self: any) => {
// ← annotate `self` so it isnt implicitly `any`
let root: RootDeps;
try {
root = getParent<RootDeps>(self);
} catch {
root = self as any;
}
function setEventSource(source: EventSource | null) {
self.eventSource = source;
}
function setEventSource(source: EventSource | null) {
self.eventSource = source;
}
function cleanup() {
try {
self.eventSource.close();
} catch (e) {
console.error("error closing event source", e);
} finally {
setEventSource(null);
}
}
function cleanup() {
try {
self.eventSource.close();
} catch (e) {
console.error('error closing event source', e);
} finally {
setEventSource(null);
}
}
const sendMessage = flow(function* () {
if (!root.input.trim() || root.isLoading) return;
cleanup();
const sendMessage = flow(function* () {
if (!root.input.trim() || root.isLoading) return;
cleanup();
// ← **DO NOT** `yield` a synchronous action
UserOptionsStore.setFollowModeEnabled(true);
root.setIsLoading(true);
// ← **DO NOT** `yield` a synchronous action
UserOptionsStore.setFollowModeEnabled(true);
root.setIsLoading(true);
const userMessage = Message.create({
content: root.input,
role: "user" as const,
});
root.add(userMessage);
root.setInput("");
const userMessage = Message.create({
content: root.input,
role: 'user' as const,
});
root.add(userMessage);
root.setInput('');
try {
const payload = { messages: root.items.slice(), model: root.model };
try {
const payload = { messages: root.items.slice(), model: root.model };
yield new Promise((r) => setTimeout(r, 500));
root.add(Message.create({ content: "", role: "assistant" }));
yield new Promise(r => setTimeout(r, 500));
root.add(Message.create({ content: '', role: 'assistant' }));
const response: Response = yield fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (response.status === 429) {
root.appendLast("\n\nError: Too many requests • please slow down.");
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
root.appendLast("\n\nError: Something went wrong.");
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
}
const { streamUrl } = (yield response.json()) as { streamUrl: string };
setEventSource(new EventSource(streamUrl));
const handleMessage = (event: MessageEvent) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.type === "error") {
// Append error message instead of replacing content
root.appendLast("\n\nError: " + parsed.error);
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
return;
}
// Get the last message
const lastMessage = root.items[root.items.length - 1];
if (
parsed.type === "chat" &&
parsed.data.choices[0]?.finish_reason === "stop"
) {
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
root.appendLast(content);
}
UserOptionsStore.setFollowModeEnabled(false);
root.setIsLoading(false);
cleanup();
return;
}
if (parsed.type === "chat") {
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error("stream parse error", err);
}
};
const handleError = () => {
root.appendLast("\n\nError: Connection lost.");
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
};
self.eventSource.onmessage = handleMessage;
self.eventSource.onerror = handleError;
} catch (err) {
console.error("sendMessage", err);
root.appendLast("\n\nError: Sorry • network error.");
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
}
const response: Response = yield fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const stopIncomingMessage = () => {
cleanup();
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
if (response.status === 429) {
root.appendLast('\n\nError: Too many requests • please slow down.');
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
root.appendLast('\n\nError: Something went wrong.');
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
}
const { streamUrl } = (yield response.json()) as { streamUrl: string };
setEventSource(new EventSource(streamUrl));
const handleMessage = (event: MessageEvent) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.type === 'error') {
// Append error message instead of replacing content
root.appendLast('\n\nError: ' + parsed.error);
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
return;
}
// Get the last message
const lastMessage = root.items[root.items.length - 1];
if (parsed.type === 'chat' && parsed.data.choices[0]?.finish_reason === 'stop') {
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? '';
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
root.appendLast(content);
}
UserOptionsStore.setFollowModeEnabled(false);
root.setIsLoading(false);
cleanup();
return;
}
if (parsed.type === 'chat') {
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? '';
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error('stream parse error', err);
}
};
const setStreamId = (id: string) => {
self.streamId = id;
const handleError = () => {
root.appendLast('\n\nError: Connection lost.');
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
};
return { sendMessage, stopIncomingMessage, cleanup, setEventSource, setStreamId };
self.eventSource.onmessage = handleMessage;
self.eventSource.onerror = handleError;
} catch (err) {
console.error('sendMessage', err);
root.appendLast('\n\nError: Sorry • network error.');
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
}
});
const stopIncomingMessage = () => {
cleanup();
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
};
const setStreamId = (id: string) => {
self.streamId = id;
};
return { sendMessage, stopIncomingMessage, cleanup, setEventSource, setStreamId };
});
export interface IStreamStore extends Instance<typeof StreamStore> {}

View File

@@ -1,21 +1,20 @@
// ---------------------------
// stores/UIStore.ts
// ---------------------------
import { type Instance, types } from "mobx-state-tree";
import { type Instance, types } from 'mobx-state-tree';
export const UIStore = types
.model("UIStore", {
input: types.optional(types.string, ""),
isLoading: types.optional(types.boolean, false),
})
.actions((self) => ({
setInput(value: string) {
self.input = value;
},
setIsLoading(value: boolean) {
self.isLoading = value;
},
}));
.model('UIStore', {
input: types.optional(types.string, ''),
isLoading: types.optional(types.boolean, false),
})
.actions(self => ({
setInput(value: string) {
self.input = value;
},
setIsLoading(value: boolean) {
self.isLoading = value;
},
}));
export interface IUIStore extends Instance<typeof UIStore> {}

Some files were not shown because too many files have changed in this diff Show More