diff --git a/packages/gitbook/src/components/Adaptive/AIPageLinkSummary.tsx b/packages/gitbook/src/components/Adaptive/AIPageLinkSummary.tsx
new file mode 100644
index 000000000..fb77d4273
--- /dev/null
+++ b/packages/gitbook/src/components/Adaptive/AIPageLinkSummary.tsx
@@ -0,0 +1,47 @@
+'use client';
+import { useEffect, useState } from 'react';
+import { streamLinkPageSummary } from './server-actions/streamLinkPageSummary';
+
+/**
+ * Summarise a page's content for use in a link preview
+ */
+export function AIPageLinkSummary(props: {
+ currentSpaceId: string;
+ currentPageId: string;
+ targetSpaceId: string;
+ targetPageId: string;
+}) {
+ const { currentSpaceId, currentPageId, targetSpaceId, targetPageId } = props;
+
+ const [summary, setSummary] = useState('');
+
+ useEffect(() => {
+ let canceled = false;
+
+ setSummary('');
+
+ (async () => {
+ const stream = await streamLinkPageSummary({
+ currentSpaceId,
+ currentPageId,
+ targetSpaceId,
+ targetPageId,
+ previousPageIds: [],
+ });
+
+ for await (const [summary] of stream) {
+ if (canceled) return;
+
+ // join the chunk if it's an array of strings or string|undefined
+ // console.log(summary[0]);
+ setSummary(summary ?? '');
+ }
+ })();
+
+ return () => {
+ canceled = true;
+ };
+ }, [currentSpaceId, currentPageId, targetSpaceId, targetPageId]);
+
+ return
{summary.length > 1 ? summary : "Loading..."}
;
+}
diff --git a/packages/gitbook/src/components/Adaptive/AdaptivePane.tsx b/packages/gitbook/src/components/Adaptive/AdaptivePane.tsx
index bf17cd8f8..dd65e2a6e 100644
--- a/packages/gitbook/src/components/Adaptive/AdaptivePane.tsx
+++ b/packages/gitbook/src/components/Adaptive/AdaptivePane.tsx
@@ -5,12 +5,15 @@ import { useLanguage } from '@/intl/client';
import { tString } from '@/intl/translate';
import { tcls } from '@/lib/tailwind';
import { Button } from '../primitives';
+import { AIPageNextRecommendedPages } from './AIPageNextRecommendedPages';
import { useAdaptivePane } from './state';
-export function AdaptivePane() {
+export function AdaptivePane(props: { spaceId: string; revisionId: string; pageId: string }) {
const language = useLanguage();
const { opened, open, close } = useAdaptivePane();
+ const {spaceId, revisionId, pageId} = props;
+
React.useEffect(() => {
if (opened) {
document.body.classList.add('adaptive-pane');
@@ -48,6 +51,9 @@ export function AdaptivePane() {
}}
/>
+
+
+
);
diff --git a/packages/gitbook/src/components/Adaptive/server-actions/streamLinkPageSummary.ts b/packages/gitbook/src/components/Adaptive/server-actions/streamLinkPageSummary.ts
new file mode 100644
index 000000000..c153bf590
--- /dev/null
+++ b/packages/gitbook/src/components/Adaptive/server-actions/streamLinkPageSummary.ts
@@ -0,0 +1,128 @@
+'use server';
+import { getV1BaseContext } from '@/lib/v1';
+import { isV2 } from '@/lib/v2';
+import { AIMessageRole } from '@gitbook/api';
+import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
+import { fetchServerActionSiteContext, getServerActionBaseContext } from '@v2/lib/server-actions';
+import { z } from 'zod';
+import { streamGenerateObject } from './api';
+
+/**
+ * Get a summary of a page, in the context of another page
+ */
+export async function* streamLinkPageSummary({
+ currentSpaceId,
+ currentPageId,
+ targetSpaceId,
+ targetPageId,
+ previousPageIds,
+}: {
+ currentSpaceId: string;
+ currentPageId: string;
+ targetSpaceId: string;
+ targetPageId: string;
+ previousPageIds: string[];
+}) {
+ const baseContext = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
+ const siteURLData = await getSiteURLDataFromMiddleware();
+
+ const [{ stream }, context] = await Promise.all([
+ streamGenerateObject(
+ baseContext,
+ {
+ organizationId: siteURLData.organization,
+ siteId: siteURLData.site,
+ },
+ {
+ schema: z.object({
+ summary: z.array(z.string().describe('The summary of the target page')),
+ // questions: z.array(z.string().describe('The questions to sea')).max(3),
+ }),
+ messages: [
+ {
+ role: AIMessageRole.Developer,
+ content: `You are a documentation navigator, tasked with generating summaries of articles the user might navigate to next.
+ Given a user's current article and the target article the user is inspecting, respond with a one-sentence summary of the target article. Make sure to tailor the summary to the user's current article, for example by examining the paragraph the link to the target article is mentioned in.
+ Do not repeat the article's title and description, because these are already shown to the user. Avoid the passive voice, and don't reference the article itself.`,
+ },
+ {
+ role: AIMessageRole.Developer,
+ content: 'Pages in the documentation:',
+ attachments: [
+ {
+ type: 'pages',
+ spaceId: currentSpaceId,
+ },
+ ],
+ },
+ ...(previousPageIds.length > 0
+ ? [
+ {
+ role: AIMessageRole.User,
+ content: `The user has visited the following pages: ${previousPageIds.join(', ')}`,
+ },
+ ]
+ : []),
+ {
+ role: AIMessageRole.User,
+ content: `The user is currently on page ID ${currentPageId}, the content of the page is:`,
+ attachments: [
+ {
+ type: 'page',
+ spaceId: currentSpaceId,
+ pageId: currentPageId,
+ },
+ ],
+ },
+ {
+ role: AIMessageRole.User,
+ content: `The user is looking at page ID ${targetPageId}, the content of the page is:`,
+ attachments: [
+ {
+ type: 'page',
+ spaceId: targetSpaceId,
+ pageId: targetPageId,
+ },
+ ],
+ },
+ ],
+ }
+ ),
+ fetchServerActionSiteContext(baseContext),
+ ]);
+
+ const emitted = new Set();
+ for await (const value of stream) {
+ const summary = value.summary;
+ if (!summary) {
+ continue;
+ }
+
+ yield summary;
+
+ // for (const pageId of pages) {
+ // if (!pageId) {
+ // continue;
+ // }
+
+ // if (emitted.has(pageId)) {
+ // continue;
+ // }
+
+ // emitted.add(pageId);
+
+ // const page = resolvePageId(context.pages, pageId);
+ // if (!page) {
+ // continue;
+ // }
+
+ // yield {
+ // title: page.page.title,
+ // href: context.linker.toPathForPage({
+ // pages: context.pages,
+ // page: page.page,
+ // }),
+ // };
+ // }
+ }
+}
diff --git a/packages/gitbook/src/components/DocumentView/InlineLink.tsx b/packages/gitbook/src/components/DocumentView/InlineLink.tsx
index 700261e49..b5808009c 100644
--- a/packages/gitbook/src/components/DocumentView/InlineLink.tsx
+++ b/packages/gitbook/src/components/DocumentView/InlineLink.tsx
@@ -6,6 +6,7 @@ import { tcls } from '@/lib/tailwind';
import { Icon } from '@gitbook/icons';
import * as Tooltip from '@radix-ui/react-tooltip';
import { Fragment } from 'react';
+import { AIPageLinkSummary } from '../Adaptive/AIPageLinkSummary';
import { Button, StyledLink } from '../primitives';
import type { InlineProps } from './Inline';
import { Inlines } from './Inlines';
@@ -139,20 +140,25 @@ export async function InlineLink(props: InlineProps) {
) : null}
- {!isExternal ? (
+ {!isExternal &&
+ context.contentContext &&
+ 'page' in context.contentContext &&
+ inline.data.ref.kind === 'page' ? (
AI Summary
-
- This integration synchronizes your docs with the code
- repository so that your API spec updates in GitBook mirror
- any changes made in GitHub or GitLab, keeping your content
- aligned with your code.
-
+
-
+ {/*
) {
It works with GitHub and GitLab.
-
-
-
-
-
+
*/}
) : null}
diff --git a/packages/gitbook/src/components/SitePage/SitePage.tsx b/packages/gitbook/src/components/SitePage/SitePage.tsx
index 5b058295e..d7dff0902 100644
--- a/packages/gitbook/src/components/SitePage/SitePage.tsx
+++ b/packages/gitbook/src/components/SitePage/SitePage.tsx
@@ -74,7 +74,7 @@ export async function SitePage(props: SitePageProps) {
) : null}
{/* We use a flex row reverse to render the aside first because the page is streamed. */}