diff --git a/.changeset/forty-trees-sniff.md b/.changeset/forty-trees-sniff.md
new file mode 100644
index 0000000000..1696f2f02c
--- /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 0000000000..7a750285b7
--- /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 69349302ac..53669a0a0b 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 031b0c4359..baaabf647c 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 1356768998..f19b400e37 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;
+
+ /**
+ * 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 = {
/**
- * Suggestions to be displayed in the placeholder.
+ * 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 a6e4980f07..0000000000
--- 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 (
-
-
-
- );
-}
diff --git a/packages/embed/src/react/GitBookFrame.tsx b/packages/embed/src/react/GitBookFrame.tsx
new file mode 100644
index 0000000000..010d87dd47
--- /dev/null
+++ b/packages/embed/src/react/GitBookFrame.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import type {
+ GetFrameURLOptions,
+ GitBookEmbeddableConfiguration,
+ GitBookFrameClient,
+} from '../client';
+import { useGitBook } from './GitBookProvider';
+
+export type GitBookFrameProps = {
+ className?: string;
+} & GetFrameURLOptions &
+ GitBookEmbeddableConfiguration;
+
+/**
+ * Render a frame with the GitBook Assistant in it.
+ */
+export function GitBookFrame(props: GitBookFrameProps) {
+ const { className, visitor, buttons, welcomeMessage, suggestions, tools } = props;
+
+ const frameRef = React.useRef(null);
+ const gitbook = useGitBook();
+ const [gitbookFrame, setGitbookFrame] = React.useState(null);
+
+ const frameURL = React.useMemo(() => gitbook.getFrameURL({ visitor }), [gitbook, visitor]);
+
+ React.useEffect(() => {
+ if (frameRef.current) {
+ setGitbookFrame(gitbook.createFrame(frameRef.current));
+ }
+ }, [gitbook]);
+
+ React.useEffect(() => {
+ gitbookFrame?.configure({
+ buttons,
+ welcomeMessage,
+ suggestions,
+ tools,
+ });
+ }, [gitbookFrame, buttons, welcomeMessage, suggestions, tools]);
+
+ return (
+
+ );
+}
diff --git a/packages/embed/src/react/index.ts b/packages/embed/src/react/index.ts
index 7b0d5b19cd..ff70bb4618 100644
--- a/packages/embed/src/react/index.ts
+++ b/packages/embed/src/react/index.ts
@@ -1 +1,2 @@
export * from './GitBookProvider';
+export * from './GitBookFrame';
diff --git a/packages/embed/src/standalone/index.ts b/packages/embed/src/standalone/index.ts
index a2543c8ba2..e9c7135b54 100644
--- a/packages/embed/src/standalone/index.ts
+++ b/packages/embed/src/standalone/index.ts
@@ -4,9 +4,8 @@ import {
type CreateGitBookOptions,
type GetFrameURLOptions,
type GitBookClient,
+ type GitBookEmbeddableConfiguration,
type GitBookFrameClient,
- type GitBookPlaceholderSettings,
- type GitBookToolDefinition,
createGitBook,
} from '../client';
@@ -29,12 +28,10 @@ type StandaloneCalls =
| ['toggle']
// Post a user message
| ['postUserMessage', string]
- // Register a tool
- | ['registerTool', GitBookToolDefinition]
// Clear the chat
| ['clearChat']
- // Configure the placeholder
- | ['setPlaceholder', GitBookPlaceholderSettings]
+ // Configure the embed
+ | ['configure', Partial]
// Navigate to a page
| ['navigateToPage', string]
// Navigate to the assistant
@@ -65,6 +62,12 @@ 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) {
@@ -132,15 +135,30 @@ const GitBook = (...args: StandaloneCalls) => {
case 'postUserMessage':
getIframe().frame.postUserMessage(args[1]);
break;
- case 'registerTool':
- getIframe().frame.registerTool(args[1]);
+ case 'configure':
+ frameConfiguration = {
+ ...frameConfiguration,
+ ...args[1],
+ };
+ 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;
- case 'setPlaceholder':
- getIframe().frame.setPlaceholder(args[1]);
- break;
case 'navigateToPage':
getIframe().frame.navigateToPage(args[1]);
break;
@@ -156,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', {});
diff --git a/packages/gitbook/src/components/AI/useAIChat.tsx b/packages/gitbook/src/components/AI/useAIChat.tsx
index 43238e21a1..992209635a 100644
--- a/packages/gitbook/src/components/AI/useAIChat.tsx
+++ b/packages/gitbook/src/components/AI/useAIChat.tsx
@@ -346,7 +346,8 @@ export function AIChatProvider(props: {
loading: false,
error: false,
}));
- } catch {
+ } catch (error) {
+ console.error('Error streaming AI response', error);
globalState.setState((state) => ({
...state,
loading: false,
@@ -359,6 +360,7 @@ export function AIChatProvider(props: {
renderMessageOptions?.withLinkPreviews,
renderMessageOptions?.withToolCalls,
renderMessageOptions?.asEmbeddable,
+ language,
]
);
diff --git a/packages/gitbook/src/components/AIChat/AIChat.tsx b/packages/gitbook/src/components/AIChat/AIChat.tsx
index b1b00c449b..3192636f6c 100644
--- a/packages/gitbook/src/components/AIChat/AIChat.tsx
+++ b/packages/gitbook/src/components/AIChat/AIChat.tsx
@@ -11,10 +11,18 @@ import {
useAIChatController,
useAIChatState,
} from '../AI';
-import { EmbeddableFrame } from '../Embeddable/EmbeddableFrame';
+import {
+ EmbeddableFrame,
+ EmbeddableFrameBody,
+ EmbeddableFrameButtons,
+ EmbeddableFrameHeader,
+ EmbeddableFrameHeaderMain,
+ EmbeddableFrameSubtitle,
+ EmbeddableFrameTitle,
+} from '../Embeddable/EmbeddableFrame';
import { useNow } from '../hooks';
import { Button } from '../primitives';
-import { DropdownMenu, DropdownMenuItem } from '../primitives';
+import { AIChatControlButton } from './AIChatControlButton';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
import { AIChatMessages } from './AIChatMessages';
@@ -66,45 +74,17 @@ export function AIChat(props: { trademark: boolean }) {
data-testid="ai-chat"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enter-from-right lg:pr-4 lg:pl-0 xl:w-96"
>
- }
- title={getAIChatName(language, trademark)}
- subtitle={
- chat.loading
- ? chat.messages[chat.messages.length - 1].content
- ? tString(language, 'ai_chat_working')
- : tString(language, 'ai_chat_thinking')
- : ''
- }
- buttons={
- <>
- {chat.messages.length > 0 ? (
- {}}
- iconOnly
- icon="ellipsis"
- label={tString(language, 'actions')}
- variant="blank"
- size="default"
- />
- }
- >
- {
- chatController.clear();
- }}
- >
-
- {t(language, 'ai_chat_clear_conversation')}
-
-
- ) : null}
+
+
+
+
+
+ {getAIChatName(language, trademark)}
+
+
+
+
+
+
+
+
+
);
@@ -139,7 +120,7 @@ export function AIChatDynamicIcon(props: {
chat.error
? 'error'
: chat.loading
- ? chat.messages[chat.messages.length - 1].content
+ ? chat.messages[chat.messages.length - 1]?.content
? 'working'
: 'thinking'
: chat.messages.length > 0
@@ -152,6 +133,24 @@ export function AIChatDynamicIcon(props: {
);
}
+/**
+ * Subtitle of the AI chat window.
+ */
+export function AIChatSubtitle(props: {
+ chat: AIChatState;
+}) {
+ const { chat } = props;
+ const language = useLanguage();
+
+ return (
+
+ {chat.messages[chat.messages.length - 1]?.content
+ ? tString(language, 'ai_chat_working')
+ : tString(language, 'ai_chat_thinking')}
+
+ );
+}
+
/**
* Body of the AI chat window.
*/
@@ -159,8 +158,10 @@ export function AIChatBody(props: {
chatController: AIChatController;
chat: AIChatState;
trademark: boolean;
+ welcomeMessage?: string;
+ suggestions?: string[];
}) {
- const { chatController, chat, trademark } = props;
+ const { chatController, chat, trademark, suggestions } = props;
const [input, setInput] = React.useState('');
@@ -246,7 +247,10 @@ export function AIChatBody(props: {
{!chat.error ? (
-
+
) : null}
) : (
diff --git a/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx b/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx
new file mode 100644
index 0000000000..f909b62ab9
--- /dev/null
+++ b/packages/gitbook/src/components/AIChat/AIChatControlButton.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { useLanguage } from '@/intl/client';
+import { t, tString } from '@/intl/translate';
+import { Icon } from '@gitbook/icons';
+import { useAIChatController, useAIChatState } from '../AI';
+import { Button, DropdownMenu, DropdownMenuItem } from '../primitives';
+
+/**
+ * Button to control the chat (clear, etc.)
+ */
+export function AIChatControlButton() {
+ const language = useLanguage();
+ const chat = useAIChatState();
+ const chatController = useAIChatController();
+
+ return chat.messages.length > 0 ? (
+ {}}
+ iconOnly
+ icon="ellipsis"
+ label={tString(language, 'actions')}
+ variant="blank"
+ size="default"
+ />
+ }
+ >
+ {
+ chatController.clear();
+ }}
+ >
+
+ {t(language, 'ai_chat_clear_conversation')}
+
+
+ ) : null;
+}
diff --git a/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx b/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx
index 5f0089ebdf..f3951698eb 100644
--- a/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx
+++ b/packages/gitbook/src/components/AIChat/AIChatSuggestedQuestions.tsx
@@ -2,19 +2,23 @@ import { tString, useLanguage } from '@/intl/client';
import type { AIChatController } from '../AI';
import { Button } from '../primitives';
-export default function AIChatSuggestedQuestions(props: { chatController: AIChatController }) {
- const { chatController } = props;
+export default function AIChatSuggestedQuestions(props: {
+ chatController: AIChatController;
+ suggestions?: string[];
+}) {
const language = useLanguage();
-
- const DEFAULT_SUGGESTED_QUESTIONS = [
- tString(language, 'ai_chat_suggested_questions_about_this_page'),
- tString(language, 'ai_chat_suggested_questions_read_next'),
- tString(language, 'ai_chat_suggested_questions_example'),
- ];
+ const {
+ chatController,
+ suggestions = [
+ tString(language, 'ai_chat_suggested_questions_about_this_page'),
+ tString(language, 'ai_chat_suggested_questions_read_next'),
+ tString(language, 'ai_chat_suggested_questions_example'),
+ ],
+ } = props;
return (
- {DEFAULT_SUGGESTED_QUESTIONS.map((question, index) => (
+ {suggestions.map((question, index) => (
;
+ return (
+
+
+
+
+ GitBook Assistant
+
+
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/packages/gitbook/src/components/Embeddable/EmbeddableDocsPage.tsx b/packages/gitbook/src/components/Embeddable/EmbeddableDocsPage.tsx
index c481363e02..685190629d 100644
--- a/packages/gitbook/src/components/Embeddable/EmbeddableDocsPage.tsx
+++ b/packages/gitbook/src/components/Embeddable/EmbeddableDocsPage.tsx
@@ -3,6 +3,15 @@ import { type PagePathParams, getSitePageData } from '@/components/SitePage';
import { PageBody } from '@/components/PageBody';
import type { GitBookSiteContext } from '@/lib/context';
import type { Metadata } from 'next';
+import { Button } from '../primitives';
+import {
+ EmbeddableFrame,
+ EmbeddableFrameBody,
+ EmbeddableFrameButtons,
+ EmbeddableFrameHeader,
+ EmbeddableFrameHeaderMain,
+} from './EmbeddableFrame';
+import { EmbeddableIframeButtons } from './EmbeddableIframeAPI';
export const dynamic = 'force-static';
@@ -22,15 +31,33 @@ export async function EmbeddableDocsPage(props: EmbeddableDocsPageProps) {
});
return (
-
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx b/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx
index a558fb2ba7..9b233da756 100644
--- a/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx
+++ b/packages/gitbook/src/components/Embeddable/EmbeddableFrame.tsx
@@ -1,27 +1,17 @@
import { tcls } from '@/lib/tailwind';
import React from 'react';
-export type EmbeddableFrameHeaderProps = {
- icon: React.ReactNode;
-
- title: string;
- subtitle?: string;
-
- buttons?: React.ReactNode;
+export type EmbeddableFrameProps = React.ComponentProps<'div'> & {
+ children: React.ReactNode;
};
-export type EmbeddableFrameProps = EmbeddableFrameHeaderProps &
- React.ComponentProps<'div'> & {
- children: React.ReactNode;
- };
-
/**
* Presentation component to display an embeddable frame.
* It is used for the AI chat window in the docs, but also when embedded in another website.
*/
export const EmbeddableFrame = React.forwardRef
(
(props, ref) => {
- const { icon, title, subtitle, buttons, children, ...divProps } = props;
+ const { children, ...divProps } = props;
return (
-
- {icon}
-
-
{title}
-
- {subtitle}
-
-
-
{buttons}
-
-
{children}
+ {children}
);
}
);
+
+export function EmbeddableFrameHeader(props: {
+ children: React.ReactNode;
+}) {
+ const { children } = props;
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function EmbeddableFrameHeaderMain(props: {
+ children: React.ReactNode;
+}) {
+ const { children } = props;
+
+ return {children}
;
+}
+
+export function EmbeddableFrameBody(props: {
+ children: React.ReactNode;
+}) {
+ const { children } = props;
+
+ return {children}
;
+}
+
+export function EmbeddableFrameTitle(props: {
+ children: React.ReactNode;
+}) {
+ const { children } = props;
+
+ return {children}
;
+}
+
+export function EmbeddableFrameSubtitle(props: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ const { children, className } = props;
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function EmbeddableFrameButtons(props: {
+ children: React.ReactNode;
+}) {
+ const { children } = props;
+
+ return {children}
;
+}
diff --git a/packages/gitbook/src/components/Embeddable/EmbeddableIframeAPI.tsx b/packages/gitbook/src/components/Embeddable/EmbeddableIframeAPI.tsx
index 85abe0cfbf..f37dd4c55f 100644
--- a/packages/gitbook/src/components/Embeddable/EmbeddableIframeAPI.tsx
+++ b/packages/gitbook/src/components/Embeddable/EmbeddableIframeAPI.tsx
@@ -1,15 +1,31 @@
'use client';
-import type { ParentToFrameMessage } from '@gitbook/embed';
+import type { GitBookEmbeddableConfiguration, ParentToFrameMessage } from '@gitbook/embed';
import { createChannel } from 'bidc';
import React from 'react';
import { useAIChatController } from '@/components/AI';
+import { useRouter } from 'next/navigation';
+import { createStore, useStore } from 'zustand';
+import { integrationsAssistantTools } from '../Integrations';
+import { Button } from '../primitives';
+
+const embeddableConfiguration = createStore(() => ({
+ buttons: [],
+ welcomeMessage: '',
+ suggestions: [],
+ tools: [],
+}));
/**
* Expose the API to communicate with the parent window.
*/
-export function EmbeddableIframeAPI() {
+export function EmbeddableIframeAPI(props: {
+ baseURL: string;
+}) {
+ const { baseURL } = props;
+
+ const router = useRouter();
const chatController = useAIChatController();
React.useEffect(() => {
@@ -17,11 +33,14 @@ export function EmbeddableIframeAPI() {
return;
}
+ console.log('[gitbook] create channel with parent window');
const channel = createChannel();
channel.receive((payload) => {
const message = payload as ParentToFrameMessage;
+ console.log('[gitbook] received message', message);
+
switch (message.type) {
case 'clearChat': {
chatController.clear();
@@ -33,12 +52,64 @@ export function EmbeddableIframeAPI() {
});
break;
}
- // TODO: Handle other messages
+ case 'configure': {
+ embeddableConfiguration.setState(message.settings);
+ integrationsAssistantTools.setState({
+ tools: message.settings.tools,
+ });
+ break;
+ }
+ case 'navigateToPage': {
+ router.push(`${baseURL}/page/${message.pagePath}`);
+ break;
+ }
+ case 'navigateToAssistant': {
+ router.push(`${baseURL}/assistant`);
+ break;
+ }
}
});
- return channel.cleanup();
- }, [chatController]);
+ return () => {
+ console.log('[gitbook] cleanup');
+ channel.cleanup();
+ };
+ }, [chatController, router, baseURL]);
return null;
}
+
+/**
+ * Hook to get the configuration from the parent window.
+ */
+export function useEmbeddableConfiguration(
+ // @ts-expect-error - This is a workaround to allow the function to be optional.
+ fn: (state: GitBookEmbeddableConfiguration) => T = (state) => state
+) {
+ return useStore(embeddableConfiguration, fn);
+}
+
+/**
+ * Display the buttons defined by the parent window.
+ */
+export function EmbeddableIframeButtons() {
+ const buttons = useEmbeddableConfiguration((state) => state.buttons);
+
+ return (
+ <>
+ {buttons.map((button) => (
+