mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Refactor AIChatButton and StructurePreview components to enhance AI assistant integration, improving button rendering and functionality
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
'use client';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t, tString } from '@/intl/translate';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import type { Assistant } from '../AI';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { Button } from '../primitives';
|
||||
@@ -8,6 +11,49 @@ import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
|
||||
|
||||
const MOBILE_BREAKPOINT = 688; // 43rem, equal to Tailwind's @max-2xl container breakpoint
|
||||
|
||||
/**
|
||||
* Button visual for an AI assistant in the header.
|
||||
*/
|
||||
export function AIChatButtonView(props: {
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
showLabel?: boolean;
|
||||
withShortcut?: boolean;
|
||||
inert?: boolean;
|
||||
}) {
|
||||
const { icon, label, onClick, showLabel = true, withShortcut = true, inert = false } = props;
|
||||
const language = useLanguage();
|
||||
const isMobile = useIsMobile(MOBILE_BREAKPOINT, '[data-gb-header-content]');
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={icon}
|
||||
data-testid="ai-chat-button"
|
||||
iconOnly={!showLabel || isMobile}
|
||||
size="medium"
|
||||
variant="header"
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{t(language, 'ai_chat_ask', label)}
|
||||
{withShortcut ? (
|
||||
<KeyboardShortcut
|
||||
keys={['mod', 'i']}
|
||||
className="border-tint-11 text-tint-1"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
aria-label={tString(language, 'ai_chat_ask', label)}
|
||||
onClick={inert ? undefined : onClick}
|
||||
tabIndex={inert ? -1 : undefined}
|
||||
className={tcls(inert ? 'pointer-events-none select-none' : null)}
|
||||
>
|
||||
{showLabel ? t(language, 'ask') : null}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Button to open/close the AI chat.
|
||||
*/
|
||||
@@ -17,31 +63,14 @@ export function AIChatButton(props: {
|
||||
withShortcut?: boolean;
|
||||
}) {
|
||||
const { assistant, showLabel = true, withShortcut = true } = props;
|
||||
const language = useLanguage();
|
||||
const isMobile = useIsMobile(MOBILE_BREAKPOINT, '[data-gb-header-content]');
|
||||
|
||||
return (
|
||||
<Button
|
||||
<AIChatButtonView
|
||||
icon={assistant.icon}
|
||||
data-testid="ai-chat-button"
|
||||
iconOnly={!showLabel || isMobile}
|
||||
size="medium"
|
||||
variant="header"
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{t(language, 'ai_chat_ask', assistant.label)}
|
||||
{withShortcut ? (
|
||||
<KeyboardShortcut
|
||||
keys={['mod', 'i']}
|
||||
className="border-tint-11 text-tint-1"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
aria-label={tString(language, 'ai_chat_ask', assistant.label)}
|
||||
label={assistant.label}
|
||||
onClick={() => assistant.open()}
|
||||
>
|
||||
{showLabel ? t(language, 'ask') : null}
|
||||
</Button>
|
||||
showLabel={showLabel}
|
||||
withShortcut={withShortcut}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import type { CustomizationContentLink, CustomizationHeaderItem, SiteSpace } from '@gitbook/api';
|
||||
import { CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import {
|
||||
CustomizationAIMode,
|
||||
CustomizationHeaderPreset,
|
||||
CustomizationSearchStyle,
|
||||
} from '@gitbook/api';
|
||||
import * as React from 'react';
|
||||
|
||||
import { SiteSectionTabs } from '@/components/SiteSections';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { AIChatButtonView, AIChatIcon, getAIChatName } from '../AIChat';
|
||||
import {
|
||||
HeaderLinkItem,
|
||||
HeaderLinkMenuItem,
|
||||
@@ -98,6 +103,7 @@ function StructurePreviewHeader(props: { snapshot: StructurePreviewSnapshot }) {
|
||||
const variants = getPreviewVariants(snapshot);
|
||||
const sections = encodePreviewSiteSections(snapshot);
|
||||
const headerSocialAccounts = getHeaderSocialAccounts(customization);
|
||||
const previewAssistants = getPreviewAssistants(snapshot, language);
|
||||
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
|
||||
const withSections = Boolean(
|
||||
sections &&
|
||||
@@ -192,6 +198,20 @@ function StructurePreviewHeader(props: { snapshot: StructurePreviewSnapshot }) {
|
||||
)}
|
||||
>
|
||||
<StructurePreviewSearch />
|
||||
{previewAssistants.map((assistant, index) => (
|
||||
<AIChatButtonView
|
||||
key={assistant.id}
|
||||
icon={assistant.icon}
|
||||
label={assistant.label}
|
||||
withShortcut={index === 0}
|
||||
showLabel={
|
||||
previewAssistants.length === 1 &&
|
||||
customization.styling.search ===
|
||||
CustomizationSearchStyle.Prominent
|
||||
}
|
||||
inert
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{customization.header.links.length > 0 ||
|
||||
@@ -315,6 +335,29 @@ function StructurePreviewSearch() {
|
||||
return <SearchHeaderInput interactive={false} />;
|
||||
}
|
||||
|
||||
function getPreviewAssistants(
|
||||
snapshot: StructurePreviewSnapshot,
|
||||
language: ReturnType<typeof useLanguage>
|
||||
) {
|
||||
if (snapshot.customization.ai?.mode !== CustomizationAIMode.Assistant) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'gitbook-assistant',
|
||||
label: getAIChatName(language, snapshot.customization.trademark.enabled),
|
||||
icon: (
|
||||
<AIChatIcon
|
||||
state="default"
|
||||
trademark={snapshot.customization.trademark.enabled}
|
||||
className="size-text-lg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function StructurePreviewHeaderLink(props: {
|
||||
snapshot: StructurePreviewSnapshot;
|
||||
link: CustomizationHeaderItem;
|
||||
|
||||
Reference in New Issue
Block a user