Compare commits

...

10 Commits

Author SHA1 Message Date
Zeno Kapitein 5d846548c8 Update AIChat.tsx 2025-08-22 19:08:59 +02:00
Zeno Kapitein 8e4c94a4e5 First test 2025-08-22 19:00:09 +02:00
Zeno Kapitein 854c448bad Custom assistants followup (#3584) 2025-08-22 17:32:10 +02:00
conico974 64a0b0f169 Add logging for request duration and response details in 2c (#3583)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-08-22 17:15:00 +02:00
Zeno Kapitein 13ff22b6a8 Fix AI Search follow-up question closing search (#3585) 2025-08-22 14:51:21 +02:00
Samy Pessé 8927e8fbbb Embeddable Docs Assistant (#3572)
Co-authored-by: Zeno Kapitein <zeno@gitbook.io>
2025-08-22 09:44:51 +02:00
Samy Pessé 25e2b40d47 Update icons to latest version (#3582) 2025-08-21 15:31:13 +02:00
Zeno Kapitein a60af8ff9c Simplify tooltip implementation, add HoverCard (#3580) 2025-08-21 11:14:08 +02:00
Zeno Kapitein 27b328cfd7 Fix pageaside height on pages with hero cover (#3581)
Co-authored-by: Claire Chabas <claire.chabas@gmail.com>
2025-08-21 10:58:13 +02:00
conico974 fa6ef0c01c Remove motion animations from ScrollSectionList (#3573)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-08-20 19:14:01 +02:00
87 changed files with 2227 additions and 632 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/embed": patch
---
Initial version of the embed SDK.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix AI Search follow-up question closing search
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/icons": minor
---
Update icons set to the latest version
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Start routes for embeddable version of the assistant and docs pages.
+6
View File
@@ -0,0 +1,6 @@
---
"@gitbook/browser-types": patch
"gitbook": patch
---
Custom assistants followup
@@ -1,37 +0,0 @@
name: 'Setup Playwright'
description: 'Install Playwright and dependencies'
runs:
using: 'composite'
steps:
# Run npm ci and get Playwright version
- name: 🏗 Prepare Playwright env
shell: bash
run: |
PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output '.dependencies["gitbook"].dependencies["@playwright/test"].version')
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
# Cache browser binaries, cache key is based on Playwright version and OS
- name: 🧰 Cache Playwright browser binaries
uses: actions/cache@v4
id: playwright-cache
with:
path: '~/.cache/ms-playwright'
key: '${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}'
restore-keys: |
${{ runner.os }}-playwright-
# Install browser binaries & OS dependencies if cache missed
- name: 🏗 Install Playwright browser binaries & OS dependencies
if: steps.playwright-cache.outputs.cache-hit != 'true'
shell: bash
working-directory: packages/gitbook
run: |
bun x playwright install --with-deps chromium
# Install only the OS dependencies if cache hit
- name: 🏗 Install Playwright OS dependencies
if: steps.playwright-cache.outputs.cache-hit == 'true'
shell: bash
working-directory: packages/gitbook
run: |
bun x playwright install-deps
+8 -8
View File
@@ -110,8 +110,8 @@ jobs:
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run Playwright tests
run: bun e2e
env:
@@ -131,8 +131,8 @@ jobs:
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run Playwright tests
run: bun e2e
env:
@@ -152,8 +152,8 @@ jobs:
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run Playwright tests
run: bun e2e-customers
env:
@@ -173,8 +173,8 @@ jobs:
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run Playwright tests
run: bun e2e-customers
env:
+152 -63
View File
@@ -12,7 +12,7 @@
},
"packages/browser-types": {
"name": "@gitbook/browser-types",
"version": "0.3.1",
"version": "0.0.0",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
@@ -39,6 +39,22 @@
"typescript": "^5.5.3",
},
},
"packages/embed": {
"name": "@gitbook/embed",
"version": "0.0.0",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
"bidc": "catalog:",
},
"devDependencies": {
"react": "^19.0.0",
"typescript": "^5.5.3",
},
"peerDependencies": {
"react": "^18.0.0",
},
},
"packages/emoji-codepoints": {
"name": "@gitbook/emoji-codepoints",
"version": "0.2.0",
@@ -75,6 +91,7 @@
"@opennextjs/cloudflare": "^1.6.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.1.8",
@@ -84,6 +101,7 @@
"@tusbar/cache-control": "^1.0.2",
"ai": "^4.2.2",
"assert-never": "^1.2.1",
"bidc": "catalog:",
"bun-types": "^1.1.20",
"classnames": "^2.5.1",
"direction": "^2.0.1",
@@ -102,7 +120,7 @@
"memoizee": "^0.4.17",
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "^15.3.2",
"next": "^15.5.0",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -170,7 +188,7 @@
"typescript": "^5.5.3",
},
"optionalDependencies": {
"@gitbook/fontawesome-pro": "1.0.8",
"@gitbook/fontawesome-pro": "1.0.11",
},
"peerDependencies": {
"react": "*",
@@ -259,6 +277,7 @@
},
"catalog": {
"@gitbook/api": "^0.136.0",
"bidc": "^0.0.2",
},
"packages": {
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
@@ -537,7 +556,7 @@
"@edge-runtime/vm": ["@edge-runtime/vm@3.2.0", "", { "dependencies": { "@edge-runtime/primitives": "4.1.0" } }, "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="],
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
@@ -617,7 +636,7 @@
"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="],
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.6.0", "", {}, "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="],
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.7.2", "", {}, "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg=="],
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@6.6.0", "", {}, "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow=="],
@@ -631,9 +650,11 @@
"@gitbook/colors": ["@gitbook/colors@workspace:packages/colors"],
"@gitbook/embed": ["@gitbook/embed@workspace:packages/embed"],
"@gitbook/emoji-codepoints": ["@gitbook/emoji-codepoints@workspace:packages/emoji-codepoints"],
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.8", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.6.0" } }, "sha512-i4PgiuGyUb52Muhc52kK3aMJIMfMkA2RbPW30tre8a6M8T6mWTfYo6gafSgjNvF1vH29zcuB8oBYnF0gO4XcHA=="],
"@gitbook/fontawesome-pro": ["@gitbook/fontawesome-pro@1.0.11", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "^6.7.2" } }, "sha512-P+GZ/X7b2Uj4uxxFX6xez+QceK211SXgMBj54lXzgo3Py34QhOuYrh/2F3ChB1iK5SXDwjn2xAuQXI7HuM66Mg=="],
"@gitbook/fonts": ["@gitbook/fonts@workspace:packages/fonts"],
@@ -661,45 +682,49 @@
"@hyperjump/uri": ["@hyperjump/uri@1.2.2", "", {}, "sha512-Zn8AZb/j54KKUCckmcOzKCSCKpIpMVBc60zYaajD8Dq/1g4UN6TfAFi+uDa5o/6rf+I+5xDZjZpdzwfuhlC0xQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.0" }, "os": "darwin", "cpu": "x64" }, "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.1.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.1.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.1.0" }, "os": "linux", "cpu": "arm" }, "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.0" }, "os": "linux", "cpu": "arm" }, "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.0" }, "os": "linux", "cpu": "arm64" }, "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.1.0" }, "os": "linux", "cpu": "s390x" }, "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.0" }, "os": "linux", "cpu": "ppc64" }, "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.0" }, "os": "linux", "cpu": "s390x" }, "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" }, "os": "linux", "cpu": "arm64" }, "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.0" }, "os": "linux", "cpu": "x64" }, "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.1", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.1.0" }, "os": "linux", "cpu": "x64" }, "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" }, "os": "linux", "cpu": "arm64" }, "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.1", "", { "dependencies": { "@emnapi/runtime": "^1.4.0" }, "cpu": "none" }, "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.0" }, "os": "linux", "cpu": "x64" }, "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.3", "", { "dependencies": { "@emnapi/runtime": "^1.4.4" }, "cpu": "none" }, "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.1", "", { "os": "win32", "cpu": "x64" }, "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.3", "", { "os": "win32", "cpu": "x64" }, "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g=="],
"@internationalized/date": ["@internationalized/date@3.7.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ=="],
@@ -751,23 +776,23 @@
"@mapbox/node-pre-gyp": ["@mapbox/node-pre-gyp@2.0.0-rc.0", "", { "dependencies": { "consola": "^3.2.3", "detect-libc": "^2.0.0", "https-proxy-agent": "^7.0.5", "node-fetch": "^2.6.7", "nopt": "^8.0.0", "semver": "^7.5.3", "tar": "^7.4.0" }, "bin": { "node-pre-gyp": "bin/node-pre-gyp" } }, "sha512-nhSMNprz3WmeRvd8iUs5JqkKr0Ncx46JtPxM3AhXes84XpSJfmIwKeWXRpsr53S7kqPkQfPhzrMFUxSNb23qSA=="],
"@next/env": ["@next/env@15.3.2", "", {}, "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="],
"@next/env": ["@next/env@15.5.0", "", {}, "sha512-sDaprBAfzCQiOgo2pO+LhnV0Wt2wBgartjrr+dpcTORYVnnXD0gwhHhiiyIih9hQbq+JnbqH4odgcFWhqCGidw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v7Jj9iqC6enxIRBIScD/o0lH7QKvSxq2LM8UTyqJi+S2w2QzhMYjven4vgu/RzgsdtdbpkyCxBTzHl/gN5rTRg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ=="],
"@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="],
@@ -819,7 +844,7 @@
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.0", "", {}, "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.0", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw=="],
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="],
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw=="],
@@ -831,7 +856,7 @@
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="],
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
"@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.12", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VJoMs+BWWE7YhzEQyVwvF9n22Eiyr83HotCVrMQzla/OwRovXCgah7AcaEr4hMNj4gJxSdtIbcHGvmJXOoJVHA=="],
@@ -839,6 +864,8 @@
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA=="],
"@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="],
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.4", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.7", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.4", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.4", "@radix-ui/react-portal": "1.1.6", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.0", "@radix-ui/react-roving-focus": "1.1.7", "@radix-ui/react-slot": "1.2.0", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+qYq6LfbiGo97Zz9fioX83HCiIYYFNs8zAsVCMQrIakoNYylIzWuoD/anAD3UzvvR6cnswmfRFJFq/zYYq/k7Q=="],
@@ -847,9 +874,9 @@
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.1", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.0", "@radix-ui/react-portal": "1.1.2", "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-slot": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", "react-remove-scroll": "2.6.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.0", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-context": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg=="],
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg=="],
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A=="],
@@ -867,19 +894,19 @@
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="],
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og=="],
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="],
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw=="],
"@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg=="],
"@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@react-aria/autocomplete": ["@react-aria/autocomplete@3.0.0-alpha.37", "", { "dependencies": { "@react-aria/combobox": "^3.11.1", "@react-aria/i18n": "^3.12.5", "@react-aria/interactions": "^3.23.0", "@react-aria/listbox": "^3.14.0", "@react-aria/searchfield": "^3.8.0", "@react-aria/textfield": "^3.16.0", "@react-aria/utils": "^3.27.0", "@react-stately/autocomplete": "3.0.0-alpha.0", "@react-stately/combobox": "^3.10.2", "@react-types/autocomplete": "3.0.0-alpha.28", "@react-types/button": "^3.10.2", "@react-types/shared": "^3.27.0", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-a7awFG3hshJ/kX7Qti/cJAKOG0XU5F/XW6fQffKGfEge7PmiWIvaLTrT5her79/v8v/bRBykIkpEgDCFE7WGzg=="],
@@ -1255,8 +1282,6 @@
"@smithy/util-waiter": ["@smithy/util-waiter@2.2.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
"@szmarczak/http-timer": ["@szmarczak/http-timer@1.1.2", "", { "dependencies": { "defer-to-connect": "^1.0.1" } }, "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA=="],
@@ -1517,6 +1542,8 @@
"better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="],
"bidc": ["bidc@0.0.2", "", {}, "sha512-+t2xK8oQEpxYEM+350mMHSx7eZNoOX6SpjqD+p+lDpVwpYVXjkTCfy0N60xyjelZ2AOa8NAg/12uhAX3MuAR0g=="],
"bignumber.js": ["bignumber.js@9.1.2", "", {}, "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
@@ -1547,8 +1574,6 @@
"bun-types": ["bun-types@1.1.30", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-mGh7NLisOXskBU62DxLS+/nwmLlCYHYAkCzdo4DZ9+fzrpP41hAdOqaN4DO6tQfenHb4pYb0/shw29k4/6I2yQ=="],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
"bytes": ["bytes@3.1.0", "", {}, "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
@@ -2405,7 +2430,7 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next": ["next@15.3.2", "", { "dependencies": { "@next/env": "15.3.2", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.2", "@next/swc-darwin-x64": "15.3.2", "@next/swc-linux-arm64-gnu": "15.3.2", "@next/swc-linux-arm64-musl": "15.3.2", "@next/swc-linux-x64-gnu": "15.3.2", "@next/swc-linux-x64-musl": "15.3.2", "@next/swc-win32-arm64-msvc": "15.3.2", "@next/swc-win32-x64-msvc": "15.3.2", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ=="],
"next": ["next@15.5.0", "", { "dependencies": { "@next/env": "15.5.0", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.0", "@next/swc-darwin-x64": "15.5.0", "@next/swc-linux-arm64-gnu": "15.5.0", "@next/swc-linux-arm64-musl": "15.5.0", "@next/swc-linux-x64-gnu": "15.5.0", "@next/swc-linux-x64-musl": "15.5.0", "@next/swc-win32-arm64-msvc": "15.5.0", "@next/swc-win32-x64-msvc": "15.5.0", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-N1lp9Hatw3a9XLt0307lGB4uTKsXDhyOKQo7uYMzX4i0nF/c27grcGXkLdb7VcT8QPYLBa8ouIyEoUQJ2OyeNQ=="],
"next-themes": ["next-themes@0.2.1", "", { "peerDependencies": { "next": "*", "react": "*", "react-dom": "*" } }, "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A=="],
@@ -2695,7 +2720,7 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"sharp": ["sharp@0.34.1", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.7.1" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.1", "@img/sharp-darwin-x64": "0.34.1", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.1", "@img/sharp-linux-arm64": "0.34.1", "@img/sharp-linux-s390x": "0.34.1", "@img/sharp-linux-x64": "0.34.1", "@img/sharp-linuxmusl-arm64": "0.34.1", "@img/sharp-linuxmusl-x64": "0.34.1", "@img/sharp-wasm32": "0.34.1", "@img/sharp-win32-ia32": "0.34.1", "@img/sharp-win32-x64": "0.34.1" } }, "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg=="],
"sharp": ["sharp@0.34.3", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.3", "@img/sharp-darwin-x64": "0.34.3", "@img/sharp-libvips-darwin-arm64": "1.2.0", "@img/sharp-libvips-darwin-x64": "1.2.0", "@img/sharp-libvips-linux-arm": "1.2.0", "@img/sharp-libvips-linux-arm64": "1.2.0", "@img/sharp-libvips-linux-ppc64": "1.2.0", "@img/sharp-libvips-linux-s390x": "1.2.0", "@img/sharp-libvips-linux-x64": "1.2.0", "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", "@img/sharp-libvips-linuxmusl-x64": "1.2.0", "@img/sharp-linux-arm": "0.34.3", "@img/sharp-linux-arm64": "0.34.3", "@img/sharp-linux-ppc64": "0.34.3", "@img/sharp-linux-s390x": "0.34.3", "@img/sharp-linux-x64": "0.34.3", "@img/sharp-linuxmusl-arm64": "0.34.3", "@img/sharp-linuxmusl-x64": "0.34.3", "@img/sharp-wasm32": "0.34.3", "@img/sharp-win32-arm64": "0.34.3", "@img/sharp-win32-ia32": "0.34.3", "@img/sharp-win32-x64": "0.34.3" } }, "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -2749,8 +2774,6 @@
"stream-to-promise": ["stream-to-promise@2.2.0", "", { "dependencies": { "any-promise": "~1.3.0", "end-of-stream": "~1.1.0", "stream-to-array": "~2.3.0" } }, "sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw=="],
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
"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=="],
"string-width-cjs": ["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=="],
@@ -3691,6 +3714,8 @@
"@formatjs/intl-localematcher/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@fortawesome/fontawesome-svg-core/@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@6.6.0", "", {}, "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="],
"@headlessui/tailwindcss/tailwindcss": ["tailwindcss@3.4.13", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw=="],
"@hyperjump/json-schema/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
@@ -3739,17 +3764,21 @@
"@playwright/test/playwright": ["playwright@1.54.2", "", { "dependencies": { "playwright-core": "1.54.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-collection/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
"@radix-ui/react-dismissable-layer/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-dropdown-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
@@ -3761,6 +3790,18 @@
"@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
"@radix-ui/react-hover-card/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
"@radix-ui/react-hover-card/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-hover-card/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-hover-card/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
"@radix-ui/react-hover-card/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-hover-card/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
"@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
@@ -3797,6 +3838,8 @@
"@radix-ui/react-navigation-menu/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="],
@@ -3807,7 +3850,25 @@
"@radix-ui/react-popover/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
"@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A=="],
"@radix-ui/react-popover/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.0", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", "@radix-ui/react-context": "1.1.0", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg=="],
"@radix-ui/react-popover/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg=="],
"@radix-ui/react-popper/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
"@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-popper/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-popper/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-popper/@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
"@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
"@radix-ui/react-portal/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-roving-focus/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="],
@@ -3847,6 +3908,8 @@
"@radix-ui/react-use-effect-event/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-use-escape-keydown/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
"@react-aria/focus/clsx": ["clsx@2.0.0", "", {}, "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="],
@@ -4353,9 +4416,7 @@
"send/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
@@ -4911,24 +4972,26 @@
"@playwright/test/playwright/playwright-core": ["playwright-core@1.54.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-dropdown-menu/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
"@radix-ui/react-hover-card/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-hover-card/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-hover-card/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
"@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@radix-ui/react-menu/@radix-ui/react-portal/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-menu/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
@@ -4943,16 +5006,38 @@
"@radix-ui/react-menu/react-remove-scroll/use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "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-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
"@radix-ui/react-navigation-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@radix-ui/react-popover/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
"@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.0", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw=="],
"@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A=="],
"@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="],
"@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
"@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@radix-ui/react-roving-focus/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="],
"@radix-ui/react-roving-focus/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
"@radix-ui/react-tooltip/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
"@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
"@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="],
"@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
"@scalar/api-client/pretty-ms/parse-ms": ["parse-ms@3.0.0", "", {}, "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw=="],
@@ -5307,6 +5392,10 @@
"@node-minify/core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
"@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="],
+2 -1
View File
@@ -34,7 +34,8 @@
"workspaces": {
"packages": ["packages/*"],
"catalog": {
"@gitbook/api": "^0.136.0"
"@gitbook/api": "^0.136.0",
"bidc": "^0.0.2"
}
},
"patchedDependencies": {
+2
View File
@@ -0,0 +1,2 @@
dist/
standalone/
+49
View File
@@ -0,0 +1,49 @@
# `@gitbook/embed`
Embed the GitBook Docs Assistant in your product or website.
# Usage
## As a script from your docs site
All GitBook docs site includes a script to easily embed the docs assistant as a widget on your website.
The script is served at `https://docs.company.com/~gitbook/embed/script.js`.
You can find the embed script from your docs site settings, or you can copy the following and replace the `docs.company.com` by your docs site hostname.
```html
<script src="https://docs.company.com/~gitbook/embed/script.js"></script>
<script>
window.GitBook('show');
</script>
```
## As a package from NPM
Install the package: `npm install @gitbook/embed` and import it in your web application:
```tsx
import { createGitBook } from '@gitbook/embed';
const gitbook = createGitBook({
siteURL: 'https://docs.company.com'
});
const iframe = document.createElement('iframe');
iframe.src = gitbook.getFrameURL();
const frame = gitbook.createFrame(iframe);
```
## As React components
After installing the NPM package, you can import prebuilt React components:
```tsx
import { GitBookProvider, GitBookAssistantFrame } from '@gitbook/embed/react';
<GitBookProvider siteURL="https://docs.company.com">
<GitBookAssistantFrame />
</GitBookProvider>
```
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@gitbook/embed",
"description": "Embeddable components for GitBook",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js",
"standalone": "./dist/standalone/index.js",
"react": "./dist/react/index.js"
}
},
"version": "0.0.0",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
"bidc": "catalog:"
},
"peerDependencies": {
"react": "^18.0.0"
},
"devDependencies": {
"typescript": "^5.5.3",
"react": "^19.0.0"
},
"scripts": {
"build": "tsc && bun build src/standalone/index.ts --bundle --minify --outdir=standalone",
"typecheck": "tsc --noEmit"
},
"files": ["dist", "README.md", "CHANGELOG.md", "standalone"]
}
@@ -0,0 +1,61 @@
import { type GitBookFrameClient, createGitBookFrame } from './createGitBookFrame';
export type CreateGitBookOptions = {
/**
* URL of the GitBook site to embed.
*/
siteURL: string;
};
export type GetFrameURLOptions = {
/**
* Authentication to use for the frame.
*/
visitor?: {
/**
* Signed JWT token for Adaptive Content or Visitor Authentication to use.
*/
token?: string;
/**
* Unsigned claims to pass to the frame.
* You can use these claims in dynamic expressions using `visitor.claims.unsigned.<claim-name>`.
*/
unsignedClaims?: Record<string, unknown>;
};
};
export type GitBookClient = {
/**
* Get the URL for a GitBook frame.
*/
getFrameURL: (options: GetFrameURLOptions) => string;
/**
* Create a new GitBook frame.
*/
createFrame: (iframe: HTMLIFrameElement) => GitBookFrameClient;
};
export function createGitBook(options: CreateGitBookOptions) {
const client: GitBookClient = {
getFrameURL: (frameOptions) => {
const url = new URL(options.siteURL);
url.pathname = `${url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/`}~gitbook/embed/assistant`;
if (frameOptions.visitor?.token) {
url.searchParams.set('token', frameOptions.visitor.token);
}
if (frameOptions.visitor?.unsignedClaims) {
Object.entries(frameOptions.visitor.unsignedClaims).forEach(([key, value]) => {
url.searchParams.set(`visitor.${key}`, String(value));
});
}
return url.toString();
},
createFrame: (iframe) => createGitBookFrame(iframe),
};
return client;
}
@@ -0,0 +1,93 @@
import { createChannel } from 'bidc';
import type {
FrameToParentMessage,
GitBookPlaceholderSettings,
GitBookToolDefinition,
ParentToFrameMessage,
} from './protocol';
export type GitBookFrameClient = {
/**
* Navigate to a page by its path.
*/
navigateToPage: (path: string) => void;
/**
* Navigate to the assistant.
*/
navigateToAssistant: () => void;
/**
* Post a message to the chat.
*/
postUserMessage: (message: string) => void;
/**
* Register a custom tool.
*/
registerTool: (tool: GitBookToolDefinition) => void;
/**
* Clear the chat.
*/
clearChat: () => void;
/**
* Set the placeholder settings.
*/
setPlaceholder: (placeholder: GitBookPlaceholderSettings) => void;
/**
* Register an event listener.
*/
on: (event: string, listener: (...args: any[]) => void) => () => void;
};
/**
* Create a client to communicate with the GitBook Assistant frame.
*/
export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClient {
if (!iframe.contentWindow) {
throw new Error('Iframe must have a content window');
}
const channel = createChannel(iframe.contentWindow);
channel.receive((message: FrameToParentMessage) => {
if (message.type === 'close') {
const listeners = events.get('close') || [];
if (listeners) {
listeners.forEach((listener) => listener());
}
}
});
const sendToFrame = (message: ParentToFrameMessage) => {
channel.send(message);
};
const events = new Map<string, Array<(...args: any[]) => void>>();
return {
navigateToPage: (pagePath) => {
sendToFrame({ type: 'navigateToPage', pagePath });
},
navigateToAssistant: () => {
sendToFrame({ type: 'navigateToAssistant' });
},
postUserMessage: (message) => sendToFrame({ type: 'postUserMessage', message }),
registerTool: (tool) => sendToFrame({ type: 'registerTool', tool }),
clearChat: () => sendToFrame({ type: 'clearChat' }),
setPlaceholder: (settings) => sendToFrame({ type: 'setPlaceholder', settings }),
on: (event, listener) => {
const listeners = events.get(event) || [];
listeners.push(listener);
events.set(event, listeners);
return () => {
events.set(
event,
listeners.filter((l) => l !== listener)
);
};
},
};
}
+3
View File
@@ -0,0 +1,3 @@
export * from './createGitBook';
export * from './createGitBookFrame';
export * from './protocol';
+70
View File
@@ -0,0 +1,70 @@
import type { AIToolCallResult, AIToolDefinition } from '@gitbook/api';
import type { IconName } from '@gitbook/icons';
/**
* Custom tool definition to be passed to the AI assistant.
*/
export type GitBookToolDefinition = AIToolDefinition & {
/**
* Confirmation action to be displayed to the user before executing the tool.
*/
confirmation?: {
icon?: IconName;
label: string;
};
/**
* Callback when the tool is executed.
* The input is provided by the AI assistant following the input schema of the tool.
*/
execute: (input: object) => Promise<Pick<AIToolCallResult, 'output' | 'summary'>>;
};
/**
* Placeholder settings.
*/
export type GitBookPlaceholderSettings = {
/**
* Welcome message to be displayed in the placeholder.
*/
welcomeMessage: string;
/**
* Suggestions to be displayed in the placeholder.
*/
suggestions: string[];
};
/**
* Messages sent from the parent to the frame.
*/
export type ParentToFrameMessage =
| {
type: 'postUserMessage';
message: string;
}
| {
type: 'registerTool';
tool: GitBookToolDefinition;
}
| {
type: 'clearChat';
}
| {
type: 'setPlaceholder';
settings: GitBookPlaceholderSettings;
}
| {
type: 'navigateToPage';
pagePath: string;
}
| {
type: 'navigateToAssistant';
};
/**
* Messages sent from the frame to the parent.
*/
export type FrameToParentMessage = {
type: 'close';
};
+1
View File
@@ -0,0 +1 @@
export * from './client';
@@ -0,0 +1,38 @@
import React from 'react';
import type { GetFrameURLOptions, GitBookFrameClient } from '../client';
import { useGitBook } from './GitBookProvider';
export type GitBookAssistantFrameProps = {
title?: string;
className?: string;
} & GetFrameURLOptions;
/**
* Render a frame with the GitBook Assistant in it.
*/
export function GitBookAssistantFrame(props: GitBookAssistantFrameProps) {
const { title, className, ...frameOptions } = props;
const frameRef = React.useRef<HTMLIFrameElement>(null);
const gitbookFrameRef = React.useRef<GitBookFrameClient | null>(null);
const gitbook = useGitBook();
const frameURL = gitbook.getFrameURL(frameOptions);
React.useEffect(() => {
if (frameRef.current) {
gitbookFrameRef.current = gitbook.createFrame(frameRef.current);
}
}, [gitbook]);
return (
<div className={className}>
<iframe
title={title ?? 'GitBook Assistant'}
ref={frameRef}
src={frameURL}
width="100%"
height="100%"
/>
</div>
);
}
@@ -0,0 +1,36 @@
'use client';
import * as React from 'react';
import { type CreateGitBookOptions, createGitBook } from '../client';
import { GitBookContext } from './context';
/**
* Provider for the GitBook client.
*/
export function GitBookProvider(props: React.PropsWithChildren<CreateGitBookOptions>) {
const { siteURL, children } = props;
const options = React.useMemo(
() => ({
siteURL,
}),
[siteURL]
);
const client = React.useMemo(() => createGitBook(options), [options]);
return <GitBookContext.Provider value={client}>{children}</GitBookContext.Provider>;
}
/**
* Hook to access the GitBook client.
*/
export function useGitBook() {
const context = React.useContext(GitBookContext);
if (!context) {
throw new Error('This component must be used within a <GitBookProvider />');
}
return context;
}
+6
View File
@@ -0,0 +1,6 @@
'use client';
import * as React from 'react';
import type { GitBookClient } from '../client';
export const GitBookContext = React.createContext<GitBookClient | null>(null);
+1
View File
@@ -0,0 +1 @@
export * from './GitBookProvider';
+158
View File
@@ -0,0 +1,158 @@
import './style.css';
import {
type CreateGitBookOptions,
type GetFrameURLOptions,
type GitBookClient,
type GitBookFrameClient,
type GitBookPlaceholderSettings,
type GitBookToolDefinition,
createGitBook,
} from '../client';
export type GitBook = () => void;
type StandaloneCalls =
// Initialize the widget
| ['init', CreateGitBookOptions, GetFrameURLOptions]
// Unload the widget
| ['unload']
// Show the widget
| ['show']
// Hide the widget
| ['hide']
// Open the window
| ['open']
// Close the window
| ['close']
// Toggle the window
| ['toggle']
// Post a user message
| ['postUserMessage', string]
// Register a tool
| ['registerTool', GitBookToolDefinition]
// Clear the chat
| ['clearChat']
// Configure the placeholder
| ['setPlaceholder', GitBookPlaceholderSettings]
// Navigate to a page
| ['navigateToPage', string]
// Navigate to the assistant
| ['navigateToAssistant'];
export type GitBookStandalone = ((...args: StandaloneCalls) => void) & {
q?: StandaloneCalls[];
};
const widgetButton = document.createElement('button');
widgetButton.id = 'gitbook-widget-button';
widgetButton.addEventListener('click', () => {
GitBook('toggle');
});
widgetButton.innerHTML = `
<span id="gitbook-widget-button-icon"></span>
<span id="gitbook-widget-button-label">Ask</span>
`;
const widgetWindow = document.createElement('div');
widgetWindow.id = 'gitbook-widget-window';
widgetWindow.classList.add('hidden');
document.body.appendChild(widgetButton);
document.body.appendChild(widgetWindow);
let widgetIframe: HTMLIFrameElement | undefined;
let _client: GitBookClient | undefined;
let _frame: GitBookFrameClient | undefined;
let frameOptions: GetFrameURLOptions | undefined;
function getClient() {
if (!_client) {
throw new Error(
'GitBook client not initialized. Call GitBook("init", { siteURL: "..." }) first.'
);
}
return _client;
}
function getIframe() {
if (!widgetIframe || !_frame) {
const client = getClient();
widgetIframe?.remove();
widgetIframe = document.createElement('iframe');
widgetIframe.id = 'gitbook-widget-iframe';
widgetIframe.src = client.getFrameURL({
...frameOptions,
});
widgetWindow.appendChild(widgetIframe);
_frame = client.createFrame(widgetIframe);
}
return { iframe: widgetIframe, frame: _frame };
}
const GitBook = (...args: StandaloneCalls) => {
switch (args[0]) {
case 'init':
if (_client) {
throw new Error(
'GitBook client already initialized. Call GitBook("unload") first.'
);
}
_client = createGitBook(args[1]);
frameOptions = args[2];
break;
case 'unload':
_client = undefined;
_frame = undefined;
widgetIframe?.remove();
widgetWindow.classList.add('hidden');
break;
case 'show':
widgetButton.classList.remove('hidden');
break;
case 'hide':
widgetButton.classList.add('hidden');
break;
case 'open':
widgetWindow.classList.remove('hidden');
widgetButton.classList.add('open');
getIframe();
break;
case 'toggle':
widgetWindow.classList.toggle('hidden');
widgetButton.classList.toggle('open');
getIframe();
break;
case 'close':
widgetWindow.classList.add('hidden');
widgetButton.classList.remove('open');
break;
case 'postUserMessage':
getIframe().frame.postUserMessage(args[1]);
break;
case 'registerTool':
getIframe().frame.registerTool(args[1]);
break;
case 'clearChat':
getIframe().frame.clearChat();
break;
case 'setPlaceholder':
getIframe().frame.setPlaceholder(args[1]);
break;
case 'navigateToPage':
getIframe().frame.navigateToPage(args[1]);
break;
case 'navigateToAssistant':
getIframe().frame.navigateToAssistant();
break;
}
};
// @ts-expect-error - GitBook is not defined in the global scope
const precalls = (window.GitBook as GitBookStandalone | undefined)?.q ?? [];
// @ts-expect-error - GitBook is not defined in the global scope
window.GitBook = GitBook;
precalls.forEach((call) => GitBook(...call));
+170
View File
@@ -0,0 +1,170 @@
:root {
--gitbook-widget-top: 1rem;
--gitbook-widget-bottom: 1rem;
--gitbook-widget-right: 1rem;
--gitbook-widget-button-height: 46px;
--gitbook-widget-radius: .5rem;
--gitbook-widget-text-size: 1rem;
--gitbook-widget-text-color: #656973;
--gitbook-widget-border-color: #e5e5e5;
--gitbook-widget-background-translucent: rgba(255, 255, 255, 0.9);
--gitbook-widget-background-translucent-hover: rgba(250, 250, 250, 0.9);
--gitbook-widget-background-solid: #FFFFFF;
--gitbook-widget-background-solid-hover: #FBFBFB;
--gitbook-widget-icon-size: 1.25rem;
--gitbook-widget-window-width: 28rem; /* 448px */
--gitbook-widget-window-height: 40rem; /* 640px */
--gitbook-widget-window-spacing: .5rem; /* Spacing between the button and the window */
--gitbook-widget-window-bottom: calc(var(--gitbook-widget-bottom) + var(--gitbook-widget-button-height) + var(--gitbook-widget-window-spacing));
--gitbook-widget-transition-duration-fast: 0.2s;
--gitbook-widget-transition-duration-slow: 0.5s;
--gitbook-widget-easing: cubic-bezier(0.25, 1, 0.5, 1);
--gitbook-widget-easing-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
}
* {
box-sizing: border-box;
}
/* Button */
#gitbook-widget-button {
position: fixed;
bottom: var(--gitbook-widget-bottom);
right: var(--gitbook-widget-right);
height: var(--gitbook-widget-button-height);
z-index: 9999;
display: flex;
align-items: center;
flex-direction: row-reverse;
gap: .5rem;
padding: .75rem 1rem;
border-radius: 100px;
border: 1px solid var(--gitbook-widget-border-color);
background-color: var(--gitbook-widget-background-translucent);
backdrop-filter: blur(16px);
font-size: var(--gitbook-widget-text-size);
color: var(--gitbook-widget-text-color);
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.05), 0 1px 2px -1px rgba(0,0,0,0.05);
transition: all var(--gitbook-widget-transition-duration-fast) var(--gitbook-widget-easing);
cursor:pointer;
animation: gitbook-widget-present var(--gitbook-widget-transition-duration-slow) var(--gitbook-widget-easing-bounce);
}
#gitbook-widget-button:hover, #gitbook-widget-button:focus-visible {
background-color: var(--gitbook-widget-background-translucent-hover);
}
#gitbook-widget-button:hover, #gitbook-widget-button:focus-visible {
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);
}
#gitbook-widget-button:active {
transform: translateY(0);
box-shadow: 0 1px 3px 0 rgba(0,0,0,0.05), 0 1px 2px -1px rgba(0,0,0,0.05);
}
@media (prefers-contrast: more) {
#gitbook-widget-button {
background-color: var(--gitbook-widget-background-solid);
}
#gitbook-widget-button:hover, #gitbook-widget-button:focus-visible {
background-color: var(--gitbook-widget-background-solid-hover);
}
}
#gitbook-widget-button.hidden {
display: none;
}
#gitbook-widget-button.open {
padding: .75rem;
gap: 0;
}
/* Button: Icon */
#gitbook-widget-button-icon {
width: var(--gitbook-widget-icon-size);
height: var(--gitbook-widget-icon-size);
mask-image: url("https://static-2v.gitbook.com/~gitbook/static/icons/svgs/custom-icons/gitbook-assistant.svg?v=2");
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
background-color: currentColor;
}
#gitbook-widget-button.open #gitbook-widget-button-icon {
mask-image: url('https://ka-p.fontawesome.com/releases/v6.6.0/svgs/regular/close.svg?v=2&token=a463935e93');
}
/* Button: Label */
#gitbook-widget-button.open #gitbook-widget-button-label {
display: none;
}
/* Window */
#gitbook-widget-window {
position: fixed;
bottom: var(--gitbook-widget-window-bottom);
right: var(--gitbook-widget-right);
z-index: 9998;
width: var(--gitbook-widget-window-width);
height: calc(min(var(--gitbook-widget-window-height), calc(100vh - var(--gitbook-widget-window-bottom) - var(--gitbook-widget-top))));
background-color: var(--gitbook-widget-background-solid);
border: 1px solid var(--gitbook-widget-border-color);
border-radius: var(--gitbook-widget-radius);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
overflow: hidden;
transition-property: transform, opacity, display;
transition-duration: var(--gitbook-widget-transition-duration-slow);
transition-timing-function: var(--gitbook-widget-easing-bounce);
transform-origin: bottom right;
transition-behavior: allow-discrete;
}
@starting-style {
#gitbook-widget-window {
opacity: 0;
transform: scale(0.9);
}
}
body:has(#gitbook-widget-button.hidden) #gitbook-widget-window {
bottom: var(--gitbook-widget-bottom);
}
#gitbook-widget-window.hidden {
transition-property: transform, opacity, display;
transition-duration: var(--gitbook-widget-transition-duration-fast);
transition-timing-function: var(--gitbook-widget-easing);
transition-behavior: allow-discrete;
display: none;
opacity: 0;
transform: scale(0.9);
}
#gitbook-widget-iframe {
width: 100%;
height: 100%;
border: none;
}
@keyframes gitbook-widget-present {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"types": []
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
+2 -2
View File
@@ -54,9 +54,9 @@ const searchTestCases: Test[] = [
await searchInput.fill('gitbook');
await expect(page.getByTestId('search-results')).toBeVisible();
const pageResults = await page.getByTestId('search-page-result').all();
await expect(pageResults.length).toBeGreaterThan(2);
await expect(pageResults.length).toBeGreaterThanOrEqual(1);
const pageSectionResults = await page.getByTestId('search-page-section-result').all();
await expect(pageSectionResults.length).toBeGreaterThan(2);
await expect(pageSectionResults.length).toBeGreaterThanOrEqual(2);
await expect(page.getByTestId('search-ask-question')).toHaveCount(0); // No AI search results with aiMode=None.
},
},
@@ -7,12 +7,37 @@ export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
//Format used by the tail logger
const formatLog = (requestDuration, responseCode, responseCache, responseRoute) =>
`%${JSON.stringify({ requestDuration, responseCode, responseCache, responseRoute })}%[main] Response Done`;
const getResolvedRoute = (reqOrResp, defaultValue) => {
try {
const resolvedRoutes = JSON.parse(
reqOrResp.headers.get('x-opennext-resolved-routes') ?? ''
)[0].route;
return resolvedRoutes ?? defaultValue;
} catch {
return defaultValue;
}
};
export default class extends WorkerEntrypoint {
async fetch(request) {
return runWithCloudflareRequestContext(request, this.env, this.ctx, async () => {
const startTime = Date.now();
// - `Request`s are handled by the Next server
const reqOrResp = await middlewareHandler(request, this.env, this.ctx);
if (reqOrResp instanceof Response) {
const duration = Date.now() - startTime;
const logMessage = formatLog(
duration,
reqOrResp.status,
reqOrResp.headers.get('x-opennext-cache') ?? 'MISS',
getResolvedRoute(reqOrResp, 'middleware')
);
// biome-ignore lint/suspicious/noConsole: <explanation>
console.log(logMessage);
return reqOrResp;
}
@@ -22,12 +47,22 @@ export default class extends WorkerEntrypoint {
'Cloudflare-Workers-Version-Overrides',
`gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"`
);
return this.env.DEFAULT_WORKER?.fetch(reqOrResp, {
const response = await this.env.DEFAULT_WORKER?.fetch(reqOrResp, {
redirect: 'manual',
cf: {
cacheEverything: false,
},
});
const formatedLog = formatLog(
Date.now() - startTime,
response.status,
`SERVER-${response.headers.get('x-nextjs-cache') ?? 'MISS'}`,
getResolvedRoute(reqOrResp, 'unresolved')
);
// biome-ignore lint/suspicious/noConsole: <explanation>
console.log(formatedLog);
return response;
}
// If we are in preview mode, we need to send the request to the preview URL
const modifiedUrl = new URL(reqOrResp.url);
+7 -6
View File
@@ -17,6 +17,7 @@
"@opennextjs/cloudflare": "^1.6.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-tooltip": "^1.1.8",
@@ -44,7 +45,7 @@
"memoizee": "^0.4.17",
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "^15.3.2",
"next": "^15.5.0",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -69,7 +70,8 @@
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
"warn-once": "^0.1.1",
"zustand": "^5.0.3"
"zustand": "^5.0.3",
"bidc": "catalog:"
},
"devDependencies": {
"@argos-ci/playwright": "^5.0.9",
@@ -98,8 +100,7 @@
"vercel": "^39.3.0"
},
"scripts": {
"generate": "gitbook-icons ./public/~gitbook/static/icons custom-icons && gitbook-math ./public/~gitbook/static/math && wrangler types",
"copy:icons": "gitbook-icons ./public/~gitbook/static/icons",
"generate": "./scripts/generate.sh",
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math",
"dev": "env-cmd --silent -f ../../.env.local next",
"build": "next build",
@@ -108,8 +109,8 @@
"dev:cloudflare": "wrangler dev --port 8771 --env preview",
"dev:cf:middleware": "wrangler dev --port 8771 --inspector-port 9230 --env dev --config ./openNext/customWorkers/middlewareWrangler.jsonc",
"dev:cf:server": "wrangler dev --port 8772 --env dev --config ./openNext/customWorkers/defaultWrangler.jsonc",
"e2e": "playwright test e2e/internal.spec.ts e2e/pdf.spec.ts",
"e2e-customers": "playwright test e2e/customers.spec.ts",
"e2e": "playwright test e2e/internal.spec.ts e2e/pdf.spec.ts --project=chromium",
"e2e-customers": "playwright test e2e/customers.spec.ts --project=chromium",
"unit": "bun test {src,packages} --preload ./tests/preload-bun.ts",
"typecheck": "tsc --noEmit"
},
+7 -1
View File
@@ -10,9 +10,15 @@ export default defineConfig({
['@argos-ci/playwright/reporter', { uploadToArgos: !!process.env.CI }],
],
projects: [
// To speed up CI, we only test on Chrome.
// https://github.com/microsoft/playwright/issues/14434
// https://playwright.dev/docs/browsers#google-chrome--microsoft-edge
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
use: {
...devices['Desktop Chrome'],
channel: 'chrome',
},
},
],
use: {
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -o errexit
set -o pipefail
rm -rf ./.next
rm -rf ./public/~gitbook/static/icons
rm -rf ./public/~gitbook/static/math
rm -rf ./public/~gitbook/static/embed
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -o errexit
set -o pipefail
# Copy the assets
gitbook-icons ./public/~gitbook/static/icons custom-icons
gitbook-math ./public/~gitbook/static/math
cp -r ../embed/standalone/ ./public/~gitbook/static/embed
# Generate the types
wrangler types
@@ -0,0 +1,16 @@
import type { RouteLayoutParams } from '@/app/utils';
import { EmbeddableAssistantPage } from '@/components/Embeddable';
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
export const dynamic = 'force-static';
type PageProps = {
params: Promise<RouteLayoutParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableDynamicContext(params);
return <EmbeddableAssistantPage context={context} />;
}
@@ -0,0 +1,30 @@
import type { RouteLayoutParams } from '@/app/utils';
import {
EmbeddableRootLayout,
generateEmbeddableMetadata,
generateEmbeddableViewport,
} from '@/components/Embeddable';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
interface SiteStaticLayoutProps {
params: Promise<RouteLayoutParams>;
}
export default async function RootLayout({
params,
children,
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
const { context } = await getEmbeddableStaticContext(await params);
return <EmbeddableRootLayout context={context}>{children}</EmbeddableRootLayout>;
}
export async function generateViewport({ params }: SiteStaticLayoutProps) {
const { context } = await getEmbeddableStaticContext(await params);
return generateEmbeddableViewport({ context });
}
export async function generateMetadata({ params }: SiteStaticLayoutProps) {
const { context } = await getEmbeddableStaticContext(await params);
return generateEmbeddableMetadata({ context });
}
@@ -0,0 +1,23 @@
import { type RouteParams, getPagePathFromParams } from '@/app/utils';
import { EmbeddableDocsPage, generateEmbeddableDocsPageMetadata } from '@/components/Embeddable';
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
import type { Metadata } from 'next';
type PageProps = {
params: Promise<RouteParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableDynamicContext(params);
const pathname = getPagePathFromParams(params);
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
}
export async function generateMetadata(props: PageProps): Promise<Metadata> {
const params = await props.params;
const { context } = await getEmbeddableDynamicContext(params);
const pathname = getPagePathFromParams(params);
return generateEmbeddableDocsPageMetadata({ context, pageParams: { pathname } });
}
@@ -0,0 +1,31 @@
import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils';
import type { NextRequest } from 'next/server';
export const dynamic = 'force-static';
/**
* This route serves a quick demo to test the script.js script.
* It is not used in production.
*/
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const { context } = await getStaticSiteContext(await params);
return new Response(
`
<html>
<head></head>
<body></body>
<script src="${context.linker.toAbsoluteURL(context.linker.toPathInSite('~gitbook/embed/script.js'))}"></script>
</html>
`,
{
headers: {
'Content-Type': 'text/html',
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
}
);
}
@@ -0,0 +1,16 @@
import type { RouteLayoutParams } from '@/app/utils';
import { EmbeddableAssistantPage } from '@/components/Embeddable';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
export const dynamic = 'force-static';
type PageProps = {
params: Promise<RouteLayoutParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableStaticContext(params);
return <EmbeddableAssistantPage context={context} />;
}
@@ -0,0 +1,30 @@
import type { RouteLayoutParams } from '@/app/utils';
import {
EmbeddableRootLayout,
generateEmbeddableMetadata,
generateEmbeddableViewport,
} from '@/components/Embeddable';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
interface SiteStaticLayoutProps {
params: Promise<RouteLayoutParams>;
}
export default async function RootLayout({
params,
children,
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
const { context } = await getEmbeddableStaticContext(await params);
return <EmbeddableRootLayout context={context}>{children}</EmbeddableRootLayout>;
}
export async function generateViewport({ params }: SiteStaticLayoutProps) {
const { context } = await getEmbeddableStaticContext(await params);
return generateEmbeddableViewport({ context });
}
export async function generateMetadata({ params }: SiteStaticLayoutProps) {
const { context } = await getEmbeddableStaticContext(await params);
return generateEmbeddableMetadata({ context });
}
@@ -0,0 +1,25 @@
import { type RouteParams, getPagePathFromParams } from '@/app/utils';
import { EmbeddableDocsPage, generateEmbeddableDocsPageMetadata } from '@/components/Embeddable';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
import type { Metadata } from 'next';
export const dynamic = 'force-static';
type PageProps = {
params: Promise<RouteParams>;
};
export default async function Page(props: PageProps) {
const params = await props.params;
const { context } = await getEmbeddableStaticContext(params);
const pathname = getPagePathFromParams(params);
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
}
export async function generateMetadata(props: PageProps): Promise<Metadata> {
const params = await props.params;
const { context } = await getEmbeddableStaticContext(params);
const pathname = getPagePathFromParams(params);
return generateEmbeddableDocsPageMetadata({ context, pageParams: { pathname } });
}
@@ -0,0 +1,77 @@
import type { RouteLayoutParams } from '@/app/utils';
import { getAssetURL } from '@/lib/assets';
import { getEmbeddableStaticContext } from '@/lib/embeddable';
import type { CreateGitBookOptions } from '@gitbook/embed';
import type { NextRequest } from 'next/server';
export const dynamic = 'force-static';
/**
* This route is used to serve the assistant.js script.
*/
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const { context } = await getEmbeddableStaticContext(await params);
const maxAge = 7 * 24 * 60 * 60;
const initOptions: CreateGitBookOptions = {
siteURL: context.linker.toAbsoluteURL(context.linker.toPathInSite('')),
};
return new Response(
`
(function () {
const w = window;
const gb = w.GitBook;
const initOptions = window.gitbookSettings || ${JSON.stringify(initOptions)};
if (typeof gb === "function") {
gb('init', initOptions);
} else {
var d = document;
var g = function () {
g.c(arguments);
};
g.q = [];
g.c = function (args) {
g.q.push(args);
};
w.GitBook = g;
g('init', initOptions);
const load = function () {
const style = document.createElement('link');
style.rel = 'stylesheet';
style.href = ${JSON.stringify(getAssetURL('embed/index.css'))};
document.head.appendChild(style);
const script = d.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = ${JSON.stringify(getAssetURL('embed/index.js'))};
var latestScript = d.getElementsByTagName('script')[0];
latestScript.parentNode.insertBefore(script, latestScript);
};
if (document.readyState === 'complete') {
load();
} else if (w.attachEvent) {
w.attachEvent('onload', load);
} else {
w.addEventListener('load', load, false);
}
}
})();
`,
{
headers: {
'Content-Type': 'application/javascript',
'Cache-Control': `public, max-age=${maxAge}, immutable`,
},
}
);
}
@@ -14,7 +14,7 @@ export function AIMessageView(
context: GitBookSiteContext;
}
) {
const { message, context, renderToolCalls = true } = props;
const { message, context, withToolCalls = true, withLinkPreviews = true } = props;
return (
<div className="flex flex-col gap-2">
@@ -33,12 +33,12 @@ export function AIMessageView(
mode: 'default',
contentContext: context,
wrapBlocksInSuspense: false,
shouldRenderLinkPreviews: true,
withLinkPreviews,
}}
style="mt-2 space-y-4 empty:hidden"
/>
{renderToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (
{withToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (
<AIToolCallsSummary toolCalls={step.toolCalls} context={context} />
) : null}
</div>
@@ -1,4 +1,5 @@
'use server';
import { getEmbeddableLinker } from '@/lib/embeddable';
import { getSiteURLDataFromMiddleware } from '@/lib/middleware';
import { getServerActionBaseContext } from '@/lib/server-actions';
import { traceErrorOnly } from '@/lib/tracing';
@@ -31,7 +32,11 @@ export async function* streamAIChatResponse({
options?: RenderAIMessageOptions;
}) {
const { stream } = await traceErrorOnly('AI.streamAIChatResponse', async () => {
const context = await getServerActionBaseContext();
let context = await getServerActionBaseContext();
if (options?.asEmbeddable) {
context = { ...context, linker: getEmbeddableLinker(context.linker) };
}
const siteURLData = await getSiteURLDataFromMiddleware();
const api = await context.dataFetcher.api();
@@ -1,3 +1,2 @@
export * from './types';
export * from './responses';
export * from './chat';
@@ -1,34 +0,0 @@
'use server';
import { getSiteURLDataFromMiddleware } from '@/lib/middleware';
import { getServerActionBaseContext } from '@/lib/server-actions';
import { traceErrorOnly } from '@/lib/tracing';
import { streamRenderAIMessage } from './api';
import type { RenderAIMessageOptions } from './types';
/**
* Stream an existing AI responses.
*/
export async function* streamAIResponseById({
responseId,
options,
}: {
responseId: string;
options?: RenderAIMessageOptions;
}) {
const { stream } = await traceErrorOnly('AI.streamAIResponseById', async () => {
const context = await getServerActionBaseContext();
const siteURLData = await getSiteURLDataFromMiddleware();
const api = await context.dataFetcher.api();
const rawStream = api.orgs.streamExistingAiResponseInSite(
siteURLData.organization,
siteURLData.site,
responseId
);
return await streamRenderAIMessage(context, rawStream, options);
});
for await (const output of stream) {
yield output;
}
}
@@ -16,5 +16,17 @@ export type RenderAIMessageOptions = {
* Render the tool calls.
* @default true
*/
renderToolCalls?: boolean;
withToolCalls?: boolean;
/**
* Render the link previews.
* @default true
*/
withLinkPreviews?: boolean;
/**
* Generate links in the context of an embeddable view.
* @default false
*/
asEmbeddable?: boolean;
};
+18 -1
View File
@@ -34,6 +34,12 @@ export type Assistant = Omit<GitBookAssistant, 'icon'> & {
*/
mode?: 'overlay' | 'sidebar' | 'search';
/**
* Whether the assistant is displayed in the page action menu.
* @default false
*/
pageAction: boolean;
/**
* Icon of the assistant displayed in the UI.
*/
@@ -77,7 +83,7 @@ export function useAI(): AIContext {
if (config.aiMode === CustomizationAIMode.Assistant) {
assistants.push({
id: 'gitbook-assistant',
label: getAIChatName(config.trademark),
label: getAIChatName(language, config.trademark),
icon: (
<AIChatIcon
state={chat.loading ? 'thinking' : 'default'}
@@ -90,6 +96,7 @@ export function useAI(): AIContext {
chatController.postMessage({ message: query });
}
},
pageAction: true,
ui: true,
mode: 'sidebar',
});
@@ -114,6 +121,7 @@ export function useAI(): AIContext {
);
}
},
pageAction: false,
ui: false,
mode: 'search',
});
@@ -125,6 +133,15 @@ export function useAI(): AIContext {
...integrationAssistants.map((assistant) => ({
...assistant,
icon: <Icon icon={assistant.icon as IconName} className="size-4" />,
open: (query?: string) => {
setSearchState((prev) => ({
ask: null, // Reset ask as we assume the assistant will handle it
query: prev?.query ?? null,
global: prev?.global ?? false,
open: false,
}));
assistant.open(query);
},
}))
);
}
@@ -14,7 +14,7 @@ import * as React from 'react';
import { useTrackEvent } from '../Insights';
import { integrationsAssistantTools } from '../Integrations';
import { useSearch } from '../Search';
import { streamAIChatResponse } from './server-actions';
import { type RenderAIMessageOptions, streamAIChatResponse } from './server-actions';
import { useAIMessageContextRef } from './useAIMessageContext';
export type AIChatMessage = {
@@ -23,6 +23,8 @@ export type AIChatMessage = {
query?: string;
};
export type AIChatSize = 'default' | 'large';
export type AIChatPendingTool = {
icon?: IconName;
label: string;
@@ -85,6 +87,11 @@ export type AIChatState = {
* display an error alert. Clearing the conversation will reset this flag.
*/
error: boolean;
/**
* The size of the chat window.
*/
size: AIChatSize;
};
export type AIChatController = {
@@ -96,10 +103,14 @@ export type AIChatController = {
postMessage: (input: { message: string }) => void;
/** Clear the conversation */
clear: () => void;
/** Toggle the size of the chat window */
setSize: (size: AIChatSize) => void;
};
const AIChatControllerContext = React.createContext<AIChatController | null>(null);
// Global state store for AI chat
const globalState = zustand.create<AIChatState>(() => {
export const globalState = zustand.create<AIChatState>(() => {
return {
opened: false,
responseId: null,
@@ -110,6 +121,7 @@ const globalState = zustand.create<AIChatState>(() => {
loading: false,
error: false,
initialQuery: null,
size: 'default',
};
});
@@ -122,10 +134,14 @@ export function useAIChatState(): AIChatState {
}
/**
* Get the controller to interact with the AI chat.
* Integrates with search state to synchronize ?ask= parameter.
* Provide the controller to interact with the AI chat.
*/
export function useAIChatController(): AIChatController {
export function AIChatProvider(props: {
renderMessageOptions?: RenderAIMessageOptions;
children: React.ReactNode;
}) {
const { renderMessageOptions, children } = props;
const messageContextRef = useAIMessageContextRef();
const trackEvent = useTrackEvent();
const [, setSearchState] = useSearch();
@@ -232,6 +248,11 @@ export function useAIChatController(): AIChatController {
description: tool.description,
inputSchema: tool.inputSchema,
})),
options: {
withLinkPreviews: renderMessageOptions?.withLinkPreviews ?? true,
withToolCalls: renderMessageOptions?.withToolCalls ?? true,
asEmbeddable: renderMessageOptions?.asEmbeddable ?? false,
},
});
// Process streaming response
@@ -343,7 +364,12 @@ export function useAIChatController(): AIChatController {
}));
}
},
[messageContextRef.current]
[
messageContextRef.current,
renderMessageOptions?.withLinkPreviews,
renderMessageOptions?.withToolCalls,
renderMessageOptions?.asEmbeddable,
]
);
// Post a message to the AI chat
@@ -422,12 +448,35 @@ export function useAIChatController(): AIChatController {
}));
}, [setSearchState]);
return React.useMemo(() => {
const onSetSize = React.useCallback((size: AIChatSize) => {
globalState.setState((state) => ({ ...state, size }));
}, []);
const controller = React.useMemo(() => {
return {
open: onOpen,
close: onClose,
clear: onClear,
postMessage: onPostMessage,
setSize: onSetSize,
};
}, [onOpen, onClose, onClear, onPostMessage]);
}, [onOpen, onClose, onClear, onPostMessage, onSetSize]);
return (
<AIChatControllerContext.Provider value={controller}>
{children}
</AIChatControllerContext.Provider>
);
}
/**
* Get the controller to interact with the AI chat.
* Integrates with search state to synchronize ?ask= parameter.
*/
export function useAIChatController(): AIChatController {
const controller = React.useContext(AIChatControllerContext);
if (!controller) {
throw new Error('useAIChatController must be used within an AIChatProvider');
}
return controller;
}
@@ -25,7 +25,9 @@ interface AIActionsDropdownProps {
*/
export function AIActionsDropdown(props: AIActionsDropdownProps) {
const ref = useRef<HTMLDivElement>(null);
const assistants = useAI().assistants.filter((assistant) => assistant.ui === true);
const assistants = useAI().assistants.filter(
(assistant) => assistant.ui === true && assistant.pageAction
);
const language = useLanguage();
return assistants.length > 0 || props.actions.markdown || props.actions.externalAI ? (
@@ -63,7 +65,9 @@ export function AIActionsDropdown(props: AIActionsDropdownProps) {
*/
function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) {
const { markdownPageUrl, actions } = props;
const assistants = useAI().assistants.filter((assistant) => assistant.ui === true);
const assistants = useAI().assistants.filter(
(assistant) => assistant.ui === true && assistant.pageAction
);
return (
<>
@@ -103,7 +107,9 @@ function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) {
*/
function DefaultAction(props: AIActionsDropdownProps) {
const { markdownPageUrl, actions } = props;
const assistants = useAI().assistants.filter((assistant) => assistant.ui === true);
const assistants = useAI().assistants.filter(
(assistant) => assistant.ui === true && assistant.pageAction
);
if (assistants.length) {
return <OpenAIAssistant assistant={assistants[0]} type="button" />;
+189 -130
View File
@@ -1,6 +1,8 @@
'use client';
import { t, tString, useLanguage } from '@/intl/client';
import type { TranslationLanguage } from '@/intl/translations';
import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -10,9 +12,10 @@ import {
useAIChatController,
useAIChatState,
} from '../AI';
import { EmbeddableFrame } from '../Embeddable/EmbeddableFrame';
import { useNow } from '../hooks';
import { Button } from '../primitives';
import { DropdownMenu, DropdownMenuItem } from '../primitives/DropdownMenu';
import { Button, DropdownMenuSeparator } from '../primitives';
import { DropdownMenu, DropdownMenuItem } from '../primitives';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
import { AIChatMessages } from './AIChatMessages';
@@ -20,8 +23,21 @@ import AIChatSuggestedQuestions from './AIChatSuggestedQuestions';
export function AIChat(props: { trademark: boolean }) {
const { trademark } = props;
const language = useLanguage();
const chat = useAIChatState();
const chatController = useAIChatController();
const containerRef = React.useRef<HTMLDivElement>(null);
// When the chat is opened, scroll to it (applicable on mobile, where it's displayed above the content)
React.useEffect(() => {
if (chat.opened) {
containerRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [chat.opened]);
useHotkeys(
'mod+i',
@@ -46,10 +62,131 @@ export function AIChat(props: { trademark: boolean }) {
return null;
}
return <AIChatWindow trademark={trademark} chatController={chatController} chat={chat} />;
return (
<div
data-testid="ai-chat"
className={tcls(
'ai-chat z-40 mx-auto flex max-h-full w-full max-w-3xl animate-present scroll-mt-36 p-4 transition-all duration-300 max-md:inset-x-0 max-md:bottom-0 lg:fixed lg:inset-y-0 lg:right-0 lg:animate-enter-from-right',
chat.size === 'large' ? 'lg:w-104 xl:w-156' : 'lg:w-80 xl:w-96'
)}
>
<EmbeddableFrame
className="relative circular-corners:rounded-3xl rounded-corners:rounded-md depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle"
icon={<AIChatDynamicIcon trademark={trademark} />}
title={getAIChatName(language, trademark)}
subtitle={
chat.loading
? chat.messages[chat.messages.length - 1].content
? tString(language, 'ai_chat_working')
: tString(language, 'ai_chat_thinking')
: ''
}
buttons={
<>
{chat.messages.length > 0 ? (
<DropdownMenu
button={
<Button
onClick={() => {}}
iconOnly
icon="ellipsis"
label={tString(language, 'actions')}
variant="blank"
size="default"
/>
}
>
<DropdownMenuItem
onClick={() => {
chatController.setSize(
chat.size === 'default' ? 'large' : 'default'
);
}}
>
<Icon
icon={
chat.size === 'default'
? 'arrow-up-right-and-arrow-down-left-from-center'
: 'arrow-down-left-and-arrow-up-right-to-center'
}
className="size-3 shrink-0 text-tint-subtle"
/>
<div className="flex flex-col gap-0.5">
<p className="font-medium">
{chat.size === 'default' ? 'Maximize' : 'Minimize'}
</p>
<p className="text-tint text-xs">
{chat.size === 'default'
? 'Longer, more detailed answers'
: 'Shorter, more concise answers'}
</p>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
chatController.clear();
}}
>
<Icon
icon="broom-wide"
className="size-3 shrink-0 text-tint-subtle"
/>
{t(language, 'ai_chat_clear_conversation')}
</DropdownMenuItem>
</DropdownMenu>
) : null}
<Button
onClick={() => chatController.close()}
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
size="default"
/>
</>
}
>
<AIChatBody chatController={chatController} chat={chat} trademark={trademark} />
</EmbeddableFrame>
</div>
);
}
export function AIChatWindow(props: {
/**
* Dynamic icon to indicate the AI chat state.
*/
export function AIChatDynamicIcon(props: {
trademark: boolean;
}) {
const { trademark } = props;
const chat = useAIChatState();
return (
<AIChatIcon
className="size-5 text-tint"
trademark={trademark}
state={
chat.error
? 'error'
: chat.loading
? chat.messages[chat.messages.length - 1].content
? 'working'
: 'thinking'
: chat.messages.length > 0
? chat.pendingTools.length > 0
? 'confirm'
: 'done'
: 'default'
}
/>
);
}
/**
* Body of the AI chat window.
*/
export function AIChatBody(props: {
chatController: AIChatController;
chat: AIChatState;
trademark: boolean;
@@ -58,7 +195,6 @@ export function AIChatWindow(props: {
const [input, setInput] = React.useState('');
const containerRef = React.useRef<HTMLDivElement>(null);
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
// Ref for the last user message element
const lastUserMessageRef = React.useRef<HTMLDivElement>(null);
@@ -89,12 +225,6 @@ export function AIChatWindow(props: {
}, [chat.messages.length]);
React.useEffect(() => {
// When the chat is opened, scroll to it (applicable on mobile, where it's displayed above the content)
containerRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
const timeout = setTimeout(() => {
if (lastUserMessageRef.current) {
lastUserMessageRef.current.scrollIntoView({
@@ -121,136 +251,67 @@ export function AIChatWindow(props: {
}, []);
return (
<div
data-testid="ai-chat"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enter-from-right lg:pr-4 lg:pl-0 xl:w-96"
ref={containerRef}
>
<div className="relative flex h-full grow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint-base text-sm text-tint depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle">
<div className="flex select-none items-center gap-2 border-tint-subtle border-b bg-tint-subtle px-4 py-2 text-tint-strong">
<AIChatIcon
className="size-5 text-tint"
trademark={trademark}
state={
chat.error
? 'error'
: chat.loading
? chat.messages[chat.messages.length - 1].content
? 'working'
: 'thinking'
: chat.messages.length > 0
? chat.pendingTools.length > 0
? 'confirm'
: 'done'
: 'default'
}
/>
<div className="flex flex-col">
<div className="font-bold">{getAIChatName(trademark)}</div>
<div
className={`text-tint text-xs leading-none transition-all duration-500 ${
chat.loading ? 'h-3 opacity-11' : 'h-0 opacity-0'
}`}
>
{chat.loading
? chat.messages[chat.messages.length - 1].content
? t(language, 'ai_chat_working')
: t(language, 'ai_chat_thinking')
: ''}
</div>
</div>
<div className="ml-auto flex gap-2">
<DropdownMenu
button={
<Button
onClick={() => {}}
iconOnly
icon="ellipsis"
label={tString(language, 'actions')}
variant="blank"
size="default"
/>
}
>
<DropdownMenuItem
onClick={() => {
chatController.clear();
}}
disabled={isEmpty}
>
<Icon
icon="broom-wide"
className="size-3 shrink-0 text-tint-subtle"
/>
{t(language, 'ai_chat_clear_conversation')}
</DropdownMenuItem>
</DropdownMenu>
<Button
onClick={() => chatController.close()}
iconOnly
icon="close"
label={tString(language, 'close')}
variant="blank"
size="default"
/>
</div>
</div>
<div
ref={scrollContainerRef}
className="flex grow scroll-pt-4 flex-col gap-4 overflow-y-auto p-4"
style={{
paddingBottom: `${inputHeight}px`,
}}
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex size-32 animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle">
<>
<div
ref={scrollContainerRef}
className={tcls(
'flex grow scroll-pt-4 flex-col gap-4 overflow-y-auto p-4 transition-all',
chat.size === 'large' ? 'text-base' : 'text-sm'
)}
style={{
paddingBottom: `${inputHeight}px`,
}}
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex items-center gap-4 lg:flex-col">
<div className="flex animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle p-4 lg:p-8">
<AIChatIcon
state="intro"
trademark={trademark}
className="size-16 animate-[present_500ms_200ms_both]"
className="size-8 animate-[present_500ms_200ms_both] lg:size-16"
/>
</div>
<div className="animate-[fadeIn_500ms_400ms_both]">
<h5 className=" text-center font-bold text-lg text-tint-strong">
<h5 className="font-bold text-lg text-tint-strong lg:text-center">
{timeGreeting}
</h5>
<p className="text-center text-tint">
<p className="text-tint lg:text-center">
{t(language, 'ai_chat_assistant_description')}
</p>
</div>
{!chat.error ? (
<AIChatSuggestedQuestions chatController={chatController} />
) : null}
</div>
) : (
<AIChatMessages
chat={chat}
chatController={chatController}
lastUserMessageRef={lastUserMessageRef}
/>
)}
</div>
<div
ref={inputRef}
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-linear-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
>
{/* Display an error banner when something went wrong. */}
{chat.error ? <AIChatError chatController={chatController} /> : null}
<AIChatInput
value={input}
onChange={setInput}
loading={chat.loading}
disabled={chat.loading || chat.error}
onSubmit={() => {
chatController.postMessage({ message: input });
setInput('');
}}
{!chat.error ? (
<AIChatSuggestedQuestions chatController={chatController} />
) : null}
</div>
) : (
<AIChatMessages
chat={chat}
chatController={chatController}
lastUserMessageRef={lastUserMessageRef}
/>
</div>
)}
</div>
</div>
<div
ref={inputRef}
className="absolute inset-x-0 bottom-0 mr-2 flex select-none flex-col gap-4 bg-linear-to-b from-transparent to-50% to-tint-base/9 p-4 pr-2"
>
{/* Display an error banner when something went wrong. */}
{chat.error ? <AIChatError chatController={chatController} /> : null}
<AIChatInput
value={input}
onChange={setInput}
loading={chat.loading}
disabled={chat.loading || chat.error}
onSubmit={() => {
chatController.postMessage({ message: input });
setInput('');
}}
/>
</div>
</>
);
}
@@ -280,9 +341,7 @@ function AIChatError(props: { chatController: AIChatController }) {
);
}
export function getAIChatName(trademark: boolean) {
const language = useLanguage();
export function getAIChatName(language: TranslationLanguage, trademark: boolean) {
return trademark
? tString(language, 'ai_chat_assistant_name')
: tString(language, 'ai_chat_assistant_name_unbranded');
@@ -3,9 +3,8 @@ import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import { useEffect, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Button } from '../primitives';
import { Button, HoverCard, HoverCardRoot, HoverCardTrigger } from '../primitives';
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
import { Tooltip } from '../primitives/Tooltip';
export function AIChatInput(props: {
value: string;
@@ -107,10 +106,15 @@ export function AIChatInput(props: {
</div>
) : null}
<div className="absolute inset-x-0 bottom-0 flex items-center gap-2 px-2 py-2">
<Tooltip
label={
<HoverCardRoot>
<HoverCard
className="max-w-xs bg-tint p-2 text-sm text-tint"
arrow={{ className: 'fill-tint-3' }}
>
<div className="flex flex-col gap-3 p-2">
<p>{t(language, 'ai_chat_context_description')}</p>
<p className="font-semibold">
{t(language, 'ai_chat_context_description')}
</p>
<ul className="flex flex-col gap-2">
<li className="flex items-center gap-2">
<Icon icon="memo" className="size-3.5 opacity-7" />
@@ -127,16 +131,19 @@ export function AIChatInput(props: {
</ul>
<p>{t(language, 'ai_chat_context_disclaimer')}</p>
</div>
}
arrow
>
<div className="flex cursor-help items-center gap-1 circular-corners:rounded-2xl rounded-corners:rounded-md px-2.5 py-1.5 text-tint/7 text-xs transition-all hover:bg-tint">
<span className="-ml-1 circular-corners:rounded-2xl rounded-corners:rounded-sm bg-tint-11/7 px-1 py-0.5 font-mono font-semibold text-[0.65rem] text-contrast-tint-11 leading-none">
{t(language, 'ai_chat_context_badge')}
</span>{' '}
<span className="leading-none">{t(language, 'ai_chat_context_title')}</span>
</div>
</Tooltip>
</HoverCard>
<HoverCardTrigger>
<div className="flex cursor-help items-center gap-1 circular-corners:rounded-2xl rounded-corners:rounded-md px-2.5 py-1.5 text-tint/7 text-xs transition-all hover:bg-tint">
<span className="-ml-1 circular-corners:rounded-2xl rounded-corners:rounded-sm bg-tint-11/7 px-1 py-0.5 font-mono font-semibold text-[0.65rem] text-contrast-tint-11 leading-none">
{t(language, 'ai_chat_context_badge')}
</span>{' '}
<span className="leading-none">
{t(language, 'ai_chat_context_title')}
</span>
<Icon icon="question-circle" className="size-3 shrink-0" />
</div>
</HoverCardTrigger>
</HoverCardRoot>
<Button
label={tString(language, 'send')}
size="medium"
@@ -13,13 +13,13 @@ export default function AIChatSuggestedQuestions(props: { chatController: AIChat
];
return (
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2 lg:flex-col">
{DEFAULT_SUGGESTED_QUESTIONS.map((question, index) => (
<Button
key={question}
variant="secondary"
size="medium"
className="max-w-full animate-[present_500ms_both] whitespace-normal"
className="shrink grow animate-[present_500ms_both] whitespace-normal"
style={{
animationDelay: `${800 + index * 100}ms`,
}}
@@ -35,7 +35,7 @@ export interface DocumentContext {
* If false, no link previews will be rendered.
* @default false
*/
shouldRenderLinkPreviews?: boolean;
withLinkPreviews?: boolean;
}
export interface DocumentContextProps {
@@ -75,7 +75,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
</StyledLink>
);
if (context.shouldRenderLinkPreviews) {
if (context.withLinkPreviews) {
return (
<InlineLinkTooltipWrapper inline={inline} language={language} resolved={resolved}>
{content}
@@ -0,0 +1,17 @@
'use client';
import { useAIChatController, useAIChatState } from '@/components/AI';
import { AIChatBody } from '@/components/AIChat';
/**
* Embeddable AI chat window in an iframe.
*/
export function EmbeddableAIChat(props: {
trademark: boolean;
}) {
const { trademark } = props;
const chat = useAIChatState();
const chatController = useAIChatController();
return <AIChatBody trademark={trademark} chatController={chatController} chat={chat} />;
}
@@ -0,0 +1,13 @@
import type { GitBookSiteContext } from '@/lib/context';
import { EmbeddableAIChat } from './EmbeddableAIChat';
/**
* Reusable page component for the embed assistant page.
*/
export async function EmbeddableAssistantPage(props: {
context: GitBookSiteContext;
}) {
const { context } = props;
return <EmbeddableAIChat trademark={context.customization.trademark.enabled} />;
}
@@ -0,0 +1,43 @@
import { type PagePathParams, getSitePageData } from '@/components/SitePage';
import { PageBody } from '@/components/PageBody';
import type { GitBookSiteContext } from '@/lib/context';
import type { Metadata } from 'next';
export const dynamic = 'force-static';
type EmbeddableDocsPageProps = {
context: GitBookSiteContext;
pageParams: PagePathParams;
};
/**
* Page component for the embed docs page.
*/
export async function EmbeddableDocsPage(props: EmbeddableDocsPageProps) {
const { context, pageParams } = props;
const { page, document, ancestors, withPageFeedback } = await getSitePageData({
context,
pageParams,
});
return (
<div className="flex-1 overflow-auto p-6">
<PageBody
context={context}
page={page}
ancestors={ancestors}
document={document}
withPageFeedback={withPageFeedback}
/>
</div>
);
}
export async function generateEmbeddableDocsPageMetadata(
_props: EmbeddableDocsPageProps
): Promise<Metadata> {
return {
robots: { index: false, follow: false },
};
}
@@ -0,0 +1,53 @@
import { tcls } from '@/lib/tailwind';
import React from 'react';
export type EmbeddableFrameHeaderProps = {
icon: React.ReactNode;
title: string;
subtitle?: string;
buttons?: React.ReactNode;
};
export type EmbeddableFrameProps = EmbeddableFrameHeaderProps &
React.ComponentProps<'div'> & {
children: React.ReactNode;
};
/**
* Presentation component to display an embeddable frame.
* It is used for the AI chat window in the docs, but also when embedded in another website.
*/
export const EmbeddableFrame = React.forwardRef<HTMLDivElement, EmbeddableFrameProps>(
(props, ref) => {
const { icon, title, subtitle, buttons, children, ...divProps } = props;
return (
<div
{...divProps}
className={tcls(
'flex h-full grow animate-fade-in-slow flex-col overflow-hidden bg-tint-base text-sm text-tint',
divProps.className
)}
ref={ref}
>
<div className="flex select-none items-center gap-2 border-tint-subtle border-b bg-tint-subtle px-4 py-2 text-tint-strong">
{icon}
<div className="flex flex-col">
<div className="font-bold">{title}</div>
<div
className={`text-tint text-xs leading-none transition-all duration-500 ${
subtitle ? 'h-3 opacity-11' : 'h-0 opacity-0'
}`}
>
{subtitle}
</div>
</div>
<div className="ml-auto flex gap-2">{buttons}</div>
</div>
<div className="flex flex-1 flex-col overflow-hidden">{children}</div>
</div>
);
}
);
@@ -0,0 +1,44 @@
'use client';
import type { ParentToFrameMessage } from '@gitbook/embed';
import { createChannel } from 'bidc';
import React from 'react';
import { useAIChatController } from '@/components/AI';
/**
* Expose the API to communicate with the parent window.
*/
export function EmbeddableIframeAPI() {
const chatController = useAIChatController();
React.useEffect(() => {
if (window.parent === window) {
return;
}
const channel = createChannel();
channel.receive((payload) => {
const message = payload as ParentToFrameMessage;
switch (message.type) {
case 'clearChat': {
chatController.clear();
break;
}
case 'postUserMessage': {
chatController.postMessage({
message: message.message,
});
break;
}
// TODO: Handle other messages
}
});
return channel.cleanup();
}, [chatController]);
return null;
}
@@ -0,0 +1,63 @@
import { AIChatProvider, AIContextProvider } from '@/components/AI';
import { AIChatDynamicIcon } from '@/components/AIChat';
import { EmbeddableFrame } from '@/components/Embeddable';
import { CustomizationRootLayout } from '@/components/RootLayout';
import {
SiteLayoutClientContexts,
generateSiteLayoutMetadata,
generateSiteLayoutViewport,
} from '@/components/SiteLayout';
import type { GitBookSiteContext } from '@/lib/context';
import { CustomizationAIMode } from '@gitbook/api';
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';
type EmbeddableRootLayoutProps = {
context: GitBookSiteContext;
};
/**
* Layout component for the embed routes.
*/
export async function EmbeddableRootLayout({
context,
children,
}: React.PropsWithChildren<EmbeddableRootLayoutProps>) {
return (
<CustomizationRootLayout customization={context.customization}>
<SiteLayoutClientContexts
forcedTheme={context.customization.themes.default}
externalLinksTarget={context.customization.externalLinks.target}
contextId={context.contextId}
>
<EmbeddableFrame
icon={<AIChatDynamicIcon trademark={context.customization.trademark.enabled} />}
title="Test"
className="fixed inset-0"
>
<AIContextProvider
aiMode={CustomizationAIMode.Assistant}
trademark={context.customization.trademark.enabled}
>
<AIChatProvider
renderMessageOptions={{
withLinkPreviews: false,
asEmbeddable: true,
}}
>
{children}
<EmbeddableIframeAPI />
</AIChatProvider>
</AIContextProvider>
</EmbeddableFrame>
</SiteLayoutClientContexts>
</CustomizationRootLayout>
);
}
export async function generateEmbeddableViewport({ context }: EmbeddableRootLayoutProps) {
return generateSiteLayoutViewport(context);
}
export async function generateEmbeddableMetadata({ context }: EmbeddableRootLayoutProps) {
return generateSiteLayoutMetadata(context);
}
@@ -0,0 +1,4 @@
export * from './EmbeddableFrame';
export * from './EmbeddableRootLayout';
export * from './EmbeddableAssistantPage';
export * from './EmbeddableDocsPage';
@@ -9,7 +9,7 @@ import type {
GitBookIntegrationEventCallback,
GitBookIntegrationTool,
} from '@gitbook/browser-types';
import type { Assistant } from '../AI';
import { type Assistant, globalState } from '../AI';
const events = new Map<GitBookIntegrationEvent, GitBookIntegrationEventCallback[]>();
@@ -50,7 +50,13 @@ if (typeof window !== 'undefined') {
integrationAssistants.setState(
(state) => [
...state,
{ ...assistant, id, ui: assistant.ui ?? true, mode: 'overlay' },
{
...assistant,
id,
ui: assistant.ui ?? true,
mode: 'overlay',
pageAction: false,
},
],
true
);
@@ -61,6 +67,24 @@ if (typeof window !== 'undefined') {
},
};
window.GitBook = gitbookGlobal;
window.GitBook?.registerTool({
name: 'ExpandChatSize',
description:
'FREE TO USE: Expand the chat to a larger size. Use this tool before you start writing your final answer if you need to show a lot of content, like step-by-step instructions or codeblocks. You do not need to preface this tool with feedback to the user, just call it and the chat will expand.',
execute: async () => {
globalState.setState((state) => ({ ...state, size: 'large' }));
return {
output: {
expanded: true,
},
summary: {
icon: 'arrow-up-right-and-arrow-down-left-from-center',
text: 'Chat size expanded',
},
};
},
});
}
/**
@@ -245,7 +245,7 @@ async function PDFPageDocument(props: {
page,
},
getId: (id) => getPagePDFContainerId(page, id),
shouldRenderLinkPreviews: false, // We don't want to render link previews in the PDF.
withLinkPreviews: false, // We don't want to render link previews in the PDF.
}}
// We consider all pages as offscreen in PDF mode
// to ensure we can efficiently render as many pages as possible
@@ -1,47 +1,41 @@
'use client';
import { type Transition, motion, useReducedMotion } from 'framer-motion';
import { type ClassValue, tcls } from '@/lib/tailwind';
import React from 'react';
export function AsideSectionHighlight({
transition,
className,
}: {
transition?: Transition;
className?: ClassValue;
}) {
const prefersReducedMotion = useReducedMotion();
export const AsideSectionHighlight = React.memo(
({
className,
}: {
className?: ClassValue;
}) => {
return (
<div
className={tcls([
'border-primary-9',
'tint:border-primary-11',
'sidebar-list-line:border-l-2',
return (
<motion.div
layout
layoutId="sections-line"
className={tcls([
'border-primary-9',
'tint:border-primary-11',
'sidebar-list-line:border-l-2',
'inset-0',
'pointer-events-none',
'absolute',
'z-0',
'sidebar-list-line:-left-px',
'inset-0',
'pointer-events-none',
'absolute',
'z-0',
'sidebar-list-line:-left-px',
'rounded-md',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'sidebar-list-line:rounded-l-none',
'rounded-md',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'sidebar-list-line:rounded-l-none',
'sidebar-list-pill:bg-primary',
'[html.theme-muted.sidebar-list-pill_&]:bg-primary-hover',
'[html.theme-gradient.sidebar-list-pill_&]:bg-primary-active',
'sidebar-list-pill:bg-primary',
'[html.theme-muted.sidebar-list-pill_&]:bg-primary-hover',
'[html.theme-gradient.sidebar-list-pill_&]:bg-primary-active',
'contrast-more:border',
'contrast-more:bg-primary',
className,
])}
transition={prefersReducedMotion ? { duration: 0 } : transition}
/>
);
}
'contrast-more:border',
'contrast-more:bg-primary',
className,
])}
/>
);
}
);
@@ -78,8 +78,8 @@ export function PageAside(props: {
'lg:site-header-sections:max-h-[calc(100vh-6.75rem)]',
// Client-side dynamic positioning (CSS vars applied by script)
'lg:[html[style*="--toc-top-offset"]_&]:top-(--toc-top-offset)!',
'lg:[html[style*="--toc-height"]_&]:max-h-(--toc-height)!',
'lg:[html[style*="--outline-top-offset"]_&]:top-(--outline-top-offset)!',
'lg:[html[style*="--outline-height"]_&]:max-h-(--outline-height)!',
// When in api page mode, we display it as an overlay on non-large resolutions
'xl:max-2xl:page-api-block:z-10',
@@ -19,13 +19,6 @@ const SECTION_INTERSECTING_THRESHOLD = 0.9;
*/
const ACTIVE_ITEM_OFFSET = 100;
const springCurve = {
type: 'spring',
stiffness: 700,
damping: 50,
mass: 0.8,
};
export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
const { sections } = props;
@@ -75,9 +68,8 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
)}
ref={activeId === section.id ? activeItemRef : null}
>
{activeId === section.id ? (
{activeId === section.id && (
<AsideSectionHighlight
transition={springCurve}
className={tcls(
'sidebar-list-default:hidden',
section?.depth > 1
@@ -91,7 +83,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
]
)}
/>
) : null}
)}
<a
href={`#${section.id}`}
className={tcls(
@@ -33,7 +33,7 @@ export function PageBody(props: {
const contentFullWidth = document ? hasFullWidthBlock(document) : false;
// Render link previews only if there are less than LINK_PREVIEW_MAX_COUNT links in the document.
const shouldRenderLinkPreviews = document
const withLinkPreviews = document
? !hasMoreThan(
document,
(inline) => inline.object === 'inline' && inline.type === 'link',
@@ -81,7 +81,7 @@ export function PageBody(props: {
context={{
mode: 'default',
contentContext: context,
shouldRenderLinkPreviews,
withLinkPreviews,
}}
/>
</React.Suspense>
@@ -76,6 +76,8 @@ export async function PageCover(props: {
return (
<div
id="page-cover"
data-full={String(as === 'full')}
className={tcls(
'overflow-hidden',
// Negative margin to balance the container padding
@@ -27,7 +27,7 @@ import { getSpaceLanguage } from '@/intl/server';
import { getAssetURL } from '@/lib/assets';
import { tcls } from '@/lib/tailwind';
import { ClientContexts } from './ClientContexts';
import { RootLayoutClientContexts } from './RootLayoutClientContexts';
import '@gitbook/icons/style.css';
import './globals.css';
@@ -185,7 +185,9 @@ export async function CustomizationRootLayout(props: {
: null) || IconStyle.Regular
}
>
<ClientContexts language={language}>{children}</ClientContexts>
<RootLayoutClientContexts language={language}>
{children}
</RootLayoutClientContexts>
</IconsProvider>
</body>
</html>
@@ -8,7 +8,10 @@ import { TooltipProvider } from '@radix-ui/react-tooltip';
import { HashProvider } from '../hooks';
import { LoadingStateProvider } from '../primitives/LoadingStateProvider';
export function ClientContexts(props: {
/**
* Client component context providers for the root layout.
*/
export function RootLayoutClientContexts(props: {
language: TranslationLanguage;
children: React.ReactNode;
}) {
@@ -190,6 +190,7 @@ function AnswerFollowupQuestions(props: { followupQuestions: string[] }) {
)}
{...getSearchLinkProps({
ask: question,
open: true,
})}
>
<Icon
@@ -355,7 +355,7 @@ async function transformAnswer(
mode: 'default',
contentContext: context,
wrapBlocksInSuspense: false,
shouldRenderLinkPreviews: false, // We don't want to render link previews in the AI answer.
withLinkPreviews: false, // We don't want to render link previews in the AI answer.
}}
style={['space-y-5']}
/>
@@ -1,36 +0,0 @@
'use client';
import type { CustomizationThemeMode, SiteExternalLinksTarget } from '@gitbook/api';
import { ThemeProvider } from 'next-themes';
import type React from 'react';
import { useClearRouterCache } from '../hooks/useClearRouterCache';
import { LinkSettingsContext } from '../primitives';
export function ClientContexts(props: {
nonce?: string;
forcedTheme: CustomizationThemeMode | undefined;
externalLinksTarget: SiteExternalLinksTarget;
contextId: string | undefined;
children: React.ReactNode;
}) {
const { children, forcedTheme, externalLinksTarget, contextId } = props;
useClearRouterCache(contextId);
/**
* A bug in ThemeProvider is causing the nonce to be included incorrectly
* on the client-side. Original issue: https://github.com/pacocoursey/next-themes/issues/218
*
* This is a workaround for it, until next-themes fixes it in their library. There is already
* a PR: https://github.com/pacocoursey/next-themes/pull/223
*/
const nonce = typeof window === 'undefined' || !props.nonce ? props.nonce : '';
return (
<ThemeProvider nonce={nonce} attribute="class" enableSystem forcedTheme={forcedTheme}>
<LinkSettingsContext.Provider value={{ externalLinksTarget }}>
{children}
</LinkSettingsContext.Provider>
</ThemeProvider>
);
}
@@ -2,12 +2,9 @@
* Insert inline JS to detect Cloudflare Rocket Loader and notify the user that the content will not load correctly.
* It needs to be inserted as a script tag so that we can pass `data-cfasync="false"` to avoid being processed by Rocket Loader.
*/
export function RocketLoaderDetector(props: { nonce?: string }) {
const { nonce } = props;
export function RocketLoaderDetector() {
return (
<script
nonce={nonce}
suppressHydrationWarning
data-cfasync="false"
dangerouslySetInnerHTML={{
@@ -1,7 +1,6 @@
import type { GitBookSiteContext } from '@/lib/context';
import { CustomizationThemeMode } from '@gitbook/api';
import type { Metadata, Viewport } from 'next';
import { NuqsAdapter } from 'nuqs/adapters/next/app';
import React from 'react';
import * as ReactDOM from 'react-dom';
@@ -14,21 +13,21 @@ import { buildVersion } from '@/lib/build';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_ASSETS_URL, GITBOOK_ICONS_URL } from '@/lib/env';
import { getResizedImageURL } from '@/lib/images';
import { isSiteIndexable } from '@/lib/seo';
import { ClientContexts } from './ClientContexts';
import { AIChatProvider, AIContextProvider } from '../AI';
import { RocketLoaderDetector } from './RocketLoaderDetector';
import { SiteLayoutClientContexts } from './SiteLayoutClientContexts';
/**
* Layout when rendering a site.
*/
export async function SiteLayout(props: {
nonce?: string;
context: GitBookSiteContext;
forcedTheme?: CustomizationThemeMode | null;
withTracking: boolean;
visitorAuthClaims: VisitorAuthClaims;
children: React.ReactNode;
}) {
const { context, nonce, forcedTheme, withTracking, visitorAuthClaims, children } = props;
const { context, forcedTheme, withTracking, visitorAuthClaims, children } = props;
const { customization } = context;
// Scripts are disabled when tracking is disabled
@@ -43,49 +42,52 @@ export async function SiteLayout(props: {
scripts.forEach(({ script }) => {
ReactDOM.preload(script, {
as: 'script',
nonce,
});
});
return (
<NuqsAdapter>
<ClientContexts
nonce={nonce}
contextId={context.contextId}
forcedTheme={
forcedTheme ??
(customization.themes.toggeable ? undefined : customization.themes.default)
}
externalLinksTarget={customization.externalLinks.target}
<SiteLayoutClientContexts
contextId={context.contextId}
forcedTheme={
forcedTheme ??
(customization.themes.toggeable ? undefined : customization.themes.default)
}
externalLinksTarget={customization.externalLinks.target}
>
<AIContextProvider
aiMode={customization.ai?.mode}
trademark={customization.trademark.enabled}
>
<SpaceLayout
context={context}
withTracking={withTracking}
visitorAuthClaims={visitorAuthClaims}
>
{children}
</SpaceLayout>
<AIChatProvider>
<SpaceLayout
context={context}
withTracking={withTracking}
visitorAuthClaims={visitorAuthClaims}
>
{children}
</SpaceLayout>
</AIChatProvider>
</AIContextProvider>
{scripts.length > 0 ? (
<>
<LoadIntegrations />
{scripts.map(({ script }) => (
<script key={script} async src={script} nonce={nonce} />
))}
</>
) : null}
{scripts.length > 0 ? (
<>
<LoadIntegrations />
{scripts.map(({ script }) => (
<script key={script} async src={script} />
))}
</>
) : null}
{scripts.some((script) => script.cookies) || customization.privacyPolicy.url ? (
<React.Suspense fallback={null}>
<CookiesToast privacyPolicy={customization.privacyPolicy.url} />
</React.Suspense>
) : null}
{scripts.some((script) => script.cookies) || customization.privacyPolicy.url ? (
<React.Suspense fallback={null}>
<CookiesToast privacyPolicy={customization.privacyPolicy.url} />
</React.Suspense>
) : null}
<RocketLoaderDetector nonce={nonce} />
<RocketLoaderDetector />
<AdminToolbar context={context} />
</ClientContexts>
</NuqsAdapter>
<AdminToolbar context={context} />
</SiteLayoutClientContexts>
);
}
@@ -0,0 +1,33 @@
'use client';
import type { CustomizationThemeMode, SiteExternalLinksTarget } from '@gitbook/api';
import { ThemeProvider } from 'next-themes';
import { NuqsAdapter } from 'nuqs/adapters/next/app';
import type React from 'react';
import { SearchContextProvider } from '../Search';
import { useClearRouterCache } from '../hooks/useClearRouterCache';
import { LinkSettingsContext } from '../primitives';
/**
* Client component context providers for the site layout.
*/
export function SiteLayoutClientContexts(props: {
forcedTheme: CustomizationThemeMode | undefined;
externalLinksTarget: SiteExternalLinksTarget;
contextId: string | undefined;
children: React.ReactNode;
}) {
const { children, forcedTheme, externalLinksTarget, contextId } = props;
useClearRouterCache(contextId);
return (
<NuqsAdapter>
<ThemeProvider attribute="class" enableSystem forcedTheme={forcedTheme}>
<LinkSettingsContext.Provider value={{ externalLinksTarget }}>
<SearchContextProvider>{children}</SearchContextProvider>
</LinkSettingsContext.Provider>
</ThemeProvider>
</NuqsAdapter>
);
}
@@ -1 +1,2 @@
export * from './SiteLayout';
export * from './SiteLayoutClientContexts';
@@ -25,46 +25,18 @@ export type SitePageProps = {
* Fetch and render a page.
*/
export async function SitePage(props: SitePageProps) {
const { context, pageTarget } = await getPageDataWithFallback({
context: props.context,
pagePathParams: props.pageParams,
});
const rawPathname = getPathnameParam(props.pageParams);
if (!pageTarget) {
const pathname = rawPathname.toLowerCase();
if (pathname !== rawPathname) {
// If the pathname was not normalized, redirect to the normalized version
// before trying to resolve the page again
redirect(context.linker.toPathInSpace(pathname));
} else {
notFound();
}
} else if (getPagePath(context.revision.pages, pageTarget.page) !== rawPathname) {
redirect(
context.linker.toPathForPage({
pages: context.revision.pages,
page: pageTarget.page,
})
);
}
const { customization, sections } = context;
const { page, ancestors } = pageTarget;
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
const withFullPageCover = !!(
page.cover &&
page.layout.cover &&
page.layout.coverSize === 'full'
);
const withPageFeedback = customization.feedback.enabled;
const withSections = Boolean(sections && sections.list.length > 0);
const {
context,
page,
ancestors,
document,
withFullPageCover,
withPageFeedback,
withSections,
withTopHeader,
} = await getSitePageData(props);
const headerOffset = { sectionsHeader: withSections, topHeader: withTopHeader };
const document = await getPageDocument(context, page);
return (
<PageContextProvider pageId={page.id} spaceId={context.space.id} title={page.title}>
{withFullPageCover && page.cover ? (
@@ -156,6 +128,61 @@ export async function generateSitePageMetadata(props: SitePageProps): Promise<Me
};
}
/**
* Fetches all the data required to render the site page.
*/
export async function getSitePageData(props: SitePageProps) {
const { context, pageTarget } = await getPageDataWithFallback({
context: props.context,
pagePathParams: props.pageParams,
});
const rawPathname = getPathnameParam(props.pageParams);
if (!pageTarget) {
const pathname = rawPathname.toLowerCase();
if (pathname !== rawPathname) {
// If the pathname was not normalized, redirect to the normalized version
// before trying to resolve the page again
redirect(context.linker.toPathInSpace(pathname));
} else {
notFound();
}
} else if (getPagePath(context.revision.pages, pageTarget.page) !== rawPathname) {
redirect(
context.linker.toPathForPage({
pages: context.revision.pages,
page: pageTarget.page,
})
);
}
const { customization, sections } = context;
const { page, ancestors } = pageTarget;
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
const withFullPageCover = !!(
page.cover &&
page.layout.cover &&
page.layout.coverSize === 'full'
);
const withPageFeedback = customization.feedback.enabled;
const withSections = Boolean(sections && sections.list.length > 0);
const document = await getPageDocument(context, page);
return {
context,
page,
ancestors,
document,
withSections,
withPageFeedback,
withFullPageCover,
withTopHeader,
};
}
/**
* Fetches the page data matching the requested pathname and fallback to root page when page is not found.
*/
@@ -14,12 +14,11 @@ import { tcls } from '@/lib/tailwind';
import type { VisitorAuthClaims } from '@/lib/adaptive';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@/lib/env';
import { AIContextProvider } from '../AI';
import { AIChat } from '../AIChat';
import { Announcement } from '../Announcement';
import { SpacesDropdown } from '../Header/SpacesDropdown';
import { InsightsProvider } from '../Insights';
import { SearchContainer, SearchContextProvider } from '../Search';
import { SearchContainer } from '../Search';
import { SiteSectionList, encodeClientSiteSections } from '../SiteSections';
import { CurrentContentProvider } from '../hooks';
import { SpaceLayoutContextProvider } from './SpaceLayoutContext';
@@ -53,8 +52,6 @@ export function SpaceLayout(props: {
customization.footer.logo ||
customization.footer.groups?.length;
const aiMode = customization.ai?.mode;
const search = (
<div className="flex grow items-center gap-2">
<React.Suspense fallback={null}>
@@ -90,92 +87,79 @@ export function SpaceLayout(props: {
apiHost={GITBOOK_API_PUBLIC_URL}
visitorCookieTrackingEnabled={customization.insights?.trackingCookie}
>
<SearchContextProvider>
<AIContextProvider
aiMode={aiMode}
trademark={customization.trademark.enabled}
>
<Announcement context={context} />
<Header
withTopHeader={withTopHeader}
context={context}
search={search}
/>
{aiMode === CustomizationAIMode.Assistant ? (
<AIChat trademark={customization.trademark.enabled} />
) : null}
<div className="scroll-nojump">
<div className="motion-safe:transition-all motion-safe:duration-300 lg:chat-open:mr-80 xl:chat-open:mr-96">
<div
className={tcls(
'flex',
'flex-col',
'lg:flex-row',
CONTAINER_STYLE,
'site-width-wide:max-w-full',
<Announcement context={context} />
<Header withTopHeader={withTopHeader} context={context} search={search} />
{customization.ai?.mode === CustomizationAIMode.Assistant ? (
<AIChat trademark={customization.trademark.enabled} />
) : null}
<div className="scroll-nojump">
<div className="motion-safe:transition-all motion-safe:duration-300 lg:chat-open:mr-80 xl:chat-open:mr-96">
<div
className={tcls(
'flex',
'flex-col',
'lg:flex-row',
CONTAINER_STYLE,
'site-width-wide:max-w-full',
// Ensure the footer is display below the viewport even if the content is not enough
withFooter && 'min-h-[calc(100vh-64px)]',
withTopHeader ? null : 'lg:min-h-screen'
)}
>
<TableOfContents
context={context}
header={
withTopHeader ? null : (
<div
className={tcls(
'hidden',
'pr-4',
'md:flex',
'grow-0',
'flex-wrap',
'dark:shadow-light/1',
'text-base/tight'
)}
>
<HeaderLogo context={context} />
</div>
)
}
innerHeader={
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
<>
{!withTopHeader && search}
{!withTopHeader && withSections && sections && (
<SiteSectionList
className={tcls('hidden', 'lg:block')}
sections={encodeClientSiteSections(
context,
sections
)}
/>
// Ensure the footer is display below the viewport even if the content is not enough
withFooter && 'min-h-[calc(100vh-64px)]',
withTopHeader ? null : 'lg:min-h-screen'
)}
>
<TableOfContents
context={context}
header={
withTopHeader ? null : (
<div
className={tcls(
'hidden',
'pr-4',
'md:flex',
'grow-0',
'flex-wrap',
'dark:shadow-light/1',
'text-base/tight'
)}
>
<HeaderLogo context={context} />
</div>
)
}
innerHeader={
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
<>
{!withTopHeader && search}
{!withTopHeader && withSections && sections && (
<SiteSectionList
className={tcls('hidden', 'lg:block')}
sections={encodeClientSiteSections(
context,
sections
)}
{isMultiVariants && !sections && (
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
className={tcls(
'w-full',
'page-no-toc:hidden',
'page-no-toc:site-header-none:flex'
)}
/>
/>
)}
{isMultiVariants && !sections && (
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
className={tcls(
'w-full',
'page-no-toc:hidden',
'page-no-toc:site-header-none:flex'
)}
</>
}
/>
<div className="flex min-w-0 flex-1 flex-col">
{children}
</div>
</div>
</div>
/>
)}
</>
}
/>
<div className="flex min-w-0 flex-1 flex-col">{children}</div>
</div>
</div>
</div>
{withFooter ? <Footer context={context} /> : null}
</AIContextProvider>
</SearchContextProvider>
{withFooter ? <Footer context={context} /> : null}
</InsightsProvider>
</CurrentContentProvider>
</SpaceLayoutContextProvider>
@@ -15,6 +15,7 @@ export function TableOfContentsScript() {
const header = document.getElementById('site-header');
const banner = document.getElementById('announcement-banner');
const footer = document.getElementById('site-footer');
const pageCover = document.getElementById('page-cover');
// Set sticky top position based on header
const headerHeight = header?.offsetHeight ?? 0;
@@ -43,6 +44,28 @@ export function TableOfContentsScript() {
// Update height
root.style.setProperty('--toc-height', `${height}px`);
root.style.setProperty('--toc-top-offset', `${offset}px`);
// Subtract visible pageCover (if any)
if (
pageCover &&
pageCover.dataset.full === 'true' &&
window.getComputedStyle(pageCover).display !== 'none'
) {
const coverRect = pageCover.getBoundingClientRect();
if (coverRect.height > 0 && coverRect.bottom > 0) {
height -= Math.min(
coverRect.height,
Math.max(coverRect.bottom - headerHeight, 0)
);
offset += Math.min(
coverRect.height,
Math.max(coverRect.bottom - headerHeight, 0)
);
}
}
root.style.setProperty('--outline-height', `${height}px`);
root.style.setProperty('--outline-top-offset', `${offset}px`);
};
// Initial update
@@ -1,32 +1,48 @@
'use client';
import { tcls } from '@/lib/tailwind';
import * as Tooltip from '@radix-ui/react-tooltip';
import * as RadixHoverCard from '@radix-ui/react-hover-card';
export function HoverCardRoot(props: Tooltip.TooltipProps) {
return <Tooltip.Root delayDuration={200} {...props} />;
}
export function HoverCardTrigger(props: { children: React.ReactNode }) {
return <Tooltip.Trigger asChild>{props.children}</Tooltip.Trigger>;
}
export function HoverCard(props: {
children: React.ReactNode;
className?: string;
}) {
export function HoverCardRoot(props: RadixHoverCard.HoverCardProps) {
return (
<Tooltip.Portal>
<Tooltip.Content className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto">
<RadixHoverCard.Root
openDelay={props.openDelay ?? 200}
closeDelay={props.closeDelay ?? 100}
{...props}
/>
);
}
export function HoverCardTrigger(props: RadixHoverCard.HoverCardTriggerProps) {
return (
<RadixHoverCard.Trigger asChild {...props}>
{props.children}
</RadixHoverCard.Trigger>
);
}
export function HoverCard(
props: RadixHoverCard.HoverCardContentProps & { arrow?: RadixHoverCard.HoverCardArrowProps }
) {
const { arrow, ...cardProps } = props;
return (
<RadixHoverCard.Portal>
<RadixHoverCard.Content
side={props.side ?? 'top'}
className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto"
>
<div
className={tcls(
'overflow-hidden rounded-md straight-corners:rounded-none bg-tint-base shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1',
props.className
cardProps.className
)}
>
{props.children}
{cardProps.children}
</div>
<Tooltip.Arrow className="fill-tint-1" />
</Tooltip.Content>
</Tooltip.Portal>
<RadixHoverCard.Arrow
className={tcls('fill-tint-1', arrow?.className)}
{...arrow}
/>
</RadixHoverCard.Content>
</RadixHoverCard.Portal>
);
}
@@ -2,8 +2,6 @@
import { tcls } from '@/lib/tailwind';
import * as RadixTooltip from '@radix-ui/react-tooltip';
import { useState } from 'react';
import { useIsMobile } from '../hooks/useIsMobile';
export function Tooltip(props: {
children: React.ReactNode;
@@ -24,28 +22,9 @@ export function Tooltip(props: {
className,
} = props;
const isMobile = useIsMobile();
const [open, setOpen] = useState(false);
return (
<RadixTooltip.Root
delayDuration={300}
open={open}
onOpenChange={(e) => setOpen(e)}
{...rootProps}
>
<RadixTooltip.Trigger
asChild
onClick={(event) => {
event.preventDefault();
if (isMobile) {
setOpen(true);
}
setOpen(true);
}}
onPointerDown={(event) => event.preventDefault()}
{...triggerProps}
>
<RadixTooltip.Root delayDuration={300} {...rootProps}>
<RadixTooltip.Trigger asChild {...triggerProps}>
{children}
</RadixTooltip.Trigger>
<RadixTooltip.Portal>
@@ -55,12 +34,6 @@ export function Tooltip(props: {
'z-50 max-w-xs animate-scale-in circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-12 px-2 py-1 text-contrast-tint-12 text-sm',
className
)}
onPointerDownOutside={(event) => {
event.preventDefault();
if (isMobile) {
setOpen(false);
}
}}
{...contentProps}
>
{label}
@@ -12,3 +12,4 @@ export * from './Tooltip';
export * from './Popover';
export * from './LoadingStateProvider';
export * from './HoverCard';
export * from './DropdownMenu';
+52
View File
@@ -0,0 +1,52 @@
import { type RouteLayoutParams, getDynamicSiteContext, getStaticSiteContext } from '@/app/utils';
import type { GitBookSiteContext } from '@/lib/context';
import type { GitBookLinker } from '@/lib/links';
import { getPagePath } from '@/lib/pages';
import { joinPath } from '@/lib/paths';
/**
* Get the context for the embeddable static routes.
*/
export async function getEmbeddableStaticContext(params: RouteLayoutParams) {
const { context: baseContext, visitorAuthClaims } = await getStaticSiteContext(params);
const context: GitBookSiteContext = {
...baseContext,
linker: getEmbeddableLinker(baseContext.linker),
};
return {
context,
visitorAuthClaims,
};
}
/**
* Get the context for the embeddable dynamic routes.
*/
export async function getEmbeddableDynamicContext(params: RouteLayoutParams) {
const { context: baseContext, visitorAuthClaims } = await getDynamicSiteContext(params);
const context: GitBookSiteContext = {
...baseContext,
linker: getEmbeddableLinker(baseContext.linker),
};
return {
context,
visitorAuthClaims,
};
}
/**
* Get a linker to generate links in the embeddable context.
*/
export function getEmbeddableLinker(linker: GitBookLinker): GitBookLinker {
return {
...linker,
toPathForPage({ pages, page, anchor }) {
const pagePath = getPagePath(pages, page);
const embedPagePath = joinPath('~gitbook/embed/page', pagePath);
return linker.toPathInSpace(embedPagePath) + (anchor ? `#${anchor}` : '');
},
};
}
+11
View File
@@ -531,13 +531,24 @@ function encodePathInSiteContent(rawPathname: string): {
};
}
// If the pathname is an embedded page
const embedPage = pathname.match(/^~gitbook\/embed\/page\/(\S+)$/);
if (embedPage) {
return {
pathname: `~gitbook/embed/page/${encodeURIComponent(embedPage[1])}`,
};
}
switch (pathname) {
case '~gitbook/embed/assistant':
case '~gitbook/icon':
return { pathname };
case 'llms.txt':
case 'sitemap.xml':
case 'sitemap-pages.xml':
case 'robots.txt':
case '~gitbook/embed/script.js':
case '~gitbook/embed/assistant-demo':
// LLMs.txt, sitemap, sitemap-pages and robots.txt are always static
// as they only depend on the site structure / pages.
return { pathname, routeType: 'static' };
+6 -1
View File
@@ -2,7 +2,12 @@
"extends": ["//"],
"tasks": {
"generate": {
"outputs": ["public/~gitbook/static/icons/**/*", "public/~gitbook/static/math/**/*"]
"outputs": [
"public/~gitbook/static/icons/**/*",
"public/~gitbook/static/math/**/*",
"public/~gitbook/static/embed/**/*"
],
"dependsOn": ["^generate", "@gitbook/embed#build"]
},
"typecheck": {
"dependsOn": ["^typecheck", "generate"]
+1 -1
View File
@@ -22,7 +22,7 @@
"@fortawesome/fontawesome-free": "^6.6.0"
},
"optionalDependencies": {
"@gitbook/fontawesome-pro": "1.0.8"
"@gitbook/fontawesome-pro": "1.0.11"
},
"devDependencies": {
"typescript": "^5.5.3"