Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout

This commit is contained in:
Zeno Kapitein
2025-12-05 14:13:46 +01:00
committed by GitHub
9 changed files with 45 additions and 11 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Add customizable suggested questions
+3 -2
View File
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "gitbook",
@@ -345,7 +346,7 @@
"react-dom": "catalog:",
},
"catalog": {
"@gitbook/api": "0.153.0",
"@gitbook/api": "0.154.0",
"@scalar/api-client-react": "^1.3.46",
"@tsconfig/node20": "^20.1.6",
"@tsconfig/strictest": "^2.0.6",
@@ -726,7 +727,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.153.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-ArNPFoqwId4Flicz8xPEdGqXQkzxyxP7S8Uv3wIfCX2e4cLhpcS7xCJGEHOJrqe1tNs1ovT1N2MWfpdIqzXqig=="],
"@gitbook/api": ["@gitbook/api@0.154.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-huEgWWa2H+H4ofYEAQl9r49u0gVjDHHWVNJKhd6SgGCfefjNPHkQj9zaky/5DBQzlu/+SDmutWiOtYdrykQO8w=="],
"@gitbook/browser-types": ["@gitbook/browser-types@workspace:packages/browser-types"],
+1 -1
View File
@@ -41,7 +41,7 @@
"catalog": {
"@tsconfig/strictest": "^2.0.6",
"@tsconfig/node20": "^20.1.6",
"@gitbook/api": "0.153.0",
"@gitbook/api": "0.154.0",
"@scalar/api-client-react": "^1.3.46",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
+6 -2
View File
@@ -15,6 +15,7 @@ import { useSearch } from '../Search/useSearch';
// Unify assistants configuration context with the assistants hook in one place
export type AIConfig = {
aiMode: CustomizationAIMode;
suggestions?: string[];
trademark: boolean;
};
@@ -49,8 +50,11 @@ export type Assistant = Omit<GitBookAssistant, 'icon'> & {
const AIContext = React.createContext<AIConfig | null>(null);
export function AIContextProvider(props: React.PropsWithChildren<AIConfig>): React.ReactElement {
const { aiMode, trademark, children } = props;
const value = React.useMemo(() => ({ aiMode, trademark }), [aiMode, trademark]);
const { aiMode, trademark, suggestions, children } = props;
const value = React.useMemo(
() => ({ aiMode, trademark, suggestions }),
[aiMode, trademark, suggestions]
);
return <AIContext.Provider value={value}>{children}</AIContext.Provider>;
}
@@ -109,7 +109,11 @@ export function AIChat() {
</EmbeddableFrameButtons>
</EmbeddableFrameHeader>
<EmbeddableFrameBody>
<AIChatBody chatController={chatController} chat={chat} />
<AIChatBody
chatController={chatController}
chat={chat}
suggestions={config.suggestions}
/>
</EmbeddableFrameBody>
</EmbeddableFrameMain>
</EmbeddableFrame>
@@ -41,6 +41,7 @@ export async function EmbeddableRootLayout({
>
<AIContextProvider
aiMode={context.customization.ai.mode}
suggestions={context.customization.ai.suggestions}
trademark={context.customization.trademark.enabled}
>
<SpaceLayoutServerContext
@@ -58,7 +58,7 @@ export function SearchContainer({
viewport,
siteSpaces,
}: SearchContainerProps) {
const { assistants } = useAI();
const { assistants, config } = useAI();
const [state, setSearchState] = useSearch();
const searchAsk = useSearchAskState();
@@ -200,7 +200,8 @@ export function SearchContainer({
siteSpaceId: siteSpace.id,
siteSpaceIds,
scope: state?.scope ?? 'default',
withAI: withAI,
withAI,
suggestions: config.suggestions,
});
const searchValue = state?.query ?? (withSearchAI || !withAI ? state?.ask : null) ?? '';
@@ -35,8 +35,9 @@ export function useSearchResults(props: {
siteSpaceIds: string[];
scope: SearchScope;
withAI: boolean;
suggestions?: string[];
}) {
const { disabled, query, siteSpaceId, siteSpaceIds, scope } = props;
const { disabled, query, siteSpaceId, siteSpaceIds, scope, suggestions } = props;
const trackEvent = useTrackEvent();
@@ -78,6 +79,22 @@ export function useSearchResults(props: {
const questions = new Set<string>();
const recommendedQuestions: ResultType[] = [];
if (suggestions && suggestions.length > 0) {
suggestions.forEach((question) => {
questions.add(question);
});
setResultsState({
results: suggestions.map((question, index) => ({
type: 'recommended-question',
id: `recommended-question-${index}`,
question,
})),
fetching: false,
error: false,
});
return;
}
const timeout = setTimeout(async () => {
if (cancelled) {
return;
@@ -175,14 +192,14 @@ export function useSearchResults(props: {
cancelled = true;
clearTimeout(timeout);
};
}, [query, scope, trackEvent, withAI, siteSpaceId, siteSpaceIds, disabled]);
}, [query, scope, trackEvent, withAI, siteSpaceId, siteSpaceIds, disabled, suggestions]);
const aiEnrichedResults: ResultType[] = React.useMemo(() => {
if (!withAI) {
return resultsState.results;
}
return withAskTriggers(resultsState.results, query, assistants);
}, [resultsState.results, query, withAI]);
}, [resultsState.results, query, withAI, assistants]);
return { ...resultsState, results: aiEnrichedResults };
}
@@ -56,6 +56,7 @@ export async function SiteLayout(props: {
>
<AIContextProvider
aiMode={customization.ai?.mode}
suggestions={context.customization.ai?.suggestions}
trademark={customization.trademark.enabled}
>
<SpaceLayout