Add assistantName property to Docs Embed (#4095)

This commit is contained in:
Zeno Kapitein
2026-03-11 10:46:10 +01:00
committed by GitHub
parent 884d900e38
commit b3875a19d7
10 changed files with 85 additions and 13 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/embed": minor
---
Support `assistantName` property to override Assistant name
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Refactor embeddable context to merge local & site properties in one unified way
+15
View File
@@ -49,6 +49,7 @@ GitBook('configure', {
} }
], ],
greeting: { title: 'Welcome!', subtitle: 'How can I help?' }, greeting: { title: 'Welcome!', subtitle: 'How can I help?' },
assistantName: 'Support Assistant',
suggestions: ['What is GitBook?', 'How do I get started?'], suggestions: ['What is GitBook?', 'How do I get started?'],
tools: [/* ... */], tools: [/* ... */],
closeButton: true, closeButton: true,
@@ -100,6 +101,7 @@ frame.configure({
} }
], ],
greeting: { title: 'Welcome!', subtitle: 'How can I help?' }, greeting: { title: 'Welcome!', subtitle: 'How can I help?' },
assistantName: 'Support Assistant',
suggestions: ['What is GitBook?', 'How do I get started?'], suggestions: ['What is GitBook?', 'How do I get started?'],
tools: [/* ... */], tools: [/* ... */],
closeButton: true closeButton: true
@@ -126,6 +128,7 @@ import { GitBookProvider, GitBookFrame } from '@gitbook/embed/react';
}} }}
tabs={['assistant', 'docs']} tabs={['assistant', 'docs']}
greeting={{ title: 'Welcome!', subtitle: 'How can I help?' }} greeting={{ title: 'Welcome!', subtitle: 'How can I help?' }}
assistantName="Support Assistant"
suggestions={['What is GitBook?', 'How do I get started?']} suggestions={['What is GitBook?', 'How do I get started?']}
actions={[ actions={[
{ {
@@ -328,6 +331,18 @@ greeting: {
} }
``` ```
### `assistantName`
Available in: Standalone script, NPM package, React components
Override the assistant name displayed in the chat header and assistant entry points (for example, sidebar tabs and action labels). This name will be limited to 32 characters to prevent text overflow.
- **Type**: `string`
```javascript
assistantName: 'Support Assistant'
```
### `suggestions` ### `suggestions`
Available in: Standalone script, NPM package, React components Available in: Standalone script, NPM package, React components
+6
View File
@@ -62,6 +62,12 @@ export type GitBookEmbeddableConfiguration = {
subtitle: string; subtitle: string;
}; };
/**
* Override the assistant name displayed in the UI.
* Limited to 32 characters.
*/
assistantName?: string;
/** Suggestions of questions to be displayed in the welcome page. */ /** Suggestions of questions to be displayed in the welcome page. */
suggestions: string[]; suggestions: string[];
+13 -1
View File
@@ -27,6 +27,7 @@ export function GitBookFrame(props: GitBookFrameProps) {
tabs = ['assistant', 'docs'], tabs = ['assistant', 'docs'],
trademark = true, trademark = true,
closeButton = false, closeButton = false,
assistantName,
} = props; } = props;
const frameRef = useRef<HTMLIFrameElement>(null); const frameRef = useRef<HTMLIFrameElement>(null);
@@ -50,8 +51,19 @@ export function GitBookFrame(props: GitBookFrameProps) {
tools, tools,
closeButton, closeButton,
trademark, trademark,
assistantName,
}); });
}, [gitbookFrame, actions, greeting, suggestions, tools, tabs, closeButton, trademark]); }, [
gitbookFrame,
actions,
greeting,
suggestions,
tools,
tabs,
closeButton,
trademark,
assistantName,
]);
return ( return (
<iframe <iframe
+5 -4
View File
@@ -17,6 +17,7 @@ export type AIConfig = {
aiMode: CustomizationAIMode; aiMode: CustomizationAIMode;
suggestions?: string[]; suggestions?: string[];
trademark: boolean; trademark: boolean;
assistantName?: string;
greeting?: { greeting?: {
title: string; title: string;
subtitle: string; subtitle: string;
@@ -54,10 +55,10 @@ export type Assistant = Omit<GitBookAssistant, 'icon'> & {
const AIContext = React.createContext<AIConfig | null>(null); const AIContext = React.createContext<AIConfig | null>(null);
export function AIContextProvider(props: React.PropsWithChildren<AIConfig>): React.ReactElement { export function AIContextProvider(props: React.PropsWithChildren<AIConfig>): React.ReactElement {
const { aiMode, trademark, suggestions, greeting, children } = props; const { aiMode, trademark, suggestions, greeting, assistantName, children } = props;
const value = React.useMemo( const value = React.useMemo(
() => ({ aiMode, trademark, suggestions, greeting }), () => ({ aiMode, trademark, suggestions, greeting, assistantName }),
[aiMode, trademark, suggestions, greeting] [aiMode, trademark, suggestions, greeting, assistantName]
); );
return <AIContext.Provider value={value}>{children}</AIContext.Provider>; return <AIContext.Provider value={value}>{children}</AIContext.Provider>;
} }
@@ -91,7 +92,7 @@ export function useAI(): AIContext {
if (config.aiMode === CustomizationAIMode.Assistant) { if (config.aiMode === CustomizationAIMode.Assistant) {
assistants.push({ assistants.push({
id: 'gitbook-assistant', id: 'gitbook-assistant',
label: getAIChatName(language, config.trademark), label: config.assistantName ?? getAIChatName(language, config.trademark),
icon: ( icon: (
<AIChatIcon <AIChatIcon
state={chat.loading ? 'thinking' : 'default'} state={chat.loading ? 'thinking' : 'default'}
@@ -92,7 +92,7 @@ export function AIChat() {
<AIChatDynamicIcon trademark={config.trademark} /> <AIChatDynamicIcon trademark={config.trademark} />
<EmbeddableFrameHeaderMain> <EmbeddableFrameHeaderMain>
<EmbeddableFrameTitle> <EmbeddableFrameTitle>
{getAIChatName(language, config.trademark)} {config.assistantName ?? getAIChatName(language, config.trademark)}
</EmbeddableFrameTitle> </EmbeddableFrameTitle>
<AIChatSubtitle chat={chat} /> <AIChatSubtitle chat={chat} />
</EmbeddableFrameHeaderMain> </EmbeddableFrameHeaderMain>
@@ -66,7 +66,7 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
const tabsRef = React.useRef<HTMLDivElement>(null); const tabsRef = React.useRef<HTMLDivElement>(null);
const hasDocsTab = embedConfig.tabs.includes('docs'); const hasDocsTab = embedConfig.tabs.includes('docs');
const trademark = siteConfig.trademark && (embedConfig.trademark ?? true); const trademark = siteConfig.trademark;
const currentLinkContext = use(LinkContext); const currentLinkContext = use(LinkContext);
const linkContext: LinkContextType = useMemo( const linkContext: LinkContextType = useMemo(
() => () =>
@@ -100,7 +100,7 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
) : null} ) : null}
<EmbeddableFrameHeaderMain> <EmbeddableFrameHeaderMain>
<EmbeddableFrameTitle> <EmbeddableFrameTitle>
{getAIChatName(language, trademark)} {siteConfig.assistantName ?? getAIChatName(language, trademark)}
</EmbeddableFrameTitle> </EmbeddableFrameTitle>
<AIChatSubtitle chat={chat} /> <AIChatSubtitle chat={chat} />
</EmbeddableFrameHeaderMain> </EmbeddableFrameHeaderMain>
@@ -113,8 +113,8 @@ export function EmbeddableAIChat(props: EmbeddableAIChatProps) {
<AIChatBody <AIChatBody
chatController={chatController} chatController={chatController}
chat={chat} chat={chat}
suggestions={embedConfig.suggestions} suggestions={siteConfig.suggestions}
greeting={embedConfig.greeting} greeting={siteConfig.greeting}
trademark={trademark} trademark={trademark}
/> />
</LinkContext> </LinkContext>
@@ -0,0 +1,28 @@
'use client';
import { type AIConfig, AIContextProvider } from '@/components/AI';
import type { PropsWithChildren } from 'react';
import { useEmbeddableConfiguration } from './EmbeddableIframeAPI';
type EmbeddableAIContextProviderProps = PropsWithChildren<AIConfig>;
/**
* AI context provider for embeddable routes.
* Merges the static site AI config with runtime embed configuration updates.
*/
export function EmbeddableAIContextProvider(props: EmbeddableAIContextProviderProps) {
const { aiMode, suggestions, greeting, trademark, children } = props;
const embedConfig = useEmbeddableConfiguration();
return (
<AIContextProvider
aiMode={aiMode}
suggestions={embedConfig.suggestions ?? suggestions}
greeting={embedConfig.greeting ?? greeting}
trademark={trademark && (embedConfig.trademark ?? true)}
assistantName={embedConfig.assistantName?.substring(0, 32)} // Limit to 32 characters
>
{children}
</AIContextProvider>
);
}
@@ -1,4 +1,3 @@
import { AIContextProvider } from '@/components/AI';
import { CustomizationRootLayout } from '@/components/RootLayout'; import { CustomizationRootLayout } from '@/components/RootLayout';
import { import {
SiteLayoutClientContexts, SiteLayoutClientContexts,
@@ -11,6 +10,7 @@ import { SiteInsightsTrademarkPlacement } from '@gitbook/api';
import { SpaceLayoutServerContext } from '../SpaceLayout'; import { SpaceLayoutServerContext } from '../SpaceLayout';
import { Trademark } from '../TableOfContents/Trademark'; import { Trademark } from '../TableOfContents/Trademark';
import { NavigationLoader } from '../primitives/NavigationLoader'; import { NavigationLoader } from '../primitives/NavigationLoader';
import { EmbeddableAIContextProvider } from './EmbeddableAIContextProvider';
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI'; import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';
import { IfEmbeddableTrademark } from './EmbeddableTrademark'; import { IfEmbeddableTrademark } from './EmbeddableTrademark';
@@ -41,7 +41,7 @@ export async function EmbeddableRootLayout({
contextId={context.contextId} contextId={context.contextId}
proxyOrigin={context.site.proxy?.origin} proxyOrigin={context.site.proxy?.origin}
> >
<AIContextProvider <EmbeddableAIContextProvider
aiMode={context.customization.ai.mode} aiMode={context.customization.ai.mode}
suggestions={context.customization.ai.suggestions} suggestions={context.customization.ai.suggestions}
trademark={context.customization.trademark.enabled} trademark={context.customization.trademark.enabled}
@@ -72,7 +72,7 @@ export async function EmbeddableRootLayout({
baseURL={context.linker.toPathInSite('~gitbook/embed/')} baseURL={context.linker.toPathInSite('~gitbook/embed/')}
/> />
</SpaceLayoutServerContext> </SpaceLayoutServerContext>
</AIContextProvider> </EmbeddableAIContextProvider>
</SiteLayoutClientContexts> </SiteLayoutClientContexts>
</CustomizationRootLayout> </CustomizationRootLayout>
); );