Skip to content

Commit cc7fbbe

Browse files
committed
Merge branch 'update-embed-frame' into embed-frame-e2e
2 parents c83ae7e + 0593045 commit cc7fbbe

File tree

39 files changed

+473
-403
lines changed

39 files changed

+473
-403
lines changed

.changeset/tiny-carrots-rush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Fix hidden section not found

bun.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@
345345
"react-dom": "catalog:",
346346
},
347347
"catalog": {
348-
"@gitbook/api": "0.152.0",
348+
"@gitbook/api": "0.153.0",
349349
"@scalar/api-client-react": "^1.3.46",
350350
"@tsconfig/node20": "^20.1.6",
351351
"@tsconfig/strictest": "^2.0.6",
@@ -726,7 +726,7 @@
726726

727727
"@fortawesome/fontawesome-svg-core": ["@fortawesome/[email protected]", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
728728

729-
"@gitbook/api": ["@gitbook/api@0.152.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-BC+SXnBybtzKYY3aUYDOjwz17uQwMM1kTNiij8OTKZyFByyWcz1FifJGNmKHmDG3XeZLJtCi3nOTEIcGIdG1VA=="],
729+
"@gitbook/api": ["@gitbook/api@0.153.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-ArNPFoqwId4Flicz8xPEdGqXQkzxyxP7S8Uv3wIfCX2e4cLhpcS7xCJGEHOJrqe1tNs1ovT1N2MWfpdIqzXqig=="],
730730

731731
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
732732

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"catalog": {
4242
"@tsconfig/strictest": "^2.0.6",
4343
"@tsconfig/node20": "^20.1.6",
44-
"@gitbook/api": "0.152.0",
44+
"@gitbook/api": "0.153.0",
4545
"@scalar/api-client-react": "^1.3.46",
4646
"@types/react": "^19.0.0",
4747
"@types/react-dom": "^19.0.0",

packages/embed/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The standalone script provides a global `GitBook` function. See the [API Referen
3838
GitBook('configure', {
3939
button: {
4040
label: 'Ask',
41-
icon: 'assistant' // 'assistant' | 'sparkle' | 'circle-question' | 'book'
41+
icon: 'assistant' // 'assistant' | 'sparkle' | 'help' | 'book'
4242
},
4343
tabs: ['assistant', 'docs'],
4444
actions: [
@@ -238,16 +238,18 @@ Available in: Standalone script, NPM package, React components
238238

239239
Custom action buttons rendered in the sidebar alongside tabs. Each action button triggers a callback when clicked.
240240

241+
**Note**: This prop was previously named `buttons`. Use `actions` instead, it has the same functionality.
242+
241243
- **Type**: `GitBookEmbeddableActionDefinition[]`
242244
- **Properties**:
243-
- `icon`: `string` - Icon name (e.g., `'circle-question'`, `'book'`, `'sparkle'`, `'rocket'`, `'assistant'`)
245+
- `icon`: `string` - Icon name. Any [FontAwesome icon](https://fontawesome.com/search) is supported. (e.g., `'rocket'`, `'comments'`, `'user-circle'`, ...)
244246
- `label`: `string` - Button label text
245247
- `onClick`: `() => void | Promise<void>` - Callback function when clicked
246248

247249
```javascript
248250
actions: [
249251
{
250-
icon: 'circle-question',
252+
icon: 'comments',
251253
label: 'Contact Support',
252254
onClick: () => window.open('https://support.example.com', '_blank')
253255
},
@@ -396,10 +398,10 @@ Available in: Standalone script only
396398

397399
Configure the widget button for the standalone script. This option is not available when using the NPM package or React components, since they can be customized completely.
398400

399-
- **Type**: `{ label: string, icon: 'assistant' | 'sparkle' | 'circle-question' | 'book' }`
401+
- **Type**: `{ label: string, icon: 'assistant' | 'sparkle' | 'help' | 'book' }`
400402
- **Properties**:
401403
- `label`: `string` - Button label text
402-
- `icon`: `'assistant' | 'sparkle' | 'circle-question' | 'book'` - Icon displayed on the button
404+
- `icon`: `'assistant' | 'sparkle' | 'help' | 'book'` - Icon displayed on the button. Choose from one of 4 presets.
403405

404406
```javascript
405407
button: {

packages/embed/src/client/protocol.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ export type GitBookEmbeddableConfiguration = {
5050
/** Additional buttons to be displayed in the header of the GitBook embed. */
5151
actions: GitBookEmbeddableActionDefinition[];
5252

53+
/**
54+
* Additional buttons to be displayed in the header of the GitBook embed.
55+
* @deprecated Use `actions` instead.
56+
*/
57+
buttons?: GitBookEmbeddableActionDefinition[];
58+
5359
/** Message to be displayed in the welcome page. */
5460
greeting: {
5561
title: string;
Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1+
import type { RouteLayoutParams } from '@/app/utils';
12
import { EmbeddableAssistantPage } from '@/components/Embeddable';
3+
import { getEmbeddableDynamicContext } from '@/lib/embeddable';
4+
import { CustomizationAIMode } from '@gitbook/api';
5+
import { redirect } from 'next/navigation';
6+
7+
type PageProps = {
8+
params: Promise<RouteLayoutParams>;
9+
};
210

311
export const dynamic = 'force-static';
412

5-
export default async function Page() {
6-
return <EmbeddableAssistantPage />;
13+
export default async function Page(props: PageProps) {
14+
const params = await props.params;
15+
const { context } = await getEmbeddableDynamicContext(params);
16+
17+
// If the assistant is not enabled, redirect to the docs
18+
if (context.customization.ai.mode !== CustomizationAIMode.Assistant) {
19+
redirect(`${context.linker.toPathInSite('~gitbook/embed/page/')}`);
20+
}
21+
22+
return (
23+
<EmbeddableAssistantPage
24+
baseURL={context.linker.toPathInSite('~gitbook/embed/')}
25+
siteTitle={context.site.title}
26+
/>
27+
);
728
}

packages/gitbook/src/app/sites/static/[mode]/[siteURL]/[siteData]/~gitbook/embed/assistant/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ export default async function Page(props: PageProps) {
1919
redirect(`${context.linker.toPathInSite('~gitbook/embed/page/')}`);
2020
}
2121

22-
return <EmbeddableAssistantPage />;
22+
return (
23+
<EmbeddableAssistantPage
24+
baseURL={context.linker.toPathInSite('~gitbook/embed/')}
25+
siteTitle={context.site.title}
26+
/>
27+
);
2328
}

packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function AIMessageView(
3535
wrapBlocksInSuspense: false,
3636
withLinkPreviews,
3737
}}
38-
style="mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
38+
style="ai-response-document mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
3939
/>
4040

4141
{withToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (

packages/gitbook/src/components/AI/server-actions/AIToolCallsSummary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ async function DescriptionForSearchToolCall(props: {
163163
const hasResults = toolCall.results.length > 0;
164164

165165
return (
166-
<details className={tcls('-ml-5 group flex w-full flex-col')}>
166+
<details className="-ml-5 group flex w-full flex-col">
167167
<summary
168168
className={tcls(
169169
'-mx-2 flex list-none items-center gap-2 circular-corners:rounded-2xl rounded-corners:rounded-md pr-4 pl-7 transition-colors marker:hidden',

packages/gitbook/src/components/AI/useAIChat.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ export type AIChatState = {
8787
error: boolean;
8888
};
8989

90+
export type AIChatEvent =
91+
| { type: 'open' }
92+
| { type: 'postMessage'; message: string }
93+
| { type: 'clear' }
94+
| { type: 'close' };
95+
96+
type AIChatEventData<T extends AIChatEvent['type']> = Omit<
97+
Extract<AIChatEvent, { type: T }>,
98+
'type'
99+
>;
100+
101+
type AIChatEventListener = (input?: Omit<AIChatEvent, 'type'>) => void;
102+
90103
export type AIChatController = {
91104
/** Open the dialog */
92105
open: () => void;
@@ -97,7 +110,10 @@ export type AIChatController = {
97110
/** Clear the conversation */
98111
clear: () => void;
99112
/** Register an event listener */
100-
on: (event: 'postMessage', listener: (input: { message: string }) => void) => () => void;
113+
on: <T extends AIChatEvent['type']>(
114+
event: T,
115+
listener: (input?: AIChatEventData<T>) => void
116+
) => () => void;
101117
};
102118

103119
const AIChatControllerContext = React.createContext<AIChatController | null>(null);
@@ -125,6 +141,17 @@ export function useAIChatState(): AIChatState {
125141
return state;
126142
}
127143

144+
function notify(
145+
listeners: AIChatEventListener[] | undefined,
146+
input: Omit<AIChatEvent, 'type'>
147+
): void {
148+
if (!listeners) return;
149+
// Defer event listeners to next tick so React can process state updates first
150+
setTimeout(() => {
151+
listeners.forEach((listener) => listener(input));
152+
}, 0);
153+
}
154+
128155
/**
129156
* Provide the controller to interact with the AI chat.
130157
*/
@@ -140,9 +167,7 @@ export function AIChatProvider(props: {
140167
const language = useLanguage();
141168

142169
// Event listeners storage
143-
const eventsRef = React.useRef<Map<'postMessage', Array<(input: { message: string }) => void>>>(
144-
new Map()
145-
);
170+
const eventsRef = React.useRef<Map<AIChatEvent['type'], AIChatEventListener[]>>(new Map());
146171

147172
// Open AI chat and sync with search state
148173
const onOpen = React.useCallback(() => {
@@ -156,6 +181,8 @@ export function AIChatProvider(props: {
156181
scope: prev?.scope ?? 'default',
157182
open: false, // Close search popover when opening chat
158183
}));
184+
185+
notify(eventsRef.current.get('open'), {});
159186
}, [setSearchState]);
160187

161188
// Close AI chat and clear ask parameter
@@ -169,6 +196,8 @@ export function AIChatProvider(props: {
169196
scope: prev?.scope ?? 'default',
170197
open: false,
171198
}));
199+
200+
notify(eventsRef.current.get('close'), {});
172201
}, [setSearchState]);
173202

174203
// Stream a message with the AI backend
@@ -386,11 +415,7 @@ export function AIChatProvider(props: {
386415
}));
387416
}
388417

389-
// Defer event listeners to next tick so React can process state updates first
390-
setTimeout(() => {
391-
const listeners = eventsRef.current.get('postMessage') || [];
392-
listeners.forEach((listener) => listener(input));
393-
}, 0);
418+
notify(eventsRef.current.get('postMessage'), { message: input.message });
394419

395420
if (query === input.message) {
396421
// Return early if the message is the same as the previous message
@@ -458,9 +483,12 @@ export function AIChatProvider(props: {
458483
}, [setSearchState]);
459484

460485
const onEvent = React.useCallback(
461-
(event: 'postMessage', listener: (input: { message: string }) => void) => {
486+
<T extends AIChatEvent['type']>(
487+
event: T,
488+
listener: (input?: AIChatEventData<T>) => void
489+
) => {
462490
const listeners = eventsRef.current.get(event) || [];
463-
listeners.push(listener);
491+
listeners.push(listener as AIChatEventListener);
464492
eventsRef.current.set(event, listeners);
465493
return () => {
466494
const currentListeners = eventsRef.current.get(event) || [];

0 commit comments

Comments
 (0)