Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/forty-trees-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gitbook/embed": minor
---

Improve API to control the GitBook embed
5 changes: 5 additions & 0 deletions .changeset/sixty-pumas-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gitbook": minor
---

Support customization of buttons and tools through iframe API
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
25 changes: 15 additions & 10 deletions packages/embed/src/client/createGitBookFrame.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { createChannel } from 'bidc';
import type {
FrameToParentMessage,
GitBookPlaceholderSettings,
GitBookToolDefinition,
GitBookEmbeddableConfiguration,
ParentToFrameMessage,
} from './protocol';

Expand All @@ -22,11 +21,6 @@ export type GitBookFrameClient = {
*/
postUserMessage: (message: string) => void;

/**
* Register a custom tool.
*/
registerTool: (tool: GitBookToolDefinition) => void;

/**
* Clear the chat.
*/
Expand All @@ -35,7 +29,7 @@ export type GitBookFrameClient = {
/**
* Set the placeholder settings.
*/
setPlaceholder: (placeholder: GitBookPlaceholderSettings) => void;
configure: (settings: Partial<GitBookEmbeddableConfiguration>) => void;

/**
* Register an event listener.
Expand All @@ -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) {
Expand All @@ -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<string, Array<(...args: any[]) => void>>();

const configuration: GitBookEmbeddableConfiguration = {
buttons: [],
welcomeMessage: '',
suggestions: [],
tools: [],
};

return {
navigateToPage: (pagePath) => {
sendToFrame({ type: 'navigateToPage', pagePath });
Expand All @@ -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);
Expand Down
42 changes: 31 additions & 11 deletions packages/embed/src/client/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
};

/**
* 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[];
};

/**
Expand All @@ -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';
Expand Down
38 changes: 0 additions & 38 deletions packages/embed/src/react/GitBookAssistantFrame.tsx

This file was deleted.

51 changes: 51 additions & 0 deletions packages/embed/src/react/GitBookFrame.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLIFrameElement>(null);
const gitbook = useGitBook();
const [gitbookFrame, setGitbookFrame] = React.useState<GitBookFrameClient | null>(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 (
<iframe
title="GitBook"
ref={frameRef}
src={frameURL}
width="100%"
height="100%"
className={className}
/>
);
}
1 change: 1 addition & 0 deletions packages/embed/src/react/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './GitBookProvider';
export * from './GitBookFrame';
42 changes: 31 additions & 11 deletions packages/embed/src/standalone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import {
type CreateGitBookOptions,
type GetFrameURLOptions,
type GitBookClient,
type GitBookEmbeddableConfiguration,
type GitBookFrameClient,
type GitBookPlaceholderSettings,
type GitBookToolDefinition,
createGitBook,
} from '../client';

Expand All @@ -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<GitBookEmbeddableConfiguration>]
// Navigate to a page
| ['navigateToPage', string]
// Navigate to the assistant
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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', {});
4 changes: 3 additions & 1 deletion packages/gitbook/src/components/AI/useAIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -359,6 +360,7 @@ export function AIChatProvider(props: {
renderMessageOptions?.withLinkPreviews,
renderMessageOptions?.withToolCalls,
renderMessageOptions?.asEmbeddable,
language,
]
);

Expand Down
Loading