mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
adds eslint
This commit is contained in:

committed by
Geoff Seemueller

parent
9698fc6f3b
commit
02c3253343
41
.eslintignore
Normal file
41
.eslintignore
Normal 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
49
.eslintrc.cjs
Normal 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
47
.prettierignore
Normal 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
19
.prettierrc.cjs
Normal 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
331
bun.lock
@@ -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=="],
|
||||
}
|
||||
}
|
||||
|
18
package.json
18
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// for future use
|
||||
export {}
|
||||
export {};
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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;
|
||||
|
@@ -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}
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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>
|
||||
|
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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>
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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>
|
||||
))}
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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 = [
|
||||
|
@@ -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,
|
||||
});
|
||||
|
@@ -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');
|
||||
}
|
||||
|
@@ -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} />;
|
||||
|
@@ -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"
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -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>;
|
||||
};
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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' }}
|
||||
/>
|
||||
));
|
||||
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
|
@@ -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',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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%"
|
||||
|
@@ -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 (
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@@ -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>
|
||||
|
@@ -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 />
|
||||
|
@@ -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);
|
||||
}, []);
|
||||
|
||||
|
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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]);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -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',
|
||||
},
|
||||
};
|
||||
|
@@ -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',
|
||||
},
|
||||
};
|
||||
|
@@ -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',
|
||||
},
|
||||
};
|
||||
|
@@ -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',
|
||||
},
|
||||
};
|
||||
|
@@ -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',
|
||||
},
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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"]),
|
||||
})
|
||||
// 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;
|
||||
};
|
||||
.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 20 ms).
|
||||
*/
|
||||
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 20 ms).
|
||||
*/
|
||||
const streamContent = flow(function* (newContent: string, chunkSize = 3, delay = 20) {
|
||||
self.isStreaming = true;
|
||||
let pointer = 0;
|
||||
|
||||
// Reveal the content chunk‑by‑chunk
|
||||
while (pointer < newContent.length) {
|
||||
append(newContent.slice(pointer, pointer + chunkSize));
|
||||
pointer += chunkSize;
|
||||
yield sleep(delay);
|
||||
}
|
||||
// Reveal the content chunk‑by‑chunk
|
||||
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;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// runs before anything else
|
||||
import UserOptionsStore from "../stores/UserOptionsStore";
|
||||
import UserOptionsStore from '../stores/UserOptionsStore';
|
||||
|
||||
UserOptionsStore.initialize();
|
||||
|
@@ -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();
|
||||
|
@@ -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 {
|
||||
|
@@ -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>
|
||||
|
@@ -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 (_) {
|
||||
|
@@ -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} />
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const privacyPolicyUpdateDate = new Date().toISOString().split("T")[0];
|
||||
const privacyPolicyUpdateDate = new Date().toISOString().split('T')[0];
|
||||
|
||||
export default `
|
||||
### Privacy Policy
|
||||
|
@@ -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} />
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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>`;
|
||||
|
||||
|
@@ -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,
|
||||
},
|
||||
};
|
||||
|
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 };
|
||||
|
@@ -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',
|
||||
],
|
||||
};
|
||||
|
@@ -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.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@@ -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;
|
||||
},
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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: '',
|
||||
});
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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> {}
|
||||
|
@@ -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> {}
|
||||
|
@@ -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;
|
||||
|
@@ -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 isn’t 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 isn’t 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> {}
|
||||
|
@@ -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
Reference in New Issue
Block a user