From 81a6bd756a0e82e4419bd1cdb20c7d308ff3881b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 26 Aug 2025 14:08:24 +0200 Subject: [PATCH] Embeddable GitBook API (#3593) --- .changeset/forty-trees-sniff.md | 5 + .changeset/sixty-pumas-tell.md | 5 + biome.json | 1 + .../embed/src/client/createGitBookFrame.ts | 25 +++-- packages/embed/src/client/protocol.ts | 42 ++++++-- .../embed/src/react/GitBookAssistantFrame.tsx | 38 ------- packages/embed/src/react/GitBookFrame.tsx | 51 +++++++++ packages/embed/src/react/index.ts | 1 + packages/embed/src/standalone/index.ts | 42 ++++++-- .../gitbook/src/components/AI/useAIChat.tsx | 4 +- .../gitbook/src/components/AIChat/AIChat.tsx | 100 +++++++++--------- .../components/AIChat/AIChatControlButton.tsx | 40 +++++++ .../AIChat/AIChatSuggestedQuestions.tsx | 22 ++-- .../gitbook/src/components/AIChat/index.ts | 1 + .../Embeddable/EmbeddableAIChat.tsx | 41 ++++++- .../Embeddable/EmbeddableDocsPage.tsx | 45 ++++++-- .../components/Embeddable/EmbeddableFrame.tsx | 94 +++++++++++----- .../Embeddable/EmbeddableIframeAPI.tsx | 81 +++++++++++++- .../Embeddable/EmbeddableRootLayout.tsx | 34 +++--- 19 files changed, 480 insertions(+), 192 deletions(-) create mode 100644 .changeset/forty-trees-sniff.md create mode 100644 .changeset/sixty-pumas-tell.md delete mode 100644 packages/embed/src/react/GitBookAssistantFrame.tsx create mode 100644 packages/embed/src/react/GitBookFrame.tsx create mode 100644 packages/gitbook/src/components/AIChat/AIChatControlButton.tsx diff --git a/.changeset/forty-trees-sniff.md b/.changeset/forty-trees-sniff.md new file mode 100644 index 000000000..1696f2f02 --- /dev/null +++ b/.changeset/forty-trees-sniff.md @@ -0,0 +1,5 @@ +--- +"@gitbook/embed": minor +--- + +Improve API to control the GitBook embed diff --git a/.changeset/sixty-pumas-tell.md b/.changeset/sixty-pumas-tell.md new file mode 100644 index 000000000..7a750285b --- /dev/null +++ b/.changeset/sixty-pumas-tell.md @@ -0,0 +1,5 @@ +--- +"gitbook": minor +--- + +Support customization of buttons and tools through iframe API diff --git a/biome.json b/biome.json index 69349302a..53669a0a0 100644 --- a/biome.json +++ b/biome.json @@ -18,6 +18,7 @@ "**/.vercel/**/*", "**/.cache/**/*", "**/.wrangler/**/*", + "packages/embed/standalone/**/*", "packages/openapi-parser/src/fixtures/**/*", "packages/emoji-codepoints/index.ts", "packages/icons/src/data/*.json", diff --git a/packages/embed/src/client/createGitBookFrame.ts b/packages/embed/src/client/createGitBookFrame.ts index 031b0c435..baaabf647 100644 --- a/packages/embed/src/client/createGitBookFrame.ts +++ b/packages/embed/src/client/createGitBookFrame.ts @@ -1,8 +1,7 @@ import { createChannel } from 'bidc'; import type { FrameToParentMessage, - GitBookPlaceholderSettings, - GitBookToolDefinition, + GitBookEmbeddableConfiguration, ParentToFrameMessage, } from './protocol'; @@ -22,11 +21,6 @@ export type GitBookFrameClient = { */ postUserMessage: (message: string) => void; - /** - * Register a custom tool. - */ - registerTool: (tool: GitBookToolDefinition) => void; - /** * Clear the chat. */ @@ -35,7 +29,7 @@ export type GitBookFrameClient = { /** * Set the placeholder settings. */ - setPlaceholder: (placeholder: GitBookPlaceholderSettings) => void; + configure: (settings: Partial) => void; /** * Register an event listener. @@ -53,6 +47,7 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien const channel = createChannel(iframe.contentWindow); channel.receive((message: FrameToParentMessage) => { + console.log('[gitbook:embed] received message', message); if (message.type === 'close') { const listeners = events.get('close') || []; if (listeners) { @@ -62,11 +57,19 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien }); const sendToFrame = (message: ParentToFrameMessage) => { + console.log('[gitbook:embed] send message', message); channel.send(message); }; const events = new Map void>>(); + const configuration: GitBookEmbeddableConfiguration = { + buttons: [], + welcomeMessage: '', + suggestions: [], + tools: [], + }; + return { navigateToPage: (pagePath) => { sendToFrame({ type: 'navigateToPage', pagePath }); @@ -75,9 +78,11 @@ export function createGitBookFrame(iframe: HTMLIFrameElement): GitBookFrameClien sendToFrame({ type: 'navigateToAssistant' }); }, postUserMessage: (message) => sendToFrame({ type: 'postUserMessage', message }), - registerTool: (tool) => sendToFrame({ type: 'registerTool', tool }), + configure: (settings) => { + Object.assign(configuration, settings); + sendToFrame({ type: 'configure', settings: configuration }); + }, clearChat: () => sendToFrame({ type: 'clearChat' }), - setPlaceholder: (settings) => sendToFrame({ type: 'setPlaceholder', settings }), on: (event, listener) => { const listeners = events.get(event) || []; listeners.push(listener); diff --git a/packages/embed/src/client/protocol.ts b/packages/embed/src/client/protocol.ts index 135676899..f19b400e3 100644 --- a/packages/embed/src/client/protocol.ts +++ b/packages/embed/src/client/protocol.ts @@ -21,18 +21,42 @@ export type GitBookToolDefinition = AIToolDefinition & { }; /** - * Placeholder settings. + * Custom button definition to be passed to the embeddable GitBook. */ -export type GitBookPlaceholderSettings = { +export type GitBookEmbeddableButtonDefinition = { /** - * Welcome message to be displayed in the placeholder. + * Icon to be displayed in the button. */ - welcomeMessage: string; + icon: IconName; /** - * Suggestions to be displayed in the placeholder. + * Label to be displayed in the button. */ + label: string; + + /** + * Callback when the button is clicked. + */ + onClick: () => void | Promise; +}; + +/** + * Overall configuration for the layout of the embeddable GitBook. + */ +export type GitBookEmbeddableConfiguration = { + /** + * Buttons to be displayed in the header of the embeddable GitBook. + */ + buttons: GitBookEmbeddableButtonDefinition[]; + + /** Message to be displayed in the welcome page. */ + welcomeMessage: string; + + /** Suggestions of questions to be displayed in the welcome page. */ suggestions: string[]; + + /** Tools to be provided to the assistant. */ + tools: GitBookToolDefinition[]; }; /** @@ -43,16 +67,12 @@ export type ParentToFrameMessage = type: 'postUserMessage'; message: string; } - | { - type: 'registerTool'; - tool: GitBookToolDefinition; - } | { type: 'clearChat'; } | { - type: 'setPlaceholder'; - settings: GitBookPlaceholderSettings; + type: 'configure'; + settings: GitBookEmbeddableConfiguration; } | { type: 'navigateToPage'; diff --git a/packages/embed/src/react/GitBookAssistantFrame.tsx b/packages/embed/src/react/GitBookAssistantFrame.tsx deleted file mode 100644 index a6e4980f0..000000000 --- a/packages/embed/src/react/GitBookAssistantFrame.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import type { GetFrameURLOptions, GitBookFrameClient } from '../client'; -import { useGitBook } from './GitBookProvider'; - -export type GitBookAssistantFrameProps = { - title?: string; - className?: string; -} & GetFrameURLOptions; - -/** - * Render a frame with the GitBook Assistant in it. - */ -export function GitBookAssistantFrame(props: GitBookAssistantFrameProps) { - const { title, className, ...frameOptions } = props; - - const frameRef = React.useRef(null); - const gitbookFrameRef = React.useRef(null); - const gitbook = useGitBook(); - const frameURL = gitbook.getFrameURL(frameOptions); - - React.useEffect(() => { - if (frameRef.current) { - gitbookFrameRef.current = gitbook.createFrame(frameRef.current); - } - }, [gitbook]); - - return ( -
-