Improve prompt & layout

This commit is contained in:
Zeno Kapitein
2025-04-03 13:18:00 +02:00
parent f4c50c7e3c
commit b38eed850f
5 changed files with 126 additions and 183 deletions
@@ -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
+1 -1
View File
@@ -330,7 +330,7 @@ const config: Config = {
present: {
from: {
opacity: '0',
transform: 'translateY(20%) scale(90%)',
transform: 'translateY(1rem) scale(90%)',
},
to: {
opacity: '1',