mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-09 23:05:20 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0c1bdf58e | |||
| d685660e1d | |||
| f1d5123c0b |
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Agents can now send a goal with any question they ask on the Markdown version of a page.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Show site UI in French or Spanish for spaces set to Canadian French, Mexican Spanish or Latin American Spanish.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { describe, expect, it } from 'bun:test';
|
||||||
|
|
||||||
|
import { es } from './es';
|
||||||
|
import { fr } from './fr';
|
||||||
|
import { isAvailableLanguage, languages, loadLanguage } from './index';
|
||||||
|
|
||||||
|
describe('regional language variants', () => {
|
||||||
|
it.each([
|
||||||
|
['fr-ca', fr],
|
||||||
|
['es-mx', es],
|
||||||
|
['es-419', es],
|
||||||
|
] as const)('loads %s with its own metadata and the base strings', async (locale, base) => {
|
||||||
|
expect(isAvailableLanguage(locale)).toBe(true);
|
||||||
|
|
||||||
|
const language = await loadLanguage(locale);
|
||||||
|
|
||||||
|
expect(language).toEqual({ ...base, ...languages[locale] });
|
||||||
|
expect(language.locale).toBe(locale);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,6 +25,10 @@ const languageDefinitions = {
|
|||||||
metadata: { locale: 'fr', language: 'Français', flag: '🇫🇷' },
|
metadata: { locale: 'fr', language: 'Français', flag: '🇫🇷' },
|
||||||
load: () => import('./fr').then((module) => module.fr),
|
load: () => import('./fr').then((module) => module.fr),
|
||||||
},
|
},
|
||||||
|
'fr-ca': {
|
||||||
|
metadata: { locale: 'fr-ca', language: 'Français (Canada)', flag: '🇨🇦' },
|
||||||
|
load: () => import('./fr').then((module) => withMetadata(module.fr, 'fr-ca')),
|
||||||
|
},
|
||||||
de: {
|
de: {
|
||||||
metadata: { locale: 'de', language: 'Deutsch', flag: '🇩🇪' },
|
metadata: { locale: 'de', language: 'Deutsch', flag: '🇩🇪' },
|
||||||
load: () => import('./de').then((module) => module.de),
|
load: () => import('./de').then((module) => module.de),
|
||||||
@@ -33,6 +37,14 @@ const languageDefinitions = {
|
|||||||
metadata: { locale: 'es', language: 'Español', flag: '🇪🇸' },
|
metadata: { locale: 'es', language: 'Español', flag: '🇪🇸' },
|
||||||
load: () => import('./es').then((module) => module.es),
|
load: () => import('./es').then((module) => module.es),
|
||||||
},
|
},
|
||||||
|
'es-mx': {
|
||||||
|
metadata: { locale: 'es-mx', language: 'Español (México)', flag: '🇲🇽' },
|
||||||
|
load: () => import('./es').then((module) => withMetadata(module.es, 'es-mx')),
|
||||||
|
},
|
||||||
|
'es-419': {
|
||||||
|
metadata: { locale: 'es-419', language: 'Español (Latinoamérica)', flag: '🌎' },
|
||||||
|
load: () => import('./es').then((module) => withMetadata(module.es, 'es-419')),
|
||||||
|
},
|
||||||
it: {
|
it: {
|
||||||
metadata: { locale: 'it', language: 'Italiano', flag: '🇮🇹' },
|
metadata: { locale: 'it', language: 'Italiano', flag: '🇮🇹' },
|
||||||
load: () => import('./it').then((module) => module.it),
|
load: () => import('./it').then((module) => module.it),
|
||||||
@@ -177,6 +189,14 @@ const languageDefinitions = {
|
|||||||
|
|
||||||
export type TranslationLocale = keyof typeof languageDefinitions;
|
export type TranslationLocale = keyof typeof languageDefinitions;
|
||||||
|
|
||||||
|
// Regional variants reuse their base language's strings but keep their own locale, name and flag.
|
||||||
|
function withMetadata(
|
||||||
|
base: TranslationLanguage,
|
||||||
|
locale: 'fr-ca' | 'es-mx' | 'es-419'
|
||||||
|
): TranslationLanguage {
|
||||||
|
return { ...base, ...languageDefinitions[locale].metadata };
|
||||||
|
}
|
||||||
|
|
||||||
export const languages = Object.fromEntries(
|
export const languages = Object.fromEntries(
|
||||||
Object.entries(languageDefinitions).map(([locale, definition]) => [locale, definition.metadata])
|
Object.entries(languageDefinitions).map(([locale, definition]) => [locale, definition.metadata])
|
||||||
) as {
|
) as {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
/**
|
import { isAIEnabled } from '@/components/utils/isAIChatEnabled';
|
||||||
* Describe the `ask` and `goal` query parameters of the ask endpoint, for agent-facing prompts.
|
import type { GitBookSiteContext } from '@/lib/context';
|
||||||
*/
|
import { linkerWithMarkdownPages } from '@/lib/links';
|
||||||
export function renderAskParametersDescription(): string {
|
|
||||||
return `\`ask\` is the immediate question: it should be specific, self-contained, and written in natural language.
|
|
||||||
\`goal\` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with \`ask=how do I create an API token\`, a goal like \`build a script that syncs our docs to a CMS\` lets GitBook tailor the answer to that use case.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the "Querying This Documentation" section of the agent instructions.
|
* Render the "Querying This Documentation" section of the agent instructions.
|
||||||
@@ -18,7 +14,46 @@ export function renderQueryingDocumentation(options: { pageUrl: string }): strin
|
|||||||
GET ${pageUrl}?ask=<question>&goal=<user_goal>
|
GET ${pageUrl}?ask=<question>&goal=<user_goal>
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
${renderAskParametersDescription()}
|
\`ask\` is the immediate question: it should be specific, self-contained, and written in natural language.
|
||||||
|
\`goal\` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with \`ask=how do I create an API token\`, a goal like \`automate deployments from our CI pipeline\` lets GitBook tailor the answer to that use case.
|
||||||
|
|
||||||
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.`;
|
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AGENT_INSTRUCTIONS_INTRO = `# Agent Instructions
|
||||||
|
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
|
||||||
|
|
||||||
|
## Querying This Documentation`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the "Agent Instructions" of a markdown page (callers add the separator).
|
||||||
|
*/
|
||||||
|
export function renderPageAgentInstructions(options: { pageUrl: string }): string {
|
||||||
|
return `${AGENT_INSTRUCTIONS_INTRO}
|
||||||
|
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.
|
||||||
|
|
||||||
|
${renderQueryingDocumentation(options)}
|
||||||
|
|
||||||
|
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the "Agent Instructions" of the site-wide llms.txt and llms-full.txt files.
|
||||||
|
* Returns `undefined` when AI is disabled for the site.
|
||||||
|
*/
|
||||||
|
export function renderSiteAgentInstructions(context: GitBookSiteContext): string | undefined {
|
||||||
|
if (!isAIEnabled(context.customization.ai.mode)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ask endpoint ignores the page path, so the top-level `index.md` works on every site.
|
||||||
|
const linker = linkerWithMarkdownPages(context.linker);
|
||||||
|
const pageUrl = linker.toAbsoluteURL(linker.toPathForPagePath({ path: 'index' }));
|
||||||
|
|
||||||
|
return `${AGENT_INSTRUCTIONS_INTRO}
|
||||||
|
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.
|
||||||
|
|
||||||
|
${renderQueryingDocumentation({ pageUrl })}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -954,6 +954,7 @@ function encodePathInSiteContent(
|
|||||||
{
|
{
|
||||||
type: 'ask_question',
|
type: 'ask_question',
|
||||||
query: ask,
|
query: ask,
|
||||||
|
goal: typeof goal === 'string' ? goal : undefined,
|
||||||
location: {
|
location: {
|
||||||
displayContext: SiteInsightsDisplayContext.Server,
|
displayContext: SiteInsightsDisplayContext.Server,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import pMap, { pMapIterable } from 'p-map';
|
|||||||
|
|
||||||
import type { RevisionPageDocument, SiteSection, SiteSpace } from '@gitbook/api';
|
import type { RevisionPageDocument, SiteSection, SiteSpace } from '@gitbook/api';
|
||||||
|
|
||||||
|
import { renderSiteAgentInstructions } from '@/lib/ask-prompt';
|
||||||
import {
|
import {
|
||||||
type GitBookSiteContext,
|
type GitBookSiteContext,
|
||||||
checkIsRootSiteContext,
|
checkIsRootSiteContext,
|
||||||
@@ -171,6 +172,12 @@ async function streamMarkdownPageEntries(
|
|||||||
stream.enqueue(new TextEncoder().encode(`${header.join('\n')}\n\n`));
|
stream.enqueue(new TextEncoder().encode(`${header.join('\n')}\n\n`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendered up front, on every part, since agents may only read the start of the file.
|
||||||
|
const agentInstructions = renderSiteAgentInstructions(context);
|
||||||
|
if (agentInstructions) {
|
||||||
|
stream.enqueue(new TextEncoder().encode(`${agentInstructions}\n---\n\n`));
|
||||||
|
}
|
||||||
|
|
||||||
// Process the pages
|
// Process the pages
|
||||||
for await (const markdown of pMapIterable(
|
for await (const markdown of pMapIterable(
|
||||||
pagesToProcess,
|
pagesToProcess,
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import { toMarkdown } from 'mdast-util-to-markdown';
|
|||||||
|
|
||||||
import type { SiteSection, SiteSpace } from '@gitbook/api';
|
import type { SiteSection, SiteSpace } from '@gitbook/api';
|
||||||
|
|
||||||
import { isAIEnabled } from '@/components/utils/isAIChatEnabled';
|
import { renderSiteAgentInstructions } from '@/lib/ask-prompt';
|
||||||
import { type GitBookSiteContext, checkIsRootSiteContext } from '@/lib/context';
|
import { type GitBookSiteContext, checkIsRootSiteContext } from '@/lib/context';
|
||||||
import { throwIfDataError } from '@/lib/data';
|
import { throwIfDataError } from '@/lib/data';
|
||||||
import { type GitBookLinker, linkerWithMarkdownPages } from '@/lib/links';
|
import { type GitBookLinker, linkerWithMarkdownPages } from '@/lib/links';
|
||||||
import { getMarkdownContentType } from '@/lib/markdown-content-type';
|
import { getMarkdownContentType } from '@/lib/markdown-content-type';
|
||||||
import { resolveFirstDocument } from '@/lib/pages';
|
|
||||||
import { type FlatPageEntry, getIndexablePages } from '@/lib/sitemap';
|
import { type FlatPageEntry, getIndexablePages } from '@/lib/sitemap';
|
||||||
import {
|
import {
|
||||||
filterSiteSpacesByLocale,
|
filterSiteSpacesByLocale,
|
||||||
@@ -49,7 +48,10 @@ export async function serveLLMsTxt(baseContext: GitBookSiteContext) {
|
|||||||
bullet: '-',
|
bullet: '-',
|
||||||
});
|
});
|
||||||
|
|
||||||
output += renderAskFooter(context);
|
const agentInstructions = renderSiteAgentInstructions(context);
|
||||||
|
if (agentInstructions) {
|
||||||
|
output += `\n\n---\n\n${agentInstructions}`;
|
||||||
|
}
|
||||||
|
|
||||||
return new Response(output, {
|
return new Response(output, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -205,27 +207,3 @@ export async function getMarkdownForPagesTree(
|
|||||||
});
|
});
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAskFooter(context: GitBookSiteContext) {
|
|
||||||
if (!isAIEnabled(context.customization.ai.mode)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `\n\n---\n\n# Agent Instructions
|
|
||||||
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
|
|
||||||
|
|
||||||
## Querying This Documentation
|
|
||||||
If you need additional information, you can query the documentation dynamically by asking a question.
|
|
||||||
Perform an HTTP GET request on a page URL with the \`ask\` query parameter:
|
|
||||||
\`\`\`
|
|
||||||
GET ${context.linker.toAbsoluteURL(
|
|
||||||
context.linker.toPathForPagePath({
|
|
||||||
path: resolveFirstDocument(context.revision.pages, [])?.page.path ?? 'index',
|
|
||||||
})
|
|
||||||
)}?ask=<question>
|
|
||||||
\`\`\`
|
|
||||||
The question should be specific, self-contained, and written in natural language.
|
|
||||||
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
|
|
||||||
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
|
|||||||
|
|
||||||
import { resolveMissingPagePath } from '@/components/SitePage/fetch';
|
import { resolveMissingPagePath } from '@/components/SitePage/fetch';
|
||||||
import { isAIEnabled } from '@/components/utils/isAIChatEnabled';
|
import { isAIEnabled } from '@/components/utils/isAIChatEnabled';
|
||||||
import { renderQueryingDocumentation } from '@/lib/ask-prompt';
|
import { renderPageAgentInstructions, renderQueryingDocumentation } from '@/lib/ask-prompt';
|
||||||
import type { GitBookSiteContext } from '@/lib/context';
|
import type { GitBookSiteContext } from '@/lib/context';
|
||||||
import { getExposableError } from '@/lib/data';
|
import { getExposableError } from '@/lib/data';
|
||||||
import { linkerWithMarkdownPages } from '@/lib/links';
|
import { linkerWithMarkdownPages } from '@/lib/links';
|
||||||
@@ -184,16 +184,7 @@ function renderAskFooter(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return `\n\n---\n\n# Agent Instructions
|
return `\n\n---\n\n${renderPageAgentInstructions({ pageUrl })}`;
|
||||||
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
|
|
||||||
|
|
||||||
## Querying This Documentation
|
|
||||||
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
|
|
||||||
|
|
||||||
${renderQueryingDocumentation({ pageUrl })}
|
|
||||||
|
|
||||||
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user