mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 10:03:31 +00:00
Improve prompt & layout
This commit is contained in:
@@ -10,9 +10,11 @@ import { streamLinkPageSummary } from './server-actions/streamLinkPageSummary';
|
||||
export function AIPageLinkSummary(props: {
|
||||
currentSpaceId: string;
|
||||
currentPageId: string;
|
||||
currentPageTitle: string;
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
linkPreview?: string;
|
||||
linkTitle?: string;
|
||||
showTrademark: boolean;
|
||||
}) {
|
||||
const {
|
||||
@@ -21,6 +23,7 @@ export function AIPageLinkSummary(props: {
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
showTrademark = true,
|
||||
} = props;
|
||||
|
||||
@@ -38,14 +41,12 @@ export function AIPageLinkSummary(props: {
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
previousPageIds: [],
|
||||
});
|
||||
|
||||
for await (const highlight of stream) {
|
||||
if (canceled) return;
|
||||
|
||||
// join the chunk if it's an array of strings or string|undefined
|
||||
// console.log(summary[0]);
|
||||
setHighlight(highlight ?? '');
|
||||
}
|
||||
})();
|
||||
@@ -53,11 +54,11 @@ export function AIPageLinkSummary(props: {
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, [currentSpaceId, currentPageId, targetSpaceId, targetPageId]);
|
||||
}, [currentSpaceId, currentPageId, targetSpaceId, targetPageId, linkPreview, linkTitle]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
<div className="flex w-screen items-center gap-1 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
{showTrademark ? (
|
||||
<Loading className="size-4" busy={!highlight || highlight.length === 0} />
|
||||
) : (
|
||||
|
||||
@@ -16,19 +16,20 @@ export async function* streamLinkPageSummary({
|
||||
targetSpaceId,
|
||||
targetPageId,
|
||||
linkPreview,
|
||||
linkTitle,
|
||||
}: {
|
||||
currentSpaceId: string;
|
||||
currentPageId: string;
|
||||
targetSpaceId: string;
|
||||
targetPageId: string;
|
||||
linkPreview?: string;
|
||||
linkContext?: string;
|
||||
linkTitle?: string;
|
||||
previousPageIds?: string[];
|
||||
}) {
|
||||
const baseContext = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
|
||||
const siteURLData = await getSiteURLDataFromMiddleware();
|
||||
|
||||
const [{ stream, response }, context] = await Promise.all([
|
||||
const [{ stream }] = await Promise.all([
|
||||
streamGenerateObject(
|
||||
baseContext,
|
||||
{
|
||||
@@ -43,16 +44,42 @@ export async function* streamLinkPageSummary({
|
||||
messages: [
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# Task
|
||||
You are a documentation navigator, tasked with extracting information from pages the user might navigate to next.
|
||||
The user is currently reading a page, and is considering clicking a link to another ("target") page.
|
||||
Use the user's context and the page they are currently on.`,
|
||||
content: `# Role
|
||||
You are a documentation navigator. Your job is to help the user read documentation more efficiently. Your aim is to prevent the user from having to read the target page by giving them all the information they need to know.
|
||||
|
||||
# Task
|
||||
Using both the current page context and the target page content, produce a page highlight that:
|
||||
- Highlights the key facts from the target page.
|
||||
- Relates strongly to the topic the user is currently reading about.
|
||||
- Is very succinct and direct, using only one or two short sentences (each sentence using no more than one comma).
|
||||
- Remains strictly factual, without referring to “the page”.
|
||||
|
||||
# Instructions
|
||||
1. Identify the key paragraph surrounding the link's text (e.g., “change request”) from the current page.
|
||||
2. Extract and combine relevant information from the target page to address the link's context.
|
||||
3. Combine in one or two short sentences that are direct and brief.
|
||||
|
||||
# Examples
|
||||
## Example 1
|
||||
- Link context: “This feature is only available on the Ultimate plan.”
|
||||
- Link preview: “Pricing: Learn about our different pricing tiers.”
|
||||
- Response: “The Ultimate plan costs $25 per month. A Pro plan is available too.”
|
||||
|
||||
## Example 2
|
||||
- Link context: “You can use keyboard shortcuts to get to the Search menu faster.”
|
||||
- Link preview: “Keyboard shortcuts: A quick reference guide to all the keyboard shortcuts available.”
|
||||
- Response: “To open the Search menu, use the keyboard shortcut ⌘K or Ctrl+K.”
|
||||
|
||||
## Example 3
|
||||
- Link context: “This feature can only be enabled by an admin.”
|
||||
- Link preview: “Roles: An overview of the different roles on the platform.”
|
||||
- Response: “The admin role is reserved for the creator of the organisation.”`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# Context
|
||||
## Current page
|
||||
The user is currently on page ID ${currentPageId}, the content of this page is:`,
|
||||
The content of the current page is:`,
|
||||
attachments: [
|
||||
{
|
||||
type: 'page',
|
||||
@@ -63,18 +90,7 @@ The user is currently on page ID ${currentPageId}, the content of this page is:`
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `## Link context
|
||||
The user is inspecting a link to page ID ${targetPageId}. Look for this ID in the current article and inspect the paragraph that surrounds it, to understand the link context.`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `## Link preview:
|
||||
This text is displayed directly above your summary. Use pronouns to reference concepts that have already been introduced in this preview.
|
||||
${linkPreview}`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `# Target page
|
||||
content: `## Target page
|
||||
The content of the target page is:`,
|
||||
attachments: [
|
||||
{
|
||||
@@ -86,44 +102,13 @@ The content of the target page is:`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.Developer,
|
||||
content: `---
|
||||
# Formatting
|
||||
## Style guide
|
||||
- Respond with one or two sentences maximum.
|
||||
- Keep sentences short. Don't use more than 1 comma per sentence.
|
||||
- Stick to the facts on the target page.
|
||||
- Do not reference "the page" itself.
|
||||
|
||||
## Example 1
|
||||
- Link context:
|
||||
> This feature is only available on the [Ultimate plan](/pricing).
|
||||
- Link preview:
|
||||
> **Pricing**
|
||||
> Learn about our different pricing tiers.
|
||||
- Correct response:
|
||||
> The Ultimate plan costs $25 per month. A Pro plan is available too.
|
||||
|
||||
## Example 2
|
||||
- Link context:
|
||||
> You can use [keyboard shortcuts](/keyboard-shortcuts) to get to the Search menu faster.
|
||||
- Link preview:
|
||||
> **Keyboard shortcuts**
|
||||
> A quick reference guide to all the keyboard shortcuts available.
|
||||
- Correct response:
|
||||
> To open the Search menu, use the keyboard shortcut ⌘K or Ctrl+K.
|
||||
|
||||
## Example 3
|
||||
- Link context:
|
||||
> This feature can only be enabled by an [admin](/roles).
|
||||
- Link preview:
|
||||
> **Roles**
|
||||
> An overview of the different roles on the platform.
|
||||
- Correct response:
|
||||
> The admin role is reserved for the creator of the organisation.`,
|
||||
content: `## Link preview
|
||||
The content of the link preview is:
|
||||
> ${linkPreview}`,
|
||||
},
|
||||
{
|
||||
role: AIMessageRole.User,
|
||||
content: `I'm considering reading page ID ${targetPageId}. Give the most relevant information from this page. Please relate it to my current page.`,
|
||||
content: `I'm considering reading the link titled "${linkTitle}" to page ID ${targetPageId}. Give the most relevant information from this page. Relate it to my current page and in particular the paragraph I'm currently reading. Be very concise.`,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -131,7 +116,6 @@ The content of the target page is:`,
|
||||
fetchServerActionSiteContext(baseContext),
|
||||
]);
|
||||
|
||||
const emitted = new Set<string>();
|
||||
for await (const value of stream) {
|
||||
const highlight = value.highlight;
|
||||
if (!highlight) {
|
||||
@@ -139,30 +123,5 @@ The content of the target page is:`,
|
||||
}
|
||||
|
||||
yield highlight;
|
||||
|
||||
// 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,
|
||||
// }),
|
||||
// };
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
|
||||
let breadcrumbs = resolved.ancestors;
|
||||
const isExternal = inline.data.ref.kind === 'url';
|
||||
const isSamePage = inline.data.ref.kind === 'anchor' && inline.data.ref.page === undefined;
|
||||
if (isExternal) {
|
||||
breadcrumbs = [
|
||||
{
|
||||
@@ -42,6 +43,15 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
},
|
||||
];
|
||||
}
|
||||
if (isSamePage) {
|
||||
breadcrumbs = [
|
||||
{
|
||||
label: 'Jump to section',
|
||||
icon: <Icon icon="arrow-down-short-wide" className="size-3" />,
|
||||
},
|
||||
];
|
||||
resolved.subText = undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip.Provider delayDuration={200}>
|
||||
@@ -72,68 +82,70 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
</StyledLink>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content className="z-40 flex w-[100vw] max-w-md animate-present px-4">
|
||||
<div className="grow items-center overflow-hidden rounded-md straight-corners:rounded-none shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1 ">
|
||||
<Tooltip.Content className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto">
|
||||
<div className="overflow-hidden rounded-md straight-corners:rounded-none shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1 ">
|
||||
<div className="bg-tint-base p-4">
|
||||
{breadcrumbs ? (
|
||||
<div className="mb-1 flex gap-4">
|
||||
<div className="flex grow flex-wrap items-center gap-x-2 gap-y-0.5 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
{breadcrumbs.map((crumb, index) => {
|
||||
const Tag = crumb.href ? StyledLink : 'div';
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-col">
|
||||
{breadcrumbs ? (
|
||||
<div className="mb-1 flex grow flex-wrap items-center gap-x-2 gap-y-0.5 font-semibold text-tint text-xs uppercase leading-tight tracking-wide">
|
||||
{breadcrumbs.map((crumb, index) => {
|
||||
const Tag = crumb.href ? StyledLink : 'div';
|
||||
|
||||
return (
|
||||
<Fragment key={crumb.label}>
|
||||
{index !== 0 ? (
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className="size-3 text-tint-subtle"
|
||||
/>
|
||||
) : null}
|
||||
<Tag
|
||||
className={tcls(
|
||||
'flex gap-1',
|
||||
crumb.href &&
|
||||
'links-default:text-tint no-underline hover:underline contrast-more:underline contrast-more:decoration-current'
|
||||
)}
|
||||
href={crumb.href ?? '#'}
|
||||
>
|
||||
{crumb.icon ? (
|
||||
<span className="mt-0.5 text-tint-subtle empty:hidden">
|
||||
{crumb.icon}
|
||||
</span>
|
||||
return (
|
||||
<Fragment key={crumb.label}>
|
||||
{index !== 0 ? (
|
||||
<Icon
|
||||
icon="chevron-right"
|
||||
className="size-3 text-tint-subtle"
|
||||
/>
|
||||
) : null}
|
||||
{crumb.label}
|
||||
</Tag>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{resolved.href ? (
|
||||
<Button
|
||||
className="-mx-2 -my-2 ml-auto"
|
||||
variant="blank"
|
||||
href={resolved.href}
|
||||
target="_blank"
|
||||
label="Open in new tab"
|
||||
size="small"
|
||||
icon="arrow-up-right-from-square"
|
||||
iconOnly={true}
|
||||
/>
|
||||
<Tag
|
||||
className={tcls(
|
||||
'flex gap-1',
|
||||
crumb.href &&
|
||||
'links-default:text-tint no-underline hover:underline contrast-more:underline contrast-more:decoration-current'
|
||||
)}
|
||||
href={crumb.href ?? '#'}
|
||||
>
|
||||
{crumb.icon ? (
|
||||
<span className="mt-0.5 text-tint-subtle empty:hidden">
|
||||
{crumb.icon}
|
||||
</span>
|
||||
) : null}
|
||||
{crumb.label}
|
||||
</Tag>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={tcls(
|
||||
'flex gap-2 leading-snug',
|
||||
isExternal && 'text-sm [overflow-wrap:anywhere]'
|
||||
)}
|
||||
>
|
||||
{resolved.icon ? (
|
||||
<div className="mt-1 text-tint-subtle empty:hidden">
|
||||
{resolved.icon}
|
||||
<div
|
||||
className={tcls(
|
||||
'flex gap-2 leading-snug',
|
||||
isExternal && 'text-sm [overflow-wrap:anywhere]'
|
||||
)}
|
||||
>
|
||||
{resolved.icon ? (
|
||||
<div className="mt-1 text-tint-subtle empty:hidden">
|
||||
{resolved.icon}
|
||||
</div>
|
||||
) : null}
|
||||
<h5 className="font-semibold">{resolved.text}</h5>
|
||||
</div>
|
||||
</div>
|
||||
{!isSamePage && resolved.href ? (
|
||||
<Button
|
||||
className="-mx-2 -my-2 ml-auto"
|
||||
variant="blank"
|
||||
href={resolved.href}
|
||||
target="_blank"
|
||||
label="Open in new tab"
|
||||
size="small"
|
||||
icon="arrow-up-right-from-square"
|
||||
iconOnly={true}
|
||||
/>
|
||||
) : null}
|
||||
<h5 className="font-semibold">{resolved.text}</h5>
|
||||
</div>
|
||||
{resolved.subText ? (
|
||||
<p className="mt-1 text-sm text-tint">{resolved.subText}</p>
|
||||
@@ -149,57 +161,28 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
|
||||
<AIPageLinkSummary
|
||||
currentPageId={context.contentContext.page.id}
|
||||
currentSpaceId={context.contentContext.space.id}
|
||||
currentPageTitle={context.contentContext.page.title}
|
||||
targetPageId={
|
||||
inline.data.ref.page ?? context.contentContext.page.id
|
||||
}
|
||||
targetSpaceId={
|
||||
inline.data.ref.space ?? context.contentContext.space.id
|
||||
}
|
||||
linkTitle={inline.nodes
|
||||
.map((node) => {
|
||||
if (node.object === 'text') {
|
||||
return node.leaves
|
||||
.map((leaf) => leaf.text)
|
||||
.join('');
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.join('')}
|
||||
linkPreview={`**${resolved.text}**: ${resolved.subText}`}
|
||||
showTrademark={
|
||||
context.contentContext.customization.trademark.enabled
|
||||
}
|
||||
/>
|
||||
|
||||
{/* <div className="-m-2 mt-0 flex flex-col rounded-md p-2 text-sm">
|
||||
<details className="-mx-2 rounded-md px-2 py-1 transition-colors has-[summary:hover]:bg-tint-hover">
|
||||
<summary className="flex items-center gap-1 text-tint hover:cursor-pointer">
|
||||
<Icon
|
||||
icon="circle-question"
|
||||
className="size-3 shrink-0"
|
||||
/>
|
||||
Who can configure it?
|
||||
</summary>
|
||||
<div className="mt-1">
|
||||
Administrators and creators set it up.
|
||||
</div>
|
||||
</details>
|
||||
<details className="-mx-2 rounded-md px-2 py-1 transition-colors has-[summary:hover]:bg-tint-hover">
|
||||
<summary className="flex items-center gap-1 text-tint hover:cursor-pointer">
|
||||
<Icon
|
||||
icon="circle-question"
|
||||
className="size-3 shrink-0"
|
||||
/>
|
||||
Does it sync both ways?
|
||||
</summary>
|
||||
<div className="mt-1">
|
||||
Edits in GitBook and commits on GitHub or GitLab
|
||||
update each other.
|
||||
</div>
|
||||
</details>
|
||||
<details className="-mx-2 rounded-md px-2 py-1 transition-colors has-[summary:hover]:bg-tint-hover">
|
||||
<summary className="flex items-center gap-1 text-tint hover:cursor-pointer">
|
||||
<Icon
|
||||
icon="circle-question"
|
||||
className="size-3 shrink-0"
|
||||
/>
|
||||
Which platforms connect?
|
||||
</summary>
|
||||
<div className="mt-1">
|
||||
It works with GitHub and GitLab.
|
||||
</div>
|
||||
</details>
|
||||
</div> */}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@ export async function CustomizationRootLayout(props: {
|
||||
'theme-muted:bg-tint-subtle',
|
||||
|
||||
'theme-gradient:bg-gradient-primary',
|
||||
'theme-gradient-tint:bg-gradient-tint'
|
||||
'theme-gradient-tint:bg-gradient-tint',
|
||||
)}
|
||||
>
|
||||
<IconsProvider
|
||||
|
||||
@@ -330,7 +330,7 @@ const config: Config = {
|
||||
present: {
|
||||
from: {
|
||||
opacity: '0',
|
||||
transform: 'translateY(20%) scale(90%)',
|
||||
transform: 'translateY(1rem) scale(90%)',
|
||||
},
|
||||
to: {
|
||||
opacity: '1',
|
||||
|
||||
Reference in New Issue
Block a user