mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 09:33:21 +00:00
Move AI Actions markdown fetching to client-side (#3459)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Move AI Actions markdown fetching to client-side
|
||||
@@ -58,22 +58,26 @@ export function OpenDocsAssistant(props: { type: AIActionType; trademark: boolea
|
||||
const useCopiedStore = create<{
|
||||
copied: boolean;
|
||||
setCopied: (copied: boolean) => void;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
}>((set) => ({
|
||||
copied: false,
|
||||
setCopied: (copied: boolean) => set({ copied }),
|
||||
loading: false,
|
||||
setLoading: (loading: boolean) => set({ loading }),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Copies the markdown version of the page to the clipboard.
|
||||
*/
|
||||
export function CopyMarkdown(props: {
|
||||
markdown: string;
|
||||
markdownPageUrl: string;
|
||||
type: AIActionType;
|
||||
isDefaultAction?: boolean;
|
||||
}) {
|
||||
const { markdown, type, isDefaultAction } = props;
|
||||
const { markdownPageUrl, type, isDefaultAction } = props;
|
||||
const language = useLanguage();
|
||||
const { copied, setCopied } = useCopiedStore();
|
||||
const { copied, setCopied, loading, setLoading } = useCopiedStore();
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// Close the dropdown menu manually after the copy button is clicked
|
||||
@@ -88,6 +92,16 @@ export function CopyMarkdown(props: {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
||||
};
|
||||
|
||||
// Fetch the markdown from the page
|
||||
const fetchMarkdown = async () => {
|
||||
setLoading(true);
|
||||
|
||||
return fetch(markdownPageUrl)
|
||||
.then((res) => res.text())
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
// Reset the copied state when the component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
@@ -96,7 +110,7 @@ export function CopyMarkdown(props: {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClick = (e: React.MouseEvent) => {
|
||||
const onClick = async (e: React.MouseEvent) => {
|
||||
// Prevent default behavior for non-default actions to avoid closing the dropdown.
|
||||
// This allows showing transient UI (e.g., a "copied" state) inside the menu item.
|
||||
// Default action buttons are excluded from this behavior.
|
||||
@@ -104,13 +118,9 @@ export function CopyMarkdown(props: {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Cancel any pending timeout
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
const markdown = await fetchMarkdown();
|
||||
|
||||
navigator.clipboard.writeText(markdown);
|
||||
|
||||
setCopied(true);
|
||||
|
||||
// Reset the copied state after 2 seconds
|
||||
@@ -132,6 +142,7 @@ export function CopyMarkdown(props: {
|
||||
shortLabel={copied ? tString(language, 'code_copied') : tString(language, 'code_copy')}
|
||||
description={tString(language, 'copy_page_markdown')}
|
||||
onClick={onClick}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -149,7 +160,7 @@ export function ViewAsMarkdown(props: { markdownPageUrl: string; type: AIActionT
|
||||
icon={<MarkdownIcon className="size-4 fill-current" />}
|
||||
label={tString(language, 'view_page_markdown')}
|
||||
description={tString(language, 'view_page_plaintext')}
|
||||
href={`${markdownPageUrl}.md`}
|
||||
href={markdownPageUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -200,13 +211,16 @@ function AIActionWrapper(props: {
|
||||
description?: string;
|
||||
href?: string;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const { type, icon, label, shortLabel, onClick, href, description, disabled } = props;
|
||||
const { type, icon, label, shortLabel, onClick, href, description, disabled, loading } = props;
|
||||
|
||||
if (type === 'button') {
|
||||
return (
|
||||
<Button
|
||||
icon={icon}
|
||||
icon={
|
||||
loading ? <Icon icon="spinner-third" className="size-4 animate-spin" /> : icon
|
||||
}
|
||||
size="xsmall"
|
||||
variant="secondary"
|
||||
label={shortLabel || label}
|
||||
@@ -214,7 +228,7 @@ function AIActionWrapper(props: {
|
||||
onClick={onClick}
|
||||
href={href}
|
||||
target={href ? '_blank' : undefined}
|
||||
disabled={disabled}
|
||||
disabled={disabled || loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@/components/AIActions/AIActions';
|
||||
import { Button } from '@/components/primitives/Button';
|
||||
import { DropdownMenu } from '@/components/primitives/DropdownMenu';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { useRef } from 'react';
|
||||
|
||||
@@ -15,7 +16,6 @@ import { useRef } from 'react';
|
||||
* Dropdown menu for the AI Actions (Ask Docs Assistant, Copy page, View as Markdown, Open in LLM).
|
||||
*/
|
||||
export function AIActionsDropdown(props: {
|
||||
markdown?: string;
|
||||
markdownPageUrl: string;
|
||||
/**
|
||||
* Whether to include the "Ask Docs Assistant" entry in the dropdown menu.
|
||||
@@ -57,29 +57,26 @@ export function AIActionsDropdown(props: {
|
||||
* The content of the dropdown menu.
|
||||
*/
|
||||
function AIActionsDropdownMenuContent(props: {
|
||||
markdown?: string;
|
||||
markdownPageUrl: string;
|
||||
withAIChat?: boolean;
|
||||
pageURL: string;
|
||||
trademark: boolean;
|
||||
}) {
|
||||
const { markdown, markdownPageUrl, withAIChat, pageURL, trademark } = props;
|
||||
const { markdownPageUrl, withAIChat, pageURL, trademark } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{withAIChat ? (
|
||||
<OpenDocsAssistant trademark={trademark} type="dropdown-menu-item" />
|
||||
) : null}
|
||||
{markdown ? (
|
||||
<>
|
||||
<CopyMarkdown
|
||||
markdown={markdown}
|
||||
isDefaultAction={!withAIChat}
|
||||
type="dropdown-menu-item"
|
||||
/>
|
||||
<ViewAsMarkdown markdownPageUrl={markdownPageUrl} type="dropdown-menu-item" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<CopyMarkdown
|
||||
isDefaultAction={!withAIChat}
|
||||
markdownPageUrl={markdownPageUrl}
|
||||
type="dropdown-menu-item"
|
||||
/>
|
||||
<ViewAsMarkdown markdownPageUrl={markdownPageUrl} type="dropdown-menu-item" />
|
||||
|
||||
<OpenInLLM provider="chatgpt" url={pageURL} type="dropdown-menu-item" />
|
||||
<OpenInLLM provider="claude" url={pageURL} type="dropdown-menu-item" />
|
||||
</>
|
||||
@@ -90,25 +87,21 @@ function AIActionsDropdownMenuContent(props: {
|
||||
* A default action shown as a quick-access button beside the dropdown menu
|
||||
*/
|
||||
function DefaultAction(props: {
|
||||
markdown?: string;
|
||||
withAIChat?: boolean;
|
||||
pageURL: string;
|
||||
markdownPageUrl: string;
|
||||
withAIChat?: boolean;
|
||||
trademark: boolean;
|
||||
}) {
|
||||
const { markdown, withAIChat, pageURL, markdownPageUrl, trademark } = props;
|
||||
const { markdownPageUrl, withAIChat, trademark } = props;
|
||||
|
||||
if (withAIChat) {
|
||||
return <OpenDocsAssistant trademark={trademark} type="button" />;
|
||||
}
|
||||
|
||||
if (markdown) {
|
||||
return <CopyMarkdown isDefaultAction={!withAIChat} markdown={markdown} type="button" />;
|
||||
}
|
||||
|
||||
if (markdownPageUrl) {
|
||||
return <ViewAsMarkdown markdownPageUrl={markdownPageUrl} type="button" />;
|
||||
}
|
||||
|
||||
return <OpenInLLM provider="chatgpt" url={pageURL} type="button" />;
|
||||
return (
|
||||
<CopyMarkdown
|
||||
isDefaultAction={!withAIChat}
|
||||
markdownPageUrl={markdownPageUrl}
|
||||
type="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AIActionsDropdown } from '@/components/AIActions/AIActionsDropdown';
|
||||
import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { getMarkdownForPage } from '@/lib/markdownPage';
|
||||
import type { AncestorRevisionPage } from '@/lib/pages';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import type { RevisionPageDocument } from '@gitbook/api';
|
||||
@@ -18,8 +17,6 @@ export async function PageHeader(props: {
|
||||
const { context, page, ancestors } = props;
|
||||
const { revision, linker } = context;
|
||||
|
||||
const markdownResult = await getMarkdownForPage(context, page.path);
|
||||
|
||||
if (!page.layout.title && !page.layout.description) {
|
||||
return null;
|
||||
}
|
||||
@@ -46,8 +43,7 @@ export async function PageHeader(props: {
|
||||
)}
|
||||
>
|
||||
<AIActionsDropdown
|
||||
markdown={markdownResult.data}
|
||||
markdownPageUrl={context.linker.toPathInSpace(page.path)}
|
||||
markdownPageUrl={`${context.linker.toPathInSpace(page.path)}.md`}
|
||||
pageURL={context.linker.toAbsoluteURL(
|
||||
context.linker.toPathForPage({
|
||||
pages: context.revision.pages,
|
||||
|
||||
Reference in New Issue
Block a user