Fix Docs Embed host actions not reaching the assistant (#4512)

This commit is contained in:
Peter White
2026-08-18 16:33:03 +02:00
committed by GitHub
parent 78c589ffba
commit 177ef8582a
5 changed files with 33 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix host action buttons in the Docs Embed not reaching the assistant.
@@ -127,7 +127,15 @@ export async function GET(
'Show me tips and tricks',
],
tabs: ['assistant', 'search', 'docs'],
closeButton: useCustomTrigger
closeButton: useCustomTrigger,
// Host-page action: posts a message into the chat, like an embedding site would.
actions: [
{
icon: 'rocket',
label: 'Ask about pricing',
onClick: () => window.GitBook('postUserMessage', 'How much does GitBook cost?'),
},
],
});
if (useCustomTrigger) {
@@ -14,7 +14,7 @@ import {
} from '@gitbook/api';
import { getInsightsSession, useTrackEvent } from '../Insights';
import { useSetSearchState } from '../Search';
import { type UpdateSearchState, useSetSearchState } from '../Search';
import { addRecentSearchQuery } from '../Search/recent-queries';
import type { AnyAIControl } from './controls';
import { ConfirmControlDef, ConfirmControlOutputSchema } from './controls/ConfirmControl';
@@ -32,6 +32,8 @@ import { useCurrentContent } from '@/components/hooks';
import { useLanguage } from '@/intl/client';
import { tString } from '@/intl/translate';
const noopSetSearchState: UpdateSearchState = () => Promise.resolve(new URLSearchParams());
export type AIChatMessage = {
role: AIMessageRole;
content: React.ReactNode;
@@ -235,7 +237,7 @@ export function AIChatProvider(props: {
const messageContextRef = useAIMessageContextRef();
const trackEvent = useTrackEvent();
const setSearchState = useSetSearchState();
const setSearchStateInURL = useSetSearchState();
const { siteSpaceId } = useCurrentContent();
const language = useLanguage();
@@ -243,6 +245,11 @@ export function AIChatProvider(props: {
? SiteInsightsDisplayContext.Embed
: SiteInsightsDisplayContext.Site;
// The embed keeps its state in its own routes, not in URL search params.
const setSearchState = renderMessageOptions?.asEmbeddable
? noopSetSearchState
: setSearchStateInURL;
// The assistant response the user is reacting to. Snapshotted when a new user turn begins
// (before it overwrites the store's responseId/query), so the self-feedback tool rates that
// previous response rather than the one this reaction turn produces.
@@ -610,14 +617,12 @@ export function AIChatProvider(props: {
// Post a message to the AI chat
const onPostMessage = React.useCallback(
async (input: { message: string }) => {
const { query, messages, control, references, responding } = globalState.getState();
const { query, messages, control, references, responding, opened } =
globalState.getState();
if (control) {
throw new Error("We can't post a message when a control is active");
}
// Still streaming: queue this follow-up instead of dropping it (flushed in order in `streamResponse`).
if (responding) {
// Still streaming, or waiting on a control (e.g. a tool confirmation): queue this
// follow-up instead of dropping it (flushed in order in `streamResponse`).
if (responding || control) {
globalState.setState((state) => ({
...state,
queuedMessages: [...state.queuedMessages, input.message],
@@ -643,9 +648,7 @@ export function AIChatProvider(props: {
notify(eventsRef.current.get('postMessage'), { message: input.message });
if (query === input.message && references.length === 0) {
// Return early if the message is the same as the previous message
// (unless new references are staged, which change the payload)
if (query === input.message && references.length === 0 && !opened) {
globalState.setState((state) => ({
...state,
opened: true,
@@ -74,6 +74,9 @@ export function EmbeddableIframeAPI(props: { baseURL: string }) {
break;
}
case 'postUserMessage': {
// The answer renders in the assistant, so bring it up if another tab is showing.
navToken.current++;
router.push(`${baseURL}/assistant`);
chatController.postMessage({
message: message.message,
});
@@ -99,7 +99,7 @@ function NavigationCard(
>
{label}
</span>
<span className="line-clamp-2 text-tint-strong leading-snug transition-colors group-hover:text-primary contrast-more:group-hover:text-primary-strong">
<span className="line-clamp-2 leading-snug text-tint-strong transition-colors group-hover:text-primary contrast-more:group-hover:text-primary-strong">
{title}
</span>
</div>