improvement(docs): overhaul docs (#1680)

* improvement(docs): overhaul docs

* lint

* light mode

* more light mode

* added llms.txt and llms-full.txt and sitemap

* fixed mobile styling and position for zoom in out

* finished styling

* improvement(docs): overhaul docs

* lint

* remove dups

* renaming components

* cleanup
This commit is contained in:
Waleed
2025-10-17 22:29:55 -07:00
committed by GitHub
parent 90c34b2c46
commit d1c08daaf4
35 changed files with 1268 additions and 599 deletions

View File

@@ -5,6 +5,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { StructuredData } from '@/components/structured-data'
import { CodeBlock } from '@/components/ui/code-block'
import { source } from '@/lib/source'
export const dynamic = 'force-dynamic'
@@ -22,31 +23,143 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
pageTreeRecord[params.lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
const neighbours = pageTree ? findNeighbour(pageTree, page.url) : null
const CustomFooter = () => (
<div className='mt-12 flex items-center justify-between border-border border-t py-8'>
{neighbours?.previous ? (
<Link
href={neighbours.previous.url}
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
>
<ChevronLeft className='group-hover:-translate-x-1 h-4 w-4 transition-transform' />
<span className='font-medium'>{neighbours.previous.name}</span>
</Link>
) : (
<div />
)}
const generateBreadcrumbs = () => {
const breadcrumbs: Array<{ name: string; url: string }> = [
{
name: 'Home',
url: baseUrl,
},
]
{neighbours?.next ? (
const urlParts = page.url.split('/').filter(Boolean)
let currentPath = ''
urlParts.forEach((part, index) => {
if (index === 0 && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(part)) {
currentPath = `/${part}`
return
}
currentPath += `/${part}`
const name = part
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
if (index === urlParts.length - 1) {
breadcrumbs.push({
name: page.data.title,
url: `${baseUrl}${page.url}`,
})
} else {
breadcrumbs.push({
name: name,
url: `${baseUrl}${currentPath}`,
})
}
})
return breadcrumbs
}
const breadcrumbs = generateBreadcrumbs()
const CustomFooter = () => (
<div className='mt-12'>
{/* Navigation links */}
<div className='flex items-center justify-between py-8'>
{neighbours?.previous ? (
<Link
href={neighbours.previous.url}
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
>
<ChevronLeft className='group-hover:-translate-x-1 h-4 w-4 transition-transform' />
<span className='font-medium'>{neighbours.previous.name}</span>
</Link>
) : (
<div />
)}
{neighbours?.next ? (
<Link
href={neighbours.next.url}
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
>
<span className='font-medium'>{neighbours.next.name}</span>
<ChevronRight className='h-4 w-4 transition-transform group-hover:translate-x-1' />
</Link>
) : (
<div />
)}
</div>
{/* Divider line */}
<div className='border-border border-t' />
{/* Social icons */}
<div className='flex items-center gap-4 py-6'>
<Link
href={neighbours.next.url}
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
href='https://x.com/simdotai'
target='_blank'
rel='noopener noreferrer'
aria-label='X (Twitter)'
>
<span className='font-medium'>{neighbours.next.name}</span>
<ChevronRight className='h-4 w-4 transition-transform group-hover:translate-x-1' />
<div
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
style={{
maskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
maskRepeat: 'no-repeat',
maskPosition: 'center center',
WebkitMaskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
WebkitMaskRepeat: 'no-repeat',
WebkitMaskPosition: 'center center',
}}
/>
</Link>
) : (
<div />
)}
<Link
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
aria-label='GitHub'
>
<div
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
style={{
maskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
maskRepeat: 'no-repeat',
maskPosition: 'center center',
WebkitMaskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
WebkitMaskRepeat: 'no-repeat',
WebkitMaskPosition: 'center center',
}}
/>
</Link>
<Link
href='https://discord.gg/Hr4UWYEcTT'
target='_blank'
rel='noopener noreferrer'
aria-label='Discord'
>
<div
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
style={{
maskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
maskRepeat: 'no-repeat',
maskPosition: 'center center',
WebkitMaskImage:
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
WebkitMaskRepeat: 'no-repeat',
WebkitMaskPosition: 'center center',
}}
/>
</Link>
</div>
</div>
)
@@ -57,6 +170,7 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
description={page.data.description || ''}
url={`${baseUrl}${page.url}`}
lang={params.lang}
breadcrumb={breadcrumbs}
/>
<DocsPage
toc={page.data.toc}
@@ -82,7 +196,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX components={defaultMdxComponents} />
<MDX
components={{
...defaultMdxComponents,
CodeBlock,
}}
/>
</DocsBody>
</DocsPage>
</>
@@ -128,8 +247,10 @@ export async function generateMetadata(props: {
url: fullUrl,
siteName: 'Sim Documentation',
type: 'article',
locale: params.lang,
alternateLocale: ['en', 'fr', 'zh'].filter((lang) => lang !== params.lang),
locale: params.lang === 'en' ? 'en_US' : `${params.lang}_${params.lang.toUpperCase()}`,
alternateLocale: ['en', 'es', 'fr', 'de', 'ja', 'zh']
.filter((lang) => lang !== params.lang)
.map((lang) => (lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`)),
},
twitter: {
card: 'summary',
@@ -152,8 +273,12 @@ export async function generateMetadata(props: {
alternates: {
canonical: fullUrl,
languages: {
en: `${baseUrl}/en${page.url.replace(`/${params.lang}`, '')}`,
'x-default': `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
en: `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
es: `${baseUrl}/es${page.url.replace(`/${params.lang}`, '')}`,
fr: `${baseUrl}/fr${page.url.replace(`/${params.lang}`, '')}`,
de: `${baseUrl}/de${page.url.replace(`/${params.lang}`, '')}`,
ja: `${baseUrl}/ja${page.url.replace(`/${params.lang}`, '')}`,
zh: `${baseUrl}/zh${page.url.replace(`/${params.lang}`, '')}`,
},
},

View File

@@ -2,11 +2,14 @@ import type { ReactNode } from 'react'
import { defineI18nUI } from 'fumadocs-ui/i18n'
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
import { RootProvider } from 'fumadocs-ui/provider/next'
import { ExternalLink, GithubIcon } from 'lucide-react'
import { Inter } from 'next/font/google'
import { Geist_Mono, Inter } from 'next/font/google'
import Image from 'next/image'
import Link from 'next/link'
import { LanguageDropdown } from '@/components/ui/language-dropdown'
import {
SidebarFolder,
SidebarItem,
SidebarSeparator,
} from '@/components/docs-layout/sidebar-components'
import { Navbar } from '@/components/navbar/navbar'
import { i18n } from '@/lib/i18n'
import { source } from '@/lib/source'
import '../global.css'
@@ -14,6 +17,12 @@ import { Analytics } from '@vercel/analytics/next'
const inter = Inter({
subsets: ['latin'],
variable: '--font-geist-sans',
})
const geistMono = Geist_Mono({
subsets: ['latin'],
variable: '--font-geist-mono',
})
const { provider } = defineI18nUI(i18n, {
@@ -27,25 +36,18 @@ const { provider } = defineI18nUI(i18n, {
fr: {
displayName: 'Français',
},
de: {
displayName: 'Deutsch',
},
ja: {
displayName: '日本語',
},
zh: {
displayName: '简体中文',
},
},
})
const GitHubLink = () => (
<div className='fixed right-4 bottom-4 z-50'>
<Link
href='https://github.com/simstudioai/sim'
target='_blank'
rel='noopener noreferrer'
className='flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background transition-colors hover:bg-muted'
>
<GithubIcon className='h-4 w-4' />
</Link>
</div>
)
type LayoutProps = {
children: ReactNode
params: Promise<{ lang: string }>
@@ -54,43 +56,82 @@ type LayoutProps = {
export default async function Layout({ children, params }: LayoutProps) {
const { lang } = await params
const structuredData = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Sim Documentation',
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI Agent Workflows.',
url: 'https://docs.sim.ai',
publisher: {
'@type': 'Organization',
name: 'Sim',
url: 'https://sim.ai',
logo: {
'@type': 'ImageObject',
url: 'https://docs.sim.ai/static/logo.png',
},
},
inLanguage: lang,
potentialAction: {
'@type': 'SearchAction',
target: {
'@type': 'EntryPoint',
urlTemplate: 'https://docs.sim.ai/api/search?q={search_term_string}',
},
'query-input': 'required name=search_term_string',
},
}
return (
<html lang={lang} className={inter.className} suppressHydrationWarning>
<body className='flex min-h-screen flex-col'>
<html
lang={lang}
className={`${inter.variable} ${geistMono.variable}`}
suppressHydrationWarning
>
<head>
<script
type='application/ld+json'
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
</head>
<body className='flex min-h-screen flex-col font-sans'>
<RootProvider i18n={provider(lang)}>
<Navbar />
<DocsLayout
tree={source.pageTree[lang]}
themeSwitch={{
enabled: false,
}}
nav={{
title: (
<div className='flex items-center gap-3'>
<Image
src='/static/logo.png'
alt='Sim'
width={60}
height={24}
className='h-6 w-auto'
/>
<LanguageDropdown />
</div>
<Image
src='/static/logo.png'
alt='Sim'
width={72}
height={28}
className='h-7 w-auto'
priority
/>
),
}}
links={[
{
text: 'Visit Sim',
url: 'https://sim.ai',
icon: <ExternalLink className='h-4 w-4' />,
},
]}
sidebar={{
defaultOpenLevel: 0,
collapsible: true,
collapsible: false,
footer: null,
banner: null,
components: {
Item: SidebarItem,
Folder: SidebarFolder,
Separator: SidebarSeparator,
},
}}
containerProps={{
className: '!pt-10',
}}
>
{children}
</DocsLayout>
<GitHubLink />
<Analytics />
</RootProvider>
</body>

View File

@@ -4,6 +4,19 @@
@theme {
--color-fd-primary: #802fff; /* Purple from control-bar component */
--font-geist-sans: var(--font-geist-sans);
--font-geist-mono: var(--font-geist-mono);
}
/* Font family utilities */
.font-sans {
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.font-mono {
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}
/* Target any potential border classes */
@@ -15,139 +28,426 @@
:root {
--fd-border: transparent !important;
--fd-border-sidebar: transparent !important;
--fd-nav-height: 64px; /* Custom navbar height (h-16 = 4rem = 64px) */
/* Content container width used to center main content */
--spacing-fd-container: 1400px;
/* Edge gutter = leftover space on each side of centered container */
--edge-gutter: max(1rem, calc((100vw - var(--spacing-fd-container)) / 2));
/* Shift the sidebar slightly left from the content edge for extra breathing room */
--sidebar-shift: 90px;
--sidebar-offset: max(0px, calc(var(--edge-gutter) - var(--sidebar-shift)));
--toc-offset: var(--edge-gutter);
/* Sidebar and TOC have 20px internal padding - navbar accounts for this directly */
/* Extra gap between sidebar/TOC and the main text content */
--content-gap: 1.75rem;
}
/* Sidebar improvements for cleaner design */
[data-sidebar] {
--fd-sidebar-width: 280px;
background-color: rgb(255 255 255);
padding-top: 16px;
/* Remove custom layout variable overrides to fallback to fumadocs defaults */
/* ============================================
Navbar Light Mode Styling
============================================ */
/* Light mode navbar and search styling */
:root:not(.dark) nav {
background-color: hsla(0, 0%, 96%, 0.92) !important;
backdrop-filter: blur(25px) saturate(180%) brightness(1.05) !important;
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(1.05) !important;
}
/* Clean sidebar container */
[data-sidebar] > div {
padding: 0 16px;
:root:not(.dark) nav button[type="button"] {
background-color: hsla(0, 0%, 93%, 0.85) !important;
backdrop-filter: blur(33px) saturate(180%) !important;
-webkit-backdrop-filter: blur(33px) saturate(180%) !important;
color: rgba(0, 0, 0, 0.6) !important;
}
/* Section headers/separators styling */
[data-sidebar] .text-sm.font-medium.text-muted-foreground,
[data-sidebar] [data-separator] {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 20px;
margin-bottom: 6px;
padding-left: 12px;
padding-right: 12px;
color: rgb(115 115 115);
border: none;
background: none;
:root:not(.dark) nav button[type="button"] kbd {
color: rgba(0, 0, 0, 0.6) !important;
}
/* First separator should have less top margin */
[data-sidebar] [data-separator]:first-of-type {
margin-top: 12px;
/* ============================================
Custom Sidebar Styling (Turborepo-inspired)
============================================ */
/* Floating sidebar appearance - remove background */
[data-sidebar-container],
#nd-sidebar {
background: transparent !important;
background-color: transparent !important;
border: none !important;
--color-fd-muted: transparent !important;
--color-fd-card: transparent !important;
--color-fd-secondary: transparent !important;
}
/* Clean sidebar item styling */
[data-sidebar] a {
padding: 8px 12px;
margin: 1px 0;
border-radius: 6px;
font-size: 14px;
font-weight: 400;
line-height: 1.4;
transition: all 0.15s ease;
aside[data-sidebar],
aside#nd-sidebar {
background: transparent !important;
background-color: transparent !important;
border: none !important;
border-right: none !important;
}
/* Responsive sidebar positioning */
/* Mobile: Fumadocs handles drawer */
@media (min-width: 768px) and (max-width: 1024px) {
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
}
}
/* Desktop layout alignment */
@media (min-width: 1025px) {
[data-sidebar-container] {
margin-left: var(--sidebar-offset) !important;
}
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
}
[data-toc] {
margin-right: var(--toc-offset) !important;
}
}
/* Sidebar spacing - compact like turborepo */
[data-sidebar-viewport] {
padding: 0.5rem 20px 12px;
background: transparent !important;
background-color: transparent !important;
}
/* Override sidebar item styling to match Raindrop */
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
/* Exclude the small chevron-only toggle buttons */
#nd-sidebar a,
#nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
font-size: 0.9375rem !important; /* 15px to match Raindrop */
line-height: 1.4 !important;
padding: 0.5rem 0.75rem !important; /* More compact like Raindrop */
font-weight: 400 !important;
border-radius: 0.75rem !important; /* More rounded like Raindrop */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif !important;
}
/* Dark mode sidebar text */
.dark #nd-sidebar a,
.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(255, 255, 255, 0.6) !important;
}
/* Light mode sidebar text */
:root:not(.dark) #nd-sidebar a,
:root:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(0, 0, 0, 0.6) !important;
}
/* Make sure chevron icons are visible and properly styled */
#nd-sidebar svg {
display: inline-block !important;
opacity: 0.6 !important;
flex-shrink: 0 !important;
width: 0.75rem !important;
height: 0.75rem !important;
}
/* Ensure the small chevron toggle buttons are visible */
#nd-sidebar button[aria-label*="ollapse"],
#nd-sidebar button[aria-label*="xpand"] {
display: flex !important;
opacity: 1 !important;
padding: 0.25rem !important;
}
/* Root-level spacing now handled by [data-sidebar-viewport] > * rule below */
/* Add tiny gap between nested items */
#nd-sidebar ul li {
margin-bottom: 0.0625rem !important;
}
#nd-sidebar ul li:last-child {
margin-bottom: 0 !important;
}
/* Section headers should be slightly larger */
[data-sidebar-viewport] [data-separator] {
font-size: 0.75rem !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
}
/* Override active state (NO PURPLE) */
#nd-sidebar a[data-active="true"],
#nd-sidebar button[data-active="true"],
#nd-sidebar a.bg-fd-primary\/10,
#nd-sidebar a.text-fd-primary,
#nd-sidebar a[class*="bg-fd-primary"],
#nd-sidebar a[class*="text-fd-primary"],
/* Override custom sidebar purple classes */
#nd-sidebar
a.bg-purple-50\/80,
#nd-sidebar a.text-purple-600,
#nd-sidebar a[class*="bg-purple"],
#nd-sidebar a[class*="text-purple"] {
background-image: none !important;
}
/* Dark mode active state */
.dark #nd-sidebar a[data-active="true"],
.dark #nd-sidebar button[data-active="true"],
.dark #nd-sidebar a.bg-fd-primary\/10,
.dark #nd-sidebar a.text-fd-primary,
.dark #nd-sidebar a[class*="bg-fd-primary"],
.dark #nd-sidebar a[class*="text-fd-primary"],
.dark #nd-sidebar a.bg-purple-50\/80,
.dark #nd-sidebar a.text-purple-600,
.dark #nd-sidebar a[class*="bg-purple"],
.dark #nd-sidebar a[class*="text-purple"] {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode active state */
:root:not(.dark) #nd-sidebar a[data-active="true"],
:root:not(.dark) #nd-sidebar button[data-active="true"],
:root:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
:root:not(.dark) #nd-sidebar a.text-fd-primary,
:root:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
:root:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80,
:root:not(.dark) #nd-sidebar a.text-purple-600,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"],
:root:not(.dark) #nd-sidebar a[class*="text-purple"] {
background-color: rgba(0, 0, 0, 0.1) !important;
color: rgba(0, 0, 0, 1) !important;
}
/* Dark mode hover state */
.dark #nd-sidebar a:hover:not([data-active="true"]),
.dark #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(255, 255, 255, 0.08) !important;
}
/* Light mode hover state */
:root:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
:root:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(0, 0, 0, 0.06) !important;
}
/* Dark mode - ensure active/selected items don't change on hover */
.dark #nd-sidebar a.bg-purple-50\/80:hover,
.dark #nd-sidebar a[class*="bg-purple"]:hover,
.dark #nd-sidebar a[data-active="true"]:hover,
.dark #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode - ensure active/selected items don't change on hover */
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
:root:not(.dark) #nd-sidebar a[data-active="true"]:hover,
:root:not(.dark) #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
color: rgba(0, 0, 0, 1) !important;
}
/* Hide search, platform, and collapse button from sidebar completely */
[data-sidebar] [data-search],
[data-sidebar] .search-toggle,
#nd-sidebar [data-search],
#nd-sidebar .search-toggle,
[data-sidebar-viewport] [data-search],
[data-sidebar-viewport] button[data-search],
aside[data-sidebar] [role="button"]:has([data-search]),
aside[data-sidebar] > div > button:first-child,
#nd-sidebar > div > button:first-child,
[data-sidebar] a[href*="sim.ai"],
#nd-sidebar a[href*="sim.ai"],
[data-sidebar-viewport] a[href*="sim.ai"],
/* Hide search buttons (but NOT folder chevron buttons) */
aside[data-sidebar] > div:first-child
> button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
#nd-sidebar > div:first-child > button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
/* Hide sidebar collapse button (panel icon) - direct children only */
aside[data-sidebar] > button:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
[data-sidebar]
> button[type="button"]:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
button[data-collapse]:not([aria-label="Collapse"]):not([aria-label="Expand"]),
[data-sidebar-header] button,
/* Hide theme toggle from sidebar footer */
aside[data-sidebar] [data-theme-toggle],
[data-sidebar-footer],
[data-sidebar] footer,
footer button[aria-label*="heme"],
aside[data-sidebar] > div:last-child:has(button[aria-label*="heme"]),
aside[data-sidebar] button[aria-label*="heme"],
[data-sidebar] button[aria-label*="Theme"],
/* Additional theme toggle selectors */
aside[data-sidebar] > *:last-child
button,
[data-sidebar-viewport] ~ *,
aside[data-sidebar] > div:not([data-sidebar-viewport]),
/* Aggressive theme toggle hiding */
aside[data-sidebar] svg[class*="sun"],
aside[data-sidebar] svg[class*="moon"],
aside[data-sidebar] button[type="button"]:last-child,
aside button:has(svg:only-child),
[data-sidebar] div:has(> button[type="button"]:only-child:last-child),
/* Hide theme toggle and other non-content elements */
aside[data-sidebar] > *:not([data-sidebar-viewport]) {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
max-height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
position: absolute !important;
left: -9999px !important;
}
/* Desktop only: Hide sidebar toggle buttons and nav title/logo (keep visible on mobile) */
@media (min-width: 1025px) {
[data-sidebar-container] > button,
[data-sidebar-container] [data-toggle],
aside[data-sidebar] [data-sidebar-toggle],
button[data-sidebar-toggle],
nav button[data-sidebar-toggle],
button[aria-label="Toggle Sidebar"],
button[aria-label="Collapse Sidebar"],
/* Hide nav title/logo in sidebar on desktop - target all possible locations */
aside[data-sidebar] a[href="/"],
aside[data-sidebar] a[href="/"] img,
aside[data-sidebar] > a:first-child,
aside[data-sidebar] > div > a:first-child,
aside[data-sidebar] img[alt="Sim"],
[data-sidebar-header],
[data-sidebar] [data-title],
#nd-sidebar > a:first-child,
#nd-sidebar > div:first-child > a:first-child,
#nd-sidebar img[alt="Sim"] {
display: none !important;
visibility: hidden !important;
height: 0 !important;
max-height: 0 !important;
overflow: hidden !important;
}
}
/* Extra aggressive - hide everything after the viewport */
aside[data-sidebar] [data-sidebar-viewport] ~ * {
display: none !important;
}
/* Tighter spacing for sidebar content */
[data-sidebar-viewport] > * {
margin-bottom: 0.0625rem;
}
[data-sidebar-viewport] > *:last-child {
margin-bottom: 0;
}
[data-sidebar-viewport] ul {
margin: 0;
padding: 0;
}
/* Ensure sidebar starts with content immediately */
aside[data-sidebar] > div:first-child {
padding-top: 0;
}
/* Remove all sidebar borders and backgrounds */
[data-sidebar-container],
aside[data-sidebar],
[data-sidebar],
[data-sidebar] *,
#nd-sidebar,
#nd-sidebar * {
border: none !important;
border-right: none !important;
border-left: none !important;
border-top: none !important;
border-bottom: none !important;
}
/* Override fumadocs background colors for sidebar */
.dark #nd-sidebar,
.dark [data-sidebar-container],
.dark aside[data-sidebar] {
--color-fd-muted: transparent !important;
--color-fd-secondary: transparent !important;
background: transparent !important;
background-color: transparent !important;
}
/* Force normal text flow in sidebar */
[data-sidebar],
[data-sidebar] *,
[data-sidebar-viewport],
[data-sidebar-viewport] * {
writing-mode: horizontal-tb !important;
}
/* ============================================
Code Block Styling (Improved)
============================================ */
/* Apply Geist Mono to code elements */
code,
pre,
pre code {
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
}
/* Inline code */
:not(pre) > code {
padding: 0.2em 0.4em;
border-radius: 0.25rem;
font-size: 0.875em;
font-weight: 500;
}
/* Light mode inline code */
:root:not(.dark) :not(pre) > code {
background-color: rgb(243 244 246);
color: rgb(220 38 38);
border: 1px solid rgb(229 231 235);
}
/* Dark mode inline code */
.dark :not(pre) > code {
background-color: rgb(31 41 55);
color: rgb(248 113 113);
border: 1px solid rgb(55 65 81);
}
/* Code block container improvements */
pre {
font-size: 0.875rem;
line-height: 1.7;
tab-size: 2;
-webkit-overflow-scrolling: touch;
}
pre code {
display: block;
color: rgb(71 85 105);
text-decoration: none;
width: fit-content;
min-width: 100%;
}
[data-sidebar] a[data-active="true"] {
background-color: rgba(128, 47, 255, 0.08);
color: var(--color-fd-primary);
font-weight: 500;
}
[data-sidebar] a:hover:not([data-active="true"]) {
background-color: rgb(248 250 252);
color: rgb(51 65 85);
}
/* Improve spacing between sidebar items */
[data-sidebar] nav > * + * {
margin-top: 2px;
}
/* Section group styling */
[data-sidebar] [data-folder] {
margin-bottom: 4px;
}
[data-sidebar] [data-folder] > div:first-child {
font-weight: 500;
font-size: 13px;
color: rgb(15 23 42);
padding: 6px 12px;
margin-bottom: 4px;
}
/* Clean up folder toggle buttons */
[data-sidebar] button[data-folder-toggle] {
padding: 4px 8px 4px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
color: rgb(51 65 85);
}
[data-sidebar] button[data-folder-toggle]:hover {
background-color: rgb(248 250 252);
}
/* Nested item indentation */
[data-sidebar] [data-folder] a {
padding-left: 24px;
font-size: 14px;
}
/* Dark mode adjustments */
@media (prefers-color-scheme: dark) {
[data-sidebar] {
background-color: rgb(2 8 23);
}
[data-sidebar] a {
color: rgb(148 163 184);
}
[data-sidebar] a:hover:not([data-active="true"]) {
background-color: rgb(30 41 59);
color: rgb(226 232 240);
}
[data-sidebar] a[data-active="true"] {
background-color: rgba(128, 47, 255, 0.15);
color: var(--color-fd-primary);
}
[data-sidebar] .text-sm.font-medium.text-muted-foreground,
[data-sidebar] [data-separator] {
color: rgb(148 163 184);
}
[data-sidebar] [data-folder] > div:first-child {
color: rgb(226 232 240);
}
[data-sidebar] button[data-folder-toggle] {
color: rgb(148 163 184);
}
[data-sidebar] button[data-folder-toggle]:hover {
background-color: rgb(30 41 59);
}
/* Syntax highlighting adjustments for better readability */
pre code .line {
padding-left: 0;
padding-right: 0;
}
/* Custom text highlighting styles */
@@ -162,15 +462,54 @@
/* Add bottom spacing to prevent abrupt page endings */
[data-content] {
padding-top: 1.5rem !important;
padding-bottom: 4rem;
}
/* Alternative fallback for different Fumadocs versions */
main article,
.docs-page main {
padding-top: 1.5rem !important;
padding-bottom: 4rem;
}
/* ============================================
Center and Constrain Main Content Width
============================================ */
/* Main content area - center and constrain like turborepo/raindrop */
main[data-main] {
max-width: var(--spacing-fd-container, 1400px);
margin-left: auto;
margin-right: auto;
padding-top: 1rem;
padding-left: calc(var(--sidebar-offset) + var(--content-gap));
padding-right: calc(var(--toc-offset) + var(--content-gap));
order: 1 !important;
}
/* Adjust for smaller screens */
@media (max-width: 768px) {
main[data-main] {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* Ensure docs page content is properly constrained */
[data-docs-page] {
max-width: 1400px;
margin-left: auto;
margin-right: auto;
padding-top: 1.5rem !important;
}
/* Override Fumadocs default content padding */
article[data-content],
div[data-content] {
padding-top: 1.5rem !important;
}
/* Remove any unwanted borders/outlines from video elements */
video {
outline: none !important;

View File

@@ -22,9 +22,15 @@ export const metadata = {
'drag and drop workflows',
'AI integrations',
'workflow canvas',
'AI development platform',
'AI Agent Workflow Builder',
'workflow orchestration',
'agent builder',
'AI workflow automation',
'visual programming',
],
authors: [{ name: 'Sim Team', url: 'https://sim.ai' }],
creator: 'Sim',
publisher: 'Sim',
category: 'Developer Tools',
classification: 'Developer Documentation',
manifest: '/favicon/site.webmanifest',
@@ -44,7 +50,7 @@ export const metadata = {
openGraph: {
type: 'website',
locale: 'en_US',
alternateLocale: ['fr_FR', 'zh_CN'],
alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'],
url: 'https://docs.sim.ai',
siteName: 'Sim Documentation',
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
@@ -52,11 +58,13 @@ export const metadata = {
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
},
twitter: {
card: 'summary',
card: 'summary_large_image',
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
description:
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
creator: '@sim_ai',
creator: '@simdotai',
site: '@simdotai',
images: ['/og-image.png'],
},
robots: {
index: true,
@@ -72,9 +80,13 @@ export const metadata = {
alternates: {
canonical: 'https://docs.sim.ai',
languages: {
en: '/en',
fr: '/fr',
zh: '/zh',
'x-default': 'https://docs.sim.ai',
en: 'https://docs.sim.ai',
es: 'https://docs.sim.ai/es',
fr: 'https://docs.sim.ai/fr',
de: 'https://docs.sim.ai/de',
ja: 'https://docs.sim.ai/ja',
zh: 'https://docs.sim.ai/zh',
},
},
}

View File

@@ -0,0 +1,31 @@
import { getLLMText } from '@/lib/llms'
import { source } from '@/lib/source'
export const revalidate = false
export async function GET() {
try {
const pages = source.getPages().filter((page) => {
if (!page || !page.data || !page.url) return false
const pathParts = page.url.split('/').filter(Boolean)
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
return !hasLangPrefix
})
const scan = pages.map((page) => getLLMText(page))
const scanned = await Promise.all(scan)
const filtered = scanned.filter((text) => text && text.length > 0)
return new Response(filtered.join('\n\n---\n\n'), {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
})
} catch (error) {
console.error('Error generating LLM full text:', error)
return new Response('Error generating full documentation text', { status: 500 })
}
}

View File

@@ -1,11 +1,87 @@
import { getLLMText } from '@/lib/llms'
import { source } from '@/lib/source'
export const revalidate = false
export async function GET() {
const scan = source.getPages().map(getLLMText)
const scanned = await Promise.all(scan)
const baseUrl = 'https://docs.sim.ai'
return new Response(scanned.join('\n\n'))
try {
const pages = source.getPages().filter((page) => {
if (!page || !page.data || !page.url) return false
const pathParts = page.url.split('/').filter(Boolean)
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
return !hasLangPrefix
})
const sections: Record<string, Array<{ title: string; url: string; description?: string }>> = {}
pages.forEach((page) => {
const pathParts = page.url.split('/').filter(Boolean)
const section =
pathParts[0] && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
? pathParts[1] || 'root'
: pathParts[0] || 'root'
if (!sections[section]) {
sections[section] = []
}
sections[section].push({
title: page.data.title || 'Untitled',
url: `${baseUrl}${page.url}`,
description: page.data.description,
})
})
const manifest = `# Sim Documentation
> Visual Workflow Builder for AI Applications
Sim is a visual workflow builder for AI applications that lets you build AI agent workflows visually. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.
## Documentation Overview
This file provides an overview of our documentation. For full content of all pages, see ${baseUrl}/llms-full.txt
## Main Sections
${Object.entries(sections)
.map(([section, items]) => {
const sectionTitle = section
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
return `### ${sectionTitle}\n\n${items.map((item) => `- ${item.title}: ${item.url}${item.description ? `\n ${item.description}` : ''}`).join('\n')}`
})
.join('\n\n')}
## Additional Resources
- Full documentation content: ${baseUrl}/llms-full.txt
- Individual page content: ${baseUrl}/llms.mdx/[page-path]
- API documentation: ${baseUrl}/sdks/
- Tool integrations: ${baseUrl}/tools/
## Statistics
- Total pages: ${pages.length} (English only)
- Other languages available at: ${baseUrl}/[lang]/ (es, fr, de, ja, zh)
---
Generated: ${new Date().toISOString()}
Format: llms.txt v0.1.0
See: https://llmstxt.org for specification`
return new Response(manifest, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
})
} catch (error) {
console.error('Error generating LLM manifest:', error)
return new Response('Error generating documentation manifest', { status: 500 })
}
}

View File

@@ -9,14 +9,26 @@ export async function GET() {
User-agent: *
Allow: /
# Allow all well-behaved crawlers
# Search engine crawlers
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
# AI and LLM crawlers
User-agent: Slurp
Allow: /
User-agent: DuckDuckBot
Allow: /
User-agent: Baiduspider
Allow: /
User-agent: YandexBot
Allow: /
# AI and LLM crawlers - explicitly allowed for documentation indexing
User-agent: GPTBot
Allow: /
@@ -32,6 +44,21 @@ Allow: /
User-agent: Claude-Web
Allow: /
User-agent: Applebot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Diffbot
Allow: /
User-agent: FacebookBot
Allow: /
User-agent: cohere-ai
Allow: /
# Disallow admin and internal paths (if any exist)
Disallow: /.next/
Disallow: /api/internal/
@@ -41,14 +68,29 @@ Disallow: /admin/
# Allow but don't prioritize these
Allow: /api/search
Allow: /llms.txt
Allow: /llms-full.txt
Allow: /llms.mdx/
# Sitemaps
Sitemap: ${baseUrl}/sitemap.xml
# Crawl delay for aggressive bots (optional)
# Crawl-delay: 1
# Additional resources for AI indexing
# See https://github.com/AnswerDotAI/llms-txt for more info
# LLM-friendly content available at: ${baseUrl}/llms.txt`
# LLM-friendly content:
# Manifest: ${baseUrl}/llms.txt
# Full content: ${baseUrl}/llms-full.txt
# Individual pages: ${baseUrl}/llms.mdx/[page-path]
# Multi-language documentation available at:
# ${baseUrl}/en - English
# ${baseUrl}/es - Español
# ${baseUrl}/fr - Français
# ${baseUrl}/de - Deutsch
# ${baseUrl}/ja - 日本語
# ${baseUrl}/zh - 简体中文`
return new Response(robotsTxt, {
headers: {

View File

@@ -8,6 +8,14 @@ export async function GET() {
const allPages = source.getPages()
const getPriority = (url: string): string => {
if (url === '/introduction' || url === '/') return '1.0'
if (url === '/getting-started') return '0.9'
if (url.match(/^\/[^/]+$/)) return '0.8'
if (url.includes('/sdks/') || url.includes('/tools/')) return '0.7'
return '0.6'
}
const urls = allPages
.flatMap((page) => {
const urlWithoutLang = page.url.replace(/^\/[a-z]{2}\//, '/')
@@ -22,7 +30,7 @@ export async function GET() {
<loc>${url}</loc>
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
<changefreq>weekly</changefreq>
<priority>${urlWithoutLang === '/introduction' ? '1.0' : '0.8'}</priority>
<priority>${getPriority(urlWithoutLang)}</priority>
${i18n.languages.length > 1 ? generateAlternateLinks(baseUrl, urlWithoutLang) : ''}
</url>`
})
@@ -37,6 +45,7 @@ ${urls}
return new Response(sitemap, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
},
})
}

View File

@@ -0,0 +1,134 @@
'use client'
import { type ReactNode, useState } from 'react'
import type { PageTree } from 'fumadocs-core/server'
import { ChevronRight } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { cn } from '@/lib/utils'
function isActive(url: string, pathname: string, nested = true): boolean {
return url === pathname || (nested && pathname.startsWith(`${url}/`))
}
export function SidebarItem({ item }: { item: PageTree.Item }) {
const pathname = usePathname()
const active = isActive(item.url, pathname, false)
return (
<li className='mb-[0.0625rem] list-none'>
<Link
href={item.url}
className={cn(
'block rounded-md px-2.5 py-1.5 font-normal text-[13px] leading-tight transition-colors',
'text-gray-600 dark:text-gray-400',
!active && 'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
active &&
'bg-purple-50/80 font-medium text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
)}
>
{item.name}
</Link>
</li>
)
}
export function SidebarFolder({
item,
level,
children,
}: {
item: PageTree.Folder
level: number
children: ReactNode
}) {
const pathname = usePathname()
const hasActiveChild = checkHasActiveChild(item, pathname)
const [open, setOpen] = useState(hasActiveChild)
return (
<li className='mb-[0.0625rem] list-none'>
{item.index ? (
<div className='flex items-center gap-0.5'>
<Link
href={item.index.url}
className={cn(
'block flex-1 rounded-md px-2.5 py-1.5 font-medium text-[13px] leading-tight transition-colors',
'text-gray-800 dark:text-gray-200',
!isActive(item.index.url, pathname, false) &&
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
isActive(item.index.url, pathname, false) &&
'bg-purple-50/80 text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
)}
>
{item.name}
</Link>
<button
onClick={() => setOpen(!open)}
className='rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
aria-label={open ? 'Collapse' : 'Expand'}
>
<ChevronRight
className={cn(
'h-3 w-3 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
</div>
) : (
<button
onClick={() => setOpen(!open)}
className={cn(
'flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
'text-gray-800 dark:text-gray-200'
)}
>
<span>{item.name}</span>
<ChevronRight
className={cn(
'ml-auto h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
)}
<div
className={cn(
'overflow-hidden transition-all duration-200 ease-in-out',
open ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'
)}
>
<ul className='mt-0.5 ml-2 space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 dark:border-gray-700/60'>
{children}
</ul>
</div>
</li>
)
}
export function SidebarSeparator({ item }: { item: PageTree.Separator }) {
return (
<p className='mt-4 mb-1.5 px-2.5 font-semibold text-[10px] text-gray-500/80 uppercase tracking-wide dark:text-gray-500'>
{item.name}
</p>
)
}
function checkHasActiveChild(node: PageTree.Folder, pathname: string): boolean {
if (node.index && isActive(node.index.url, pathname)) {
return true
}
for (const child of node.children) {
if (child.type === 'page' && isActive(child.url, pathname)) {
return true
}
if (child.type === 'folder' && checkHasActiveChild(child, pathname)) {
return true
}
}
return false
}

View File

@@ -0,0 +1,67 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import { LanguageDropdown } from '@/components/ui/language-dropdown'
import { SearchTrigger } from '@/components/ui/search-trigger'
import { ThemeToggle } from '@/components/ui/theme-toggle'
export function Navbar() {
return (
<nav
className='sticky top-0 z-50 border-border/50 border-b'
style={{
backgroundColor: 'hsla(0, 0%, 7.04%, 0.92)',
backdropFilter: 'blur(25px) saturate(180%) brightness(0.6)',
WebkitBackdropFilter: 'blur(25px) saturate(180%) brightness(0.6)',
}}
>
{/* Desktop: Single row layout */}
<div className='hidden h-16 w-full items-center lg:flex'>
<div
className='grid w-full grid-cols-[auto_1fr_auto] items-center'
style={{
paddingLeft: 'calc(var(--sidebar-offset) + 20px)',
paddingRight: 'calc(var(--toc-offset) + 20px)',
}}
>
{/* Left cluster: translate by sidebar delta to align with sidebar edge */}
<div className='flex items-center'>
<Link href='/' className='flex min-w-[100px] items-center'>
<Image
src='/static/logo.png'
alt='Sim'
width={72}
height={28}
className='h-7 w-auto'
/>
</Link>
</div>
{/* Center cluster: search */}
<div className='flex flex-1 items-center justify-center'>
<SearchTrigger />
</div>
{/* Right cluster aligns with TOC edge using the same right gutter */}
<div className='flex items-center gap-4'>
<Link
href='https://sim.ai'
target='_blank'
rel='noopener noreferrer'
className='rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground'
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
}}
>
Platform
</Link>
<LanguageDropdown />
<ThemeToggle />
</div>
</div>
</div>
</nav>
)
}

View File

@@ -88,7 +88,7 @@ export function StructuredData({
},
'query-input': 'required name=search_term_string',
},
inLanguage: ['en', 'fr', 'zh'],
inLanguage: ['en', 'es', 'fr', 'de', 'ja', 'zh'],
}
const faqStructuredData = title.toLowerCase().includes('faq') && {

View File

@@ -0,0 +1,50 @@
'use client'
import { useState } from 'react'
import { CodeBlock as FumadocsCodeBlock } from 'fumadocs-ui/components/codeblock'
import { Check, Copy } from 'lucide-react'
import { cn } from '@/lib/utils'
export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>) {
const [copied, setCopied] = useState(false)
const handleCopy = async (text: string) => {
await navigator.clipboard.writeText(text)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<FumadocsCodeBlock
{...props}
Actions={({ children, className }) => (
<div className={cn('empty:hidden', className)}>
{/* Custom copy button */}
<button
type='button'
aria-label={copied ? 'Copied Text' : 'Copy Text'}
onClick={(e) => {
const pre = (e.currentTarget as HTMLElement)
.closest('.nd-codeblock')
?.querySelector('pre')
if (pre) handleCopy(pre.textContent || '')
}}
className={cn(
'rounded-md p-2 transition-all',
'border border-border bg-background/80 hover:bg-muted',
'backdrop-blur-sm'
)}
>
<span className='flex items-center justify-center'>
{copied ? (
<Check size={16} className='text-green-600 dark:text-green-400' />
) : (
<Copy size={16} className='text-muted-foreground' />
)}
</span>
</button>
</div>
)}
/>
)
}

View File

@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
import { Check, ChevronDown } from 'lucide-react'
import { Check, ChevronRight } from 'lucide-react'
import { useParams, usePathname, useRouter } from 'next/navigation'
const languages = {
@@ -82,15 +82,14 @@ export function LanguageDropdown() {
aria-haspopup='listbox'
aria-expanded={isOpen}
aria-controls='language-menu'
className='flex items-center gap-1.5 rounded-lg border border-border/30 bg-muted/40 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur-sm transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
className='flex items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
}}
>
<span className='text-sm'>{languages[currentLang as keyof typeof languages]?.flag}</span>
<span className='font-medium text-foreground'>
{languages[currentLang as keyof typeof languages]?.name}
</span>
<ChevronDown
className={`h-3 w-3 text-muted-foreground transition-transform ${isOpen ? 'rotate-180' : ''}`}
/>
<span>{languages[currentLang as keyof typeof languages]?.name}</span>
<ChevronRight className='h-3.5 w-3.5' />
</button>
{isOpen && (
@@ -99,7 +98,7 @@ export function LanguageDropdown() {
<div
id='language-menu'
role='listbox'
className='absolute top-full left-0 z-[1001] mt-1 max-h-[75vh] w-56 overflow-auto rounded-xl border border-border/50 bg-white shadow-2xl md:w-44 md:bg-background/95 md:backdrop-blur-md dark:bg-neutral-950 md:dark:bg-background/95'
className='absolute top-full right-0 z-[1001] mt-1 max-h-[75vh] w-56 overflow-auto rounded-xl border border-border/50 bg-white shadow-2xl md:w-44 md:bg-background/95 md:backdrop-blur-md dark:bg-neutral-950 md:dark:bg-background/95'
>
{Object.entries(languages).map(([code, lang]) => (
<button

View File

@@ -0,0 +1,38 @@
'use client'
import { Search } from 'lucide-react'
export function SearchTrigger() {
const handleClick = () => {
const event = new KeyboardEvent('keydown', {
key: 'k',
metaKey: true,
bubbles: true,
})
document.dispatchEvent(event)
}
return (
<button
type='button'
className='flex h-10 w-[500px] items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
style={{
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
backdropFilter: 'blur(33px) saturate(180%)',
WebkitBackdropFilter: 'blur(33px) saturate(180%)',
color: 'rgba(255, 255, 255, 0.6)',
}}
onClick={handleClick}
>
<Search className='h-4 w-4' />
<span>Search...</span>
<kbd
className='ml-auto flex items-center gap-0.5 font-medium'
style={{ color: 'rgba(255, 255, 255, 0.6)' }}
>
<span style={{ fontSize: '15px', lineHeight: '1' }}></span>
<span style={{ fontSize: '13px', lineHeight: '1' }}>K</span>
</kbd>
</button>
)
}

View File

@@ -0,0 +1,32 @@
'use client'
import { useEffect, useState } from 'react'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return (
<button className='flex items-center justify-center rounded-md p-1 text-muted-foreground'>
<Moon className='h-4 w-4' />
</button>
)
}
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className='flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
aria-label='Toggle theme'
>
{theme === 'dark' ? <Moon className='h-4 w-4' /> : <Sun className='h-4 w-4' />}
</button>
)
}

View File

@@ -1,5 +1,5 @@
---
title: Blocks
title: Overview
description: The building components of your AI workflows
---

View File

@@ -0,0 +1,16 @@
{
"pages": [
"index",
"agent",
"api",
"condition",
"evaluator",
"function",
"guardrails",
"loop",
"parallel",
"response",
"router",
"workflow"
]
}

View File

@@ -1,5 +1,5 @@
---
title: Connection Basics
title: Basics
---
import { Callout } from 'fumadocs-ui/components/callout'

View File

@@ -1,5 +1,5 @@
---
title: Connection Data Structure
title: Data Structure
---
import { Callout } from 'fumadocs-ui/components/callout'

View File

@@ -1,5 +1,5 @@
---
title: Connections
title: Overview
description: Connect your blocks to one another.
---

View File

@@ -0,0 +1,3 @@
{
"pages": ["index", "basics", "data-structure", "tags"]
}

View File

@@ -1,5 +1,5 @@
---
title: Connection Tags
title: Tags
---
import { Callout } from 'fumadocs-ui/components/callout'

View File

@@ -1,5 +1,5 @@
---
title: Execution Basics
title: Basics
---
import { Callout } from 'fumadocs-ui/components/callout'

View File

@@ -1,5 +1,5 @@
---
title: Execution
title: Overview
---
import { Callout } from 'fumadocs-ui/components/callout'

View File

@@ -0,0 +1,3 @@
{
"pages": ["index", "basics", "api", "logging", "costs"]
}

View File

@@ -3,7 +3,6 @@
"pages": [
"./introduction/index",
"./getting-started/index",
"---Building Workflows---",
"triggers",
"blocks",
"tools",
@@ -11,14 +10,9 @@
"mcp",
"copilot",
"knowledgebase",
"---Configuration---",
"variables",
"---Execution---",
"execution",
"---Advanced---",
"permissions",
"yaml",
"---SDKs---",
"sdks"
],
"defaultOpen": false

View File

@@ -0,0 +1,4 @@
{
"title": "SDKs",
"pages": ["python", "typescript"]
}

View File

@@ -1,5 +1,5 @@
---
title: Python SDK
title: Python
---
import { Callout } from 'fumadocs-ui/components/callout'
@@ -752,10 +752,6 @@ Configure the client using environment variables:
</Step>
</Steps>
<Callout type="warning">
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
</Callout>
## Requirements
- Python 3.8+

View File

@@ -1,5 +1,5 @@
---
title: TypeScript/JavaScript SDK
title: TypeScript
---
import { Callout } from 'fumadocs-ui/components/callout'
@@ -1026,10 +1026,6 @@ function StreamingWorkflow() {
</Step>
</Steps>
<Callout type="warning">
Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management.
</Callout>
## Requirements
- Node.js 16+

View File

@@ -1,5 +1,5 @@
---
title: Tools
title: Overview
description: Powerful tools to enhance your agentic workflows
---

View File

@@ -1,5 +1,5 @@
{
"items": [
"pages": [
"index",
"airtable",
"arxiv",

View File

@@ -1,5 +1,5 @@
---
title: Triggers
title: Overview
description: Core ways to start Sim workflows
---

View File

@@ -0,0 +1,3 @@
{
"pages": ["index", "api", "chat", "input-form", "manual", "schedule", "starter", "webhook"]
}

View File

@@ -1,357 +0,0 @@
---
title: Loop Block YAML Schema
description: YAML configuration reference for Loop blocks
---
## Schema Definition
```yaml
type: object
required:
- type
- name
- connections
properties:
type:
type: string
enum: [loop]
description: Block type identifier
name:
type: string
description: Display name for this loop block
inputs:
type: object
description: Optional. If omitted, defaults will be applied.
properties:
loopType:
type: string
enum: [for, forEach]
description: Type of loop to execute
default: for
iterations:
type: number
description: Number of iterations (for 'for' loops)
default: 5
minimum: 1
maximum: 1000
collection:
type: string
description: Collection to iterate over (for 'forEach' loops)
default: ""
maxConcurrency:
type: number
description: Maximum concurrent executions
default: 1
minimum: 1
maximum: 10
connections:
type: object
properties:
# Nested format (recommended)
loop:
type: object
properties:
start:
type: string
description: Target block ID to execute inside the loop
end:
type: string
description: Target block ID for loop completion (optional)
# Direct handle format (alternative)
loop-start-source:
type: string | string[]
description: Target block ID to execute inside the loop (direct format)
loop-end-source:
type: string | string[]
description: Target block ID for loop completion (direct format, optional)
error:
type: string
description: Target block ID for error handling
note: Use either the nested 'loop' format OR the direct 'loop-start-source' format, not both
```
## Connection Configuration
Loop blocks support two connection formats:
### Direct Handle Format (Alternative)
```yaml
connections:
loop-start-source: <string> # Target block ID to execute inside the loop
loop-end-source: <string> # Target block ID after loop completion (optional)
error: <string> # Target block ID for error handling (optional)
```
Both formats work identically. Use whichever you prefer.
## Child Block Configuration
Blocks inside a loop must have their `parentId` set to the loop block ID. The `extent` property is automatically set to `'parent'` and doesn't need to be specified:
```yaml
loop-1:
type: loop
name: "Process Items"
inputs:
loopType: forEach
collection: <start.items>
connections:
loop:
start: process-item
end: final-results
# Child block inside the loop
process-item:
type: agent
name: "Process Item"
parentId: loop-1 # References the loop block
inputs:
systemPrompt: "Process this item"
userPrompt: <loop.currentItem>
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
```
## Examples
### For Loop (Fixed Iterations)
```yaml
countdown-loop:
type: loop
name: "Countdown Loop"
inputs:
loopType: for
iterations: 5
connections:
loop:
start: countdown-agent
end: countdown-complete
countdown-agent:
type: agent
name: "Countdown Agent"
parentId: countdown-loop
inputs:
systemPrompt: "Generate a countdown message"
userPrompt: "Count down from 5. Current number: <loop.index>"
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
```
### ForEach Loop (Collection Processing)
```yaml
email-processor-loop:
type: loop
name: "Email Processor Loop"
inputs:
loopType: forEach
collection: <start.emails>
connections:
loop:
start: process-single-email
end: all-emails-processed
process-single-email:
type: agent
name: "Process Single Email"
parentId: email-processor-loop
inputs:
systemPrompt: "Classify and respond to this email"
userPrompt: "Email content: <loop.currentItem>"
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
```
### Loop with Multiple Child Blocks
```yaml
data-analysis-loop:
type: loop
name: "Data Analysis Loop"
inputs:
loopType: forEach
collection: <data-fetcher.records>
maxConcurrency: 3
connections:
loop:
start: validate-record
end: generate-report
error: handle-loop-error
validate-record:
type: function
name: "Validate Record"
parentId: data-analysis-loop
inputs:
code: |
const record = <loop.currentItem>;
const index = <loop.index>;
// Validate the record
if (!record.id || !record.data) {
throw new Error(`Invalid record at index ${index}`);
}
return {
valid: true,
recordId: record.id,
processedAt: new Date().toISOString()
};
connections:
success: analyze-record
error: record-error
analyze-record:
type: agent
name: "Analyze Record"
parentId: data-analysis-loop
inputs:
systemPrompt: "Analyze this data record and extract insights"
userPrompt: |
Record ID: <validaterecord.recordId>
Data: <loop.currentItem.data>
Position in collection: <loop.index>
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
connections:
success: store-analysis
store-analysis:
type: function
name: "Store Analysis"
parentId: data-analysis-loop
inputs:
code: |
const analysis = <analyzerecord.content>;
const recordId = <validaterecord.recordId>;
// Store analysis result
return {
recordId,
analysis,
completedAt: new Date().toISOString()
};
```
### Concurrent Processing Loop
```yaml
parallel-processing-loop:
type: loop
name: "Parallel Processing Loop"
inputs:
loopType: forEach
collection: <start.tasks>
maxConcurrency: 5
connections:
loop:
start: process-task
end: aggregate-results
process-task:
type: api
name: "Process Task"
parentId: parallel-processing-loop
inputs:
url: "https://api.example.com/process"
method: POST
headers:
- key: "Authorization"
value: "Bearer {{API_TOKEN}}"
body: |
{
"taskId": "<loop.currentItem.id>",
"data": "<loop.currentItem.data>"
}
connections:
success: task-completed
```
### Direct Handle Format Example
The same loop can be written using the direct handle format:
```yaml
my-loop:
type: loop
name: "Process Items"
inputs:
loopType: forEach
collection: <start.items>
connections:
loop-start-source: process-item # Direct handle format
loop-end-source: final-results # Direct handle format
error: handle-error
process-item:
type: agent
name: "Process Item"
parentId: my-loop
inputs:
systemPrompt: "Process this item"
userPrompt: <loop.currentItem>
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
```
### Minimal Loop Example (Using Defaults)
You can omit the `inputs` section entirely, and defaults will be applied:
```yaml
simple-loop:
type: loop
name: "Simple Loop"
# No inputs section - defaults to loopType: 'for', iterations: 5
connections:
loop-start-source: process-step
loop-end-source: complete
process-step:
type: agent
name: "Process Step"
parentId: simple-loop
inputs:
systemPrompt: "Execute step"
userPrompt: "Step <loop.index>"
model: gpt-4o
apiKey: '{{OPENAI_API_KEY}}'
```
This loop will execute 5 iterations by default.
## Loop Variables
Inside loop child blocks, these special variables are available:
```yaml
# Available in all child blocks of the loop
<loop.index> # Current iteration number (0-based)
<loop.currentItem> # Current item being processed (forEach loops)
<loop.items> # Full collection (forEach loops)
```
## Output References
After a loop completes, you can reference its aggregated results:
```yaml
# In blocks after the loop
final-processor:
inputs:
all-results: <loop-name.results> # Array of all iteration results
total-count: <loop-name.count> # Number of iterations completed
```
## Best Practices
- Set reasonable iteration limits to avoid long execution times
- Use forEach for collection processing, for loops for fixed iterations
- Consider using maxConcurrency for I/O bound operations
- Include error handling for robust loop execution
- Use descriptive names for loop child blocks
- Test with small collections first
- Monitor execution time for large collections

View File

@@ -8,15 +8,31 @@ import type { source } from '@/lib/source'
const processor = remark().use(remarkMdx).use(remarkInclude).use(remarkGfm)
export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await processor.process({
path: page.data._file.absolutePath,
value: page.data.content,
})
// Skip pages without proper file data
if (!page?.data?._file?.absolutePath || !page?.data?.content) {
return `# ${page.data.title || 'Untitled'}
URL: ${page.url || 'Unknown'}
return `# ${page.data.title}
URL: ${page.url}
${page.data.description || 'No description available'}`
}
${page.data.description}
try {
const processed = await processor.process({
path: page.data._file.absolutePath,
value: page.data.content,
})
return `# ${page.data.title || 'Untitled'}
URL: ${page.url || 'Unknown'}
${page.data.description || ''}
${processed.value}`
} catch (error) {
console.error(`Error processing page ${page.url}:`, error)
return `# ${page.data.title || 'Untitled'}
URL: ${page.url || 'Unknown'}
${page.data.description || 'No description available'}`
}
}