mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 14:00:28 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 229f2ba9d5 | |||
| 6da36558f6 | |||
| ca71c1fad6 | |||
| 262afa357b |
@@ -182,9 +182,9 @@ jobs:
|
|||||||
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
|
||||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||||
ARGOS_BUILD_NAME: 'customers-v2'
|
ARGOS_BUILD_NAME: 'customers-v2'
|
||||||
pagespeed-testing-v2:
|
browserless-testing-v2-vercel:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: PageSpeed Testing v1
|
name: Browserless Testing v2 (Vercel)
|
||||||
needs: deploy-v2-vercel
|
needs: deploy-v2-vercel
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -195,8 +195,26 @@ jobs:
|
|||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 1
|
PUPPETEER_SKIP_DOWNLOAD: 1
|
||||||
- name: Run pagespeed tests
|
- name: Run tests
|
||||||
run: bun ./packages/gitbook/tests/pagespeed-testing.ts
|
run: cd ./packages/gitbook && bun e2e-browserless
|
||||||
env:
|
env:
|
||||||
BASE_URL: ${{needs.deploy-v2-vercel.outputs.deployment-url}}
|
BASE_URL: ${{needs.deploy-v2-vercel.outputs.deployment-url}}
|
||||||
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
|
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
|
||||||
|
# browserless-testing-v2-cloudflare:
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# name: Browserless Testing v2 (Cloudflare)
|
||||||
|
# needs: deploy-v2-cloudflare
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout
|
||||||
|
# uses: actions/checkout@v4
|
||||||
|
# - name: Setup Bun
|
||||||
|
# uses: ./.github/composite/setup-bun
|
||||||
|
# - name: Install dependencies
|
||||||
|
# run: bun install --frozen-lockfile
|
||||||
|
# env:
|
||||||
|
# PUPPETEER_SKIP_DOWNLOAD: 1
|
||||||
|
# - name: Run tests
|
||||||
|
# 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/
|
||||||
+1
-1
@@ -34,7 +34,7 @@
|
|||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": ["packages/*"],
|
"packages": ["packages/*"],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@gitbook/api": "^0.140.0",
|
"@gitbook/api": "^0.141.0",
|
||||||
"bidc": "^0.0.2"
|
"bidc": "^0.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @gitbook/expr
|
# @gitbook/expr
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 6da3655: Fix exports in gitbook/expr package.json
|
||||||
|
|
||||||
## 1.1.1
|
## 1.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/expr",
|
"name": "@gitbook/expr",
|
||||||
"description": "Safely evaluate & parse user-defined GitBook expressions.",
|
"description": "Safely evaluate & parse user-defined GitBook expressions.",
|
||||||
"version": "1.1.1",
|
"version": "1.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"development": "./src/index.ts",
|
"import": "./dist/index.js",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# gitbook
|
# gitbook
|
||||||
|
|
||||||
|
## 0.18.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 262afa3: Expose a MCP server for the docs site under /~gitbook/mcp
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [262afa3]
|
||||||
|
- @gitbook/icons@0.3.1
|
||||||
|
- @gitbook/react-openapi@1.4.3
|
||||||
|
- @gitbook/react-contentkit@0.7.5
|
||||||
|
|
||||||
## 0.17.2
|
## 0.17.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -336,6 +336,7 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
|
|||||||
pageActions: {
|
pageActions: {
|
||||||
externalAI: true,
|
externalAI: true,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
|
mcp: true,
|
||||||
},
|
},
|
||||||
trademark: {
|
trademark: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.17.2",
|
"version": "0.18.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "catalog:",
|
"@gitbook/api": "catalog:",
|
||||||
@@ -71,7 +71,10 @@
|
|||||||
"url-join": "^5.0.0",
|
"url-join": "^5.0.0",
|
||||||
"usehooks-ts": "^3.1.0",
|
"usehooks-ts": "^3.1.0",
|
||||||
"warn-once": "^0.1.1",
|
"warn-once": "^0.1.1",
|
||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3",
|
||||||
|
"mcp-handler": "^1.0.2",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||||
|
"zod": "^3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@argos-ci/playwright": "^5.0.9",
|
"@argos-ci/playwright": "^5.0.9",
|
||||||
@@ -84,7 +87,6 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/object-hash": "^3.0.6",
|
"@types/object-hash": "^3.0.6",
|
||||||
"@types/parse-cache-control": "^1.0.4",
|
"@types/parse-cache-control": "^1.0.4",
|
||||||
"@types/psi": "^4.1.6",
|
|
||||||
"@types/react": "18.3.13",
|
"@types/react": "18.3.13",
|
||||||
"@types/react-dom": "18.3.1",
|
"@types/react-dom": "18.3.1",
|
||||||
"@types/rison": "^0.0.9",
|
"@types/rison": "^0.0.9",
|
||||||
@@ -92,7 +94,6 @@
|
|||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"psi": "^4.1.0",
|
|
||||||
"stylelint": "^16.16.0",
|
"stylelint": "^16.16.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"ts-essentials": "^10.0.1",
|
"ts-essentials": "^10.0.1",
|
||||||
@@ -113,6 +114,7 @@
|
|||||||
"e2e": "playwright test e2e/internal.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",
|
"e2e-customers": "playwright test e2e/customers.spec.ts --project=chromium",
|
||||||
"unit": "bun test {src,packages} --preload ./tests/preload-bun.ts",
|
"unit": "bun test {src,packages} --preload ./tests/preload-bun.ts",
|
||||||
|
"e2e-browserless": "bun test ./tests/",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
import { type RouteLayoutParams, getStaticSiteContext } from '@/app/utils';
|
||||||
|
import { throwIfDataError } from '@/lib/data';
|
||||||
|
import { joinPathWithBaseURL } from '@/lib/paths';
|
||||||
|
import { findSiteSpaceBy } from '@/lib/sites';
|
||||||
|
import { createMcpHandler } from 'mcp-handler';
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
nextRequest: NextRequest,
|
||||||
|
{ params }: { params: Promise<RouteLayoutParams> }
|
||||||
|
) {
|
||||||
|
const { context } = await getStaticSiteContext(await params);
|
||||||
|
const { dataFetcher, linker, site } = context;
|
||||||
|
|
||||||
|
const mcpHandler = createMcpHandler(
|
||||||
|
(server) => {
|
||||||
|
server.tool(
|
||||||
|
'searchDocumentation',
|
||||||
|
`Search across the documentation to find relevant information, code examples, API references, and guides. Use this tool when you need to answer questions about ${site.title}, find specific documentation, understand how features work, or locate implementation details. The search returns contextual content with titles and direct links to the documentation pages.`,
|
||||||
|
{
|
||||||
|
query: z.string(),
|
||||||
|
},
|
||||||
|
async ({ query }) => {
|
||||||
|
const results = await throwIfDataError(
|
||||||
|
dataFetcher.searchSiteContent({
|
||||||
|
organizationId: context.organizationId,
|
||||||
|
siteId: site.id,
|
||||||
|
query,
|
||||||
|
scope: { mode: 'all' },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: results.flatMap((spaceResult) => {
|
||||||
|
const found = findSiteSpaceBy(
|
||||||
|
context.structure,
|
||||||
|
(siteSpace) => siteSpace.space.id === spaceResult.id
|
||||||
|
);
|
||||||
|
const spaceURL = found?.siteSpace.urls.published;
|
||||||
|
if (!spaceURL) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaceResult.pages.map((pageResult) => {
|
||||||
|
const pageURL = linker.toAbsoluteURL(
|
||||||
|
linker.toLinkForContent(
|
||||||
|
joinPathWithBaseURL(spaceURL, pageResult.path)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = pageResult.sections
|
||||||
|
?.map((section) => section.body)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
text: [
|
||||||
|
`Title: ${pageResult.title}`,
|
||||||
|
`Link: ${pageURL}`,
|
||||||
|
body ? `Content: ${body}` : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
basePath: context.linker.toPathInSite('~gitbook/'),
|
||||||
|
streamableHttpEndpoint: '/mcp',
|
||||||
|
maxDuration: 60,
|
||||||
|
verboseLogs: true,
|
||||||
|
disableSse: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Next.js request.url is the original URL and not the rewritten one from the middleware
|
||||||
|
const requestURL = new URL(
|
||||||
|
context.linker.toAbsoluteURL(context.linker.toPathInSite('~gitbook/mcp'))
|
||||||
|
);
|
||||||
|
requestURL.search = nextRequest.nextUrl.search;
|
||||||
|
|
||||||
|
const request = new Request(requestURL, nextRequest);
|
||||||
|
return mcpHandler(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler as GET, handler as POST };
|
||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import { useAIChatState } from '@/components/AI';
|
import { useAIChatState } from '@/components/AI';
|
||||||
import type { Assistant } from '@/components/AI';
|
import type { Assistant } from '@/components/AI';
|
||||||
import { ChatGPTIcon } from '@/components/PageActions/assets/ChatGPTIcon';
|
|
||||||
import { ClaudeIcon } from '@/components/PageActions/assets/ClaudeIcon';
|
|
||||||
import { MarkdownIcon } from '@/components/PageActions/assets/MarkdownIcon';
|
|
||||||
import { Button } from '@/components/primitives/Button';
|
import { Button } from '@/components/primitives/Button';
|
||||||
import { DropdownMenuItem, useDropdownMenuClose } from '@/components/primitives/DropdownMenu';
|
import { DropdownMenuItem, useDropdownMenuClose } from '@/components/primitives/DropdownMenu';
|
||||||
import { tString, useLanguage } from '@/intl/client';
|
import { tString, useLanguage } from '@/intl/client';
|
||||||
@@ -13,15 +10,15 @@ import type { GitSyncState } from '@gitbook/api';
|
|||||||
import { Icon, type IconName, IconStyle } from '@gitbook/icons';
|
import { Icon, type IconName, IconStyle } from '@gitbook/icons';
|
||||||
import assertNever from 'assert-never';
|
import assertNever from 'assert-never';
|
||||||
import QuickLRU from 'quick-lru';
|
import QuickLRU from 'quick-lru';
|
||||||
import type React from 'react';
|
import React from 'react';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
type PageActionType = 'button' | 'dropdown-menu-item';
|
type PageActionType = 'button' | 'dropdown-menu-item';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens our AI Docs Assistant.
|
* Action to open the GitBook Assistant.
|
||||||
*/
|
*/
|
||||||
export function OpenAIAssistant(props: { assistant: Assistant; type: PageActionType }) {
|
export function ActionOpenAssistant(props: { assistant: Assistant; type: PageActionType }) {
|
||||||
const { assistant, type } = props;
|
const { assistant, type } = props;
|
||||||
const chat = useAIChatState();
|
const chat = useAIChatState();
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
@@ -87,14 +84,14 @@ const useCopiedStore = create<
|
|||||||
const markdownCache = new QuickLRU<string, string>({ maxSize: 10 });
|
const markdownCache = new QuickLRU<string, string>({ maxSize: 10 });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the markdown version of the page to the clipboard.
|
* Action to copy the markdown version of the page to the clipboard.
|
||||||
*/
|
*/
|
||||||
export function CopyMarkdown(props: {
|
export function ActionCopyMarkdown(props: {
|
||||||
markdownPageUrl: string;
|
markdownPageURL: string;
|
||||||
type: PageActionType;
|
type: PageActionType;
|
||||||
isDefaultAction?: boolean;
|
isDefaultAction?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { markdownPageUrl, type, isDefaultAction } = props;
|
const { markdownPageURL, type, isDefaultAction } = props;
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
|
|
||||||
const closeDropdown = useDropdownMenuClose();
|
const closeDropdown = useDropdownMenuClose();
|
||||||
@@ -105,8 +102,8 @@ export function CopyMarkdown(props: {
|
|||||||
const fetchMarkdown = async () => {
|
const fetchMarkdown = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const result = await fetch(markdownPageUrl).then((res) => res.text());
|
const result = await fetch(markdownPageURL).then((res) => res.text());
|
||||||
markdownCache.set(markdownPageUrl, result);
|
markdownCache.set(markdownPageURL, result);
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
@@ -120,7 +117,7 @@ export function CopyMarkdown(props: {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(markdownCache.get(markdownPageUrl) || (await fetchMarkdown()), {
|
copy(markdownCache.get(markdownPageURL) || (await fetchMarkdown()), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// We close the dropdown menu if the action is a dropdown menu item and not the default action.
|
// We close the dropdown menu if the action is a dropdown menu item and not the default action.
|
||||||
if (type === 'dropdown-menu-item' && !isDefaultAction) {
|
if (type === 'dropdown-menu-item' && !isDefaultAction) {
|
||||||
@@ -146,17 +143,17 @@ export function CopyMarkdown(props: {
|
|||||||
/**
|
/**
|
||||||
* Redirects to the markdown version of the page.
|
* Redirects to the markdown version of the page.
|
||||||
*/
|
*/
|
||||||
export function ViewAsMarkdown(props: { markdownPageUrl: string; type: PageActionType }) {
|
export function ActionViewAsMarkdown(props: { markdownPageURL: string; type: PageActionType }) {
|
||||||
const { markdownPageUrl, type } = props;
|
const { markdownPageURL, type } = props;
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageActionWrapper
|
<PageActionWrapper
|
||||||
type={type}
|
type={type}
|
||||||
icon={<MarkdownIcon className="size-4 fill-current" />}
|
icon="markdown"
|
||||||
label={tString(language, 'view_page_markdown')}
|
label={tString(language, 'view_page_markdown')}
|
||||||
description={tString(language, 'view_page_plaintext')}
|
description={tString(language, 'view_page_plaintext')}
|
||||||
href={markdownPageUrl}
|
href={markdownPageURL}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -164,7 +161,7 @@ export function ViewAsMarkdown(props: { markdownPageUrl: string; type: PageActio
|
|||||||
/**
|
/**
|
||||||
* Open the page in a LLM with a pre-filled prompt. Either ChatGPT or Claude.
|
* Open the page in a LLM with a pre-filled prompt. Either ChatGPT or Claude.
|
||||||
*/
|
*/
|
||||||
export function OpenInLLM(props: {
|
export function ActionOpenInLLM(props: {
|
||||||
provider: 'chatgpt' | 'claude';
|
provider: 'chatgpt' | 'claude';
|
||||||
url: string;
|
url: string;
|
||||||
type: PageActionType;
|
type: PageActionType;
|
||||||
@@ -177,13 +174,7 @@ export function OpenInLLM(props: {
|
|||||||
return (
|
return (
|
||||||
<PageActionWrapper
|
<PageActionWrapper
|
||||||
type={type}
|
type={type}
|
||||||
icon={
|
icon={provider}
|
||||||
provider === 'chatgpt' ? (
|
|
||||||
<ChatGPTIcon className="size-3.5 fill-current" />
|
|
||||||
) : (
|
|
||||||
<ClaudeIcon className="size-3.5 fill-current" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={tString(language, 'open_in', providerLabel)}
|
label={tString(language, 'open_in', providerLabel)}
|
||||||
shortLabel={providerLabel}
|
shortLabel={providerLabel}
|
||||||
description={tString(language, 'ai_chat_ask_about_page', providerLabel)}
|
description={tString(language, 'ai_chat_ask_about_page', providerLabel)}
|
||||||
@@ -192,7 +183,10 @@ export function OpenInLLM(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GitEditLink(props: {
|
/**
|
||||||
|
* Action to open the page in the associated Git repository.
|
||||||
|
*/
|
||||||
|
export function ActionOpenEditOnGit(props: {
|
||||||
type: PageActionType;
|
type: PageActionType;
|
||||||
provider: GitSyncState['installationProvider'];
|
provider: GitSyncState['installationProvider'];
|
||||||
url: string;
|
url: string;
|
||||||
@@ -214,7 +208,66 @@ export function GitEditLink(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ViewAsPDF(props: { url: string; type: PageActionType }) {
|
/**
|
||||||
|
* Action to copy the MCP URL to the clipboard.
|
||||||
|
*/
|
||||||
|
export function ActionCopyMCPURL(props: { mcpURL: string; type: PageActionType }) {
|
||||||
|
const { mcpURL, type } = props;
|
||||||
|
const language = useLanguage();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CopyToClipboard
|
||||||
|
type={type}
|
||||||
|
data={mcpURL}
|
||||||
|
label={tString(language, 'connect_with_mcp')}
|
||||||
|
description={tString(language, 'copy_mcp_url')}
|
||||||
|
icon="mcp"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to open the MCP server in a specific editor.
|
||||||
|
*/
|
||||||
|
export function ActionOpenMCP(props: {
|
||||||
|
siteTitle: string;
|
||||||
|
mcpURL: string;
|
||||||
|
provider: 'vscode';
|
||||||
|
type: PageActionType;
|
||||||
|
}) {
|
||||||
|
const { siteTitle, provider, mcpURL, type } = props;
|
||||||
|
const language = useLanguage();
|
||||||
|
|
||||||
|
const providerInfo = React.useMemo<{ label: string; icon: IconName; url: string }>(() => {
|
||||||
|
switch (provider) {
|
||||||
|
case 'vscode': {
|
||||||
|
const vscodeConfig = { name: siteTitle, url: mcpURL };
|
||||||
|
return {
|
||||||
|
label: 'VSCode',
|
||||||
|
icon: 'vscode',
|
||||||
|
url: `vscode:mcp/install?${encodeURIComponent(JSON.stringify(vscodeConfig))}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assertNever(provider);
|
||||||
|
}
|
||||||
|
}, [provider, mcpURL, siteTitle]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageActionWrapper
|
||||||
|
type={type}
|
||||||
|
href={providerInfo.url}
|
||||||
|
label={tString(language, 'connect_mcp_to', providerInfo.label)}
|
||||||
|
description={tString(language, 'install_mcp_on', providerInfo.label)}
|
||||||
|
icon={providerInfo.icon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to view the page as a PDF.
|
||||||
|
*/
|
||||||
|
export function ActionViewAsPDF(props: { url: string; type: PageActionType }) {
|
||||||
const { url, type } = props;
|
const { url, type } = props;
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
|
|
||||||
@@ -229,6 +282,30 @@ export function ViewAsPDF(props: { url: string; type: PageActionType }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to copy a string to the clipboard.
|
||||||
|
*/
|
||||||
|
export function CopyToClipboard(props: {
|
||||||
|
type: PageActionType;
|
||||||
|
data: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
icon: IconName;
|
||||||
|
}) {
|
||||||
|
const { type, data, label, description, icon } = props;
|
||||||
|
return (
|
||||||
|
<PageActionWrapper
|
||||||
|
type={type}
|
||||||
|
icon={icon}
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(data);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps an action in a button (for the default action) or dropdown menu item.
|
* Wraps an action in a button (for the default action) or dropdown menu item.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
|
||||||
CopyMarkdown,
|
|
||||||
GitEditLink,
|
|
||||||
OpenAIAssistant,
|
|
||||||
OpenInLLM,
|
|
||||||
ViewAsMarkdown,
|
|
||||||
ViewAsPDF,
|
|
||||||
} from '@/components/PageActions/PageActions';
|
|
||||||
import { Button, ButtonGroup } from '@/components/primitives/Button';
|
import { Button, ButtonGroup } from '@/components/primitives/Button';
|
||||||
import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu';
|
import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu';
|
||||||
import { tString, useLanguage } from '@/intl/client';
|
import { tString, useLanguage } from '@/intl/client';
|
||||||
import type { GitSyncState, SiteCustomizationSettings } from '@gitbook/api';
|
import type { GitSyncState, SiteCustomizationSettings } from '@gitbook/api';
|
||||||
import { Icon } from '@gitbook/icons';
|
import { Icon } from '@gitbook/icons';
|
||||||
import { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { useAI } from '../AI';
|
import { useAI } from '../AI';
|
||||||
|
import {
|
||||||
|
ActionCopyMCPURL,
|
||||||
|
ActionCopyMarkdown,
|
||||||
|
ActionOpenAssistant,
|
||||||
|
ActionOpenEditOnGit,
|
||||||
|
ActionOpenInLLM,
|
||||||
|
ActionOpenMCP,
|
||||||
|
ActionViewAsMarkdown,
|
||||||
|
ActionViewAsPDF,
|
||||||
|
} from './PageActions';
|
||||||
|
|
||||||
interface PageActionsDropdownProps {
|
interface PageActionsDropdownProps {
|
||||||
markdownPageUrl: string;
|
siteTitle: string;
|
||||||
|
markdownPageURL: string;
|
||||||
|
mcpURL?: string;
|
||||||
|
pdfURL?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
actions: SiteCustomizationSettings['pageActions'];
|
actions: SiteCustomizationSettings['pageActions'];
|
||||||
editOnGit?: {
|
editOnGit?: {
|
||||||
provider: GitSyncState['installationProvider'];
|
provider: GitSyncState['installationProvider'];
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
pdfUrl?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,19 +36,14 @@ interface PageActionsDropdownProps {
|
|||||||
*/
|
*/
|
||||||
export function PageActionsDropdown(props: PageActionsDropdownProps) {
|
export function PageActionsDropdown(props: PageActionsDropdownProps) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const assistants = useAI().assistants.filter(
|
|
||||||
(assistant) => assistant.ui === true && assistant.pageAction
|
|
||||||
);
|
|
||||||
const language = useLanguage();
|
const language = useLanguage();
|
||||||
|
|
||||||
const defaultActions = [assistants.length > 0, props.actions.markdown, props.editOnGit].filter(
|
const defaultAction = getPageDefaultAction(props);
|
||||||
Boolean
|
const dropdownActions = getPageDropdownActions(props);
|
||||||
);
|
|
||||||
const dropdownActions = [props.actions.externalAI, props.pdfUrl].filter(Boolean);
|
|
||||||
|
|
||||||
return [...defaultActions, ...dropdownActions].length > 0 ? (
|
return defaultAction || dropdownActions.length > 0 ? (
|
||||||
<ButtonGroup ref={ref} className={props.className}>
|
<ButtonGroup ref={ref} className={props.className}>
|
||||||
{defaultActions.length > 0 ? <DefaultAction {...props} /> : null}
|
{defaultAction}
|
||||||
{dropdownActions.length > 0 ? (
|
{dropdownActions.length > 0 ? (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
align="end"
|
align="end"
|
||||||
@@ -57,18 +56,15 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) {
|
|||||||
className="size-3 transition-transform group-data-[state=open]/button:rotate-180"
|
className="size-3 transition-transform group-data-[state=open]/button:rotate-180"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={tString(
|
label={tString(language, defaultAction ? 'more' : 'actions')}
|
||||||
language,
|
iconOnly={!!defaultAction}
|
||||||
defaultActions.some(Boolean) ? 'more' : 'actions'
|
|
||||||
)}
|
|
||||||
iconOnly={defaultActions.some(Boolean)}
|
|
||||||
size="xsmall"
|
size="xsmall"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="bg-tint-base text-sm"
|
className="bg-tint-base text-sm"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PageActionsDropdownMenuContent {...props} />
|
{dropdownActions}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
) : null}
|
) : null}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
@@ -76,80 +72,99 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The content of the dropdown menu.
|
* Return the list of actions to show in the dropdown menu.
|
||||||
*/
|
*/
|
||||||
function PageActionsDropdownMenuContent(props: PageActionsDropdownProps) {
|
function getPageDropdownActions(props: PageActionsDropdownProps): React.ReactNode[] {
|
||||||
const { markdownPageUrl, actions } = props;
|
const { siteTitle, markdownPageURL, mcpURL, actions } = props;
|
||||||
const assistants = useAI().assistants.filter(
|
const assistants = useAI().assistants.filter(
|
||||||
(assistant) => assistant.ui === true && assistant.pageAction
|
(assistant) => assistant.ui === true && assistant.pageAction
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return [
|
||||||
<>
|
...assistants.map((assistant) => (
|
||||||
{assistants.map((assistant) => (
|
<ActionOpenAssistant
|
||||||
<OpenAIAssistant
|
|
||||||
key={assistant.label}
|
key={assistant.label}
|
||||||
assistant={assistant}
|
assistant={assistant}
|
||||||
type="dropdown-menu-item"
|
type="dropdown-menu-item"
|
||||||
/>
|
/>
|
||||||
))}
|
)),
|
||||||
|
|
||||||
{actions.markdown ? (
|
actions.markdown ? (
|
||||||
<>
|
<React.Fragment key="markdown">
|
||||||
<DropdownMenuSeparator className="first:hidden" />
|
<DropdownMenuSeparator className="first:hidden" />
|
||||||
<CopyMarkdown
|
<ActionCopyMarkdown
|
||||||
isDefaultAction={!assistants.length}
|
isDefaultAction={!assistants.length}
|
||||||
markdownPageUrl={markdownPageUrl}
|
markdownPageURL={markdownPageURL}
|
||||||
type="dropdown-menu-item"
|
type="dropdown-menu-item"
|
||||||
/>
|
/>
|
||||||
<ViewAsMarkdown markdownPageUrl={markdownPageUrl} type="dropdown-menu-item" />
|
<ActionViewAsMarkdown markdownPageURL={markdownPageURL} type="dropdown-menu-item" />
|
||||||
</>
|
</React.Fragment>
|
||||||
) : null}
|
) : null,
|
||||||
|
|
||||||
{actions.externalAI ? (
|
actions.externalAI ? (
|
||||||
<>
|
<React.Fragment key="externalAI">
|
||||||
<DropdownMenuSeparator className="first:hidden" />
|
<DropdownMenuSeparator className="first:hidden" />
|
||||||
<OpenInLLM provider="chatgpt" url={markdownPageUrl} type="dropdown-menu-item" />
|
<ActionOpenInLLM
|
||||||
<OpenInLLM provider="claude" url={markdownPageUrl} type="dropdown-menu-item" />
|
provider="chatgpt"
|
||||||
</>
|
url={markdownPageURL}
|
||||||
) : null}
|
type="dropdown-menu-item"
|
||||||
|
/>
|
||||||
|
<ActionOpenInLLM
|
||||||
|
provider="claude"
|
||||||
|
url={markdownPageURL}
|
||||||
|
type="dropdown-menu-item"
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
) : null,
|
||||||
|
|
||||||
{props.editOnGit || props.pdfUrl ? (
|
actions.mcp && mcpURL ? (
|
||||||
<>
|
<React.Fragment key="mcp">
|
||||||
|
<DropdownMenuSeparator className="first:hidden" />
|
||||||
|
<ActionCopyMCPURL mcpURL={mcpURL} type="dropdown-menu-item" />
|
||||||
|
<ActionOpenMCP
|
||||||
|
provider="vscode"
|
||||||
|
mcpURL={mcpURL}
|
||||||
|
siteTitle={siteTitle}
|
||||||
|
type="dropdown-menu-item"
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
) : null,
|
||||||
|
|
||||||
|
props.editOnGit || props.pdfURL ? (
|
||||||
|
<React.Fragment key="editOnGit">
|
||||||
<DropdownMenuSeparator className="first:hidden" />
|
<DropdownMenuSeparator className="first:hidden" />
|
||||||
{props.editOnGit ? (
|
{props.editOnGit ? (
|
||||||
<GitEditLink
|
<ActionOpenEditOnGit
|
||||||
type="dropdown-menu-item"
|
type="dropdown-menu-item"
|
||||||
provider={props.editOnGit.provider}
|
provider={props.editOnGit.provider}
|
||||||
url={props.editOnGit.url}
|
url={props.editOnGit.url}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{props.pdfUrl ? (
|
{props.pdfURL ? (
|
||||||
<ViewAsPDF url={props.pdfUrl} type="dropdown-menu-item" />
|
<ActionViewAsPDF url={props.pdfURL} type="dropdown-menu-item" />
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</React.Fragment>
|
||||||
) : null}
|
) : null,
|
||||||
</>
|
].filter(Boolean);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default action shown as a quick-access button beside the dropdown menu
|
* A default action shown as a quick-access button beside the dropdown menu
|
||||||
*/
|
*/
|
||||||
function DefaultAction(props: PageActionsDropdownProps) {
|
function getPageDefaultAction(props: PageActionsDropdownProps) {
|
||||||
const { markdownPageUrl, actions } = props;
|
const { markdownPageURL, actions } = props;
|
||||||
const assistants = useAI().assistants.filter(
|
const assistants = useAI().assistants.filter(
|
||||||
(assistant) => assistant.ui === true && assistant.pageAction
|
(assistant) => assistant.ui === true && assistant.pageAction
|
||||||
);
|
);
|
||||||
|
|
||||||
const assistant = assistants[0];
|
const assistant = assistants[0];
|
||||||
if (assistant) {
|
if (assistant) {
|
||||||
return <OpenAIAssistant assistant={assistant} type="button" />;
|
return <ActionOpenAssistant assistant={assistant} type="button" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.editOnGit) {
|
if (props.editOnGit) {
|
||||||
return (
|
return (
|
||||||
<GitEditLink
|
<ActionOpenEditOnGit
|
||||||
type="button"
|
type="button"
|
||||||
provider={props.editOnGit.provider}
|
provider={props.editOnGit.provider}
|
||||||
url={props.editOnGit.url}
|
url={props.editOnGit.url}
|
||||||
@@ -159,11 +174,13 @@ function DefaultAction(props: PageActionsDropdownProps) {
|
|||||||
|
|
||||||
if (actions.markdown) {
|
if (actions.markdown) {
|
||||||
return (
|
return (
|
||||||
<CopyMarkdown
|
<ActionCopyMarkdown
|
||||||
isDefaultAction={!assistant}
|
isDefaultAction={!assistant}
|
||||||
markdownPageUrl={markdownPageUrl}
|
markdownPageURL={markdownPageURL}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export function ChatGPTIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" {...props}>
|
|
||||||
<title>ChatGPT</title>
|
|
||||||
<path d="m297.06 130.97c7.26-21.79 4.76-45.66-6.85-65.48-17.46-30.4-52.56-46.04-86.84-38.68-15.25-17.18-37.16-26.95-60.13-26.81-35.04-.08-66.13 22.48-76.91 55.82-22.51 4.61-41.94 18.7-53.31 38.67-17.59 30.32-13.58 68.54 9.92 94.54-7.26 21.79-4.76 45.66 6.85 65.48 17.46 30.4 52.56 46.04 86.84 38.68 15.24 17.18 37.16 26.95 60.13 26.8 35.06.09 66.16-22.49 76.94-55.86 22.51-4.61 41.94-18.7 53.31-38.67 17.57-30.32 13.55-68.51-9.94-94.51zm-120.28 168.11c-14.03.02-27.62-4.89-38.39-13.88.49-.26 1.34-.73 1.89-1.07l63.72-36.8c3.26-1.85 5.26-5.32 5.24-9.07v-89.83l26.93 15.55c.29.14.48.42.52.74v74.39c-.04 33.08-26.83 59.9-59.91 59.97zm-128.84-55.03c-7.03-12.14-9.56-26.37-7.15-40.18.47.28 1.3.79 1.89 1.13l63.72 36.8c3.23 1.89 7.23 1.89 10.47 0l77.79-44.92v31.1c.02.32-.13.63-.38.83l-64.41 37.19c-28.69 16.52-65.33 6.7-81.92-21.95zm-16.77-139.09c7-12.16 18.05-21.46 31.21-26.29 0 .55-.03 1.52-.03 2.2v73.61c-.02 3.74 1.98 7.21 5.23 9.06l77.79 44.91-26.93 15.55c-.27.18-.61.21-.91.08l-64.42-37.22c-28.63-16.58-38.45-53.21-21.95-81.89zm221.26 51.49-77.79-44.92 26.93-15.54c.27-.18.61-.21.91-.08l64.42 37.19c28.68 16.57 38.51 53.26 21.94 81.94-7.01 12.14-18.05 21.44-31.2 26.28v-75.81c.03-3.74-1.96-7.2-5.2-9.06zm26.8-40.34c-.47-.29-1.3-.79-1.89-1.13l-63.72-36.8c-3.23-1.89-7.23-1.89-10.47 0l-77.79 44.92v-31.1c-.02-.32.13-.63.38-.83l64.41-37.16c28.69-16.55 65.37-6.7 81.91 22 6.99 12.12 9.52 26.31 7.15 40.1zm-168.51 55.43-26.94-15.55c-.29-.14-.48-.42-.52-.74v-74.39c.02-33.12 26.89-59.96 60.01-59.94 14.01 0 27.57 4.92 38.34 13.88-.49.26-1.33.73-1.89 1.07l-63.72 36.8c-3.26 1.85-5.26 5.31-5.24 9.06l-.04 89.79zm14.63-31.54 34.65-20.01 34.65 20v40.01l-34.65 20-34.65-20z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
export function ClaudeIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 256 257"
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
preserveAspectRatio="xMidYMid"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<title>Claude</title>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M50.2278481,170.321013 L100.585316,142.063797 L101.427848,139.601013 L100.585316,138.24 L98.1225316,138.24 L89.6972152,137.721519 L60.921519,136.943797 L35.9696203,135.906835 L11.795443,134.610633 L5.70329114,133.31443 L0,125.796456 L0.583291139,122.037468 L5.70329114,118.602532 L13.0268354,119.250633 L29.2293671,120.352405 L53.5331646,122.037468 L71.161519,123.07443 L97.28,125.796456 L101.427848,125.796456 L102.011139,124.111392 L100.585316,123.07443 L99.4835443,122.037468 L74.3372152,104.992405 L47.116962,86.9751899 L32.8587342,76.6055696 L25.1463291,71.3559494 L21.2577215,66.4303797 L19.5726582,55.6718987 L26.5721519,47.9594937 L35.9696203,48.6075949 L38.3675949,49.2556962 L47.8946835,56.5792405 L68.2450633,72.3281013 L94.8172152,91.9007595 L98.7058228,95.1412658 L100.261266,94.0394937 L100.455696,93.2617722 L98.7058228,90.3453165 L84.2531646,64.2268354 L68.8283544,37.6546835 L61.958481,26.636962 L60.1437975,20.0263291 C59.4956962,17.3043038 59.0420253,15.0359494 59.0420253,12.2491139 L67.0136709,1.42582278 L71.4207595,-1.42108547e-14 L82.0496203,1.42582278 L86.521519,5.31443038 L93.1321519,20.4151899 L103.825823,44.2005063 L120.417215,76.5407595 L125.277975,86.1326582 L127.87038,95.0116456 L128.842532,97.7336709 L130.527595,97.7336709 L130.527595,96.1782278 L131.888608,77.9665823 L134.416203,55.6070886 L136.878987,26.8313924 L137.721519,18.7301266 L141.739747,9.00860759 L149.711392,3.75898734 L155.933165,6.74025316 L161.053165,14.0637975 L160.340253,18.7949367 L157.294177,38.5620253 L151.331646,69.5412658 L147.443038,90.2805063 L149.711392,90.2805063 L152.303797,87.6881013 L162.803038,73.7539241 L180.431392,51.718481 L188.208608,42.9691139 L197.282025,33.3124051 L203.114937,28.7108861 L214.132658,28.7108861 L222.233924,40.7655696 L218.604557,53.2091139 L207.262785,67.596962 L197.865316,79.7812658 L184.38481,97.9281013 L175.959494,112.44557 L176.737215,113.612152 L178.746329,113.417722 L209.207089,106.936709 L225.668861,103.955443 L245.306329,100.585316 L254.185316,104.733165 L255.157468,108.945823 L251.657722,117.56557 L230.659241,122.75038 L206.031392,127.675949 L169.348861,136.360506 L168.89519,136.684557 L169.413671,137.332658 L185.940253,138.888101 L193.004557,139.276962 L210.308861,139.276962 L242.519494,141.674937 L250.94481,147.248608 L256,154.053671 L255.157468,159.238481 L242.195443,165.849114 L224.696709,161.701266 L183.866329,151.979747 L169.867342,148.48 L167.923038,148.48 L167.923038,149.646582 L179.588861,161.053165 L200.976203,180.366582 L227.742785,205.253671 L229.103797,211.410633 L225.668861,216.271392 L222.039494,215.752911 L198.513418,198.059747 L189.44,190.088101 L168.89519,172.783797 L167.534177,172.783797 L167.534177,174.598481 L172.265316,181.533165 L197.282025,219.123038 L198.578228,230.659241 L196.763544,234.418228 L190.282532,236.686582 L183.153418,235.39038 L168.506329,214.84557 L153.40557,191.708354 L141.221266,170.969114 L139.730633,171.811646 L132.536709,249.259747 L129.166582,253.213165 L121.389367,256.19443 L114.908354,251.268861 L111.473418,243.297215 L114.908354,227.548354 L119.056203,207.003544 L122.426329,190.671392 L125.472405,170.385823 L127.287089,163.64557 L127.157468,163.191899 L125.666835,163.386329 L110.371646,184.38481 L87.1048101,215.817722 L68.6987342,235.52 L64.2916456,237.269873 L56.6440506,233.316456 L57.356962,226.252152 L61.6344304,219.96557 L87.1048101,187.560506 L102.46481,167.469367 L112.380759,155.868354 L112.315949,154.183291 L111.732658,154.183291 L44.0708861,198.124557 L32.0162025,199.68 L26.8313924,194.819241 L27.4794937,186.847595 L29.9422785,184.25519 L50.2926582,170.256203 L50.2278481,170.321013 Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export function MarkdownIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
||||||
return (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 471 289.85" {...props}>
|
|
||||||
<title>Markdown</title>
|
|
||||||
<path d="M437,289.85H34a34,34,0,0,1-34-34V34A34,34,0,0,1,34,0H437a34,34,0,0,1,34,34V255.88A34,34,0,0,1,437,289.85ZM34,22.64A11.34,11.34,0,0,0,22.64,34V255.88A11.34,11.34,0,0,0,34,267.2H437a11.34,11.34,0,0,0,11.33-11.32V34A11.34,11.34,0,0,0,437,22.64Z" />
|
|
||||||
<path d="M67.93,221.91v-154h45.29l45.29,56.61L203.8,67.93h45.29v154H203.8V133.6l-45.29,56.61L113.22,133.6v88.31Zm283.06,0-67.94-74.72h45.29V67.93h45.29v79.26h45.29Z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { GitBookSiteContext } from '@/lib/context';
|
import type { GitBookSiteContext } from '@/lib/context';
|
||||||
import type { AncestorRevisionPage } from '@/lib/pages';
|
import type { AncestorRevisionPage } from '@/lib/pages';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
import type { RevisionPageDocument } from '@gitbook/api';
|
import { type RevisionPageDocument, SiteVisibility } from '@gitbook/api';
|
||||||
import { Icon } from '@gitbook/icons';
|
import { Icon } from '@gitbook/icons';
|
||||||
import urlJoin from 'url-join';
|
import urlJoin from 'url-join';
|
||||||
import { getPDFURLSearchParams } from '../PDF';
|
import { getPDFURLSearchParams } from '../PDF';
|
||||||
@@ -39,7 +39,8 @@ export async function PageHeader(props: {
|
|||||||
{page.layout.tableOfContents ? (
|
{page.layout.tableOfContents ? (
|
||||||
// Show page actions if *any* of the actions are enabled
|
// Show page actions if *any* of the actions are enabled
|
||||||
<PageActionsDropdown
|
<PageActionsDropdown
|
||||||
markdownPageUrl={`${context.linker.toAbsoluteURL(context.linker.toPathInSpace(page.path))}.md`}
|
siteTitle={context.site.title}
|
||||||
|
markdownPageURL={`${context.linker.toAbsoluteURL(context.linker.toPathInSpace(page.path))}.md`}
|
||||||
editOnGit={
|
editOnGit={
|
||||||
context.customization.git.showEditLink &&
|
context.customization.git.showEditLink &&
|
||||||
context.space.gitSync?.url &&
|
context.space.gitSync?.url &&
|
||||||
@@ -50,7 +51,7 @@ export async function PageHeader(props: {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
pdfUrl={
|
pdfURL={
|
||||||
context.customization.pdf.enabled
|
context.customization.pdf.enabled
|
||||||
? context.linker.toPathInSpace(
|
? context.linker.toPathInSpace(
|
||||||
`~gitbook/pdf?${getPDFURLSearchParams({
|
`~gitbook/pdf?${getPDFURLSearchParams({
|
||||||
@@ -61,6 +62,13 @@ export async function PageHeader(props: {
|
|||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
mcpURL={
|
||||||
|
context.site.visibility !== SiteVisibility.VisitorAuth
|
||||||
|
? context.linker.toAbsoluteURL(
|
||||||
|
context.linker.toPathInSpace('~gitbook/mcp')
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
actions={context.customization.pageActions}
|
actions={context.customization.pageActions}
|
||||||
className={tcls(
|
className={tcls(
|
||||||
'float-right ml-4 xl:max-2xl:page-api-block:mr-62',
|
'float-right ml-4 xl:max-2xl:page-api-block:mr-62',
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ export const de = {
|
|||||||
view_page_plaintext: 'Diese Seite als Klartext anzeigen',
|
view_page_plaintext: 'Diese Seite als Klartext anzeigen',
|
||||||
open_in: 'Öffnen in ${1}',
|
open_in: 'Öffnen in ${1}',
|
||||||
open_in_llms_pre_prompt: 'Lies ${1} und beantworte Fragen zum Inhalt.',
|
open_in_llms_pre_prompt: 'Lies ${1} und beantworte Fragen zum Inhalt.',
|
||||||
|
connect_mcp_to: 'Mit ${1} verbinden',
|
||||||
|
install_mcp_on: 'MCP-Server auf ${1} installieren',
|
||||||
|
connect_with_mcp: 'Mit MCP verbinden',
|
||||||
|
copy_mcp_url: 'MCP-Server-URL kopieren',
|
||||||
press_to_confirm: 'Drücke ${1} zum Bestätigen',
|
press_to_confirm: 'Drücke ${1} zum Bestätigen',
|
||||||
tool_call_skipped: 'Übersprungen "${1}"',
|
tool_call_skipped: 'Übersprungen "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ export const en = {
|
|||||||
view_page_plaintext: 'View this page as plain text',
|
view_page_plaintext: 'View this page as plain text',
|
||||||
open_in: 'Open in ${1}',
|
open_in: 'Open in ${1}',
|
||||||
open_in_llms_pre_prompt: 'Read ${1} and answer questions about the content.',
|
open_in_llms_pre_prompt: 'Read ${1} and answer questions about the content.',
|
||||||
|
connect_mcp_to: 'Connect to ${1}',
|
||||||
|
install_mcp_on: 'Install MCP Server on ${1}',
|
||||||
|
connect_with_mcp: 'Connect with MCP',
|
||||||
|
copy_mcp_url: 'Copy the MCP Server URL',
|
||||||
press_to_confirm: 'Press ${1} to confirm',
|
press_to_confirm: 'Press ${1} to confirm',
|
||||||
tool_call_skipped: 'Skipped "${1}"',
|
tool_call_skipped: 'Skipped "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ export const es: TranslationLanguage = {
|
|||||||
view_page_plaintext: 'Ver esta página como texto plano',
|
view_page_plaintext: 'Ver esta página como texto plano',
|
||||||
open_in: 'Abrir en ${1}',
|
open_in: 'Abrir en ${1}',
|
||||||
open_in_llms_pre_prompt: 'Lee ${1} y responde preguntas sobre el contenido.',
|
open_in_llms_pre_prompt: 'Lee ${1} y responde preguntas sobre el contenido.',
|
||||||
|
connect_mcp_to: 'Conectar a ${1}',
|
||||||
|
install_mcp_on: 'Instalar servidor MCP en ${1}',
|
||||||
|
connect_with_mcp: 'Conectar con MCP',
|
||||||
|
copy_mcp_url: 'Copiar URL del servidor MCP',
|
||||||
press_to_confirm: 'Presiona ${1} para confirmar',
|
press_to_confirm: 'Presiona ${1} para confirmar',
|
||||||
tool_call_skipped: 'Omitido "${1}"',
|
tool_call_skipped: 'Omitido "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ export const fr = {
|
|||||||
view_page_plaintext: 'Voir en texte brut',
|
view_page_plaintext: 'Voir en texte brut',
|
||||||
open_in: 'Ouvrir dans ${1}',
|
open_in: 'Ouvrir dans ${1}',
|
||||||
open_in_llms_pre_prompt: 'Lire ${1} et répondre aux questions sur le contenu.',
|
open_in_llms_pre_prompt: 'Lire ${1} et répondre aux questions sur le contenu.',
|
||||||
|
connect_mcp_to: 'Se connecter à ${1}',
|
||||||
|
install_mcp_on: 'Installer le serveur MCP sur ${1}',
|
||||||
|
connect_with_mcp: 'Se connecter avec MCP',
|
||||||
|
copy_mcp_url: "Copier l'URL du serveur MCP",
|
||||||
press_to_confirm: 'Appuyez sur ${1} pour confirmer',
|
press_to_confirm: 'Appuyez sur ${1} pour confirmer',
|
||||||
tool_call_skipped: 'Ignoré "${1}"',
|
tool_call_skipped: 'Ignoré "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ export const ja: TranslationLanguage = {
|
|||||||
view_page_plaintext: 'このページをプレーンテキストで表示',
|
view_page_plaintext: 'このページをプレーンテキストで表示',
|
||||||
open_in: '${1} で開く',
|
open_in: '${1} で開く',
|
||||||
open_in_llms_pre_prompt: '${1} を読んで内容に関する質問に答えてください。',
|
open_in_llms_pre_prompt: '${1} を読んで内容に関する質問に答えてください。',
|
||||||
|
connect_mcp_to: '${1} に接続',
|
||||||
|
install_mcp_on: '${1} にMCPサーバーをインストール',
|
||||||
|
connect_with_mcp: 'MCPに接続',
|
||||||
|
copy_mcp_url: 'MCPサーバーのURLをコピー',
|
||||||
press_to_confirm: '確認するには${1}を押してください',
|
press_to_confirm: '確認するには${1}を押してください',
|
||||||
tool_call_skipped: '"${1}" をスキップしました',
|
tool_call_skipped: '"${1}" をスキップしました',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ export const nl: TranslationLanguage = {
|
|||||||
view_page_plaintext: 'Bekijk deze pagina als platte tekst',
|
view_page_plaintext: 'Bekijk deze pagina als platte tekst',
|
||||||
open_in: 'Open in ${1}',
|
open_in: 'Open in ${1}',
|
||||||
open_in_llms_pre_prompt: 'Lees ${1} en beantwoord vragen over de inhoud.',
|
open_in_llms_pre_prompt: 'Lees ${1} en beantwoord vragen over de inhoud.',
|
||||||
|
connect_mcp_to: 'Verbind met ${1}',
|
||||||
|
install_mcp_on: 'Installeer MCP-server op ${1}',
|
||||||
|
connect_with_mcp: 'Verbind met MCP',
|
||||||
|
copy_mcp_url: 'Kopieer MCP-server URL',
|
||||||
press_to_confirm: 'Druk op ${1} om te bevestigen',
|
press_to_confirm: 'Druk op ${1} om te bevestigen',
|
||||||
tool_call_skipped: '"${1}" overgeslagen',
|
tool_call_skipped: '"${1}" overgeslagen',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ export const no: TranslationLanguage = {
|
|||||||
view_page_plaintext: 'Vis denne siden som ren tekst',
|
view_page_plaintext: 'Vis denne siden som ren tekst',
|
||||||
open_in: 'Åpne i ${1}',
|
open_in: 'Åpne i ${1}',
|
||||||
open_in_llms_pre_prompt: 'Les ${1} og besvar spørsmål om innholdet.',
|
open_in_llms_pre_prompt: 'Les ${1} og besvar spørsmål om innholdet.',
|
||||||
|
connect_mcp_to: 'Koble til ${1}',
|
||||||
|
install_mcp_on: 'Installer MCP-server på ${1}',
|
||||||
|
connect_with_mcp: 'Koble til MCP',
|
||||||
|
copy_mcp_url: 'Kopier MCP-server URL',
|
||||||
press_to_confirm: 'Trykk ${1} for å bekrefte',
|
press_to_confirm: 'Trykk ${1} for å bekrefte',
|
||||||
tool_call_skipped: 'Hoppet over "${1}"',
|
tool_call_skipped: 'Hoppet over "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ export const pt_br = {
|
|||||||
view_page_plaintext: 'Ver esta página em texto simples',
|
view_page_plaintext: 'Ver esta página em texto simples',
|
||||||
open_in: 'Abrir no ${1}',
|
open_in: 'Abrir no ${1}',
|
||||||
open_in_llms_pre_prompt: 'Leia ${1} e responda perguntas sobre o conteúdo.',
|
open_in_llms_pre_prompt: 'Leia ${1} e responda perguntas sobre o conteúdo.',
|
||||||
|
connect_mcp_to: 'Conectar a ${1}',
|
||||||
|
install_mcp_on: 'Instalar servidor MCP em ${1}',
|
||||||
|
connect_with_mcp: 'Conectar com MCP',
|
||||||
|
copy_mcp_url: 'Copiar URL do servidor MCP',
|
||||||
press_to_confirm: 'Pressione ${1} para confirmar',
|
press_to_confirm: 'Pressione ${1} para confirmar',
|
||||||
tool_call_skipped: 'Pulado "${1}"',
|
tool_call_skipped: 'Pulado "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ export const ru = {
|
|||||||
view_page_plaintext: 'Посмотреть как текст без форматирования',
|
view_page_plaintext: 'Посмотреть как текст без форматирования',
|
||||||
open_in: 'Открыть в ${1}',
|
open_in: 'Открыть в ${1}',
|
||||||
open_in_llms_pre_prompt: 'Прочитай ${1} и ответь на вопросы о содержимом.',
|
open_in_llms_pre_prompt: 'Прочитай ${1} и ответь на вопросы о содержимом.',
|
||||||
|
connect_mcp_to: 'Подключиться к ${1}',
|
||||||
|
install_mcp_on: 'Установить MCP-сервер на ${1}',
|
||||||
|
connect_with_mcp: 'Подключиться к MCP',
|
||||||
|
copy_mcp_url: 'Скопировать URL MCP-сервера',
|
||||||
press_to_confirm: 'Нажмите ${1} для подтверждения',
|
press_to_confirm: 'Нажмите ${1} для подтверждения',
|
||||||
tool_call_skipped: 'Пропущен "${1}"',
|
tool_call_skipped: 'Пропущен "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ export const zh: TranslationLanguage = {
|
|||||||
view_page_plaintext: '以纯文本查看此页面',
|
view_page_plaintext: '以纯文本查看此页面',
|
||||||
open_in: '在 ${1} 中打开',
|
open_in: '在 ${1} 中打开',
|
||||||
open_in_llms_pre_prompt: '阅读 ${1} 并回答内容相关的问题。',
|
open_in_llms_pre_prompt: '阅读 ${1} 并回答内容相关的问题。',
|
||||||
|
connect_mcp_to: '连接到 ${1}',
|
||||||
|
install_mcp_on: '在 ${1} 上安装 MCP 服务器',
|
||||||
|
connect_with_mcp: '连接 MCP',
|
||||||
|
copy_mcp_url: '复制 MCP 服务器 URL',
|
||||||
press_to_confirm: '按 ${1} 确认',
|
press_to_confirm: '按 ${1} 确认',
|
||||||
tool_call_skipped: '已跳过 "${1}"',
|
tool_call_skipped: '已跳过 "${1}"',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export function defaultCustomization(): api.SiteCustomizationSettings {
|
|||||||
pageActions: {
|
pageActions: {
|
||||||
externalAI: true,
|
externalAI: true,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
|
mcp: true,
|
||||||
},
|
},
|
||||||
trademark: {
|
trademark: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ function encodePathInSiteContent(rawPathname: string): {
|
|||||||
case '~gitbook/embed/assistant':
|
case '~gitbook/embed/assistant':
|
||||||
case '~gitbook/icon':
|
case '~gitbook/icon':
|
||||||
return { pathname };
|
return { pathname };
|
||||||
|
case '~gitbook/mcp':
|
||||||
case 'llms.txt':
|
case 'llms.txt':
|
||||||
case 'sitemap.xml':
|
case 'sitemap.xml':
|
||||||
case 'sitemap-pages.xml':
|
case 'sitemap-pages.xml':
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { expect, it } from 'bun:test';
|
||||||
|
import { Client } from '@modelcontextprotocol/sdk/client';
|
||||||
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||||
|
import { getContentTestURL } from './utils';
|
||||||
|
|
||||||
|
it('should expose a MCP server', async () => {
|
||||||
|
const client = new Client({
|
||||||
|
name: 'test',
|
||||||
|
version: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect(
|
||||||
|
new StreamableHTTPClientTransport(
|
||||||
|
new URL(getContentTestURL('https://gitbook.com/docs/~gitbook/mcp'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const tools = await client.listTools();
|
||||||
|
expect(tools.tools[0]?.name).toBe('searchDocumentation');
|
||||||
|
|
||||||
|
const response = await client.callTool({
|
||||||
|
name: 'searchDocumentation',
|
||||||
|
arguments: {
|
||||||
|
query: 'git',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error - response.content is of type unknown
|
||||||
|
expect(response.content[0]?.text).toContain('Title:');
|
||||||
|
});
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import psi from 'psi';
|
|
||||||
|
|
||||||
import { getContentTestURL } from './utils';
|
|
||||||
|
|
||||||
interface Test {
|
|
||||||
url: string;
|
|
||||||
strategy: 'mobile' | 'desktop';
|
|
||||||
threshold: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use low values for now to avoid failing the tests
|
|
||||||
// and to be able to see the results, and only catch major regressions.
|
|
||||||
const tests: Array<Test> = [
|
|
||||||
{
|
|
||||||
url: 'https://gitbook.com/docs',
|
|
||||||
strategy: 'desktop',
|
|
||||||
threshold: 60,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
url: 'https://gitbook.com/docs',
|
|
||||||
strategy: 'mobile',
|
|
||||||
threshold: 30,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const test of tests) {
|
|
||||||
const url = getContentTestURL(test.url);
|
|
||||||
await psi.output(url, {
|
|
||||||
strategy: test.strategy,
|
|
||||||
threshold: test.threshold,
|
|
||||||
key: process.env.PAGESPEED_API_KEY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
# @gitbook/icons
|
# @gitbook/icons
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 262afa3: Fix types for custom icons and update list of icons
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
"default": "./src/style.css"
|
"default": "./src/style.css"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0"
|
"@fortawesome/fontawesome-free": "^6.6.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@gitbook/fontawesome-pro": "1.0.11"
|
"@gitbook/fontawesome-pro": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.5.3"
|
"typescript": "^5.5.3"
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ export enum IconStyle {
|
|||||||
SharpThin = 'sharp-thin',
|
SharpThin = 'sharp-thin',
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomIconName = 'gitbook';
|
type CustomIconName =
|
||||||
|
| 'gitbook'
|
||||||
|
| 'gitbook-assistant'
|
||||||
|
| 'cursor'
|
||||||
|
| 'vscode'
|
||||||
|
| 'mcp'
|
||||||
|
| 'chatgpt'
|
||||||
|
| 'claude';
|
||||||
|
|
||||||
export type IconName = BasicIconName | CustomIconName;
|
export type IconName = BasicIconName | CustomIconName;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @gitbook/react-contentkit
|
# @gitbook/react-contentkit
|
||||||
|
|
||||||
|
## 0.7.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [262afa3]
|
||||||
|
- @gitbook/icons@0.3.1
|
||||||
|
|
||||||
## 0.7.4
|
## 0.7.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@gitbook/react-contentkit",
|
"name": "@gitbook/react-contentkit",
|
||||||
"version": "0.7.4",
|
"version": "0.7.5",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @gitbook/react-openapi
|
# @gitbook/react-openapi
|
||||||
|
|
||||||
|
## 1.4.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [6da3655]
|
||||||
|
- @gitbook/expr@1.2.0
|
||||||
|
|
||||||
## 1.4.2
|
## 1.4.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version": "1.4.2",
|
"version": "1.4.3",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/expr": "workspace:*",
|
"@gitbook/expr": "workspace:*",
|
||||||
|
|||||||
Reference in New Issue
Block a user