Skip to content

Commit f5ba352

Browse files
committed
Add event listener to AIChatController
To switch to AI tab in embed upon posting a new message
1 parent be4ceae commit f5ba352

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ export type AIChatController = {
9696
postMessage: (input: { message: string }) => void;
9797
/** Clear the conversation */
9898
clear: () => void;
99+
/** Register an event listener */
100+
on: (event: 'postMessage', listener: (input: { message: string }) => void) => () => void;
99101
};
100102

101103
const AIChatControllerContext = React.createContext<AIChatController | null>(null);
@@ -137,6 +139,11 @@ export function AIChatProvider(props: {
137139
const [, setSearchState] = useSearch();
138140
const language = useLanguage();
139141

142+
// Event listeners storage
143+
const eventsRef = React.useRef<Map<'postMessage', Array<(input: { message: string }) => void>>>(
144+
new Map()
145+
);
146+
140147
// Open AI chat and sync with search state
141148
const onOpen = React.useCallback(() => {
142149
const { initialQuery } = globalState.getState();
@@ -379,8 +386,18 @@ export function AIChatProvider(props: {
379386
}));
380387
}
381388

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);
394+
382395
if (query === input.message) {
383396
// Return early if the message is the same as the previous message
397+
globalState.setState((state) => ({
398+
...state,
399+
opened: true,
400+
}));
384401
return;
385402
}
386403

@@ -440,14 +457,31 @@ export function AIChatProvider(props: {
440457
}));
441458
}, [setSearchState]);
442459

460+
const onEvent = React.useCallback(
461+
(event: 'postMessage', listener: (input: { message: string }) => void) => {
462+
const listeners = eventsRef.current.get(event) || [];
463+
listeners.push(listener);
464+
eventsRef.current.set(event, listeners);
465+
return () => {
466+
const currentListeners = eventsRef.current.get(event) || [];
467+
eventsRef.current.set(
468+
event,
469+
currentListeners.filter((l) => l !== listener)
470+
);
471+
};
472+
},
473+
[]
474+
);
475+
443476
const controller = React.useMemo(() => {
444477
return {
445478
open: onOpen,
446479
close: onClose,
447480
clear: onClear,
448481
postMessage: onPostMessage,
482+
on: onEvent,
449483
};
450-
}, [onOpen, onClose, onClear, onPostMessage]);
484+
}, [onOpen, onClose, onClear, onPostMessage, onEvent]);
451485

452486
return (
453487
<AIChatControllerContext.Provider value={controller}>

packages/gitbook/src/components/Embeddable/EmbeddableAIChat.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export function EmbeddableAIChat() {
3737
const configuration = useEmbeddableConfiguration();
3838
const language = useLanguage();
3939

40+
React.useEffect(() => {
41+
chatController.open();
42+
}, [chatController]);
43+
4044
// Track the view of the AI chat
4145
const trackEvent = useTrackEvent();
4246
React.useEffect(() => {

packages/gitbook/src/components/Embeddable/EmbeddableIframeAPI.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export function EmbeddableIframeAPI(props: {
4040
embeddableConfiguration.setState({ siteTitle });
4141
}, [baseURL, siteTitle]);
4242

43+
React.useEffect(() => {
44+
return chatController.on('postMessage', () => {
45+
router.push(`${baseURL}/assistant`);
46+
});
47+
}, [router, baseURL, chatController]);
48+
4349
React.useEffect(() => {
4450
if (window.parent === window) {
4551
return;
@@ -62,6 +68,7 @@ export function EmbeddableIframeAPI(props: {
6268
chatController.postMessage({
6369
message: message.message,
6470
});
71+
router.push(`${baseURL}/assistant`);
6572
break;
6673
}
6774
case 'configure': {
@@ -192,6 +199,7 @@ export function EmbeddableIframeTabs(props: { active?: string }) {
192199
? tabs.map((tab) => (
193200
<Button
194201
key={tab.key}
202+
data-testid={`embed-tab-${tab.key}`}
195203
label={tab.label}
196204
size="default"
197205
variant="blank"

0 commit comments

Comments
 (0)