mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-25 20:03:16 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e24e412adb |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@gitbook/embed": minor
|
||||
---
|
||||
|
||||
Support `assistantName` property to override Assistant name
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix webframe height issue
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add backward compatibility for OpenAPI spec content URL
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
SearchInput fixes — Translate Clear button, Bold theme color, "Esc" keyboard shortcut
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix search container shadow
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Revert contentref anchor title
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@gitbook/openapi-parser": patch
|
||||
"@gitbook/react-openapi": patch
|
||||
---
|
||||
|
||||
Support x-enable-proxy at operation level
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Reword "Everywhere" search scope to "All docs"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Move the "Ask <query>" to the bottom of search results if query is not a question
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Improve AI Search branding
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Refactor embeddable context to merge local & site properties in one unified way
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add alt text support to card covers
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve ancestor label in references
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Tweak language dropdown styling
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Fix ToggeableLinkItem display
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix icon resolution for page group
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Support translations and generic variants together
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add confirmation to "Copy MCP URL" page action
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add padding to TOC page group items
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Update search scope UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Fix OpenAPI path display
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@gitbook/react-openapi": patch
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Move ~scalar/proxy route to site route group
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add OpenAPI servers selection
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/react-openapi': patch
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Improve OAuth2 scopes handling in OpenAPI
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@gitbook/react-openapi": patch
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add token verification on OpenAPI proxy
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix opacity of ScrollContainer buttons on bold themes
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Add support for localized site section titles
|
||||
@@ -30,7 +30,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
|
||||
|
||||
@@ -49,7 +49,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
|
||||
|
||||
@@ -61,7 +61,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
|
||||
|
||||
@@ -71,7 +71,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ inputs:
|
||||
description: 'Commit message to associate with the deployment'
|
||||
required: true
|
||||
outputs:
|
||||
deployment-url:
|
||||
deployment-url:
|
||||
description: "Deployment URL"
|
||||
value: ${{ steps.upload_middleware.outputs.deployment-url }}
|
||||
runs:
|
||||
@@ -52,8 +52,6 @@ runs:
|
||||
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
|
||||
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
|
||||
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
|
||||
GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL
|
||||
GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL
|
||||
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
|
||||
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
|
||||
@@ -72,7 +70,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: deploy --config ./packages/gitbook/openNext/customWorkers/doWrangler.jsonc
|
||||
|
||||
@@ -83,7 +81,7 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
|
||||
|
||||
@@ -106,10 +104,10 @@ runs:
|
||||
apiToken: ${{ inputs.apiToken }}
|
||||
accountId: ${{ inputs.accountId }}
|
||||
workingDirectory: ./
|
||||
wranglerVersion: '4.43.0'
|
||||
wranglerVersion: '4.10.0'
|
||||
environment: ${{ inputs.environment }}
|
||||
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
|
||||
|
||||
|
||||
- name: Extract middleware version worker ID
|
||||
shell: bash
|
||||
id: extract_middleware_version_id
|
||||
|
||||
@@ -20,7 +20,7 @@ inputs:
|
||||
description: 'Environment to deploy to'
|
||||
required: true
|
||||
outputs:
|
||||
deployment-url:
|
||||
deployment-url:
|
||||
description: "Deployment URL"
|
||||
value: ${{ steps.deploy.outputs.deployment-url }}
|
||||
runs:
|
||||
@@ -51,8 +51,6 @@ runs:
|
||||
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
|
||||
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
|
||||
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
|
||||
GITBOOK_OAUTH_SERVER_URL: ${{ inputs.opItem }}/GITBOOK_OAUTH_SERVER_URL
|
||||
GITBOOK_PREVIEW_BASE_URL: ${{ inputs.opItem }}/GITBOOK_PREVIEW_BASE_URL
|
||||
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
|
||||
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
|
||||
|
||||
@@ -5,6 +5,8 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- changeset-release/main
|
||||
env:
|
||||
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,10 +36,13 @@ jobs:
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
||||
- run: bun unit
|
||||
build:
|
||||
build-oss:
|
||||
# CI to check that the repository builds correctly on a machine without the credentials
|
||||
runs-on: ubuntu-latest
|
||||
name: Build
|
||||
name: Build (Open Source)
|
||||
timeout-minutes: 6
|
||||
env:
|
||||
BUN_NPM_TOKEN: ''
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
name: Preview
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
|
||||
jobs:
|
||||
approval:
|
||||
# Ensure that external contributors' PRs are manually approved before deployment
|
||||
name: Approve Preview Deployment
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.fork }}
|
||||
environment:
|
||||
name: preview-approval
|
||||
steps:
|
||||
- name: Approval gate
|
||||
run: echo "Preview deployment approved."
|
||||
deploy-v2-vercel:
|
||||
name: Deploy v2 to Vercel (preview)
|
||||
runs-on: ubuntu-latest
|
||||
needs: approval
|
||||
if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }}
|
||||
environment:
|
||||
name: 2v-preview
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
@@ -28,8 +18,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Deploy to Vercel
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-vercel
|
||||
@@ -43,8 +31,6 @@ jobs:
|
||||
deploy-v2-cloudflare:
|
||||
name: Deploy v2 to Cloudflare Worker (preview)
|
||||
runs-on: ubuntu-latest
|
||||
needs: approval
|
||||
if: ${{ always() && (needs.approval.result == 'success' || needs.approval.result == 'skipped') }}
|
||||
environment:
|
||||
name: 2c-preview
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
@@ -53,8 +39,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Deploy to Cloudflare
|
||||
id: deploy
|
||||
uses: ./.github/composite/deploy-cloudflare
|
||||
@@ -81,7 +65,7 @@ jobs:
|
||||
comment-deployments:
|
||||
runs-on: ubuntu-latest
|
||||
name: Comment Deployments (preview)
|
||||
if: always() && github.event_name != 'push'
|
||||
if: always() && !startsWith(github.ref, 'refs/heads/main')
|
||||
needs:
|
||||
- deploy-v2-vercel
|
||||
- deploy-v2-cloudflare
|
||||
@@ -118,13 +102,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing v2
|
||||
needs: deploy-v2-vercel
|
||||
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
@@ -136,20 +117,16 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||
SITE_PREVIEW_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/preview/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: v2-vercel
|
||||
ARGOS_BUILD_NAME: 'v2-vercel'
|
||||
visual-testing-v2-cloudflare:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing v2 (Cloudflare)
|
||||
needs: deploy-v2-cloudflare
|
||||
if: ${{ always() && needs.deploy-v2-cloudflare.result == 'success' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
@@ -161,20 +138,16 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
SITE_PREVIEW_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/preview/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: v2-cloudflare
|
||||
ARGOS_BUILD_NAME: 'v2-cloudflare'
|
||||
visual-testing-customers-v2:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2
|
||||
needs: deploy-v2-vercel
|
||||
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
@@ -186,20 +159,16 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||
SITE_PREVIEW_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/preview/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: customers-v2-vercel
|
||||
ARGOS_BUILD_NAME: 'customers-v2'
|
||||
visual-testing-customers-v2-cloudflare:
|
||||
runs-on: ubuntu-latest
|
||||
name: Visual Testing Customers v2 (Cloudflare)
|
||||
needs: deploy-v2-cloudflare
|
||||
if: ${{ always() && needs.deploy-v2-cloudflare.result == 'success' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
@@ -211,19 +180,15 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
SITE_PREVIEW_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/preview/
|
||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||
ARGOS_BUILD_NAME: customers-v2-cloudflare
|
||||
ARGOS_BUILD_NAME: 'customers-v2'
|
||||
browserless-testing-v2-vercel:
|
||||
runs-on: ubuntu-latest
|
||||
name: Browserless Testing v2 (Vercel)
|
||||
needs: deploy-v2-vercel
|
||||
if: ${{ always() && needs.deploy-v2-vercel.result == 'success' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
@@ -235,7 +200,6 @@ jobs:
|
||||
env:
|
||||
BASE_URL: ${{needs.deploy-v2-vercel.outputs.deployment-url}}
|
||||
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||
SITE_PREVIEW_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/preview/
|
||||
# browserless-testing-v2-cloudflare:
|
||||
# runs-on: ubuntu-latest
|
||||
# name: Browserless Testing v2 (Cloudflare)
|
||||
@@ -253,4 +217,4 @@ jobs:
|
||||
# run: cd ./packages/gitbook && bun e2e-browserless
|
||||
# env:
|
||||
# BASE_URL: ${{needs.deploy-v2-cloudflare.outputs.deployment-url}}
|
||||
# SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
# SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||
@@ -3,6 +3,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
|
||||
jobs:
|
||||
deploy-v2-vercel:
|
||||
name: Deploy v2 to Vercel (production)
|
||||
|
||||
@@ -3,6 +3,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN_READONLY }}
|
||||
jobs:
|
||||
deploy-v2-vercel:
|
||||
name: Deploy v2 to Vercel (staging)
|
||||
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
BUN_NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -11,38 +14,28 @@ jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||
fetch-depth: 0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
# Ensure npm 11.5.1 or later is installed
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
- name: Setup Bun
|
||||
uses: ./.github/composite/setup-bun
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
||||
- name: Create Release Pull Request
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
publish: bun run publish-all-packages
|
||||
version: bun run changeset-version
|
||||
publish: npm run release
|
||||
version: npm run changeset-version
|
||||
env:
|
||||
# Using a PAT instead of GITHUB_TOKEN because we need to run workflows when releases are created
|
||||
# https://github.com/orgs/community/discussions/26875#discussioncomment-3253761
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
|
||||
+1
-4
@@ -25,7 +25,4 @@ yarn-error.log*
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
.npmrc
|
||||
|
||||
# Bun pack artifacts
|
||||
packages/*/*.tgz
|
||||
.npmrc
|
||||
@@ -1,3 +0,0 @@
|
||||
# AGENTS Instructions
|
||||
|
||||
- After making code changes, run `bun format`.
|
||||
@@ -77,7 +77,23 @@ All pull-requests will be tested against both visual and performances testing to
|
||||
|
||||
## Fonts and Icons
|
||||
|
||||
GitBook Open uses fontawesome. For self-hosting and local development, for licensing reasons, only the icons from the free version should be used.
|
||||
GitBook Open uses fontawesome. During development, your local environment will use the free version. However, only the pro version will be accepted by CI. If you see the following error:
|
||||
|
||||
```
|
||||
The GitBook icon is missing. It indicates that the dependencies were installed without the correct font-awesome package. These changes have probably been persisted in the Bun lockfile. Read the README for more information.
|
||||
```
|
||||
|
||||
It means that you've changed the GBO dependencies and bundled in the free version. Only GitBook staff can help with this - if you're not on the GitBook team, please ping us in the PR and we'll help get things moving.
|
||||
|
||||
If you are GitBook staff, you'll need our NPM token in your local environment.
|
||||
|
||||
```
|
||||
.env.local
|
||||
|
||||
BUN_NPM_TOKEN=xxx
|
||||
```
|
||||
|
||||
and then reinstall dependencies.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 491 KiB After Width: | Height: | Size: 622 KiB |
@@ -0,0 +1,2 @@
|
||||
[install.scopes]
|
||||
"gitbook" = { token = "$BUN_NPM_TOKEN", url = "https://registry.npmjs.org" }
|
||||
+9
-8
@@ -6,11 +6,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.30.0",
|
||||
"turbo": "^2.8.13",
|
||||
"vercel": "^50.26.1"
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"turbo": "^2.5.8",
|
||||
"vercel": "^39.4.2"
|
||||
},
|
||||
"packageManager": "bun@1.3.7",
|
||||
"packageManager": "bun@1.3.0",
|
||||
"overrides": {
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@types/react": "catalog:",
|
||||
@@ -32,7 +32,8 @@
|
||||
"e2e-customers": "turbo run e2e-customers",
|
||||
"changeset": "changeset",
|
||||
"changeset-version": "changeset version && bun run format && bun update",
|
||||
"publish-all-packages": "turbo run publish-to-npm --continue=dependencies-successful",
|
||||
"release": "turbo run release && bun run publish-all-packages",
|
||||
"publish-all-packages": "for dir in packages/*; do (cd \"$dir\" && bun publish || true); done && changeset tag",
|
||||
"download:env": "op read op://gitbook-x-dev/gitbook-open/.env.local >> .env.local",
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
@@ -41,7 +42,7 @@
|
||||
"catalog": {
|
||||
"@tsconfig/strictest": "^2.0.6",
|
||||
"@tsconfig/node20": "^20.1.6",
|
||||
"@gitbook/api": "0.167.0",
|
||||
"@gitbook/api": "0.146.0",
|
||||
"@scalar/api-client-react": "^1.3.46",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
@@ -49,8 +50,8 @@
|
||||
"bidc": "^0.0.2",
|
||||
"bun-types": "^1.1.20",
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tsdown": "^0.15.6",
|
||||
"typescript": "^5.5.3",
|
||||
"usehooks-ts": "^3.1.1"
|
||||
|
||||
@@ -1,28 +1,5 @@
|
||||
# @gitbook/browser-types
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5f66860: Use isCookiesTrackingDisabled for cookie consent integrations
|
||||
- 2e495cb: Add Global Privacy Control (GPC) support
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7a11861: Add support for custom cookie banner registration
|
||||
- Updated dependencies [6f1db32]
|
||||
- @gitbook/icons@0.4.3
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
- Updated dependencies [10995e0]
|
||||
- @gitbook/icons@0.3.4
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.2",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/icons": "workspace:"
|
||||
@@ -22,15 +22,11 @@
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"dev": "bun run build -- --watch ./src"
|
||||
},
|
||||
"files": ["dist", "README.md", "CHANGELOG.md"],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,6 @@ export type GitBookAssistant = {
|
||||
ui?: boolean;
|
||||
};
|
||||
|
||||
export type GitBookCookieBannerHandler = (options: {
|
||||
onApprove: () => void;
|
||||
onReject: () => void;
|
||||
}) => void;
|
||||
|
||||
export type GitBookGlobal = {
|
||||
/**
|
||||
* Register an event listener.
|
||||
@@ -77,25 +72,6 @@ export type GitBookGlobal = {
|
||||
* Register a custom assistant to be available on the site.
|
||||
*/
|
||||
registerAssistant: (assistant: GitBookAssistant) => () => void;
|
||||
|
||||
/**
|
||||
* Register a custom cookie banner handler.
|
||||
* When registered, the built-in cookie banner will not be displayed.
|
||||
*/
|
||||
registerCookieBanner: (handler: GitBookCookieBannerHandler) => void;
|
||||
|
||||
/**
|
||||
* Indicates whether cookies tracking is disabled for the current user.
|
||||
* Returns `true` when tracking is disabled, `false` when tracking is enabled,
|
||||
* and `undefined` when the tracking preference is unknown or not yet determined.
|
||||
*/
|
||||
isCookiesTrackingDisabled: () => boolean | undefined;
|
||||
|
||||
/**
|
||||
* Indicates whether global privacy control is enabled for the current user.
|
||||
* Returns `true` when global privacy control is enabled, `false` when it is disabled.
|
||||
*/
|
||||
isGlobalPrivacyControlEnabled: () => boolean;
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @gitbook/cache-tags
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.3",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:",
|
||||
"assert-never": "catalog:"
|
||||
@@ -21,15 +21,11 @@
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"dev": "bun run build -- --watch ./src"
|
||||
},
|
||||
"files": ["dist", "README.md", "CHANGELOG.md"],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @gitbook/colors
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.4.3",
|
||||
"version": "0.4.2",
|
||||
"devDependencies": {
|
||||
"bun-types": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
@@ -17,15 +17,11 @@
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"dev": "bun run build -- --watch ./src"
|
||||
},
|
||||
"files": ["dist", "README.md", "CHANGELOG.md"],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,5 @@
|
||||
# @gitbook/embed
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 370b731: Docs Embed: Make trademark optional
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b4b471f: Fix unexpected error thrown when using the embed in standalone mode
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 96e24a1: Fix signed JWT token usage
|
||||
- 0aa34cc: Fix Embed options in React
|
||||
- Updated dependencies [6f1db32]
|
||||
- @gitbook/icons@0.4.3
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 1430cee: Improve Docs Embed with separate Assistant and Docs tabs
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [62438f4]
|
||||
- @gitbook/icons@0.4.0
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
- Updated dependencies [10995e0]
|
||||
- @gitbook/icons@0.3.4
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+8
-435
@@ -1,63 +1,24 @@
|
||||
# GitBook Docs Embed (`@gitbook/embed`)
|
||||
# `@gitbook/embed`
|
||||
|
||||
Embed your GitBook docs in your product or website.
|
||||
|
||||
The Docs Embed can contain two tabs:
|
||||
- **Assistant**: The [GitBook Assistant](https://gitbook.com/docs/publishing-documentation/gitbook-ai-assistant) - an AI-powered chat interface to help users find answers
|
||||
- **Docs**: A browser for navigating your documentation site
|
||||
|
||||
The embed is set up automatically based on your site's configuration. You can optionally customize and override the configuration with custom actions, tools, suggested questions, [Authenticated Access](https://gitbook.com/docs/publishing-documentation/authenticated-access), and more. See the [Configuration](#configuration) section for all available options.
|
||||
Embed the GitBook Docs Assistant in your product or website.
|
||||
|
||||
# Usage
|
||||
|
||||
## As a standalone script from your docs site
|
||||
## As a script from your docs site
|
||||
|
||||
All GitBook docs sites include a script to easily add the Docs Embed as a widget on your website.
|
||||
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 `docs.company.com` with your docs site hostname.
|
||||
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>
|
||||
// Initialize with Authenticated Access (optional)
|
||||
window.GitBook('init',
|
||||
{ siteURL: 'https://docs.company.com' },
|
||||
{ visitor: { token: 'your-jwt-token' } }
|
||||
);
|
||||
window.GitBook('show');
|
||||
</script>
|
||||
```
|
||||
|
||||
The standalone script provides a global `GitBook` function. See the [API Reference](#api-reference) section for all available methods.
|
||||
|
||||
### Example: Configuring the widget
|
||||
|
||||
```javascript
|
||||
GitBook('configure', {
|
||||
button: {
|
||||
label: 'Ask',
|
||||
icon: 'assistant' // 'assistant' | 'sparkle' | 'help' | 'book'
|
||||
},
|
||||
tabs: ['assistant', 'docs'],
|
||||
actions: [
|
||||
{
|
||||
icon: 'circle-question',
|
||||
label: 'Contact Support',
|
||||
onClick: () => window.open('https://support.example.com', '_blank')
|
||||
}
|
||||
],
|
||||
greeting: { title: 'Welcome!', subtitle: 'How can I help?' },
|
||||
assistantName: 'Support Assistant',
|
||||
suggestions: ['What is GitBook?', 'How do I get started?'],
|
||||
tools: [/* ... */],
|
||||
closeButton: true,
|
||||
});
|
||||
```
|
||||
|
||||
See the [Configuration](#configuration) section for all available options.
|
||||
|
||||
## As a package from NPM
|
||||
|
||||
Install the package: `npm install @gitbook/embed` and import it in your web application:
|
||||
@@ -69,48 +30,10 @@ const gitbook = createGitBook({
|
||||
siteURL: 'https://docs.company.com'
|
||||
});
|
||||
|
||||
// Create an iframe and get its URL
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.src = gitbook.getFrameURL({
|
||||
visitor: {
|
||||
token: 'your-jwt-token', // Optional: for Adaptive Content or Authenticated Access
|
||||
unsignedClaims: { // Optional: custom claims for dynamic expressions
|
||||
userId: '123',
|
||||
plan: 'premium'
|
||||
}
|
||||
}
|
||||
});
|
||||
iframe.src = gitbook.getFrameURL();
|
||||
|
||||
// Create a frame client to communicate with the iframe
|
||||
const frame = gitbook.createFrame(iframe);
|
||||
|
||||
// Use the frame client methods
|
||||
frame.navigateToPage('/getting-started'); // Navigate to a page in the docs tab
|
||||
frame.navigateToAssistant(); // Switch to the assistant tab
|
||||
frame.postUserMessage('How do I get started?');
|
||||
frame.clearChat();
|
||||
|
||||
// Configure the embed (see Configuration section for all options)
|
||||
frame.configure({
|
||||
tabs: ['assistant', 'docs'],
|
||||
actions: [
|
||||
{
|
||||
icon: 'circle-question',
|
||||
label: 'Contact Support',
|
||||
onClick: () => window.open('https://support.example.com', '_blank')
|
||||
}
|
||||
],
|
||||
greeting: { title: 'Welcome!', subtitle: 'How can I help?' },
|
||||
assistantName: 'Support Assistant',
|
||||
suggestions: ['What is GitBook?', 'How do I get started?'],
|
||||
tools: [/* ... */],
|
||||
closeButton: true
|
||||
});
|
||||
|
||||
// Listen to events
|
||||
frame.on('close', () => {
|
||||
console.log('Frame closed');
|
||||
});
|
||||
```
|
||||
|
||||
## As React components
|
||||
@@ -118,359 +41,9 @@ frame.on('close', () => {
|
||||
After installing the NPM package, you can import prebuilt React components:
|
||||
|
||||
```tsx
|
||||
import { GitBookProvider, GitBookFrame } from '@gitbook/embed/react';
|
||||
import { GitBookProvider, GitBookAssistantFrame } from '@gitbook/embed/react';
|
||||
|
||||
<GitBookProvider siteURL="https://docs.company.com">
|
||||
<GitBookFrame
|
||||
visitor={{
|
||||
token: 'your-jwt-token', // Optional: for Adaptive Content or Authenticated Access
|
||||
unsignedClaims: { userId: '123' } // Optional: custom claims for dynamic expressions
|
||||
}}
|
||||
tabs={['assistant', 'docs']}
|
||||
greeting={{ title: 'Welcome!', subtitle: 'How can I help?' }}
|
||||
assistantName="Support Assistant"
|
||||
suggestions={['What is GitBook?', 'How do I get started?']}
|
||||
actions={[
|
||||
{
|
||||
icon: 'circle-question',
|
||||
label: 'Contact Support',
|
||||
onClick: () => window.open('https://support.example.com', '_blank')
|
||||
}
|
||||
]}
|
||||
tools={[/* ... */]}
|
||||
closeButton
|
||||
/>
|
||||
<GitBookAssistantFrame />
|
||||
</GitBookProvider>
|
||||
```
|
||||
|
||||
You can also use the `useGitBook` hook to access the client:
|
||||
|
||||
```tsx
|
||||
import { useGitBook } from '@gitbook/embed/react';
|
||||
|
||||
function MyComponent() {
|
||||
const gitbook = useGitBook();
|
||||
const frameURL = gitbook.getFrameURL({ visitor: { token: '...' } });
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
# API Reference
|
||||
|
||||
## Method Comparison
|
||||
|
||||
| Method | Standalone Script | NPM Package | React Components |
|
||||
|--------|------------------|-------------|------------------|
|
||||
| **Initialize** | `GitBook('init', options, frameOptions)` | `createGitBook(options)` | `<GitBookProvider siteURL="...">` |
|
||||
| **Get frame URL** | ❌ (handled internally) | `client.getFrameURL(options)` | `useGitBook().getFrameURL(options)` |
|
||||
| **Create frame client** | ❌ (handled internally) | `client.createFrame(iframe)` | `useGitBook().createFrame(iframe)` |
|
||||
| **Show/Hide widget** | `GitBook('show')` / `GitBook('hide')` | ❌ | ❌ |
|
||||
| **Open/Close window** | `GitBook('open')` / `GitBook('close')` / `GitBook('toggle')` | ❌ | ❌ |
|
||||
| **Navigate to page** | `GitBook('navigateToPage', path)` | `frame.navigateToPage(path)` | Via frame client |
|
||||
| **Navigate to assistant** | `GitBook('navigateToAssistant')` | `frame.navigateToAssistant()` | Via frame client |
|
||||
| **Post message** | `GitBook('postUserMessage', message)` | `frame.postUserMessage(message)` | Via frame client |
|
||||
| **Clear chat** | `GitBook('clearChat')` | `frame.clearChat()` | Via frame client |
|
||||
| **Configure** | `GitBook('configure', settings)` | `frame.configure(settings)` | Props on `<GitBookFrame>` |
|
||||
| **Event listeners** | ❌ | `frame.on(event, listener)` | Via frame client |
|
||||
| **Unload** | `GitBook('unload')` | ❌ | ❌ |
|
||||
|
||||
## Method Signatures
|
||||
|
||||
### Standalone Script
|
||||
|
||||
- `GitBook('init', options: { siteURL: string }, frameOptions?: { visitor?: {...} })` - Initialize widget
|
||||
- `GitBook('show')` - Show widget button
|
||||
- `GitBook('hide')` - Hide widget button
|
||||
- `GitBook('open')` - Open widget window
|
||||
- `GitBook('close')` - Close widget window
|
||||
- `GitBook('toggle')` - Toggle widget window
|
||||
- `GitBook('navigateToPage', path: string)` - Navigate to page
|
||||
- `GitBook('navigateToAssistant')` - Navigate to assistant tab
|
||||
- `GitBook('postUserMessage', message: string)` - Post message to chat
|
||||
- `GitBook('clearChat')` - Clear chat history
|
||||
- `GitBook('configure', settings: {...})` - Configure widget
|
||||
- `GitBook('unload')` - Unload widget
|
||||
|
||||
### NPM Package
|
||||
|
||||
**Client Factory:**
|
||||
- `createGitBook(options: { siteURL: string })` → `GitBookClient`
|
||||
- `client.getFrameURL(options?: { visitor?: {...} })` → `string`
|
||||
- `client.createFrame(iframe: HTMLIFrameElement)` → `GitBookFrameClient`
|
||||
|
||||
**Frame Client:**
|
||||
- `frame.navigateToPage(path: string)` → `void`
|
||||
- `frame.navigateToAssistant()` → `void`
|
||||
- `frame.postUserMessage(message: string)` → `void`
|
||||
- `frame.clearChat()` → `void`
|
||||
- `frame.configure(settings: Partial<GitBookEmbeddableConfiguration>)` → `void`
|
||||
- `frame.on(event: string, listener: Function)` → `() => void` (unsubscribe)
|
||||
|
||||
### React Components
|
||||
|
||||
**Components:**
|
||||
- `<GitBookProvider siteURL: string>` - Provider component
|
||||
- `<GitBookFrame {...props}>` - Frame component (accepts all config options as props)
|
||||
|
||||
**Hooks:**
|
||||
- `useGitBook()` → `GitBookClient` (must be used within `<GitBookProvider>`)
|
||||
|
||||
# Configuration
|
||||
|
||||
Configuration options are available across usage methods as follows:
|
||||
- **Standalone script**: via `GitBook('configure', {...})`
|
||||
- **NPM package**: via `frame.configure({...})`
|
||||
- **React components**: via props on `<GitBookFrame>`
|
||||
|
||||
### `tabs`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Override which tabs are displayed. Defaults to your site's configuration.
|
||||
|
||||
- **Type**: `('assistant' | 'docs')[]`
|
||||
|
||||
```javascript
|
||||
tabs: ['assistant', 'docs']
|
||||
```
|
||||
|
||||
### `closeButton`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Display a close (`x`) button in the embed sidebar.
|
||||
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `false`
|
||||
|
||||
```javascript
|
||||
closeButton: true
|
||||
```
|
||||
|
||||
Behavior:
|
||||
- When clicked, the embed sends a `close` event to the parent page.
|
||||
- In the standalone script, this event is handled automatically and the widget closes.
|
||||
- In custom iframe integrations (NPM package), you must listen for the `close` event and decide how to hide/collapse your UI.
|
||||
- In React, this works automatically when using the standard widget flow. If you build custom frame wiring, handle `close` the same way as the NPM package.
|
||||
|
||||
NPM package example:
|
||||
|
||||
```typescript
|
||||
const frame = gitbook.createFrame(iframe);
|
||||
|
||||
frame.configure({
|
||||
closeButton: true
|
||||
});
|
||||
|
||||
const unsubscribe = frame.on('close', () => {
|
||||
// Hide your modal/drawer/container
|
||||
container.classList.add('hidden');
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The close button is rendered in the same sidebar area as tabs/actions.
|
||||
- If your UI hides that sidebar or doesn't render it, the button will not be visible.
|
||||
|
||||
### `trademark`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Display GitBook branding in the embed. Defaults to true.
|
||||
|
||||
- **Type**: `boolean`
|
||||
- **Default**: `true`
|
||||
|
||||
```javascript
|
||||
trademark: true
|
||||
```
|
||||
|
||||
### `actions`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Custom action buttons rendered in the sidebar alongside tabs. Each action button triggers a callback when clicked.
|
||||
|
||||
**Note**: This prop was previously named `buttons`. Use `actions` instead, it has the same functionality.
|
||||
|
||||
- **Type**: `GitBookEmbeddableActionDefinition[]`
|
||||
- **Properties**:
|
||||
- `icon`: `string` - Icon name. Any [FontAwesome icon](https://fontawesome.com/search) is supported. (e.g., `'rocket'`, `'comments'`, `'user-circle'`, ...)
|
||||
- `label`: `string` - Button label text
|
||||
- `onClick`: `() => void | Promise<void>` - Callback function when clicked
|
||||
|
||||
```javascript
|
||||
actions: [
|
||||
{
|
||||
icon: 'comments',
|
||||
label: 'Contact Support',
|
||||
onClick: () => window.open('https://support.example.com', '_blank')
|
||||
},
|
||||
{
|
||||
icon: 'rocket',
|
||||
label: 'Get started',
|
||||
onClick: () => {
|
||||
GitBook('navigateToPage', '/getting-started');
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `greeting`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Welcome message displayed in the [Assistant](https://gitbook.com/docs/publishing-documentation/gitbook-ai-assistant) tab.
|
||||
|
||||
- **Type**: `{ title: string, subtitle: string }`
|
||||
|
||||
```javascript
|
||||
greeting: {
|
||||
title: 'Welcome!',
|
||||
subtitle: 'How can I help you today?'
|
||||
}
|
||||
```
|
||||
|
||||
### `assistantName`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Override the assistant name displayed in the chat header and assistant entry points (for example, sidebar tabs and action labels). This name will be limited to 32 characters to prevent text overflow.
|
||||
|
||||
- **Type**: `string`
|
||||
|
||||
```javascript
|
||||
assistantName: 'Support Assistant'
|
||||
```
|
||||
|
||||
### `suggestions`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Suggested questions displayed in the [Assistant](https://gitbook.com/docs/publishing-documentation/gitbook-ai-assistant) welcome screen.
|
||||
|
||||
- **Type**: `string[]`
|
||||
|
||||
```javascript
|
||||
suggestions: [
|
||||
'What is GitBook?',
|
||||
'How do I get started?',
|
||||
'What can you do?'
|
||||
]
|
||||
```
|
||||
|
||||
### `tools`
|
||||
|
||||
Available in: Standalone script, NPM package, React components
|
||||
|
||||
Custom AI tools to extend the [Assistant](https://gitbook.com/docs/publishing-documentation/gitbook-ai-assistant). Tools allow the assistant to execute functions and integrate with your own systems.
|
||||
|
||||
**Note**: In addition to custom tools, the Assistant will always have access to any [MCP servers you define](https://gitbook.com/docs/publishing-documentation/gitbook-ai-assistant#extend-gitbook-assistant-with-mcp-servers) in your site's AI settings.
|
||||
|
||||
- **Type**: `GitBookToolDefinition[]`
|
||||
- **Properties**:
|
||||
- `name`: `string` - Unique tool identifier
|
||||
- `description`: `string` - Description of what the tool does (used by the AI to decide when and how to use it).
|
||||
- `inputSchema`: `object` - JSON schema defining the tool's input parameters
|
||||
- `execute`: `(input: object) => Promise<{ output: any, summary: string }>` - Async function that executes the tool.
|
||||
- `output`: The result of the tool execution, provided to the AI to continue working with. Not shown to the user.
|
||||
- `summary`: The visual summary of the tool execution, shown in the user's chat window.
|
||||
- `confirmation`: `{ icon?: string, label: string }` (optional) - Confirmation button shown before execution, useful for actions that require the user's express approval.
|
||||
|
||||
```javascript
|
||||
tools: [
|
||||
{
|
||||
name: 'get_user_info',
|
||||
description: 'Get information about the current user',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: {
|
||||
type: 'string',
|
||||
description: 'The user ID to look up'
|
||||
}
|
||||
},
|
||||
required: ['userId']
|
||||
},
|
||||
execute: async (input) => {
|
||||
const user = await fetch(`/api/users/${input.userId}`).then(r => r.json());
|
||||
return {
|
||||
output: { name: user.name, plan: user.plan },
|
||||
summary: `Retrieved info for user ${user.name}`
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'create_ticket',
|
||||
description: 'Create a support ticket',
|
||||
confirmation: {
|
||||
icon: 'circle-question',
|
||||
label: 'Create support ticket?'
|
||||
},
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
subject: { type: 'string' },
|
||||
description: { type: 'string' }
|
||||
},
|
||||
required: ['subject', 'description']
|
||||
},
|
||||
execute: async (input) => {
|
||||
const ticket = await fetch('/api/tickets', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input)
|
||||
}).then(r => r.json());
|
||||
return {
|
||||
output: { ticketId: ticket.id },
|
||||
summary: `Created ticket #${ticket.id}`
|
||||
};
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `visitor` (Authenticated Access)
|
||||
|
||||
Available in: Standalone script (via `init`), NPM package (via `getFrameURL()`), React components (as prop)
|
||||
|
||||
[Authenticated Access](https://gitbook.com/docs/publishing-documentation/authenticated-access) options passed when creating the frame URL. Used for [Adaptive Content](https://gitbook.com/docs/publishing-documentation/adaptive-content) and [Authenticated Access](https://gitbook.com/docs/publishing-documentation/authenticated-access).
|
||||
|
||||
**Note**: This is not a configuration option but rather a parameter when initializing the frame or creating the frame URL.
|
||||
|
||||
**Standalone script**: Pass as the second argument to `GitBook('init', options, frameOptions)`
|
||||
**NPM package**: Pass to `getFrameURL({ visitor: {...} })`
|
||||
**React components**: Pass as the `visitor` prop on `<GitBookFrame>`
|
||||
|
||||
- **Type**: `{ token?: string, unsignedClaims?: Record<string, unknown> }`
|
||||
- **Properties**:
|
||||
- `token`: `string` (optional) - Signed JWT token for [Adaptive Content](https://gitbook.com/docs/publishing-documentation/adaptive-content) or [Authenticated Access](https://gitbook.com/docs/publishing-documentation/authenticated-access)
|
||||
- `unsignedClaims`: `Record<string, unknown>` (optional) - Unsigned claims that can be used in dynamic expressions via `visitor.claims.unsigned.<claim-name>`
|
||||
|
||||
```javascript
|
||||
visitor: {
|
||||
token: 'your-jwt-token',
|
||||
unsignedClaims: {
|
||||
userId: '123',
|
||||
plan: 'premium',
|
||||
role: 'admin'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `button`
|
||||
|
||||
Available in: Standalone script only
|
||||
|
||||
Configure the widget button for the standalone script. This option is not available when using the NPM package or React components, since they can be customized completely.
|
||||
|
||||
- **Type**: `{ label: string, icon: 'assistant' | 'sparkle' | 'help' | 'book' }`
|
||||
- **Properties**:
|
||||
- `label`: `string` - Button label text
|
||||
- `icon`: `'assistant' | 'sparkle' | 'help' | 'book'` - Icon displayed on the button. Choose from one of 4 presets.
|
||||
|
||||
```javascript
|
||||
button: {
|
||||
label: 'Ask',
|
||||
icon: 'assistant'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.3.0",
|
||||
"version": "0.1.4",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:",
|
||||
"@gitbook/icons": "workspace:",
|
||||
@@ -23,7 +23,6 @@
|
||||
"react": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
@@ -34,17 +33,12 @@
|
||||
"build-lib": "tsdown",
|
||||
"build-standalone": "bun build src/standalone/index.ts --bundle --minify --outdir=standalone",
|
||||
"clean": "rm -rf ./dist",
|
||||
"unit": "bun test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"dev": "bun run build -- --watch ./src"
|
||||
},
|
||||
"files": ["dist", "README.md", "CHANGELOG.md"],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
|
||||
import { createGitBook } from './createGitBook';
|
||||
|
||||
describe('createGitBook.getFrameURL', () => {
|
||||
it('builds the embed URL when the site URL has no trailing slash', () => {
|
||||
const client = createGitBook({ siteURL: 'https://example.com/docs' });
|
||||
|
||||
const url = new URL(client.getFrameURL({}));
|
||||
|
||||
expect(url.origin).toBe('https://example.com');
|
||||
expect(url.pathname).toBe('/docs/~gitbook/embed');
|
||||
expect(url.searchParams.toString()).toBe('');
|
||||
});
|
||||
|
||||
it('handles a trailing slash and adds visitor parameters', () => {
|
||||
const client = createGitBook({ siteURL: 'https://example.com/docs/' });
|
||||
|
||||
const url = new URL(
|
||||
client.getFrameURL({
|
||||
visitor: {
|
||||
token: 'signed-token',
|
||||
unsignedClaims: {
|
||||
role: 'editor',
|
||||
count: 3,
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(url.pathname).toBe('/docs/~gitbook/embed');
|
||||
expect(url.searchParams.get('jwt_token')).toBe('signed-token');
|
||||
expect(url.searchParams.get('visitor.role')).toBe('editor');
|
||||
expect(url.searchParams.get('visitor.count')).toBe('3');
|
||||
expect(url.searchParams.get('visitor.enabled')).toBe('false');
|
||||
});
|
||||
});
|
||||
@@ -40,10 +40,10 @@ 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`;
|
||||
url.pathname = `${url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/`}~gitbook/embed/assistant`;
|
||||
|
||||
if (frameOptions.visitor?.token) {
|
||||
url.searchParams.set('jwt_token', frameOptions.visitor.token);
|
||||
url.searchParams.set('token', frameOptions.visitor.token);
|
||||
}
|
||||
|
||||
if (frameOptions.visitor?.unsignedClaims) {
|
||||
|
||||
@@ -64,12 +64,10 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien
|
||||
const events = new Map<string, Array<(...args: any[]) => void>>();
|
||||
|
||||
const configuration: GitBookEmbeddableConfiguration = {
|
||||
tabs: ['assistant', 'docs'],
|
||||
actions: [],
|
||||
greeting: { title: '', subtitle: '' },
|
||||
buttons: [],
|
||||
welcomeMessage: '',
|
||||
suggestions: [],
|
||||
tools: [],
|
||||
trademark: true,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,7 +23,7 @@ export type GitBookToolDefinition = AIToolDefinition & {
|
||||
/**
|
||||
* Custom button definition to be passed to the embeddable GitBook.
|
||||
*/
|
||||
export type GitBookEmbeddableActionDefinition = {
|
||||
export type GitBookEmbeddableButtonDefinition = {
|
||||
/**
|
||||
* Icon to be displayed in the button.
|
||||
*/
|
||||
@@ -41,48 +41,22 @@ export type GitBookEmbeddableActionDefinition = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Overall configuration for the layout of the GitBook embed.
|
||||
* Overall configuration for the layout of the embeddable GitBook.
|
||||
*/
|
||||
export type GitBookEmbeddableConfiguration = {
|
||||
/** Tabs to display in the embed (if enabled on the site). */
|
||||
tabs: ('assistant' | 'docs')[];
|
||||
|
||||
/** Additional buttons to be displayed in the header of the GitBook embed. */
|
||||
actions: GitBookEmbeddableActionDefinition[];
|
||||
|
||||
/**
|
||||
* Additional buttons to be displayed in the header of the GitBook embed.
|
||||
* @deprecated Use `actions` instead.
|
||||
* Buttons to be displayed in the header of the embeddable GitBook.
|
||||
*/
|
||||
buttons?: GitBookEmbeddableActionDefinition[];
|
||||
buttons: GitBookEmbeddableButtonDefinition[];
|
||||
|
||||
/** Message to be displayed in the welcome page. */
|
||||
greeting: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the assistant name displayed in the UI.
|
||||
* Limited to 32 characters.
|
||||
*/
|
||||
assistantName?: string;
|
||||
welcomeMessage: string;
|
||||
|
||||
/** Suggestions of questions to be displayed in the welcome page. */
|
||||
suggestions: string[];
|
||||
|
||||
/** Tools to be provided to the assistant. */
|
||||
tools: GitBookToolDefinition[];
|
||||
|
||||
/**
|
||||
* Display GitBook branding in the embed.
|
||||
*/
|
||||
trademark?: boolean;
|
||||
|
||||
/**
|
||||
* Display a close button inside the assistant.
|
||||
*/
|
||||
closeButton?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './client';
|
||||
export * from './standalone';
|
||||
|
||||
@@ -11,24 +11,13 @@ import { useGitBook } from './GitBookProvider';
|
||||
export type GitBookFrameProps = {
|
||||
className?: string;
|
||||
} & GetFrameURLOptions &
|
||||
Partial<GitBookEmbeddableConfiguration>;
|
||||
GitBookEmbeddableConfiguration;
|
||||
|
||||
/**
|
||||
* Render a frame with the GitBook Assistant in it.
|
||||
*/
|
||||
export function GitBookFrame(props: GitBookFrameProps) {
|
||||
const {
|
||||
className,
|
||||
visitor,
|
||||
actions = [],
|
||||
greeting,
|
||||
suggestions = [],
|
||||
tools = [],
|
||||
tabs = ['assistant', 'docs'],
|
||||
trademark = true,
|
||||
closeButton = false,
|
||||
assistantName,
|
||||
} = props;
|
||||
const { className, visitor, buttons, welcomeMessage, suggestions, tools } = props;
|
||||
|
||||
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||
const gitbook = useGitBook();
|
||||
@@ -44,26 +33,12 @@ export function GitBookFrame(props: GitBookFrameProps) {
|
||||
|
||||
useEffect(() => {
|
||||
gitbookFrame?.configure({
|
||||
tabs,
|
||||
actions,
|
||||
greeting,
|
||||
buttons,
|
||||
welcomeMessage,
|
||||
suggestions,
|
||||
tools,
|
||||
closeButton,
|
||||
trademark,
|
||||
assistantName,
|
||||
});
|
||||
}, [
|
||||
gitbookFrame,
|
||||
actions,
|
||||
greeting,
|
||||
suggestions,
|
||||
tools,
|
||||
tabs,
|
||||
closeButton,
|
||||
trademark,
|
||||
assistantName,
|
||||
]);
|
||||
}, [gitbookFrame, buttons, welcomeMessage, suggestions, tools]);
|
||||
|
||||
return (
|
||||
<iframe
|
||||
|
||||
@@ -31,51 +31,24 @@ type StandaloneCalls =
|
||||
// Clear the chat
|
||||
| ['clearChat']
|
||||
// Configure the embed
|
||||
| ['configure', Partial<GitBookEmbeddableConfiguration & StandaloneConfiguration>]
|
||||
| ['configure', Partial<GitBookEmbeddableConfiguration>]
|
||||
// Navigate to a page
|
||||
| ['navigateToPage', string]
|
||||
// Navigate to the assistant
|
||||
| ['navigateToAssistant'];
|
||||
|
||||
type StandaloneConfiguration = {
|
||||
/** Configure the button to open the embed */
|
||||
button: {
|
||||
/** Label to be displayed in the button. */
|
||||
label: string;
|
||||
/** Icon to be displayed in the button. */
|
||||
icon: 'assistant' | 'sparkle' | 'help' | 'book';
|
||||
};
|
||||
};
|
||||
|
||||
export type GitBookStandalone = ((...args: StandaloneCalls) => void) & {
|
||||
q?: StandaloneCalls[];
|
||||
};
|
||||
|
||||
let widgetIframe: HTMLIFrameElement | undefined;
|
||||
let _client: GitBookClient | undefined;
|
||||
let _frame: GitBookFrameClient | undefined;
|
||||
let frameOptions: GetFrameURLOptions | undefined;
|
||||
let frameConfiguration: GitBookEmbeddableConfiguration & StandaloneConfiguration = {
|
||||
button: {
|
||||
label: 'Ask',
|
||||
icon: 'assistant',
|
||||
},
|
||||
actions: [],
|
||||
greeting: { title: '', subtitle: '' },
|
||||
suggestions: [],
|
||||
tools: [],
|
||||
tabs: ['assistant', 'docs'],
|
||||
trademark: true,
|
||||
};
|
||||
|
||||
const widgetButton = document.createElement('button');
|
||||
widgetButton.id = 'gitbook-widget-button';
|
||||
widgetButton.addEventListener('click', () => {
|
||||
GitBook('toggle');
|
||||
});
|
||||
widgetButton.innerHTML = `
|
||||
<span id="gitbook-widget-button-icon" data-icon="${frameConfiguration.button.icon}"></span>
|
||||
<span id="gitbook-widget-button-label">${frameConfiguration.button.label}</span>
|
||||
<span id="gitbook-widget-button-icon"></span>
|
||||
<span id="gitbook-widget-button-label">Ask</span>
|
||||
`;
|
||||
|
||||
const widgetWindow = document.createElement('div');
|
||||
@@ -85,6 +58,17 @@ 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;
|
||||
let frameConfiguration: GitBookEmbeddableConfiguration = {
|
||||
buttons: [],
|
||||
welcomeMessage: '',
|
||||
suggestions: [],
|
||||
tools: [],
|
||||
};
|
||||
|
||||
function getClient() {
|
||||
if (!_client) {
|
||||
throw new Error(
|
||||
@@ -107,10 +91,6 @@ function getIframe() {
|
||||
widgetWindow.appendChild(widgetIframe);
|
||||
|
||||
_frame = client.createFrame(widgetIframe);
|
||||
_frame.on('close', () => {
|
||||
widgetWindow.classList.add('hidden');
|
||||
widgetButton.classList.remove('open');
|
||||
});
|
||||
}
|
||||
return { iframe: widgetIframe, frame: _frame };
|
||||
}
|
||||
@@ -155,44 +135,27 @@ const GitBook = (...args: StandaloneCalls) => {
|
||||
case 'postUserMessage':
|
||||
getIframe().frame.postUserMessage(args[1]);
|
||||
break;
|
||||
case 'configure': {
|
||||
const settings = args[1];
|
||||
|
||||
// If trademark is disabled, change the (branded) icon to the sparkle icon
|
||||
if (
|
||||
settings.trademark === false &&
|
||||
!settings.button?.icon &&
|
||||
frameConfiguration.button.icon === 'assistant'
|
||||
) {
|
||||
settings.button = {
|
||||
label: frameConfiguration.button.label,
|
||||
icon: 'sparkle',
|
||||
};
|
||||
}
|
||||
|
||||
case 'configure':
|
||||
frameConfiguration = {
|
||||
...frameConfiguration,
|
||||
...settings,
|
||||
...args[1],
|
||||
};
|
||||
// Update the button label and icon
|
||||
if (settings.button?.label) {
|
||||
const label = widgetButton.querySelector('#gitbook-widget-button-label');
|
||||
if (label) {
|
||||
label.textContent = settings.button.label;
|
||||
}
|
||||
}
|
||||
if (settings.button?.icon) {
|
||||
const icon = widgetButton.querySelector('#gitbook-widget-button-icon');
|
||||
if (icon) {
|
||||
icon.setAttribute('data-icon', settings.button.icon);
|
||||
}
|
||||
}
|
||||
|
||||
getIframe().frame.configure({
|
||||
...frameConfiguration,
|
||||
buttons: [
|
||||
...frameConfiguration.buttons,
|
||||
|
||||
// Always include a close button
|
||||
{
|
||||
icon: 'close',
|
||||
label: 'Close',
|
||||
onClick: () => {
|
||||
GitBook('close');
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'clearChat':
|
||||
getIframe().frame.clearChat();
|
||||
break;
|
||||
@@ -211,3 +174,5 @@ 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));
|
||||
|
||||
GitBook('configure', {});
|
||||
|
||||
@@ -29,16 +29,6 @@
|
||||
--gitbook-widget-easing-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--gitbook-widget-text-color: #FFFFFF;
|
||||
--gitbook-widget-border-color: #202020;
|
||||
--gitbook-widget-background-translucent: rgba(15, 15, 15, 0.9);
|
||||
--gitbook-widget-background-translucent-hover: rgba(20, 20, 20, 0.9);
|
||||
--gitbook-widget-background-solid: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -113,18 +103,6 @@
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
#gitbook-widget-button-icon[data-icon="sparkle"] {
|
||||
mask-image: url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/regular/sparkle.svg?v=2&token=a463935e93");
|
||||
}
|
||||
|
||||
#gitbook-widget-button-icon[data-icon="help"] {
|
||||
mask-image: url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/regular/circle-question.svg?v=2&token=a463935e93");
|
||||
}
|
||||
|
||||
#gitbook-widget-button-icon[data-icon="book"] {
|
||||
mask-image: url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/regular/book-open.svg?v=2&token=a463935e93");
|
||||
}
|
||||
|
||||
#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');
|
||||
}
|
||||
@@ -142,8 +120,7 @@
|
||||
z-index: 9998;
|
||||
width: calc(min(var(--gitbook-widget-window-width), calc(100vw - var(--gitbook-widget-right) - var(--gitbook-widget-left))));
|
||||
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-translucent);
|
||||
backdrop-filter: blur(48px);
|
||||
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);
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"types": [
|
||||
"bun-types" // add Bun global
|
||||
]
|
||||
"types": []
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# @gitbook/expr
|
||||
|
||||
## 1.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
- 10995e0: Use NPM dependency for eval-estree-expression
|
||||
|
||||
## 1.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@gitbook/expr",
|
||||
"description": "Safely evaluate & parse user-defined GitBook expressions.",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.3",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -11,12 +11,12 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-loose": "^8.5.2",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn-loose": "8.4.0",
|
||||
"acorn-walk": "^8.3.4",
|
||||
"assert-never": "catalog:",
|
||||
"escodegen": "^2.1.0",
|
||||
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#fb0246a"
|
||||
"eval-estree-expression": "github:jonschlinkert/eval-estree-expression#9cf28d2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/strictest": "catalog:",
|
||||
@@ -33,15 +33,11 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"unit": "bun test",
|
||||
"clean": "rm -rf ./dist",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"dev": "bun run build -- --watch ./src"
|
||||
},
|
||||
"files": ["dist", "README.md", "CHANGELOG.md"],
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# @gitbook/fonts
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.2",
|
||||
"dependencies": {
|
||||
"@gitbook/api": "catalog:"
|
||||
},
|
||||
@@ -24,8 +24,7 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "bun run build -- --watch ./src",
|
||||
"clean": "rm -rf ./dist && rm -rf ./src/data",
|
||||
"unit": "bun test",
|
||||
"publish-to-npm": "../../scripts/publish-if-new.sh"
|
||||
"unit": "bun test"
|
||||
},
|
||||
"files": ["dist", "bin", "README.md", "CHANGELOG.md"],
|
||||
"engines": {
|
||||
@@ -34,8 +33,5 @@
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/GitbookIO/gitbook"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,211 +1,5 @@
|
||||
# gitbook
|
||||
|
||||
## 0.22.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e074283: Add server-side proxy for Scalar API client to bypass CORS
|
||||
- afa868e: Show "Ask Assistant" search result immediately
|
||||
- 0803db0: Track opening of search results for records and in the assistant.
|
||||
- cb71511: Wrap tracking call with waitUntil
|
||||
- 4a6f91a: Show social accounts in header
|
||||
- 370b731: Docs Embed: Make trademark optional
|
||||
- afa476c: Add server-side insight event tracking for MCP route
|
||||
- Updated dependencies [e074283]
|
||||
- Updated dependencies [370b731]
|
||||
- @gitbook/openapi-parser@3.0.9
|
||||
- @gitbook/react-openapi@1.5.9
|
||||
- @gitbook/embed@0.3.0
|
||||
|
||||
## 0.22.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d5e397: Remove code syntax highlighting patch for Safari
|
||||
- 3d8e89c: Show favicon for record search results in the assistant.
|
||||
- 5f66860: Use isCookiesTrackingDisabled for cookie consent integrations
|
||||
- 426aabe: Fix TOC header logo on bold themes
|
||||
- ae30a57: Integrate Ask AI with upcoming insights by passing visitor session IDs.
|
||||
- f7c251e: Update Reddit social account rendering
|
||||
- e31a8f4: Hide cookie banner for AI user agents
|
||||
- 2e495cb: Add Global Privacy Control (GPC) support
|
||||
- Updated dependencies [1a7ef78]
|
||||
- Updated dependencies [93eea0b]
|
||||
- Updated dependencies [b4b471f]
|
||||
- Updated dependencies [5f66860]
|
||||
- Updated dependencies [2ebb18d]
|
||||
- Updated dependencies [11d9b80]
|
||||
- Updated dependencies [2e495cb]
|
||||
- Updated dependencies [f517035]
|
||||
- @gitbook/react-openapi@1.5.8
|
||||
- @gitbook/embed@0.2.2
|
||||
- @gitbook/browser-types@0.1.5
|
||||
|
||||
## 0.22.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- e00d1c5: Support code themes customization
|
||||
- f294818: Serve a markdown version of the page when the "Accept" header contains "text/markdown".
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d6457f: OpenAPI styling tweaks
|
||||
- d738e11: Prevent form submission during IME composition
|
||||
- 1e53376: Add sidesheet component, use it for TOC and AIChat
|
||||
- f7f6f9d: Set <html lang> based on translated language
|
||||
- 89de966: Support social links
|
||||
- b53cb1d: Align styling of PageLinkItem and PageDocumentItem
|
||||
- 27a34b7: Support `defaultExpanded` in Expandable block
|
||||
- a5ef44a: Better conditional display of page actions
|
||||
- 07c02eb: Header & TOC tweaks
|
||||
- 29e2b22: Fix ordered list item index calculation
|
||||
- f6cf62a: Standardise toggle chevrons across the app
|
||||
- b57561d: Fix rendering of images in reusable content from an external space with the new dereferencing in the API.
|
||||
- e172b25: Grow InlineActionButton to take available width on line
|
||||
- 7a11861: Add support for custom cookie banner registration
|
||||
- 8f32f4f: Better handle huge section groups
|
||||
- 0aa34cc: Fix Embed options in React
|
||||
- Updated dependencies [e00d1c5]
|
||||
- Updated dependencies [96e24a1]
|
||||
- Updated dependencies [4e2d863]
|
||||
- Updated dependencies [d3f6c38]
|
||||
- Updated dependencies [3ba9e46]
|
||||
- Updated dependencies [717ad0b]
|
||||
- Updated dependencies [6f1db32]
|
||||
- Updated dependencies [7a11861]
|
||||
- Updated dependencies [0aa34cc]
|
||||
- Updated dependencies [48ca21e]
|
||||
- @gitbook/react-openapi@1.5.7
|
||||
- @gitbook/embed@0.2.1
|
||||
- @gitbook/openapi-parser@3.0.8
|
||||
- @gitbook/icons@0.4.3
|
||||
- @gitbook/browser-types@0.1.4
|
||||
- @gitbook/react-contentkit@0.7.12
|
||||
|
||||
## 0.21.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [bf63045]
|
||||
- @gitbook/react-openapi@1.5.6
|
||||
|
||||
## 0.21.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ca68236: Fix 404 when accessing rss.xml for the root page.
|
||||
- 21851d3: Round page cover corners on extrawide screens
|
||||
- 695e99f: Update button sizing
|
||||
- 7517e98: Clean up button and input across the app
|
||||
- 99d788e: Update page cover styling
|
||||
- ebf3b2b: Fix an issue where content refs to spaces outside of site was resolving with share links
|
||||
- 35efc9d: Fix TOCPageIcons backgrounds on gradient and bold themes
|
||||
- a7f58ab: Fix embed tab button size regression
|
||||
- 360b525: Switch icon masking method to fix visual cutoffs in FA7
|
||||
- Updated dependencies [985c1b4]
|
||||
- Updated dependencies [360b525]
|
||||
- @gitbook/openapi-parser@3.0.7
|
||||
- @gitbook/icons@0.4.2
|
||||
- @gitbook/react-openapi@1.5.5
|
||||
- @gitbook/react-contentkit@0.7.11
|
||||
|
||||
## 0.21.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e7fb771]
|
||||
- @gitbook/icons@0.4.1
|
||||
- @gitbook/react-contentkit@0.7.10
|
||||
|
||||
## 0.21.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3553db0: Add a RSS feed to all pages with update blocks.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f478ddc: Add Input component
|
||||
- 3817289: Fix overflowing section groups
|
||||
- 86e7aa6: Fix search results cursor
|
||||
- 1430cee: Improve Docs Embed with separate Assistant and Docs tabs
|
||||
- ed9e8fe: Add support for button actions
|
||||
- 9c69cc4: Add customizable suggested questions
|
||||
- c51076e: Fix links to other spaces and root page in embeddable view.
|
||||
- 6278a18: Support `greeting` and fix suggested questions if there are no custom ones defined
|
||||
- 04a4175: Remove hidden site spaces from navigation
|
||||
- 74f8931: Fix hidden section not found
|
||||
- 344842f: Improve OpenAPI circular references
|
||||
- Updated dependencies [4766092]
|
||||
- Updated dependencies [8761cee]
|
||||
- Updated dependencies [461e15f]
|
||||
- Updated dependencies [1430cee]
|
||||
- Updated dependencies [62438f4]
|
||||
- Updated dependencies [3e40b4d]
|
||||
- Updated dependencies [87d68ea]
|
||||
- Updated dependencies [344842f]
|
||||
- @gitbook/react-openapi@1.5.4
|
||||
- @gitbook/openapi-parser@3.0.6
|
||||
- @gitbook/embed@0.2.0
|
||||
- @gitbook/icons@0.4.0
|
||||
- @gitbook/react-contentkit@0.7.9
|
||||
|
||||
## 0.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 1d92dc1: Update site layout and blocks with container queries and better transitions
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8fdc5c8: Fix webframe height issue
|
||||
- 9022d87: Add breadcrumbs to search results
|
||||
- 0c01832: SearchInput fixes — Translate Clear button, Bold theme color, "Esc" keyboard shortcut
|
||||
- 1d1f0a7: Fix search container shadow
|
||||
- 2aa4f2e: Update dropdown menu styling
|
||||
- a49781e: Printable collapsed code block
|
||||
- 7d6a316: Revert contentref anchor title
|
||||
- e27986b: Reword "Everywhere" search scope to "All docs"
|
||||
- 758b34d: Move the "Ask <query>" to the bottom of search results if query is not a question
|
||||
- 0ae7cfd: Improve AI Search branding
|
||||
- f9f8011: Add alt text support to card covers
|
||||
- ebbb476: Improve ancestor label in references
|
||||
- 1b0613e: Tweak language dropdown styling
|
||||
- 3355960: Fix ToggeableLinkItem display
|
||||
- c31e564: Fix icon resolution for page group
|
||||
- 55c0b03: Support translations and generic variants together
|
||||
- 3876f70: Edgeless Assistant window on `depth: flat` sites
|
||||
- 683bb8f: Add confirmation to "Copy MCP URL" page action
|
||||
- 4534ea8: Implement unique title tags for sections & variants
|
||||
- 6d56df7: Treat pages without visible TOC items as `no-toc`
|
||||
- 8606f22: Add padding to TOC page group items
|
||||
- 3676d64: Update search scope UI
|
||||
- c32d449: Fix OpenAPI path display
|
||||
- 8ce7322: Add OpenAPI servers selection
|
||||
- 2c3066e: Improve OAuth2 scopes handling in OpenAPI
|
||||
- 6b1c9fe: Fix opacity of ScrollContainer buttons on bold themes
|
||||
- Updated dependencies [b4a021a]
|
||||
- Updated dependencies [a512c90]
|
||||
- Updated dependencies [df1966d]
|
||||
- Updated dependencies [b45feaf]
|
||||
- Updated dependencies [10995e0]
|
||||
- Updated dependencies [f9f8011]
|
||||
- Updated dependencies [10995e0]
|
||||
- Updated dependencies [8ce7322]
|
||||
- Updated dependencies [2c3066e]
|
||||
- @gitbook/react-openapi@1.5.3
|
||||
- @gitbook/openapi-parser@3.0.5
|
||||
- @gitbook/react-contentkit@0.7.8
|
||||
- @gitbook/browser-types@0.1.3
|
||||
- @gitbook/cache-tags@0.3.4
|
||||
- @gitbook/react-math@0.6.4
|
||||
- @gitbook/colors@0.4.3
|
||||
- @gitbook/embed@0.1.5
|
||||
- @gitbook/fonts@0.1.3
|
||||
- @gitbook/icons@0.3.4
|
||||
- @gitbook/expr@1.2.4
|
||||
|
||||
## 0.19.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { type TestsCase, getCustomizationURL, runTestCases } from './util';
|
||||
|
||||
const testCases: TestsCase[] = [
|
||||
{
|
||||
name: 'Cookie Banner',
|
||||
contentBaseURL: 'https://gitbook.com/docs/',
|
||||
tests: [
|
||||
{
|
||||
name: 'should show built-in banner when no custom banner is registered',
|
||||
url: getCustomizationURL({
|
||||
privacyPolicy: {
|
||||
url: 'https://policies.gitbook.com/privacy/cookies',
|
||||
},
|
||||
}),
|
||||
screenshot: false,
|
||||
run: async (page) => {
|
||||
// Check that built-in banner is visible
|
||||
const dialog = page.getByTestId('cookies-dialog');
|
||||
await expect(dialog).toBeVisible({ timeout: 5000 });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not show built-in banner when UserAgent is AI',
|
||||
url: getCustomizationURL({
|
||||
privacyPolicy: {
|
||||
url: 'https://policies.gitbook.com/privacy/cookies',
|
||||
},
|
||||
}),
|
||||
screenshot: false,
|
||||
run: async (page) => {
|
||||
// Override navigator.userAgent to simulate AI crawler (affects isAIUserAgent())
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'userAgent', {
|
||||
get: () =>
|
||||
'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot',
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
await page.reload();
|
||||
|
||||
// Check that built-in banner is not visible for AI UserAgent
|
||||
const dialog = page.getByTestId('cookies-dialog');
|
||||
await expect(dialog).not.toBeVisible({ timeout: 5000 });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not show built-in banner when custom banner is registered',
|
||||
url: getCustomizationURL({
|
||||
privacyPolicy: {
|
||||
url: 'https://policies.gitbook.com/privacy/cookies',
|
||||
},
|
||||
}),
|
||||
screenshot: false,
|
||||
run: async (page) => {
|
||||
// Register a custom cookie banner handler
|
||||
await page.waitForFunction(() => {
|
||||
return typeof window !== 'undefined' && window.GitBook !== undefined;
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
window.GitBook?.registerCookieBanner(() => {
|
||||
// Custom cookie banner handler - no-op for testing to avoid reload
|
||||
});
|
||||
});
|
||||
|
||||
// Check that built-in banner is not visible
|
||||
const dialog = page.getByTestId('cookies-dialog');
|
||||
await expect(dialog).not.toBeVisible({ timeout: 5000 });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
runTestCases(testCases);
|
||||
@@ -10,18 +10,18 @@ const testCases: TestsCase[] = [
|
||||
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Nexthink',
|
||||
contentBaseURL: 'https://docs.nexthink.com',
|
||||
tests: [
|
||||
{
|
||||
name: 'Home',
|
||||
url: '/',
|
||||
screenshot: { waitForTOCScrolling: false },
|
||||
run: waitForCookiesDialog,
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: 'Nexthink',
|
||||
// contentBaseURL: 'https://docs.nexthink.com',
|
||||
// tests: [
|
||||
// {
|
||||
// name: 'Home',
|
||||
// url: '/',
|
||||
// screenshot: { waitForTOCScrolling: false },
|
||||
// run: waitForCookiesDialog,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: 'asiksupport-stg.dto.kemkes.go.id',
|
||||
contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id',
|
||||
@@ -37,6 +37,11 @@ const testCases: TestsCase[] = [
|
||||
contentBaseURL: 'https://faq.deltaemulator.com',
|
||||
tests: [{ name: 'Home', url: '/' }],
|
||||
},
|
||||
{
|
||||
name: 'docs.dify.ai',
|
||||
contentBaseURL: 'https://docs.dify.ai',
|
||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||
},
|
||||
{
|
||||
name: 'seeddao.gitbook.io',
|
||||
contentBaseURL: 'https://seeddao.gitbook.io',
|
||||
@@ -232,6 +237,7 @@ const testCases: TestsCase[] = [
|
||||
{
|
||||
name: 'run-ai-docs.nvidia.com',
|
||||
contentBaseURL: 'https://run-ai-docs.nvidia.com',
|
||||
skip: process.env.ARGOS_BUILD_NAME !== 'customers-v2',
|
||||
tests: [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'OG Image', url: '/~gitbook/ogimage/h17zQIFwy3MaafVNmItO', mode: 'image' },
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ test.describe('PDF export', () => {
|
||||
const printBtn = page.getByTestId('print-button');
|
||||
await expect(printBtn).toBeVisible();
|
||||
|
||||
await argosScreenshot(page, 'pdf - e2e - all pages', {
|
||||
await argosScreenshot(page, 'pdf - all pages', {
|
||||
viewports: ['macbook-13'],
|
||||
argosCSS: `
|
||||
/* Hide Intercom */
|
||||
@@ -67,7 +67,7 @@ test.describe('PDF export', () => {
|
||||
const printBtn = page.getByTestId('print-button');
|
||||
await expect(printBtn).toBeVisible();
|
||||
|
||||
await argosScreenshot(page, 'pdf - gb-docs - all pages', {
|
||||
await argosScreenshot(page, 'pdf - all pages', {
|
||||
viewports: ['macbook-13'],
|
||||
argosCSS: `
|
||||
/* Hide Intercom */
|
||||
@@ -109,7 +109,7 @@ test.describe('PDF export', () => {
|
||||
const printBtn = page.getByTestId('print-button');
|
||||
await expect(printBtn).toBeVisible();
|
||||
|
||||
await argosScreenshot(page, 'pdf - e2e - single-page', {
|
||||
await argosScreenshot(page, 'pdf - all pages', {
|
||||
viewports: ['macbook-13'],
|
||||
argosCSS: `
|
||||
/* Hide Intercom */
|
||||
@@ -151,7 +151,7 @@ test.describe('PDF export', () => {
|
||||
const printBtn = page.getByTestId('print-button');
|
||||
await expect(printBtn).toBeVisible();
|
||||
|
||||
await argosScreenshot(page, 'pdf - gb-docs - single-page', {
|
||||
await argosScreenshot(page, 'pdf - all pages', {
|
||||
viewports: ['macbook-13'],
|
||||
argosCSS: `
|
||||
/* Hide Intercom */
|
||||
|
||||
@@ -2,7 +2,6 @@ import { argosScreenshot } from '@argos-ci/playwright';
|
||||
import {
|
||||
CustomizationAIMode,
|
||||
CustomizationBackground,
|
||||
CustomizationCodeTheme,
|
||||
CustomizationCorners,
|
||||
CustomizationDefaultFont,
|
||||
CustomizationDefaultMonospaceFont,
|
||||
@@ -155,23 +154,6 @@ export async function waitForNotFound(_page: Page, response: Response | null) {
|
||||
expect(response?.status()).toBe(404);
|
||||
}
|
||||
|
||||
export async function setTimeToMorning(page: Page) {
|
||||
const now = new Date();
|
||||
now.setHours(8, 0, 0, 0); // 8:00:00.000 AM (local time)
|
||||
|
||||
await page.clock.install({ time: now });
|
||||
}
|
||||
|
||||
export async function waitForCoverImages(page: Page, options?: { darkMode?: boolean }) {
|
||||
const selector = options?.darkMode
|
||||
? 'img[alt="Page cover"].dark\\:inline'
|
||||
: 'img[alt="Page cover"]:not(.dark\\:inline)';
|
||||
// Wait for cover images to exist (not the shimmer placeholder)
|
||||
await expect(page.locator(selector)).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform test cases into Playwright tests and run it.
|
||||
*/
|
||||
@@ -309,16 +291,6 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
||||
background: CustomizationBackground.Plain,
|
||||
icons: CustomizationIconsStyle.Regular,
|
||||
links: CustomizationLinksStyle.Default,
|
||||
codeTheme: {
|
||||
default: {
|
||||
light: CustomizationCodeTheme.DefaultLight,
|
||||
dark: CustomizationCodeTheme.DefaultDark,
|
||||
},
|
||||
openapi: {
|
||||
light: CustomizationCodeTheme.DefaultLight,
|
||||
dark: CustomizationCodeTheme.DefaultDark,
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
background: CustomizationSidebarBackgroundStyle.Default,
|
||||
list: CustomizationSidebarListStyle.Default,
|
||||
@@ -376,7 +348,6 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
||||
url: 'https://www.gitbook.com/privacy',
|
||||
},
|
||||
socialPreview: {},
|
||||
socialAccounts: [],
|
||||
};
|
||||
|
||||
const encoded = rison.encode_object(deepMerge(DEFAULT_CUSTOMIZATION, partial));
|
||||
@@ -439,15 +410,14 @@ export async function waitForIcons(page: Page) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const maskImage = icon.querySelector('[data-testid="mask-image"]');
|
||||
if (!maskImage) {
|
||||
throw new Error('No mask-image element');
|
||||
}
|
||||
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93")
|
||||
const maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
|
||||
const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
|
||||
const url = urlMatch?.[1];
|
||||
|
||||
const url = maskImage.getAttribute('href');
|
||||
// If URL is invalid we throw an error.
|
||||
if (!url) {
|
||||
throw new Error('No mask-image url');
|
||||
throw new Error('No mask-image');
|
||||
}
|
||||
|
||||
// If the URL is already queued for loading, we return the state.
|
||||
@@ -477,8 +447,7 @@ export async function waitForIcons(page: Page) {
|
||||
*/
|
||||
async function waitForTOCScrolling(page: Page) {
|
||||
const viewport = await page.viewportSize();
|
||||
if (viewport && viewport.width >= 1024 && !page.url().includes('~gitbook/embed/demo')) {
|
||||
// The embed demo is an iframe, which means the viewport is only a fraction of the main document. So there is no open TOC to scroll to.
|
||||
if (viewport && viewport.width >= 1024) {
|
||||
const toc = page.getByTestId('table-of-contents');
|
||||
await expect(toc).toBeVisible();
|
||||
await page.evaluate(() => {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {
|
||||
deploymentId: process.env.GITHUB_SHA || Date.now().toString(), // Needed because we use a custom deployment method i.e. https://vercel.com/docs/skew-protection#custom-deployment-id
|
||||
experimental: {
|
||||
// This is needed to throw "forbidden" when the api token expired during revalidation
|
||||
authInterrupts: true,
|
||||
@@ -26,7 +25,6 @@ const nextConfig = {
|
||||
// GitBook envs
|
||||
GITBOOK_API_URL: process.env.GITBOOK_API_URL,
|
||||
GITBOOK_APP_URL: process.env.GITBOOK_APP_URL,
|
||||
GITBOOK_OAUTH_SERVER_URL: process.env.GITBOOK_OAUTH_SERVER_URL,
|
||||
GITBOOK_INTEGRATIONS_HOST: process.env.GITBOOK_INTEGRATIONS_HOST,
|
||||
GITBOOK_IMAGE_RESIZE_URL: process.env.GITBOOK_IMAGE_RESIZE_URL,
|
||||
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
|
||||
|
||||
@@ -6,13 +6,12 @@ import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-ca
|
||||
const originalTagCache = doShardedTagCache({
|
||||
baseShardSize: 12,
|
||||
regionalCache: true,
|
||||
// We can set a long TTL for the regional cache, as we invalidate it on update
|
||||
regionalCacheTtlSec: 24 * 60 * 60, // 1 day
|
||||
regionalCacheTtlSec: 60 * 5 /* 5 minutes */,
|
||||
// Because we invalidate the Cache API on update, we can safely set this to true
|
||||
regionalCacheDangerouslyPersistMissingTags: true,
|
||||
shardReplication: {
|
||||
numberOfSoftReplicas: 2,
|
||||
numberOfHardReplicas: 2,
|
||||
numberOfHardReplicas: 1,
|
||||
regionalReplication: {
|
||||
defaultRegion: 'enam',
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gitbook",
|
||||
"version": "0.22.2",
|
||||
"version": "0.19.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@cloudflare/workers-types": "^4.20251011.0",
|
||||
@@ -8,19 +8,18 @@
|
||||
"@gitbook/browser-types": "workspace:*",
|
||||
"@gitbook/cache-tags": "workspace:*",
|
||||
"@gitbook/colors": "workspace:*",
|
||||
"@gitbook/embed": "workspace:*",
|
||||
"@gitbook/emoji-codepoints": "workspace:*",
|
||||
"@gitbook/expr": "workspace:*",
|
||||
"@gitbook/embed": "workspace:*",
|
||||
"@gitbook/fonts": "workspace:*",
|
||||
"@gitbook/icons": "workspace:*",
|
||||
"@gitbook/openapi-parser": "workspace:*",
|
||||
"@gitbook/react-contentkit": "workspace:*",
|
||||
"@gitbook/react-math": "workspace:*",
|
||||
"@gitbook/react-openapi": "workspace:*",
|
||||
"@mermaid-js/mermaid-zenuml": "^0.2.2",
|
||||
"@modelcontextprotocol/sdk": "1.17.5",
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@opennextjs/aws": "^3.8.5",
|
||||
"@opennextjs/cloudflare": "^1.14.4",
|
||||
"@opennextjs/cloudflare": "^1.11.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
||||
"@radix-ui/react-hover-card": "^1.1.15",
|
||||
@@ -29,6 +28,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@sindresorhus/fnv1a": "^3.1.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tusbar/cache-control": "^1.0.2",
|
||||
"ai": "^4.2.2",
|
||||
"assert-never": "catalog:",
|
||||
@@ -36,7 +36,6 @@
|
||||
"classnames": "catalog:",
|
||||
"direction": "^2.0.1",
|
||||
"event-iterator": "^2.0.0",
|
||||
"feed": "^5.1.0",
|
||||
"image-size": "^2.0.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsontoxml": "^1.0.1",
|
||||
@@ -47,12 +46,10 @@
|
||||
"mdast-util-gfm": "^3.1.0",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"memoizee": "^0.4.17",
|
||||
"mermaid": "^11.12.3",
|
||||
"micromark-extension-frontmatter": "^2.0.0",
|
||||
"micromark-extension-gfm": "^3.0.0",
|
||||
"motion": "^12.23.24",
|
||||
"negotiator": "^1.0.0",
|
||||
"next": "15.4.11",
|
||||
"next": "15.4.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"nuqs": "^2.2.3",
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -70,7 +67,7 @@
|
||||
"remark-rehype": "^11.1.1",
|
||||
"rison": "^0.1.1",
|
||||
"server-only": "^0.0.1",
|
||||
"shiki": "^3.21.0",
|
||||
"shiki": "^3.2.0",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwind-shades": "^1.1.2",
|
||||
"unified": "^11.0.5",
|
||||
@@ -80,12 +77,11 @@
|
||||
"usehooks-ts": "catalog:",
|
||||
"warn-once": "^0.1.1",
|
||||
"zod": "^3",
|
||||
"zod-to-json-schema": "^3.25.1",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@argos-ci/playwright": "^6.4.2",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@argos-ci/playwright": "^5.0.9",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@scalar/api-client-react": "catalog:",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
@@ -98,7 +94,6 @@
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@types/rison": "^0.0.9",
|
||||
"@types/negotiator": "^0.6.4",
|
||||
"bun-types": "catalog:",
|
||||
"deepmerge": "^4.3.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
@@ -108,9 +103,8 @@
|
||||
"tailwindcss": "^4.1.11",
|
||||
"ts-essentials": "^10.0.1",
|
||||
"typescript": "catalog:",
|
||||
"vercel": "^50.15.1",
|
||||
"wrangler": "^4.43.0",
|
||||
"rss-parser": "^3.13.0"
|
||||
"vercel": "^39.3.0",
|
||||
"wrangler": "^4.43.0"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "./scripts/generate.sh",
|
||||
@@ -123,7 +117,7 @@
|
||||
"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/cookie-banner.spec.ts e2e/pdf.spec.ts --project=chromium",
|
||||
"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",
|
||||
"e2e-browserless": "bun test ./tests/",
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getDynamicSiteContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <SitePage context={context} pageParams={{ pathname }} staticRoute={false} />;
|
||||
return <SitePage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateViewport(props: PageProps): Promise<Viewport> {
|
||||
|
||||
+1
-6
@@ -22,12 +22,7 @@ export default async function SiteDynamicLayout({
|
||||
const withTracking = shouldTrackEvents(await headers());
|
||||
|
||||
return (
|
||||
<CustomizationRootLayout
|
||||
htmlClassName="sheet-open:gutter-stable"
|
||||
bodyClassName="site-background"
|
||||
forcedTheme={forcedTheme}
|
||||
context={context}
|
||||
>
|
||||
<CustomizationRootLayout forcedTheme={forcedTheme} context={context}>
|
||||
<SiteLayout
|
||||
context={context}
|
||||
forcedTheme={forcedTheme}
|
||||
|
||||
+3
-15
@@ -1,28 +1,16 @@
|
||||
import type { RouteLayoutParams } from '@/app/utils';
|
||||
import { EmbeddableAssistantPage } from '@/components/Embeddable';
|
||||
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<RouteLayoutParams>;
|
||||
};
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
export default async function Page(props: PageProps) {
|
||||
const params = await props.params;
|
||||
const { context } = await getEmbeddableDynamicContext(params);
|
||||
|
||||
// If the assistant is not enabled, redirect to the docs
|
||||
if (context.customization.ai.mode !== CustomizationAIMode.Assistant) {
|
||||
redirect(`${context.linker.toPathInSite('~gitbook/embed/page/')}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<EmbeddableAssistantPage
|
||||
baseURL={context.linker.toPathInSite('~gitbook/embed/')}
|
||||
siteTitle={context.site.title}
|
||||
/>
|
||||
);
|
||||
return <EmbeddableAssistantPage context={context} />;
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import type { RouteLayoutParams } from '@/app/utils';
|
||||
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<RouteLayoutParams>;
|
||||
};
|
||||
|
||||
export default async function Page(props: PageProps) {
|
||||
const params = await props.params;
|
||||
const { context } = await getEmbeddableDynamicContext(params);
|
||||
const baseURL = context.linker.toPathInSite('~gitbook/embed/');
|
||||
|
||||
// If assistant is enabled, redirect to assistant, otherwise to docs
|
||||
if (context.customization.ai.mode === CustomizationAIMode.Assistant) {
|
||||
redirect(`${baseURL}/assistant`);
|
||||
} else {
|
||||
redirect(`${baseURL}/page/`);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getEmbeddableDynamicContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} staticRoute={false} />;
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: PageProps): Promise<Metadata> {
|
||||
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
import type {
|
||||
ComputedPageResult,
|
||||
ComputedSectionResult,
|
||||
OrderedComputedResult,
|
||||
SearchSiteContentRequest,
|
||||
} from '@/components/Search/search-types';
|
||||
import type { GitBookBaseContext } from '@/lib/context';
|
||||
import { throwIfDataError } from '@/lib/data';
|
||||
import { getSiteURLDataFromMiddleware } from '@/lib/middleware';
|
||||
import { joinPathWithBaseURL } from '@/lib/paths';
|
||||
import { getServerActionBaseContext } from '@/lib/server-actions';
|
||||
import { findSiteSpaceBy } from '@/lib/sites';
|
||||
import type {
|
||||
SearchPageResult,
|
||||
SearchSpaceResult,
|
||||
SiteSection,
|
||||
SiteSectionGroup,
|
||||
SiteSpace,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import type { IconName } from '@gitbook/icons';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const [context, { organization, site, shareKey }] = await Promise.all([
|
||||
getServerActionBaseContext(),
|
||||
getSiteURLDataFromMiddleware(),
|
||||
]);
|
||||
const body = (await request.json()) as SearchSiteContentRequest;
|
||||
const { query, scope } = body;
|
||||
|
||||
if (query.length <= 1) {
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
|
||||
const [searchResults, { structure }] = await Promise.all([
|
||||
(async () => {
|
||||
const result = await throwIfDataError(
|
||||
context.dataFetcher.searchSiteContent({
|
||||
organizationId: organization,
|
||||
siteId: site,
|
||||
query,
|
||||
scope,
|
||||
})
|
||||
);
|
||||
return result;
|
||||
})(),
|
||||
(async () => {
|
||||
const result = await throwIfDataError(
|
||||
context.dataFetcher.getPublishedContentSite({
|
||||
organizationId: organization,
|
||||
siteId: site,
|
||||
siteShareKey: shareKey,
|
||||
})
|
||||
);
|
||||
return result;
|
||||
})(),
|
||||
]);
|
||||
|
||||
const results = searchResults
|
||||
.map((resultItem) => {
|
||||
if (resultItem.type === 'record') {
|
||||
const result: OrderedComputedResult = {
|
||||
type: 'record',
|
||||
id: resultItem.id,
|
||||
title: resultItem.title,
|
||||
description: resultItem.description,
|
||||
href: resultItem.url,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
const found = findSiteSpaceBy(
|
||||
structure,
|
||||
(siteSpace) => siteSpace.space.id === resultItem.id
|
||||
);
|
||||
const siteSection = found?.siteSection;
|
||||
const siteSectionGroup = found?.siteSectionGroup;
|
||||
|
||||
return resultItem.pages.map((pageItem) =>
|
||||
transformSitePageResult(context, {
|
||||
pageItem,
|
||||
spaceItem: resultItem,
|
||||
siteSpace: found?.siteSpace,
|
||||
space: found?.siteSpace.space,
|
||||
spaceURL: found?.siteSpace.urls.published,
|
||||
siteSection: siteSection ?? undefined,
|
||||
siteSectionGroup: (siteSectionGroup as SiteSectionGroup) ?? undefined,
|
||||
})
|
||||
);
|
||||
})
|
||||
.flat(2);
|
||||
|
||||
return NextResponse.json(results);
|
||||
}
|
||||
|
||||
function transformSitePageResult(
|
||||
context: GitBookBaseContext,
|
||||
args: {
|
||||
pageItem: SearchPageResult;
|
||||
spaceItem: SearchSpaceResult;
|
||||
space?: Space;
|
||||
siteSpace?: SiteSpace;
|
||||
spaceURL?: string;
|
||||
siteSection?: SiteSection;
|
||||
siteSectionGroup?: SiteSectionGroup;
|
||||
}
|
||||
): OrderedComputedResult[] {
|
||||
const { pageItem, spaceItem, spaceURL, siteSection, siteSectionGroup, siteSpace } = args;
|
||||
const { linker } = context;
|
||||
|
||||
const page: ComputedPageResult = {
|
||||
type: 'page',
|
||||
id: `${spaceItem.id}/${pageItem.id}`,
|
||||
title: pageItem.title,
|
||||
href: spaceURL
|
||||
? linker.toLinkForContent(joinPathWithBaseURL(spaceURL, pageItem.path))
|
||||
: linker.toPathInSpace(pageItem.path),
|
||||
pageId: pageItem.id,
|
||||
spaceId: spaceItem.id,
|
||||
breadcrumbs: [
|
||||
siteSectionGroup && {
|
||||
icon: siteSectionGroup?.icon as IconName,
|
||||
label: siteSectionGroup.title,
|
||||
},
|
||||
siteSection && {
|
||||
icon: siteSection?.icon as IconName,
|
||||
label: siteSection.title,
|
||||
},
|
||||
(siteSection?.siteSpaces?.filter(
|
||||
(space) =>
|
||||
siteSection?.siteSpaces?.filter(
|
||||
(s) => s.space.language === space.space.language
|
||||
).length > 1
|
||||
).length ?? 0) > 1 && siteSpace
|
||||
? {
|
||||
label: siteSpace.title,
|
||||
}
|
||||
: undefined,
|
||||
...pageItem.ancestors.map((ancestor) => ({
|
||||
label: ancestor.title,
|
||||
})),
|
||||
].filter((item) => item !== undefined),
|
||||
};
|
||||
|
||||
const pageSections =
|
||||
pageItem.sections
|
||||
?.filter((section) => section.title || section.body)
|
||||
.map<ComputedSectionResult>((section) => ({
|
||||
type: 'section',
|
||||
id: `${page.id}/${section.id}`,
|
||||
title: section.title,
|
||||
href: spaceURL
|
||||
? linker.toLinkForContent(joinPathWithBaseURL(spaceURL, section.path))
|
||||
: linker.toPathInSpace(pageItem.path),
|
||||
body: section.body,
|
||||
pageId: pageItem.id,
|
||||
spaceId: spaceItem.id,
|
||||
})) ?? [];
|
||||
|
||||
return [page, ...pageSections];
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
import { handleOpenAPIProxyOptions, handleOpenAPIProxyRequest } from '@/routes/openapi-proxy';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function HEAD(request: NextRequest) {
|
||||
return handleOpenAPIProxyRequest(request);
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return handleOpenAPIProxyOptions();
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getStaticSiteContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <SitePage context={context} pageParams={{ pathname }} staticRoute />;
|
||||
return <SitePage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateViewport(props: PageProps): Promise<Viewport> {
|
||||
|
||||
+1
-5
@@ -19,11 +19,7 @@ export default async function SiteStaticLayout({
|
||||
const withTracking = shouldTrackEvents();
|
||||
|
||||
return (
|
||||
<CustomizationRootLayout
|
||||
htmlClassName="sheet-open:gutter-stable"
|
||||
bodyClassName="site-background"
|
||||
context={context}
|
||||
>
|
||||
<CustomizationRootLayout context={context}>
|
||||
<SiteLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
|
||||
+3
-15
@@ -1,28 +1,16 @@
|
||||
import type { RouteParams } from '@/app/utils';
|
||||
import type { RouteLayoutParams } from '@/app/utils';
|
||||
import { EmbeddableAssistantPage } from '@/components/Embeddable';
|
||||
import { getEmbeddableStaticContext } from '@/lib/embeddable';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<RouteParams>;
|
||||
params: Promise<RouteLayoutParams>;
|
||||
};
|
||||
|
||||
export default async function Page(props: PageProps) {
|
||||
const params = await props.params;
|
||||
const { context } = await getEmbeddableStaticContext(params);
|
||||
|
||||
// If the assistant is not enabled, redirect to the docs
|
||||
if (context.customization.ai.mode !== CustomizationAIMode.Assistant) {
|
||||
redirect(`${context.linker.toPathInSite('~gitbook/embed/page/')}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<EmbeddableAssistantPage
|
||||
baseURL={context.linker.toPathInSite('~gitbook/embed/')}
|
||||
siteTitle={context.site.title}
|
||||
/>
|
||||
);
|
||||
return <EmbeddableAssistantPage context={context} />;
|
||||
}
|
||||
|
||||
+8
-28
@@ -18,14 +18,6 @@ export async function GET(
|
||||
<html>
|
||||
<head>
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<style type="text/css">
|
||||
.control {
|
||||
display: none;
|
||||
}
|
||||
.custom-trigger .control {
|
||||
display: inline-flex;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg style="position: absolute; bottom: 6rem; right: 4rem;" width="719" height="644" viewBox="0 0 719 644" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -113,29 +105,17 @@ export async function GET(
|
||||
<line x1="658.186" y1="444" x2="635.471" y2="444" stroke="#A4A7B0" stroke-width="8" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
<button type="button" class="control" onclick="window.GitBook('open')">Open</button>
|
||||
<button type="button" class="control" onclick="window.GitBook('close')">Close</button>
|
||||
</body>
|
||||
<script src="${context.linker.toAbsoluteURL(context.linker.toPathInSite('~gitbook/embed/script.js'))}"></script>
|
||||
<script>
|
||||
const useCustomTrigger = new URLSearchParams(window.location.search).get('trigger') === 'custom';
|
||||
window.GitBook('configure', {
|
||||
suggestions: [
|
||||
'Help me get started',
|
||||
'What can I ask you?',
|
||||
'Show me tips and tricks',
|
||||
],
|
||||
tabs: ['assistant', 'docs'],
|
||||
closeButton: useCustomTrigger
|
||||
});
|
||||
|
||||
if (useCustomTrigger) {
|
||||
document.body.classList.add('custom-trigger');
|
||||
window.GitBook('hide');
|
||||
} else {
|
||||
window.GitBook('open');
|
||||
}
|
||||
|
||||
window.GitBook('configure', {
|
||||
suggestions: [
|
||||
'Help me get started',
|
||||
'What can I ask you?',
|
||||
'Show me tips and tricks',
|
||||
],
|
||||
});
|
||||
window.GitBook('open');
|
||||
</script>
|
||||
</html>
|
||||
`,
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import type { RouteLayoutParams } from '@/app/utils';
|
||||
import { getEmbeddableStaticContext } from '@/lib/embeddable';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
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);
|
||||
const baseURL = context.linker.toPathInSite('~gitbook/embed/');
|
||||
|
||||
// If assistant is enabled, redirect to assistant, otherwise to docs
|
||||
if (context.customization.ai.mode === CustomizationAIMode.Assistant) {
|
||||
redirect(`${baseURL}/assistant`);
|
||||
} else {
|
||||
redirect(`${baseURL}/page/`);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ export default async function Page(props: PageProps) {
|
||||
const { context } = await getEmbeddableStaticContext(params);
|
||||
const pathname = getPagePathFromParams(params);
|
||||
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} staticRoute />;
|
||||
return <EmbeddableDocsPage context={context} pageParams={{ pathname }} />;
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: PageProps): Promise<Metadata> {
|
||||
|
||||
+2
-14
@@ -24,22 +24,10 @@ export async function GET(
|
||||
(function () {
|
||||
const w = window;
|
||||
const gb = w.GitBook;
|
||||
|
||||
function getScriptSearchParams() {
|
||||
const script = document.currentScript;
|
||||
if (!script) return new URLSearchParams();
|
||||
|
||||
const url = new URL(script.src);
|
||||
return url.searchParams;
|
||||
}
|
||||
|
||||
const searchParams = getScriptSearchParams()
|
||||
const token = searchParams.get('jwt_token');
|
||||
const initOptions = window.gitbookSettings || ${JSON.stringify(initOptions)};
|
||||
const initFrameOptions = token ? { visitor: { token } } : undefined;
|
||||
|
||||
if (typeof gb === "function") {
|
||||
gb('init', initOptions, initFrameOptions);
|
||||
gb('init', initOptions);
|
||||
} else {
|
||||
var d = document;
|
||||
|
||||
@@ -52,7 +40,7 @@ export async function GET(
|
||||
};
|
||||
w.GitBook = g;
|
||||
|
||||
g('init', initOptions, initFrameOptions);
|
||||
g('init', initOptions);
|
||||
|
||||
const load = function () {
|
||||
const style = document.createElement('link');
|
||||
|
||||
+3
-38
@@ -1,11 +1,7 @@
|
||||
import { SiteInsightsDisplayContext } from '@gitbook/api';
|
||||
|
||||
import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils';
|
||||
import { throwIfDataError } from '@/lib/data';
|
||||
import { joinPathWithBaseURL } from '@/lib/paths';
|
||||
import { findSiteSpaceBy } from '@/lib/sites';
|
||||
import { trackServerInsightsEvents } from '@/lib/tracking';
|
||||
import { waitUntil } from '@/lib/waitUntil';
|
||||
import { createMcpHandler } from 'mcp-handler';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
@@ -35,49 +31,18 @@ async function handler(
|
||||
})
|
||||
);
|
||||
|
||||
// Track the search event server-side
|
||||
waitUntil(
|
||||
trackServerInsightsEvents({
|
||||
organizationId: context.organizationId,
|
||||
siteId: site.id,
|
||||
events: [
|
||||
{
|
||||
type: 'search_type_query',
|
||||
query,
|
||||
location: {
|
||||
displayContext: SiteInsightsDisplayContext.Mcp,
|
||||
},
|
||||
},
|
||||
],
|
||||
request: nextRequest,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
content: results.flatMap((result) => {
|
||||
if (result.type === 'record') {
|
||||
return {
|
||||
type: 'text',
|
||||
text: [
|
||||
`Title: ${result.title}`,
|
||||
`Link: ${result.url}`,
|
||||
result.description ? `Content: ${result.description}` : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
content: results.flatMap((spaceResult) => {
|
||||
const found = findSiteSpaceBy(
|
||||
context.structure,
|
||||
(siteSpace) => siteSpace.space.id === result.id
|
||||
(siteSpace) => siteSpace.space.id === spaceResult.id
|
||||
);
|
||||
const spaceURL = found?.siteSpace.urls.published;
|
||||
if (!spaceURL) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result.pages.map((pageResult) => {
|
||||
return spaceResult.pages.map((pageResult) => {
|
||||
const pageURL = linker.toAbsoluteURL(
|
||||
linker.toLinkForContent(
|
||||
joinPathWithBaseURL(spaceURL, pageResult.path)
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { type RouteParams, getPagePathFromParams, getStaticSiteContext } from '@/app/utils';
|
||||
import { servePageRSS } from '@/routes/rss';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: { params: Promise<RouteParams> }) {
|
||||
const { context } = await getStaticSiteContext(await params);
|
||||
const pathname = getPagePathFromParams(await params);
|
||||
|
||||
return servePageRSS(context, pathname);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { type SiteURLData, fetchSiteContextByURLLookup, getBaseContext } from '@
|
||||
import { getDynamicCustomizationSettings } from '@/lib/customization';
|
||||
import type { SiteAPIToken } from '@gitbook/api';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { forbidden, notFound } from 'next/navigation';
|
||||
import { forbidden } from 'next/navigation';
|
||||
import rison from 'rison';
|
||||
|
||||
export type RouteParamMode = 'url-host' | 'url';
|
||||
@@ -80,32 +80,14 @@ export async function getDynamicSiteContext(params: RouteLayoutParams) {
|
||||
* Get the decoded page path from the params.
|
||||
*/
|
||||
export function getPagePathFromParams(params: RouteParams) {
|
||||
// If decoding the param fails, return a 404 instead of crashing
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.pagePath);
|
||||
|
||||
// For the root page, we encode '/' to avoid an empty param being passed.
|
||||
if (decoded === '/') {
|
||||
return '';
|
||||
}
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Returning 404 after failing to decode page path ${params.pagePath}: ${error}`
|
||||
);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.pagePath);
|
||||
return decoded;
|
||||
}
|
||||
|
||||
function getSiteURLFromParams(params: RouteLayoutParams) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.siteURL);
|
||||
const url = new URL(`https://${decoded}`);
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.error(`Returning 404 after failing to decode site URL ${params.siteURL}: ${error}`);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.siteURL);
|
||||
const url = new URL(`https://${decoded}`);
|
||||
return url;
|
||||
}
|
||||
|
||||
function getModeFromParams(mode: string): RouteParamMode {
|
||||
@@ -120,13 +102,6 @@ function getModeFromParams(mode: string): RouteParamMode {
|
||||
* Get the decoded site data from the params.
|
||||
*/
|
||||
function getSiteURLDataFromParams(params: RouteLayoutParams): SiteURLData {
|
||||
try {
|
||||
const decoded = decodeURIComponent(params.siteData);
|
||||
return rison.decode(decoded);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Returning 404 after failing to decode site data ${params.siteData}: ${error}`
|
||||
);
|
||||
notFound();
|
||||
}
|
||||
const decoded = decodeURIComponent(params.siteData);
|
||||
return rison.decode(decoded);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
GITBOOK_ICONS_URL,
|
||||
GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
|
||||
GITBOOK_INTEGRATIONS_HOST,
|
||||
GITBOOK_OAUTH_SERVER_URL,
|
||||
GITBOOK_SECRET,
|
||||
GITBOOK_URL,
|
||||
GITBOOK_USER_AGENT,
|
||||
@@ -26,7 +25,6 @@ export async function GET(_req: NextRequest) {
|
||||
GITBOOK_APP_URL,
|
||||
GITBOOK_API_URL,
|
||||
GITBOOK_API_PUBLIC_URL,
|
||||
GITBOOK_OAUTH_SERVER_URL,
|
||||
GITBOOK_ASSETS_URL,
|
||||
GITBOOK_FONTS_URL,
|
||||
GITBOOK_ICONS_URL,
|
||||
|
||||
@@ -21,15 +21,16 @@ export async function getSpacePDFContext(
|
||||
const apiToken = await getAPITokenFromMiddleware();
|
||||
|
||||
const basePath = getPDFRoutePath(params);
|
||||
const linker = createLinker({
|
||||
spaceBasePath: basePath,
|
||||
siteBasePath: basePath,
|
||||
});
|
||||
const dataFetcher = createDataFetcher({
|
||||
apiToken: apiToken,
|
||||
});
|
||||
|
||||
const baseContext: GitBookBaseContext = {
|
||||
linker: createLinker({
|
||||
spaceBasePath: basePath,
|
||||
siteBasePath: basePath,
|
||||
}),
|
||||
linker,
|
||||
dataFetcher,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/primitives';
|
||||
import { z } from 'zod';
|
||||
import { AIToolContainer } from './common';
|
||||
import { type GetAIControlProps, createAIControl } from './helpers';
|
||||
|
||||
export const ConfirmControlOutputSchema = z.object({
|
||||
result: z.enum(['confirmed', 'cancelled']),
|
||||
});
|
||||
|
||||
export const ConfirmControlDef = createAIControl({
|
||||
name: 'confirm',
|
||||
exposeAsTool: false,
|
||||
description:
|
||||
'Display a confirmation prompt to the user (Confirm / Cancel) to approve or abort a pending action. Use this when an operation is irreversible, sensitive, or should only proceed with explicit user consent. Returns either a `confirmed` or `cancelled` result based on the user’s click.',
|
||||
inputSchema: z.object({
|
||||
icon: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Icon to be shown in the confirm button (any Fontawesome icon name).'),
|
||||
label: z.string().describe('Label to be shown in the confirm button.'),
|
||||
}),
|
||||
outputSchema: ConfirmControlOutputSchema,
|
||||
render: (props) => {
|
||||
return <ConfirmControl {...props} />;
|
||||
},
|
||||
});
|
||||
|
||||
function ConfirmControl(props: GetAIControlProps<typeof ConfirmControlDef>) {
|
||||
const { label, icon, onSubmit } = props;
|
||||
return (
|
||||
<AIToolContainer className="flex w-full flex-col gap-2">
|
||||
<Button
|
||||
data-testid="ai-chat-tool-confirm-cancel"
|
||||
onClick={() => {
|
||||
onSubmit({ result: 'cancelled' });
|
||||
}}
|
||||
variant="secondary"
|
||||
icon="xmark"
|
||||
label="Cancel"
|
||||
/>
|
||||
<Button
|
||||
data-testid="ai-chat-tool-confirm-accept"
|
||||
onClick={() => {
|
||||
onSubmit({ result: 'confirmed' });
|
||||
}}
|
||||
className="flex-1"
|
||||
variant="primary"
|
||||
icon={icon}
|
||||
label={label}
|
||||
/>
|
||||
</AIToolContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Button, Input } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import * as React from 'react';
|
||||
import { z } from 'zod';
|
||||
import { AIToolContainer } from './common';
|
||||
import { type GetAIControlProps, createAIControl } from './helpers';
|
||||
|
||||
const OTHER_OPTION_ID = '$other';
|
||||
|
||||
export const SingleChoiceControlOutputSchema = z.object({
|
||||
id: z.string().describe('The identifier of the option selected by the user.'),
|
||||
input: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The custom text entered by the user when "Other" is enabled and selected.'),
|
||||
});
|
||||
|
||||
export const SingleChoiceControlDef = createAIControl({
|
||||
name: 'single-choice',
|
||||
exposeAsTool: true,
|
||||
description:
|
||||
'Use this control when you need the user to choose exactly one option from a predefined list.',
|
||||
inputSchema: z.object({
|
||||
prompt: z
|
||||
.string()
|
||||
.describe(
|
||||
'Provide the question or instruction that tells the user what single choice they need to make.'
|
||||
),
|
||||
options: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'Provide a unique, stable identifier for this option. This is the value returned to the agent when the user selects it.'
|
||||
),
|
||||
label: z
|
||||
.string()
|
||||
.describe('Provide the short label the user sees for this option.'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Optionally provide supporting details to help the user understand this option.'
|
||||
),
|
||||
})
|
||||
.describe('Define one selectable option the user can pick.')
|
||||
)
|
||||
.describe(
|
||||
'Provide the list of options the user can choose from. The user must select exactly one.'
|
||||
),
|
||||
allowOther: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Set to true to let the user select an "Other" option and enter a custom text response.'
|
||||
),
|
||||
}),
|
||||
outputSchema: SingleChoiceControlOutputSchema,
|
||||
render: (props) => {
|
||||
return <SingleChoiceControl {...props} />;
|
||||
},
|
||||
});
|
||||
|
||||
function SingleChoiceControl(props: GetAIControlProps<typeof SingleChoiceControlDef>) {
|
||||
const { prompt, options, allowOther, onSubmit } = props;
|
||||
const [selectedId, setSelectedId] = React.useState<string | null>(null);
|
||||
const [otherInput, setOtherInput] = React.useState('');
|
||||
|
||||
const canSubmit =
|
||||
selectedId !== null &&
|
||||
(selectedId !== OTHER_OPTION_ID || (allowOther && otherInput.trim().length > 0));
|
||||
|
||||
return (
|
||||
<AIToolContainer className="flex w-full flex-col gap-2">
|
||||
<div className="no-scrollbar flex flex-1 flex-col gap-1 overflow-auto">
|
||||
<p className="mb-1 font-semibold text-sm">{prompt}</p>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedId === option.id;
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
data-testid={`ai-chat-tool-single-choice-option-${option.id}`}
|
||||
onClick={() => {
|
||||
setSelectedId(option.id);
|
||||
}}
|
||||
className={tcls(
|
||||
'circular-corners:rounded-3xl rounded-corners:rounded-xl border px-3 py-2 text-left transition-colors',
|
||||
isSelected
|
||||
? 'border-primary-original bg-primary-subtle text-tint-strong'
|
||||
: 'border-tint bg-tint-base hover:bg-tint-subtle'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
aria-hidden
|
||||
className={tcls(
|
||||
'mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border transition-colors',
|
||||
isSelected
|
||||
? 'border-primary-original'
|
||||
: 'border-tint-subtle'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={tcls(
|
||||
'size-2.5 rounded-full transition-colors',
|
||||
isSelected ? 'bg-primary-original' : 'bg-transparent'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<p className="font-medium text-sm">{option.label}</p>
|
||||
{option.description ? (
|
||||
<p className="mt-0.5 text-sm text-tint-subtle">
|
||||
{option.description}
|
||||
</p>
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
{allowOther ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="ai-chat-tool-single-choice-option-other"
|
||||
onClick={() => {
|
||||
setSelectedId(OTHER_OPTION_ID);
|
||||
}}
|
||||
className={tcls(
|
||||
'circular-corners:rounded-3xl rounded-corners:rounded-xl border px-3 py-2 text-left transition-colors',
|
||||
selectedId === OTHER_OPTION_ID
|
||||
? 'border-primary-original bg-primary-subtle text-tint-strong'
|
||||
: 'border-tint bg-tint-base hover:bg-tint-subtle'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
aria-hidden
|
||||
className={tcls(
|
||||
'mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border transition-colors',
|
||||
selectedId === OTHER_OPTION_ID
|
||||
? 'border-primary-original'
|
||||
: 'border-tint-subtle'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={tcls(
|
||||
'size-2.5 rounded-full transition-colors',
|
||||
selectedId === OTHER_OPTION_ID
|
||||
? 'bg-primary-original'
|
||||
: 'bg-transparent'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
<div className="flex flex-1 flex-col gap-1">
|
||||
<p className="font-medium text-sm">Other</p>
|
||||
{allowOther && selectedId === OTHER_OPTION_ID ? (
|
||||
<Input
|
||||
label="Enter your answer"
|
||||
value={otherInput}
|
||||
onValueChange={setOtherInput}
|
||||
data-testid="ai-chat-tool-single-choice-other-input"
|
||||
autoFocus
|
||||
className="w-full"
|
||||
sizing="small"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
data-testid="ai-chat-tool-single-choice-submit"
|
||||
variant="primary"
|
||||
label="Submit answer"
|
||||
disabled={!canSubmit}
|
||||
onClick={() => {
|
||||
if (!canSubmit || !selectedId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedId === OTHER_OPTION_ID) {
|
||||
onSubmit({
|
||||
id: OTHER_OPTION_ID,
|
||||
input: otherInput.trim(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit({ id: selectedId });
|
||||
}}
|
||||
/>
|
||||
</AIToolContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
|
||||
export function AIToolContainer(props: ComponentPropsWithRef<'div'>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={tcls(
|
||||
'min-h-0 animate-present-slow circular-corners:rounded-3xl rounded-corners:rounded-xl border border-tint bg-tint-subtle p-2',
|
||||
props.className
|
||||
)}
|
||||
style={{ animationDelay: '0.5s', ...props.style }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import type {
|
||||
AIStreamResponseToolCallPending,
|
||||
AIToolCallResult,
|
||||
AIToolDefinition,
|
||||
} from '@gitbook/api';
|
||||
import type { ZodType, z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
|
||||
type AIUIToolContext = Pick<AIStreamResponseToolCallPending, 'toolCall' | 'toolCallId'>;
|
||||
|
||||
type AIControlDefinition<
|
||||
Name extends string = any,
|
||||
Input extends Record<string, unknown> = any,
|
||||
Output extends Record<string, unknown> = any,
|
||||
> = AIToolDefinition & {
|
||||
createControl: (args: {
|
||||
context: AIUIToolContext;
|
||||
input: Input;
|
||||
send: (result: Pick<AIToolCallResult, 'output' | 'summary'>) => Promise<void>;
|
||||
}) => AIControl<Name, Input, Output>;
|
||||
exposeAsTool: boolean;
|
||||
};
|
||||
|
||||
type AIControlProps<Input = Record<string, unknown>, Output = Record<string, unknown>> = Input & {
|
||||
onSubmit: (output: Output) => Promise<void>;
|
||||
};
|
||||
|
||||
type AIControl<Name = string, Input = Record<string, unknown>, Output = Record<string, unknown>> = {
|
||||
props: AIControlProps<Input, Output>;
|
||||
name: Name;
|
||||
context: AIUIToolContext;
|
||||
render: () => React.ReactNode;
|
||||
};
|
||||
|
||||
type AIControlFromDef<T extends AIControlDefinition> = ReturnType<T['createControl']>;
|
||||
|
||||
export type GetAIControlProps<T extends AIControlDefinition> = AIControlFromDef<T>['props'];
|
||||
|
||||
export function createAIControl<
|
||||
Name extends string,
|
||||
InputSchema extends ZodType<Record<string, unknown>>,
|
||||
OutputSchema extends ZodType<Record<string, unknown>>,
|
||||
>(def: {
|
||||
name: Name;
|
||||
description: string;
|
||||
inputSchema: InputSchema;
|
||||
outputSchema: OutputSchema;
|
||||
render: (props: AIControlProps<z.infer<InputSchema>, z.infer<OutputSchema>>) => React.ReactNode;
|
||||
/**
|
||||
* Indicates if the control should be exposed as a tool or not.
|
||||
*/
|
||||
exposeAsTool: boolean;
|
||||
}): AIControlDefinition<Name, z.infer<InputSchema>, z.infer<OutputSchema>> {
|
||||
return {
|
||||
name: `ui--${def.name}`,
|
||||
description: def.description,
|
||||
inputSchema: zodToJsonSchema(def.inputSchema as any) as AIToolDefinition['inputSchema'],
|
||||
createControl: ({ context, input, send }) => {
|
||||
const props: AIControlProps<z.infer<InputSchema>, z.infer<OutputSchema>> = {
|
||||
...input,
|
||||
onSubmit: async (output) => {
|
||||
await send({
|
||||
output,
|
||||
summary: {
|
||||
icon: 'check',
|
||||
text: 'Submitted',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
return {
|
||||
props,
|
||||
name: def.name,
|
||||
context,
|
||||
render: () => def.render(props),
|
||||
};
|
||||
},
|
||||
exposeAsTool: def.exposeAsTool,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { ConfirmControlDef } from './ConfirmControl';
|
||||
import { SingleChoiceControlDef } from './SingleChoiceControl';
|
||||
|
||||
const CONTROLS = [SingleChoiceControlDef, ConfirmControlDef];
|
||||
|
||||
export type AnyAIControlTool = (typeof CONTROLS)[number];
|
||||
export type AnyAIControl = ReturnType<AnyAIControlTool['createControl']>;
|
||||
|
||||
export function getControlTools(): AnyAIControlTool[] {
|
||||
return CONTROLS.filter((control) => control.exposeAsTool);
|
||||
}
|
||||
@@ -16,14 +16,14 @@ export function AIMessageView(
|
||||
) {
|
||||
const { message, context, withToolCalls = true, withLinkPreviews = true } = props;
|
||||
|
||||
return message.steps.length > 0 ? (
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{message.steps.map((step, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={tcls(
|
||||
'flex flex-col gap-2',
|
||||
'flex animate-fade-in-slow flex-col gap-2',
|
||||
step.content.nodes.length > 0 ? 'has-content' : ''
|
||||
)}
|
||||
>
|
||||
@@ -35,7 +35,7 @@ export function AIMessageView(
|
||||
wrapBlocksInSuspense: false,
|
||||
withLinkPreviews,
|
||||
}}
|
||||
style="ai-response-document mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
|
||||
style="mt-2 space-y-4 empty:hidden"
|
||||
/>
|
||||
|
||||
{withToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (
|
||||
@@ -45,5 +45,5 @@ export function AIMessageView(
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null;
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { HighlightQuery } from '@/components/Search/HighlightQuery';
|
||||
import { Link, StyledLink } from '@/components/primitives';
|
||||
import { Favicon } from '@/components/utils';
|
||||
import { getSpaceLanguage } from '@/intl/server';
|
||||
import { t } from '@/intl/translate';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
@@ -16,7 +15,6 @@ import type {
|
||||
ContentRef,
|
||||
} from '@gitbook/api';
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import assertNever from 'assert-never';
|
||||
import type * as React from 'react';
|
||||
|
||||
/**
|
||||
@@ -41,10 +39,7 @@ function ToolCallSummary(props: { toolCall: AIToolCall; context: GitBookSiteCont
|
||||
const { toolCall, context } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="ai-chat-tool-summary"
|
||||
className="mt-2 flex origin-top-left animate-blur-in-slow items-start gap-2 text-sm text-tint-subtle"
|
||||
>
|
||||
<div className="flex origin-left animate-scale-in-slow items-start gap-2 text-sm text-tint-subtle">
|
||||
<Icon
|
||||
icon={getIconForToolCall(toolCall)}
|
||||
className="mt-1 size-3 shrink-0 text-tint-subtle/8"
|
||||
@@ -140,44 +135,32 @@ async function DescriptionForSearchToolCall(props: {
|
||||
// Resolve all hrefs for search results in parallel
|
||||
const searchResultsWithHrefs = await Promise.all(
|
||||
toolCall.results.map(async (result) => {
|
||||
switch (result.type) {
|
||||
case 'page': {
|
||||
const resolved = await resolveContentRef(
|
||||
result.anchor
|
||||
? {
|
||||
kind: 'anchor',
|
||||
page: result.pageId,
|
||||
space: result.spaceId,
|
||||
anchor: result.anchor,
|
||||
}
|
||||
: {
|
||||
kind: 'page',
|
||||
page: result.pageId,
|
||||
space: result.spaceId,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
return {
|
||||
...result,
|
||||
href: resolved?.href || '#',
|
||||
};
|
||||
}
|
||||
case 'record':
|
||||
return {
|
||||
...result,
|
||||
href: result.url ?? '#',
|
||||
};
|
||||
default:
|
||||
assertNever(result);
|
||||
}
|
||||
const resolved = await resolveContentRef(
|
||||
result.anchor
|
||||
? {
|
||||
kind: 'anchor',
|
||||
page: result.pageId,
|
||||
space: result.spaceId,
|
||||
anchor: result.anchor,
|
||||
}
|
||||
: {
|
||||
kind: 'page',
|
||||
page: result.pageId,
|
||||
space: result.spaceId,
|
||||
},
|
||||
context
|
||||
);
|
||||
return {
|
||||
...result,
|
||||
href: resolved?.href || '#',
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const hasResults = toolCall.results.length > 0;
|
||||
|
||||
return (
|
||||
<details className="-ml-5 group flex w-full flex-col">
|
||||
<details className={tcls('-ml-5 group flex w-full flex-col', hasResults ? 'gap-2' : '')}>
|
||||
<summary
|
||||
className={tcls(
|
||||
'-mx-2 flex list-none items-center gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md pr-4 pl-7 transition-colors marker:hidden',
|
||||
@@ -204,82 +187,47 @@ async function DescriptionForSearchToolCall(props: {
|
||||
) : null}
|
||||
</summary>
|
||||
{hasResults ? (
|
||||
<div className="hide-scrollbar mt-4 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all transition-discrete duration-500 group-open:max-h-96 group-open:opacity-11">
|
||||
<div className="hide-scrollbar mt-1 max-h-0 overflow-y-auto circular-corners:rounded-2xl rounded-corners:rounded-lg border border-tint-subtle p-2 opacity-0 transition-all transition-discrete duration-500 group-open:max-h-96 group-open:opacity-11">
|
||||
<ol className="space-y-1">
|
||||
{searchResultsWithHrefs.map((result, index) => {
|
||||
const resultKey = (() => {
|
||||
switch (result.type) {
|
||||
case 'page':
|
||||
return `${result.spaceId}/${result.pageId}`;
|
||||
case 'record':
|
||||
return result.recordId;
|
||||
default:
|
||||
assertNever(result);
|
||||
}
|
||||
})();
|
||||
|
||||
const iconClassName = 'mt-1 size-3 shrink-0 text-tint-subtle';
|
||||
const icon = <Icon icon="memo" className={iconClassName} />;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={`${resultKey}-${index}`}
|
||||
className="animate-fade-in-slow"
|
||||
style={{
|
||||
animationDelay: `${index * 25}ms`,
|
||||
}}
|
||||
{searchResultsWithHrefs.map((result, index) => (
|
||||
<li
|
||||
key={`${result.pageId}-${index}`}
|
||||
className="animate-fade-in-slow"
|
||||
style={{
|
||||
animationDelay: `${index * 25}ms`,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
href={result.href}
|
||||
className="flex items-start gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md px-3 py-2 transition-colors hover:bg-primary-hover"
|
||||
>
|
||||
<Link
|
||||
href={result.href}
|
||||
className="flex items-start gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md px-3 py-2 transition-colors hover:bg-primary-hover"
|
||||
insights={
|
||||
result.type === 'record'
|
||||
? {
|
||||
type: 'search_open_result',
|
||||
query: toolCall.query,
|
||||
result: {
|
||||
recordId: result.recordId,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: 'search_open_result',
|
||||
query: toolCall.query,
|
||||
result: {
|
||||
spaceId: result.spaceId,
|
||||
pageId: result.pageId,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
{result.type === 'record' ? (
|
||||
<Favicon url={result.href} className={iconClassName} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
<div className="flex flex-col gap-1 text-tint">
|
||||
<h3 className="line-clamp-2 font-medium text-sm text-tint">
|
||||
<Icon
|
||||
icon="memo"
|
||||
className="mt-1 size-3 shrink-0 text-tint-subtle"
|
||||
/>
|
||||
<div className="flex flex-col gap-1 text-tint">
|
||||
<h3 className="line-clamp-2 font-medium text-sm text-tint">
|
||||
<HighlightQuery
|
||||
query={toolCall.query}
|
||||
text={result.title}
|
||||
/>
|
||||
</h3>
|
||||
{result.description && (
|
||||
<p className="line-clamp-2 text-tint-subtle text-xs">
|
||||
<HighlightQuery
|
||||
query={toolCall.query}
|
||||
text={result.title}
|
||||
text={result.description}
|
||||
/>
|
||||
</h3>
|
||||
{result.description && (
|
||||
<p className="line-clamp-2 text-tint-subtle text-xs">
|
||||
<HighlightQuery
|
||||
query={toolCall.query}
|
||||
text={result.description}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className="ml-auto size-3 shrink-0 self-center"
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className="ml-auto size-3 shrink-0 self-center"
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user