Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
704b002
[docs] Add AI Agent docs
VaguelySerious Nov 26, 2025
0a65357
Fix scrolling of left sidebar
VaguelySerious Nov 26, 2025
eb49e18
Polish
VaguelySerious Nov 26, 2025
e83bbf3
Empty changeset
VaguelySerious Nov 26, 2025
8e8acab
Polish
VaguelySerious Nov 26, 2025
51e7aac
Move to new section
VaguelySerious Nov 26, 2025
108ac78
Polish
VaguelySerious Nov 26, 2025
b04412d
Show more code snippets and full state
VaguelySerious Nov 26, 2025
4811a94
Fix
VaguelySerious Nov 26, 2025
da6e94e
Delete previous
VaguelySerious Nov 26, 2025
2b3c672
fix
VaguelySerious Nov 26, 2025
7f3514f
Fix routing
VaguelySerious Nov 26, 2025
11edb5b
Add nice graphic
VaguelySerious Nov 26, 2025
64d7265
Polish
VaguelySerious Nov 26, 2025
afdb0b1
Package install polish
VaguelySerious Nov 26, 2025
465c18d
CHanges
VaguelySerious Nov 26, 2025
5dada91
Human in the loop polish
VaguelySerious Nov 27, 2025
6db66ec
Polish sleep docs
VaguelySerious Nov 27, 2025
596f7ad
Tool polish
VaguelySerious Nov 27, 2025
756a11c
Move sections
VaguelySerious Nov 27, 2025
739f732
love these graphs
VaguelySerious Nov 27, 2025
dad2a63
remove auto-close example
VaguelySerious Nov 27, 2025
249bf54
Queueing
VaguelySerious Nov 27, 2025
634df7f
prepareStep, queueing, and modeling
VaguelySerious Nov 27, 2025
bbe7df9
Small polish
VaguelySerious Nov 27, 2025
7e50e2e
remove unneeded file
VaguelySerious Nov 27, 2025
e97a2b3
Polish pass
VaguelySerious Nov 27, 2025
9a1a2b5
Update multi-turn example
VaguelySerious Nov 27, 2025
87aa49f
Empty commit
VaguelySerious Nov 27, 2025
719700e
Merge branch 'main' into peter/agent-docs
VaguelySerious Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/three-hands-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
151 changes: 151 additions & 0 deletions docs/app/guides/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import {
DocsBody as FumadocsDocsBody,
DocsDescription as FumadocsDocsDescription,
DocsPage as FumadocsDocsPage,
DocsTitle as FumadocsDocsTitle,
} from 'fumadocs-ui/page';
import { createRelativeLink } from 'fumadocs-ui/mdx';
import { notFound, redirect } from 'next/navigation';
import type { CSSProperties } from 'react';
import { AskAI } from '@/components/geistdocs/ask-ai';
import { CopyPage } from '@/components/geistdocs/copy-page';
import { EditSource } from '@/components/geistdocs/edit-source';
import { Feedback } from '@/components/geistdocs/feedback';
import { getMDXComponents } from '@/components/geistdocs/mdx-components';
import { AgentTraces } from '@/components/guides/agent-traces';
import { OpenInChat } from '@/components/geistdocs/open-in-chat';
import { ScrollTop } from '@/components/geistdocs/scroll-top';
import { TableOfContents } from '@/components/geistdocs/toc';
import * as AccordionComponents from '@/components/ui/accordion';
import { Badge } from '@/components/ui/badge';
import {
getGuidesLLMText,
getPageImage,
guidesSource,
} from '@/lib/geistdocs/source';
import { TSDoc } from '@/lib/tsdoc';
import { cn } from '@/lib/utils';
import type { Metadata } from 'next';

const containerStyle = {
'--fd-nav-height': '4rem',
} as CSSProperties;

const Page = async (props: PageProps<'/guides/[[...slug]]'>) => {
const params = await props.params;

// Redirect /guides to /guides/ai-agents
if (!params.slug || params.slug.length === 0) {
redirect('/guides/ai-agents');
}

const page = guidesSource.getPage(params.slug);

if (!page) {
notFound();
}

const markdown = await getGuidesLLMText(page);
const MDX = page.data.body;

return (
<FumadocsDocsPage
full={page.data.full}
toc={page.data.toc}
article={{ className: 'max-w-[754px]' }}
container={{
style: containerStyle,
className: 'col-span-2',
}}
tableOfContent={{
component: (
<TableOfContents>
<EditSource path={page.path} />
<ScrollTop />
<Feedback />
<CopyPage text={markdown} />
<AskAI href={page.url} />
<OpenInChat href={page.url} />
</TableOfContents>
),
}}
>
<FumadocsDocsTitle className={cn('text-4xl tracking-tight')}>
{page.data.title}
</FumadocsDocsTitle>
<FumadocsDocsDescription>{page.data.description}</FumadocsDocsDescription>
<FumadocsDocsBody className={cn('mx-auto w-full')}>
<MDX
components={getMDXComponents({
a: createRelativeLink(guidesSource, page),

// Add your custom components here
AgentTraces,
Badge,
TSDoc,
Step,
Steps,
...AccordionComponents,
Tabs,
Tab,
})}
/>
</FumadocsDocsBody>
</FumadocsDocsPage>
);
};

export const generateStaticParams = () => [
{ slug: [] }, // Root redirect
...guidesSource.generateParams().map((params) => ({
slug: params.slug,
})),
];

export const generateMetadata = async (
props: PageProps<'/guides/[[...slug]]'>
): Promise<Metadata> => {
const params = await props.params;

// Root path redirects, no metadata needed
if (!params.slug || params.slug.length === 0) {
return { title: 'Guides' };
}

const page = guidesSource.getPage(params.slug);

if (!page) {
notFound();
}

const { segments, url } = getPageImage(page);

return {
title: page.data.title,
description: page.data.description,
openGraph: {
title: page.data.title,
description: page.data.description,
type: 'article',
url: page.url,
images: [
{
url,
width: 1200,
height: 630,
alt: segments.join(' - '),
},
],
},
twitter: {
card: 'summary_large_image',
title: page.data.title,
description: page.data.description,
images: [url],
},
};
};

export default Page;
43 changes: 43 additions & 0 deletions docs/app/guides/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DocsLayout as FumadocsDocsLayout } from 'fumadocs-ui/layouts/docs';
import { Folder, Item, Separator } from '@/components/geistdocs/sidebar';
import { guidesSource } from '@/lib/geistdocs/source';

export const GuidesLayout = ({
children,
}: Pick<LayoutProps<'/guides'>, 'children'>) => (
<FumadocsDocsLayout
containerProps={{
className:
'md:grid md:grid-cols-[286px_1fr_286px] md:pl-0! md:mx-auto! md:w-full md:max-w-(--fd-layout-width)!',
}}
nav={{
enabled: false,
}}
searchToggle={{
enabled: false,
}}
sidebar={{
className:
'md:static md:sticky md:top-16 md:max-h-[calc(100vh-4rem)] md:overflow-y-auto md:w-auto! bg-background! md:bg-transparent! border-none transition-none',
collapsible: false,
components: {
Folder,
Item,
Separator,
},
}}
tabMode="auto"
themeSwitch={{
enabled: false,
}}
tree={guidesSource.pageTree}
>
{children}
</FumadocsDocsLayout>
);

const Layout = ({ children }: LayoutProps<'/guides'>) => (
<GuidesLayout>{children}</GuidesLayout>
);

export default Layout;
4 changes: 4 additions & 0 deletions docs/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const links = [
label: 'Docs',
href: '/docs',
},
{
label: 'Guides',
href: '/guides',
},
{
label: 'Examples',
href: 'https://github.com/vercel/workflow-examples',
Expand Down
2 changes: 1 addition & 1 deletion docs/components/geistdocs/docs-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const DocsLayout = ({
}}
sidebar={{
className:
'md:static md:sticky md:top-16 md:h-fit md:w-auto! bg-background! md:bg-transparent! border-none transition-none',
'md:static md:sticky md:top-16 md:max-h-[calc(100vh-4rem)] md:overflow-y-auto md:w-auto! bg-background! md:bg-transparent! border-none transition-none',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes an issue with the sidebar not scrolling until the main content is scrolled to the bottom, which only now became apparent since we have enough sidebar items to fill a small screen (if they're all expanded)

collapsible: false,
components: {
Folder,
Expand Down
139 changes: 139 additions & 0 deletions docs/components/guides/agent-traces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use client';

import { motion } from 'motion/react';
import { cn } from '@/lib/utils';

// Color presets for trace rows
const colors = {
workflow:
'bg-[#E1F0FF] dark:bg-[#00254D] border-[#99CEFF] text-[#0070F3] dark:border-[#0067D6] dark:text-[#52AEFF]',
stream:
'bg-[#DCF6DC] dark:bg-[#1B311E] border-[#99E59F] text-[#46A758] dark:border-[#297C3B] dark:text-[#6CDA76]',
tool: 'bg-[#FFF4E5] dark:bg-[#3D2800] border-[#FFCC80] text-[#F5A623] dark:border-[#9A6700] dark:text-[#FFCA28]',
approval:
'bg-[#FCE7F3] dark:bg-[#4A1D34] border-[#F9A8D4] text-[#EC4899] dark:border-[#BE185D] dark:text-[#F472B6]',
webhook:
'bg-[#EDE9FE] dark:bg-[#2E1065] border-[#C4B5FD] text-[#7C3AED] dark:border-[#6D28D9] dark:text-[#A78BFA]',
};

type TraceRow = {
label: string;
className: string;
start: number;
duration: number;
};

const defaultRows: TraceRow[] = [
{
label: 'chatWorkflow',
className: colors.workflow,
start: 0,
duration: 100,
},
{ label: 'agent.stream', className: colors.stream, start: 2, duration: 16 },
{ label: 'searchWeb', className: colors.tool, start: 20, duration: 13 },
{ label: 'agent.stream', className: colors.stream, start: 37, duration: 16 },
{
label: 'waitForHumanApproval',
className: colors.approval,
start: 57,
duration: 24,
},
{ label: 'agent.stream', className: colors.stream, start: 84, duration: 16 },
];

const messageQueueRows: TraceRow[] = [
{
label: 'chatWorkflow',
className: colors.workflow,
start: 0,
duration: 100,
},
{ label: 'agent.stream', className: colors.stream, start: 2, duration: 16 },
{
label: 'hook.enqueue()',
className: colors.webhook,
start: 12,
duration: 24,
},
{
label: 'tool.checkDB()',
className: colors.tool,
start: 18,
duration: 18,
},
{ label: 'agent.stream', className: colors.stream, start: 36, duration: 16 },
{
label: 'hook.enqueue()',
className: colors.webhook,
start: 46,
duration: 24,
},
{
label: 'tool.search()',
className: colors.tool,
start: 52,
duration: 18,
},
{ label: 'agent.stream', className: colors.stream, start: 70, duration: 16 },
];

const variants = {
default: defaultRows,
'message-queue': messageQueueRows,
} as const;

type Variant = keyof typeof variants;

interface AgentTracesProps {
variant?: Variant;
}

export const AgentTraces = ({ variant = 'default' }: AgentTracesProps) => {
const rows = variants[variant];

return (
<div className="not-prose my-8 rounded-lg border bg-card p-4 sm:p-6">
<div className="space-y-2 w-full">
{rows.map((row, index) => (
<div
key={`${row.label}-${index}`}
className="flex flex-col overflow-hidden"
style={{
marginLeft: `${row.start}%`,
width: `${row.duration}%`,
}}
>
<div className="relative h-6 w-full">
<motion.div
initial={{ width: 0, opacity: 0 }}
whileInView={{ width: 'auto', opacity: 1 }}
viewport={{ once: true, amount: 0.8 }}
transition={{
duration: 0.55,
delay: index * 0.12,
ease: [0.22, 1, 0.36, 1],
}}
className={cn(
'h-full rounded-sm border overflow-hidden',
row.className
)}
>
<div className="flex justify-between items-center h-full px-2">
<span className="text-[10px] sm:text-[11px] font-mono font-medium text-foreground truncate leading-none">
{row.label}
</span>
{index === 0 && (
<span className="text-[10px] sm:text-[11px] hidden sm:inline leading-none">
{row.duration}ms
</span>
)}
</div>
</motion.div>
</div>
</div>
))}
</div>
</div>
);
};
Loading
Loading