Skip to content

Commit

Permalink
V2: search and ask AI (#2889)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamyPesse authored Mar 3, 2025
1 parent 8beb5d6 commit 6597f85
Showing 24 changed files with 603 additions and 403 deletions.
55 changes: 42 additions & 13 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/gitbook-v2/package.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
"version": "0.1.1",
"private": true,
"dependencies": {
"next": "canary",
"next": "^15.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@gitbook/api": "0.96.1",
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import {
} from '@/components/SiteLayout';
import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils';
import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env';
import { getThemeFromMiddleware } from '@v2/lib/middleware';
import { getThemeFromMiddleware, getVisitorAuthTokenFromMiddleware } from '@v2/lib/middleware';

interface SiteDynamicLayoutProps {
params: Promise<RouteLayoutParams>;
@@ -18,13 +18,15 @@ export default async function SiteDynamicLayout({
}: React.PropsWithChildren<SiteDynamicLayoutProps>) {
const context = await getDynamicSiteContext(await params);
const forcedTheme = await getThemeFromMiddleware();
const visitorAuthToken = await getVisitorAuthTokenFromMiddleware();

return (
<CustomizationRootLayout customization={context.customization}>
<SiteLayout
context={context}
forcedTheme={forcedTheme}
withTracking={!GITBOOK_DISABLE_TRACKING}
visitorAuthToken={visitorAuthToken}
>
{children}
</SiteLayout>
Original file line number Diff line number Diff line change
@@ -25,7 +25,11 @@ export default async function SiteStaticLayout({

return (
<CustomizationRootLayout customization={context.customization}>
<SiteLayout context={context} withTracking={!GITBOOK_DISABLE_TRACKING}>
<SiteLayout
context={context}
withTracking={!GITBOOK_DISABLE_TRACKING}
visitorAuthToken={null}
>
{children}
</SiteLayout>
</CustomizationRootLayout>
109 changes: 23 additions & 86 deletions packages/gitbook-v2/src/app/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fetchSiteContextByURL } from '@v2/lib/context';
import { createDataFetcher } from '@v2/lib/data';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_URL } from '@v2/lib/env';
import { createImageResizer } from '@v2/lib/images';
import { createLinker } from '@v2/lib/links';
import { headers } from 'next/headers';
import {
fetchSiteContextByURL,
fetchSiteContextByURLLookup,
getBaseContext,
} from '@v2/lib/context';
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';

export type RouteParamMode = 'url-host' | 'url';

@@ -21,66 +21,36 @@ export type RouteParams = RouteLayoutParams & {
/**
* Get the static context when rendering statically a site.
*/
export async function getStaticSiteContext(params: RouteLayoutParams) {
const url = getSiteURLFromParams(params);

const dataFetcher = createDataFetcher();
const { linker, host } = createLinkerFromParams(params);
const context = await fetchSiteContextByURL(
{
dataFetcher,
linker,
},
export function getStaticSiteContext(params: RouteLayoutParams) {
const siteURL = getSiteURLFromParams(params);
return fetchSiteContextByURL(
getBaseContext({
siteURL,
urlMode: getModeFromParams(params.mode),
}),
{
url: url.toString(),
url: siteURL.toString(),
visitorAuthToken: null,
redirectOnError: false,
}
);

context.imageResizer = createImageResizer({
host,
linker: context.linker,
});

return context;
}

/**
* Get the site context when rendering dynamically.
* The context will depend on the request.
*/
export async function getDynamicSiteContext(params: RouteLayoutParams) {
const url = getSiteURLFromParams(params);
const headersSet = await headers();

const dataFetcher = createDataFetcher({
apiToken: headersSet.get('x-gitbook-token') ?? GITBOOK_API_TOKEN,
apiEndpoint: headersSet.get('x-gitbook-api') ?? GITBOOK_API_URL,
});

const { linker, host } = createLinkerFromParams(params);

const context = await fetchSiteContextByURL(
{
dataFetcher,
linker,
},
{
url: url.toString(),
visitorAuthToken: headersSet.get('x-gitbook-visitor-token'),

// TODO: set it only when the token comes from the cookies.
redirectOnError: true,
}
const siteURL = getSiteURLFromParams(params);
const siteURLData = await getSiteURLDataFromMiddleware();

return fetchSiteContextByURLLookup(
getBaseContext({
siteURL,
urlMode: getModeFromParams(params.mode),
}),
siteURLData
);

context.imageResizer = createImageResizer({
host,
linker: context.linker,
});

return context;
}

/**
@@ -91,39 +61,6 @@ export function getPagePathFromParams(params: RouteParams) {
return decoded;
}

function createLinkerFromParams(params: RouteLayoutParams) {
const url = getSiteURLFromParams(params);
const mode = getModeFromParams(params.mode);

if (mode === 'url-host') {
return {
linker: createLinker({
host: url.host,
pathname: '/',
}),
host: url.host,
};
}

const gitbookURL = new URL(GITBOOK_URL);
const linker = createLinker({
protocol: gitbookURL.protocol,
host: gitbookURL.host,
pathname: `/url/${url.host}`,
});

// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}`;
};

return {
linker,
host: gitbookURL.host,
};
}

function getSiteURLFromParams(params: RouteLayoutParams) {
const decoded = decodeURIComponent(params.siteURL);
const url = new URL(`https://${decoded}`);
87 changes: 69 additions & 18 deletions packages/gitbook-v2/src/lib/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getSiteStructureSections } from '@/lib/sites';
import type {
ChangeRequest,
PublishedSiteContentLookup,
RevisionPage,
RevisionPageDocument,
Site,
@@ -15,8 +16,9 @@ import type {
import { type GitBookDataFetcher, createDataFetcher } from '@v2/lib/data';
import { redirect } from 'next/navigation';
import { assert } from 'ts-essentials';
import type { ImageResizer } from './images';
import { type GitBookSpaceLinker, appendBasePathToLinker } from './links';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_URL } from './env';
import { type ImageResizer, createImageResizer } from './images';
import { type GitBookSpaceLinker, createLinker } from './links';

/**
* Generic context when rendering content.
@@ -90,9 +92,6 @@ export type GitBookSiteContext = GitBookSpaceContext & {

/** Scripts to load for the site. */
scripts: SiteIntegrationScript[];

/** Visitor token used to fetch the site */
visitorAuthToken: string | null;
};

/**
@@ -102,6 +101,58 @@ export type GitBookPageContext = (GitBookSpaceContext | GitBookSiteContext) & {
page: RevisionPageDocument;
};

/**
* Get the base context for a request.
*/
export function getBaseContext(input: {
siteURL: URL | string;
urlMode: 'url' | 'url-host';
apiToken?: string | null;
}) {
const url = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL;
const urlMode = input.urlMode;

const dataFetcher = createDataFetcher({
apiToken: input.apiToken ?? GITBOOK_API_TOKEN,
apiEndpoint: GITBOOK_API_URL,
});
const gitbookURL = new URL(GITBOOK_URL);

const linker =
urlMode === 'url-host'
? createLinker({
host: url.host,
pathname: url.pathname,
})
: createLinker({
protocol: gitbookURL.protocol,
host: gitbookURL.host,
pathname: `/url/${url.host}${url.pathname}`,
});

if (urlMode === 'url') {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}`;
};
}

const imageResizer = createImageResizer({
host: urlMode === 'url-host' ? url.host : gitbookURL.host,

// To ensure image resizing work for proxied sites,
// we serve images from the root of the site.
linker: linker,
});

return {
dataFetcher,
linker,
imageResizer,
};
}

/**
* Fetch the context of a site for a given URL and a base context.
*/
@@ -120,15 +171,25 @@ export async function fetchSiteContextByURL(
redirectOnError: input.redirectOnError,
});

return fetchSiteContextByURLLookup(baseContext, data);
}

/**
* Fetch the context of a site using the resolution of a URL
*/
export async function fetchSiteContextByURLLookup(
baseContext: GitBookBaseContext,
data: PublishedSiteContentLookup
): Promise<GitBookSiteContext> {
const { dataFetcher } = baseContext;
if ('redirect' in data) {
redirect(data.redirect);
}

const context = await fetchSiteContextByIds(
return await fetchSiteContextByIds(
{
...baseContext,
dataFetcher: createDataFetcher({
apiEndpoint: dataFetcher.apiEndpoint,
dataFetcher: dataFetcher.withToken({
apiToken: data.apiToken,
}),
},
@@ -141,16 +202,8 @@ export async function fetchSiteContextByURL(
shareKey: data.shareKey,
changeRequest: data.changeRequest,
revision: data.revision,
visitorAuthToken: input.visitorAuthToken,
}
);

const siteContext = {
...context,
linker: appendBasePathToLinker(context.linker, data.basePath),
};

return siteContext;
}

/**
@@ -167,7 +220,6 @@ export async function fetchSiteContextByIds(
shareKey: string | undefined;
changeRequest: string | undefined;
revision: string | undefined;
visitorAuthToken: string | null;
}
): Promise<GitBookSiteContext> {
const { dataFetcher } = baseContext;
@@ -233,7 +285,6 @@ export async function fetchSiteContextByIds(
structure: siteStructure,
sections,
scripts,
visitorAuthToken: ids.visitorAuthToken,
};
}

31 changes: 31 additions & 0 deletions packages/gitbook-v2/src/lib/data/api.ts
Original file line number Diff line number Diff line change
@@ -35,6 +35,17 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
return {
apiEndpoint: input.apiEndpoint,

async api() {
return getAPI(input);
},

withToken({ apiToken }) {
return createDataFetcher({
...input,
apiToken,
});
},

//
// API that are tied to the token
//
@@ -124,6 +135,9 @@ export function createDataFetcher(input: DataFetcherInput = commonInput): GitBoo
spaceId: params.spaceId,
});
},
searchSiteContent(params) {
return searchSiteContent(input, params);
},

//
// API that are not tied to the token
@@ -498,6 +512,23 @@ async function getEmbedByUrl(
return res.data;
}

async function searchSiteContent(
input: DataFetcherInput,
params: Parameters<GitBookDataFetcher['searchSiteContent']>[0]
) {
'use cache';

const { organizationId, siteId, query, scope } = params;

cacheLife('days');

const res = await getAPI(input).orgs.searchSiteContent(organizationId, siteId, {
query,
...scope,
});
return res.data.items;
}

function getAPI(input: DataFetcherInput) {
const { apiEndpoint, apiToken } = input;
const api = new GitBookAPI({
Loading
Oops, something went wrong.

0 comments on commit 6597f85

Please sign in to comment.