Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/gitbook/src/components/AI/useAIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type AIChatMessage = {
query?: string;
};

export type AIChatSize = 'default' | 'large';

export type AIChatPendingTool = {
icon?: IconName;
label: string;
Expand Down Expand Up @@ -85,6 +87,11 @@ export type AIChatState = {
* display an error alert. Clearing the conversation will reset this flag.
*/
error: boolean;

/**
* The size of the chat window.
*/
size: AIChatSize;
};

export type AIChatController = {
Expand All @@ -96,12 +103,14 @@ export type AIChatController = {
postMessage: (input: { message: string }) => void;
/** Clear the conversation */
clear: () => void;
/** Toggle the size of the chat window */
setSize: (size: AIChatSize) => void;
};

const AIChatControllerContext = React.createContext<AIChatController | null>(null);

// Global state store for AI chat
const globalState = zustand.create<AIChatState>(() => {
export const globalState = zustand.create<AIChatState>(() => {
return {
opened: false,
responseId: null,
Expand All @@ -112,6 +121,7 @@ const globalState = zustand.create<AIChatState>(() => {
loading: false,
error: false,
initialQuery: null,
size: 'default',
};
});

Expand Down Expand Up @@ -438,14 +448,19 @@ export function AIChatProvider(props: {
}));
}, [setSearchState]);

const onSetSize = React.useCallback((size: AIChatSize) => {
globalState.setState((state) => ({ ...state, size }));
}, []);

const controller = React.useMemo(() => {
return {
open: onOpen,
close: onClose,
clear: onClear,
postMessage: onPostMessage,
setSize: onSetSize,
};
}, [onOpen, onClose, onClear, onPostMessage]);
}, [onOpen, onClose, onClear, onPostMessage, onSetSize]);

return (
<AIChatControllerContext.Provider value={controller}>
Expand Down
70 changes: 53 additions & 17 deletions packages/gitbook/src/components/AIChat/AIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { t, tString, useLanguage } from '@/intl/client';
import type { TranslationLanguage } from '@/intl/translations';
import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
Expand All @@ -13,7 +14,7 @@ import {
} from '../AI';
import { EmbeddableFrame } from '../Embeddable/EmbeddableFrame';
import { useNow } from '../hooks';
import { Button } from '../primitives';
import { Button, DropdownMenuSeparator } from '../primitives';
import { DropdownMenu, DropdownMenuItem } from '../primitives';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
Expand Down Expand Up @@ -64,7 +65,10 @@ export function AIChat(props: { trademark: boolean }) {
return (
<div
data-testid="ai-chat"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enter-from-right lg:pr-4 lg:pl-0 xl:w-96"
className={tcls(
'ai-chat z-40 mx-auto flex max-h-full w-full max-w-3xl animate-present scroll-mt-36 p-4 transition-all duration-300 max-md:inset-x-0 max-md:bottom-0 lg:fixed lg:inset-y-0 lg:right-0 lg:animate-enter-from-right',
chat.size === 'large' ? 'lg:w-104 xl:w-156' : 'lg:w-80 xl:w-96'
)}
>
<EmbeddableFrame
className="relative circular-corners:rounded-3xl rounded-corners:rounded-md depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle"
Expand Down Expand Up @@ -92,6 +96,33 @@ export function AIChat(props: { trademark: boolean }) {
/>
}
>
<DropdownMenuItem
onClick={() => {
chatController.setSize(
chat.size === 'default' ? 'large' : 'default'
);
}}
>
<Icon
icon={
chat.size === 'default'
? 'arrow-up-right-and-arrow-down-left-from-center'
: 'arrow-down-left-and-arrow-up-right-to-center'
}
className="size-3 shrink-0 text-tint-subtle"
/>
<div className="flex flex-col gap-0.5">
<p className="font-medium">
{chat.size === 'default' ? 'Maximize' : 'Minimize'}
</p>
<p className="text-tint text-xs">
{chat.size === 'default'
? 'Longer, more detailed answers'
: 'Shorter, more concise answers'}
</p>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
chatController.clear();
Expand Down Expand Up @@ -223,27 +254,32 @@ export function AIChatBody(props: {
<>
<div
ref={scrollContainerRef}
className="flex grow scroll-pt-4 flex-col gap-4 overflow-y-auto p-4"
className={tcls(
'flex grow scroll-pt-4 flex-col gap-4 overflow-y-auto p-4 transition-all',
chat.size === 'large' ? 'text-base' : 'text-sm'
)}
style={{
paddingBottom: `${inputHeight}px`,
}}
>
{isEmpty ? (
<div className="flex min-h-full w-full shrink-0 flex-col items-center justify-center gap-6 py-4">
<div className="flex size-32 animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle">
<AIChatIcon
state="intro"
trademark={trademark}
className="size-16 animate-[present_500ms_200ms_both]"
/>
</div>
<div className="animate-[fadeIn_500ms_400ms_both]">
<h5 className=" text-center font-bold text-lg text-tint-strong">
{timeGreeting}
</h5>
<p className="text-center text-tint">
{t(language, 'ai_chat_assistant_description')}
</p>
<div className="flex items-center gap-4 lg:flex-col">
<div className="flex animate-fade-in-slow items-center justify-center rounded-full bg-tint-subtle p-4 lg:p-8">
<AIChatIcon
state="intro"
trademark={trademark}
className="size-8 animate-[present_500ms_200ms_both] lg:size-16"
/>
</div>
<div className="animate-[fadeIn_500ms_400ms_both]">
<h5 className="font-bold text-lg text-tint-strong lg:text-center">
{timeGreeting}
</h5>
<p className="text-tint lg:text-center">
{t(language, 'ai_chat_assistant_description')}
</p>
</div>
</div>
{!chat.error ? (
<AIChatSuggestedQuestions chatController={chatController} />
Expand Down
2 changes: 1 addition & 1 deletion packages/gitbook/src/components/AIChat/AIChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function AIChatInput(props: {
<span className="leading-none">
{t(language, 'ai_chat_context_title')}
</span>
<Icon icon="question-circle" className="size-3" />
<Icon icon="question-circle" className="size-3 shrink-0" />
</div>
</HoverCardTrigger>
</HoverCardRoot>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ export default function AIChatSuggestedQuestions(props: { chatController: AIChat
];

return (
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2 lg:flex-col">
{DEFAULT_SUGGESTED_QUESTIONS.map((question, index) => (
<Button
key={question}
variant="secondary"
size="medium"
className="max-w-full animate-[present_500ms_both] whitespace-normal"
className="shrink grow animate-[present_500ms_both] whitespace-normal"
style={{
animationDelay: `${800 + index * 100}ms`,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
GitBookIntegrationEventCallback,
GitBookIntegrationTool,
} from '@gitbook/browser-types';
import type { Assistant } from '../AI';
import { type Assistant, globalState } from '../AI';

const events = new Map<GitBookIntegrationEvent, GitBookIntegrationEventCallback[]>();

Expand Down Expand Up @@ -67,6 +67,24 @@ if (typeof window !== 'undefined') {
},
};
window.GitBook = gitbookGlobal;

window.GitBook?.registerTool({
name: 'ExpandChatSize',
description:
'FREE TO USE: Expand the chat to a larger size. Use this tool before you start writing your final answer if you need to show a lot of content, like step-by-step instructions or codeblocks. You do not need to preface this tool with feedback to the user, just call it and the chat will expand.',
execute: async () => {
globalState.setState((state) => ({ ...state, size: 'large' }));
return {
output: {
expanded: true,
},
summary: {
icon: 'arrow-up-right-and-arrow-down-left-from-center',
text: 'Chat size expanded',
},
};
},
});
}

/**
Expand Down
Loading