feat: redesign docs site pages, migrate legal/about to App Router, add feedback widget (#502)

* feat: update table of content style in DocsPage component

* feat: add feedback widget, redesign DocsHub, and add sidebar logo

* feat: update demo images for dark and light themes

* feat: update demo images for dark and light themes

* feat: replace logo image and remove duplicate SVG file

* feat: add LocalInstallHub component and update documentation for local installation

* feat: enhance UI by updating feature icons and descriptions, and improving layout responsiveness

* Add legal pages: Cookie Policy, Privacy Policy, and Terms of Service

- Implemented Cookie Policy page with details on cookie usage and user privacy.
- Created Privacy Policy page outlining data collection practices and user rights.
- Developed Terms of Service page defining the usage terms for the documentation site.
- Removed outdated MDX files for cookie, privacy, and terms of service.
- Updated FeaturesHub component to include new feature highlights and improved layout.

* feat: enhance GitHub data fetching to include contributor count and update CommunitySection layout
This commit is contained in:
Marco Beretta
2026-02-19 00:06:58 +01:00
committed by GitHub
parent bd2eb566df
commit e3ad59744d
29 changed files with 2168 additions and 746 deletions

View File

@@ -1,3 +1,13 @@
# Docs feedback (GitHub Discussions + Discord)
# GitHub personal access token with discussions:write scope (optional)
GITHUB_FEEDBACK_TOKEN=
# GitHub repository node ID — run: gh api graphql -f query='{repository(owner:"LibreChat-AI",name:"librechat.ai"){id}}'
GITHUB_DISCUSSION_REPO_ID=
# GitHub discussion category node ID — run: gh api graphql -f query='{repository(owner:"LibreChat-AI",name:"librechat.ai"){discussionCategory(slug:"feedback"){id}}}'
GITHUB_DISCUSSION_CATEGORY_ID=
# Discord webhook URL for real-time feedback notifications (optional)
DISCORD_FEEDBACK_WEBHOOK=
# Analytics and feedback
# Slack webhook URL for notifications (optional)
SLACK_WEBHOOK_URL=

496
app/about/page.tsx Normal file
View File

@@ -0,0 +1,496 @@
import Link from 'next/link'
import {
Github,
Star,
ArrowRight,
Unlock,
Layers,
Globe2,
ShieldCheck,
Puzzle,
Scale,
Heart,
Users,
Mail,
Linkedin,
Youtube,
} from 'lucide-react'
import { HomeLayout } from 'fumadocs-ui/layouts/home'
import { baseOptions } from '@/app/layout.config'
import FooterMenu from '@/components/FooterMenu'
import Discord from '@/components/icons/discord'
import X from '@/components/icons/x'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'About LibreChat',
description:
'LibreChat is a free, open-source AI platform that brings together all your AI conversations in one unified, customizable interface.',
}
async function getGitHubData(): Promise<{
stars: number
forks: number
contributors: number
}> {
try {
const [repoRes, contribRes] = await Promise.all([
fetch('https://api.github.com/repos/danny-avila/LibreChat', {
next: { revalidate: 3600 },
}),
fetch(
'https://api.github.com/repos/danny-avila/LibreChat/contributors?per_page=1&anon=true',
{
next: { revalidate: 3600 },
},
),
])
const repoData = repoRes.ok ? await repoRes.json() : {}
const stars = repoData.stargazers_count ?? 0
const forks = repoData.forks_count ?? 0
// GitHub returns total count in Link header for paginated responses
let contributors = 0
if (contribRes.ok) {
const linkHeader = contribRes.headers.get('link')
if (linkHeader) {
const match = linkHeader.match(/page=(\d+)>;\s*rel="last"/)
contributors = match ? parseInt(match[1], 10) : 0
}
}
return { stars, forks, contributors }
} catch {
return { stars: 0, forks: 0, contributors: 0 }
}
}
function formatNumber(num: number): string {
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
if (num >= 1_000) return `${(num / 1_000).toFixed(1).replace(/\.0$/, '')}k`
return num.toString()
}
/* ---------------------------------------------------------------------------
* Hero
* --------------------------------------------------------------------------- */
function HeroSection() {
return (
<section className="px-4 pb-20 pt-16 sm:px-6 md:pt-24 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<p className="mb-6 text-sm font-medium uppercase tracking-widest text-muted-foreground">
About LibreChat
</p>
<h1 className="text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
Every AI, for Everyone
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-muted-foreground">
LibreChat is a free, open-source AI platform that brings together the best language models
from every major provider into one unified, customizable interface. No vendor lock-in, no
subscriptions, full control.
</p>
<nav
className="mt-10 flex items-center justify-center gap-4"
aria-label="About page actions"
>
<Link
href="/docs"
className="inline-flex items-center rounded-lg bg-primary px-6 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
Get Started
<ArrowRight className="ml-2 size-4" aria-hidden="true" />
</Link>
<Link
href="https://github.com/danny-avila/LibreChat"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg border border-border bg-background px-6 py-3 text-sm font-medium text-foreground transition-colors hover:bg-muted"
>
<Github className="size-4" aria-hidden="true" />
View on GitHub
</Link>
</nav>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Stats Bar
* --------------------------------------------------------------------------- */
function StatsSection({
stars,
forks,
contributors,
}: {
stars: number
forks: number
contributors: number
}) {
const stats = [
{ label: 'GitHub Stars', value: stars, icon: Star },
{ label: 'Forks', value: forks, icon: Github },
{ label: 'Contributors', value: contributors, icon: Users },
]
return (
<section className="border-y border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-8 sm:grid-cols-3">
{stats
.filter((s) => s.value > 0)
.map((stat) => {
const Icon = stat.icon
return (
<div key={stat.label} className="text-center">
<div className="mb-2 flex items-center justify-center gap-2">
<Icon className="size-5 text-muted-foreground" aria-hidden="true" />
<span className="text-4xl font-bold tracking-tight text-foreground">
{formatNumber(stat.value)}
</span>
</div>
<p className="text-sm text-muted-foreground">{stat.label}</p>
</div>
)
})}
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Values / Why LibreChat
* --------------------------------------------------------------------------- */
const values = [
{
icon: Unlock,
title: 'Open Source & Free',
description:
'MIT licensed. No subscriptions, no restrictions. Use, modify, and distribute freely.',
},
{
icon: Layers,
title: 'Multi-Provider',
description:
'OpenAI, Anthropic, Google, Azure, AWS Bedrock, and dozens more — all in one place.',
},
{
icon: Puzzle,
title: 'Extensible',
description:
'MCP support, custom endpoints, plugins, and agents. Tailor it to your exact workflow.',
},
{
icon: Globe2,
title: 'Multilingual',
description: 'Interface available in 30+ languages with community-driven translations.',
},
{
icon: ShieldCheck,
title: 'Enterprise Ready',
description:
'SSO with OAuth, SAML, LDAP, two-factor auth, rate limiting, and moderation tools.',
},
{
icon: Scale,
title: 'Self-Hosted',
description:
'Deploy on your own infrastructure. Your data stays yours — full privacy and compliance.',
},
]
function ValuesSection() {
return (
<section className="px-4 py-24 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<header className="mb-16 text-center">
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Why LibreChat
</h2>
<p className="mt-4 text-lg text-muted-foreground">
Built for developers, teams, and enterprises who need control
</p>
</header>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{values.map((value) => {
const Icon = value.icon
return (
<article key={value.title} className="rounded-xl border border-border bg-card p-6">
<Icon className="mb-4 size-6 text-muted-foreground" aria-hidden="true" />
<h3 className="mb-2 text-base font-semibold text-foreground">{value.title}</h3>
<p className="text-sm leading-relaxed text-muted-foreground">{value.description}</p>
</article>
)
})}
</div>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Mission
* --------------------------------------------------------------------------- */
function MissionSection() {
return (
<section className="border-y border-border px-4 py-24 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl">
<header className="mb-12 text-center">
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Our Mission
</h2>
</header>
<div className="space-y-6 text-base leading-relaxed text-muted-foreground">
<p>
AI is transforming how we work, create, and communicate. But access to powerful AI tools
shouldn&apos;t require vendor lock-in, opaque pricing, or surrendering your data.
</p>
<p>
LibreChat exists to democratize AI. We believe everyone should have a unified,
customizable interface that works with any model provider one they can self-host,
audit, and extend without limits.
</p>
<p>
What started as a single developer&apos;s project has grown into a community of hundreds
of contributors and thousands of organizations worldwide. From startups to Fortune 500
companies, universities to government agencies LibreChat is the open-source foundation
they build on.
</p>
</div>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Contributors
* --------------------------------------------------------------------------- */
function ContributorsSection() {
return (
<section className="px-4 py-24 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl text-center">
<div className="mb-4 flex items-center justify-center gap-2">
<Heart className="size-5 text-muted-foreground" aria-hidden="true" />
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Built by the Community
</h2>
</div>
<p className="mb-12 text-lg text-muted-foreground">
LibreChat exists thanks to every person who contributes code, documentation, translations,
and ideas
</p>
<Link
href="https://github.com/danny-avila/LibreChat/graphs/contributors"
target="_blank"
rel="noopener noreferrer"
className="inline-block transition-opacity hover:opacity-80"
aria-label="View all LibreChat contributors on GitHub"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="https://contrib.rocks/image?repo=danny-avila/LibreChat&columns=16&max=160"
alt="LibreChat contributors"
className="mx-auto rounded-xl"
loading="lazy"
width={800}
height={200}
/>
</Link>
<div className="mt-10">
<Link
href="https://github.com/danny-avila/LibreChat"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg border border-border bg-background px-5 py-3 text-sm font-medium text-foreground transition-colors hover:bg-muted"
>
<Github className="size-4" aria-hidden="true" />
Contribute on GitHub
</Link>
</div>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Community / Contact
* --------------------------------------------------------------------------- */
const communityLinks = [
{
icon: Github,
title: 'GitHub Discussions',
description: 'Ask questions, share ideas, report bugs',
href: 'https://github.com/danny-avila/LibreChat/discussions',
},
{
icon: Discord,
title: 'Discord',
description: 'Chat with the community in real-time',
href: 'https://discord.librechat.ai',
},
{
icon: Mail,
title: 'Email',
description: 'contact@librechat.ai',
href: 'mailto:contact@librechat.ai',
},
{
icon: Linkedin,
title: 'LinkedIn',
description: 'Follow for updates and news',
href: 'https://linkedin.librechat.ai',
},
{
icon: X,
title: 'X (Twitter)',
description: '@LibreChatAI',
href: 'https://x.com/LibreChatAI',
},
{
icon: Youtube,
title: 'YouTube',
description: 'Tutorials and demos',
href: 'https://www.youtube.com/@LibreChat',
},
]
function CommunitySection() {
return (
<section className="border-t border-border px-4 py-24 sm:px-6 lg:px-8" id="contact">
<div className="mx-auto max-w-6xl">
<header className="mb-16 text-center">
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Get in Touch
</h2>
<p className="mt-4 text-lg text-muted-foreground">
Join the community or reach out directly
</p>
</header>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{communityLinks.map((link) => {
const Icon = link.icon
return (
<Link
key={link.title}
href={link.href}
target={link.href.startsWith('http') ? '_blank' : undefined}
rel={link.href.startsWith('http') ? 'noopener noreferrer' : undefined}
className="group flex items-center gap-4 rounded-xl border border-border bg-card p-5 transition-colors hover:bg-muted"
>
<div className="shrink-0 rounded-lg bg-muted p-2.5 transition-colors group-hover:bg-background">
<Icon
className="size-5 text-muted-foreground transition-colors group-hover:text-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0">
<span className="block text-sm font-semibold text-foreground">{link.title}</span>
<span className="text-xs text-muted-foreground">{link.description}</span>
</div>
</Link>
)
})}
</div>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* License
* --------------------------------------------------------------------------- */
function LicenseSection() {
return (
<section className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<p className="text-sm text-muted-foreground">
LibreChat is released under the{' '}
<Link
href="https://github.com/danny-avila/LibreChat/blob/main/LICENSE"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-foreground underline decoration-border underline-offset-4 transition-colors hover:decoration-foreground"
>
MIT License
</Link>
. Free to use, modify, and distribute.
</p>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Sponsor CTA
* --------------------------------------------------------------------------- */
function SponsorSection() {
return (
<section className="px-4 py-24 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Support the Project
</h2>
<p className="mt-4 text-lg text-muted-foreground">
LibreChat is maintained by a dedicated team and community of volunteers. Sponsorships help
keep the project sustainable.
</p>
<div className="mt-10 flex items-center justify-center gap-4">
<Link
href="https://github.com/sponsors/danny-avila"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg bg-primary px-6 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
<Heart className="size-4" aria-hidden="true" />
Become a Sponsor
</Link>
<Link
href="https://github.com/danny-avila/LibreChat"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-lg border border-border bg-background px-6 py-3 text-sm font-medium text-foreground transition-colors hover:bg-muted"
>
<Star className="size-4" aria-hidden="true" />
Star on GitHub
</Link>
</div>
</div>
</section>
)
}
/* ---------------------------------------------------------------------------
* Page
* --------------------------------------------------------------------------- */
export default async function AboutPage() {
const { stars, forks, contributors } = await getGitHubData()
return (
<HomeLayout {...baseOptions}>
<main className="min-h-screen">
<HeroSection />
<StatsSection stars={stars} forks={forks} contributors={contributors} />
<ValuesSection />
<MissionSection />
<ContributorsSection />
<SponsorSection />
<CommunitySection />
<LicenseSection />
</main>
<div className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<FooterMenu />
</div>
</div>
</HomeLayout>
)
}

97
app/actions/feedback.ts Normal file
View File

@@ -0,0 +1,97 @@
'use server'
interface FeedbackPayload {
opinion: 'good' | 'bad'
message: string
url: string
}
const GITHUB_GRAPHQL = 'https://api.github.com/graphql'
async function createGitHubDiscussion(payload: FeedbackPayload): Promise<void> {
const token = process.env.GITHUB_FEEDBACK_TOKEN
const categoryId = process.env.GITHUB_DISCUSSION_CATEGORY_ID
const repoId = process.env.GITHUB_DISCUSSION_REPO_ID
if (!token || !categoryId || !repoId) return
const emoji = payload.opinion === 'good' ? '👍' : '👎'
const title = `${emoji} Feedback on ${payload.url}`
const body = [
`**Page:** ${payload.url}`,
`**Rating:** ${payload.opinion}`,
payload.message ? `**Message:**\n${payload.message}` : '',
`---`,
`*Submitted via docs feedback widget*`,
]
.filter(Boolean)
.join('\n\n')
const query = `
mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
createDiscussion(input: {
repositoryId: $repoId,
categoryId: $categoryId,
title: $title,
body: $body
}) {
discussion { url }
}
}
`
const res = await fetch(GITHUB_GRAPHQL, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables: { repoId, categoryId, title, body },
}),
})
if (!res.ok) {
const text = await res.text()
console.error('[Feedback] GitHub Discussion creation failed:', res.status, text)
}
}
async function postToDiscord(payload: FeedbackPayload): Promise<void> {
const webhookUrl = process.env.DISCORD_FEEDBACK_WEBHOOK
if (!webhookUrl) return
const emoji = payload.opinion === 'good' ? ':thumbsup:' : ':thumbsdown:'
const embed = {
title: `${emoji} Docs Feedback`,
color: payload.opinion === 'good' ? 0x22c55e : 0xef4444,
fields: [
{ name: 'Page', value: payload.url, inline: true },
{ name: 'Rating', value: payload.opinion, inline: true },
...(payload.message ? [{ name: 'Message', value: payload.message }] : []),
],
timestamp: new Date().toISOString(),
}
const res = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ embeds: [embed] }),
})
if (!res.ok) {
const text = await res.text()
console.error('[Feedback] Discord webhook failed:', res.status, text)
}
}
export async function submitFeedback(payload: FeedbackPayload): Promise<{ success: boolean }> {
try {
await Promise.allSettled([createGitHubDiscussion(payload), postToDiscord(payload)])
return { success: true }
} catch (err) {
console.error('[Feedback] Unexpected error:', err)
return { success: false }
}
}

107
app/cookie/page.tsx Normal file
View File

@@ -0,0 +1,107 @@
import Link from 'next/link'
import { HomeLayout } from 'fumadocs-ui/layouts/home'
import { baseOptions } from '@/app/layout.config'
import FooterMenu from '@/components/FooterMenu'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Cookie Policy',
description: 'Cookie Policy for the LibreChat documentation website.',
}
export default function CookiePolicyPage() {
return (
<HomeLayout {...baseOptions}>
<main className="min-h-screen px-4 py-16 sm:px-6 md:py-24 lg:px-8">
<article className="prose prose-neutral dark:prose-invert mx-auto max-w-3xl">
<header className="mb-12 not-prose text-center">
<p className="mb-4 text-sm font-medium uppercase tracking-widest text-muted-foreground">
Legal
</p>
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Cookie Policy
</h1>
<p className="mt-4 text-sm text-muted-foreground">Last updated: June 13, 2025</p>
</header>
<h2>No Cookies Used</h2>
<p>
The LibreChat documentation website (
<Link href="https://librechat.ai">librechat.ai</Link>){' '}
<strong>does not use cookies</strong> or any similar tracking technologies.
</p>
<h2>What This Means</h2>
<ul>
<li>We don&apos;t set or read any cookies</li>
<li>We don&apos;t track your browsing behavior</li>
<li>We don&apos;t collect any personal information</li>
<li>We don&apos;t use analytics or advertising cookies</li>
<li>We don&apos;t use third-party tracking services</li>
</ul>
<h2>Browser Storage</h2>
<p>
While we don&apos;t use cookies, this documentation site may use browser features like:
</p>
<ul>
<li>
<strong>Local Storage</strong>: To remember your theme preference (light/dark mode)
</li>
<li>
<strong>Session Storage</strong>: To maintain navigation state during your visit
</li>
</ul>
<p>This data:</p>
<ul>
<li>Is stored only in your browser</li>
<li>Is never transmitted to any server</li>
<li>Can be cleared by clearing your browser data</li>
<li>Does not identify you personally</li>
</ul>
<h2>Third-Party Links</h2>
<p>
Our documentation may contain links to external websites that might use cookies. We are
not responsible for the cookie practices of external sites. Please review their cookie
policies when visiting them.
</p>
<h2>Your Privacy</h2>
<p>Since we don&apos;t use cookies:</p>
<ul>
<li>There&apos;s nothing to accept or decline</li>
<li>No tracking occurs</li>
<li>Your browsing is completely private</li>
<li>No consent banner is needed</li>
</ul>
<h2>Changes to This Policy</h2>
<p>
We may update this cookie policy for clarity. Any updates will be posted on this page
with a new &ldquo;Last updated&rdquo; date. However, we do not anticipate adding cookies
to this documentation site.
</p>
<h2>Contact</h2>
<p>
For questions about this policy or the LibreChat project, please visit{' '}
<Link href="https://github.com/danny-avila/LibreChat">our GitHub repository</Link>.
</p>
<hr />
<p className="text-sm text-muted-foreground">
By using this documentation site, you acknowledge that no cookies or tracking
technologies are employed.
</p>
</article>
</main>
<div className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<FooterMenu />
</div>
</div>
</HomeLayout>
)
}

View File

@@ -3,6 +3,7 @@ import { mdxComponents } from '@/lib/mdx-components'
import { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page'
import { notFound } from 'next/navigation'
import { LLMCopyButton, ViewOptions } from '@/components/page-actions'
import { Feedback } from '@/components/Feedback'
import type { Metadata } from 'next'
interface PageProps {
@@ -19,6 +20,7 @@ export default async function Page(props: PageProps) {
return (
<DocsPage
toc={page.data.toc}
tableOfContent={{ style: 'clerk' }}
lastUpdate={page.data.lastModified}
editOnGithub={{
owner: 'LibreChat-AI',
@@ -36,6 +38,7 @@ export default async function Page(props: PageProps) {
<DocsBody>
<MDX components={mdxComponents} />
</DocsBody>
<Feedback />
</DocsPage>
)
}

View File

@@ -2,7 +2,13 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'
export const baseOptions: BaseLayoutProps = {
nav: {
title: 'LibreChat',
title: (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="/librechat.svg" alt="" width={20} height={20} aria-hidden="true" />
LibreChat
</>
),
},
links: [
{

View File

@@ -3,11 +3,13 @@ import Link from 'next/link'
import {
Bot,
Terminal,
BrainCog,
Settings2,
Code,
ImageIcon,
GitFork,
Search,
Plug,
Brain,
Globe,
ShieldCheck,
ArrowRight,
Github,
Star,
@@ -33,16 +35,33 @@ export const metadata: Metadata = {
* Data fetching (server-side, cached)
* --------------------------------------------------------------------------- */
async function getGitHubStars(): Promise<number> {
async function getGitHubData(): Promise<{ stars: number; contributors: number }> {
try {
const res = await fetch('https://api.github.com/repos/danny-avila/LibreChat', {
next: { revalidate: 3600 },
})
if (!res.ok) return 0
const data = await res.json()
return data.stargazers_count ?? 0
const [repoRes, contribRes] = await Promise.all([
fetch('https://api.github.com/repos/danny-avila/LibreChat', {
next: { revalidate: 3600 },
}),
fetch(
'https://api.github.com/repos/danny-avila/LibreChat/contributors?per_page=1&anon=true',
{ next: { revalidate: 3600 } },
),
])
const repoData = repoRes.ok ? await repoRes.json() : {}
const stars = repoData.stargazers_count ?? 0
let contributors = 0
if (contribRes.ok) {
const linkHeader = contribRes.headers.get('link')
if (linkHeader) {
const match = linkHeader.match(/page=(\d+)>;\s*rel="last"/)
contributors = match ? parseInt(match[1], 10) : 0
}
}
return { stars, contributors }
} catch {
return 0
return { stars: 0, contributors: 0 }
}
}
@@ -163,7 +182,7 @@ const features = [
href: '/docs/features/code_interpreter',
},
{
icon: BrainCog,
icon: Settings2,
title: 'Models',
description: 'AI model selection including Anthropic, AWS, OpenAI, Azure, and more',
href: '/docs/configuration/pre_configured_ai',
@@ -174,24 +193,36 @@ const features = [
description: 'Create React, HTML code, and Mermaid diagrams in chat',
href: '/docs/features/artifacts',
},
{
icon: ImageIcon,
title: 'Multimodal',
description: 'Analyze images and chat with files using various endpoints',
href: '/docs/features',
},
{
icon: GitFork,
title: 'Fork',
description: 'Split messages into multiple conversation threads for better context',
href: '/docs/features/fork',
},
{
icon: Search,
title: 'Search',
description: 'Search for messages, files, and code snippets in an instant',
href: '/docs/configuration/meilisearch',
},
{
icon: Plug,
title: 'MCP',
description: 'Connect to any tool or service with Model Context Protocol support',
href: '/docs/features/mcp',
},
{
icon: Brain,
title: 'Memory',
description: 'Persistent context across conversations so your AI remembers you',
href: '/docs/features/memory',
},
{
icon: Globe,
title: 'Web Search',
description: 'Give any model live internet access with built-in search and reranking',
href: '/docs/features/web_search',
},
{
icon: ShieldCheck,
title: 'Authentication',
description: 'Enterprise-ready SSO with OAuth, SAML, LDAP, and two-factor auth',
href: '/docs/configuration/authentication',
},
]
/* ---------------------------------------------------------------------------
@@ -394,7 +425,15 @@ function FeaturesSection() {
* Community Section
* --------------------------------------------------------------------------- */
function CommunitySection({ stars, pulls }: { stars: number; pulls: number }) {
function CommunitySection({
stars,
pulls,
contributors,
}: {
stars: number
pulls: number
contributors: number
}) {
return (
<section className="border-y border-border px-4 py-24 sm:px-6 lg:px-8">
<div className="mx-auto max-w-4xl">
@@ -408,7 +447,7 @@ function CommunitySection({ stars, pulls }: { stars: number; pulls: number }) {
</header>
{/* Stats */}
<div className="mb-16 grid grid-cols-2 gap-8">
<div className="mb-16 grid grid-cols-3 gap-8">
<div className="text-center">
<p className="text-4xl font-bold tracking-tight text-foreground sm:text-5xl">
{stars > 0 ? formatNumber(stars) : '--'}
@@ -421,6 +460,12 @@ function CommunitySection({ stars, pulls }: { stars: number; pulls: number }) {
</p>
<p className="mt-2 text-sm text-muted-foreground">Docker Pulls</p>
</div>
<div className="text-center">
<p className="text-4xl font-bold tracking-tight text-foreground sm:text-5xl">
{contributors > 0 ? formatNumber(contributors) : '--'}
</p>
<p className="mt-2 text-sm text-muted-foreground">Contributors</p>
</div>
</div>
{/* Links */}
@@ -485,7 +530,7 @@ function CTASection() {
* --------------------------------------------------------------------------- */
export default async function HomePage() {
const [stars, pulls] = await Promise.all([getGitHubStars(), getContainerPulls()])
const [{ stars, contributors }, pulls] = await Promise.all([getGitHubData(), getContainerPulls()])
return (
<HomeLayout {...baseOptions} nav={{ ...baseOptions.nav, transparentMode: 'top' }}>
@@ -493,7 +538,7 @@ export default async function HomePage() {
<HeroSection stars={stars} />
<TrustedBySection />
<FeaturesSection />
<CommunitySection stars={stars} pulls={pulls} />
<CommunitySection stars={stars} pulls={pulls} contributors={contributors} />
<CTASection />
</main>
<div className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">

129
app/privacy/page.tsx Normal file
View File

@@ -0,0 +1,129 @@
import Link from 'next/link'
import { HomeLayout } from 'fumadocs-ui/layouts/home'
import { baseOptions } from '@/app/layout.config'
import FooterMenu from '@/components/FooterMenu'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Privacy Policy',
description: 'Privacy Policy for the LibreChat documentation website.',
}
export default function PrivacyPolicyPage() {
return (
<HomeLayout {...baseOptions}>
<main className="min-h-screen px-4 py-16 sm:px-6 md:py-24 lg:px-8">
<article className="prose prose-neutral dark:prose-invert mx-auto max-w-3xl">
<header className="mb-12 not-prose text-center">
<p className="mb-4 text-sm font-medium uppercase tracking-widest text-muted-foreground">
Legal
</p>
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Privacy Policy
</h1>
<p className="mt-4 text-sm text-muted-foreground">Last updated: June 13, 2025</p>
</header>
<h2>Overview</h2>
<p>
This privacy policy describes how the LibreChat documentation website (
<Link href="https://librechat.ai">librechat.ai</Link>) handles user privacy.
</p>
<p>
<strong>In short: We don&apos;t collect any personal data.</strong>
</p>
<h2>No Data Collection</h2>
<p>The LibreChat documentation website:</p>
<ul>
<li>
<strong>Does not collect</strong> any personal information
</li>
<li>
<strong>Does not use</strong> cookies or tracking technologies
</li>
<li>
<strong>Does not require</strong> user registration or accounts
</li>
<li>
<strong>Does not use</strong> analytics or monitoring tools
</li>
<li>
<strong>Does not store</strong> any user data
</li>
</ul>
<h2>Purpose of This Site</h2>
<p>
This website serves exclusively as documentation for the open-source LibreChat project.
It provides:
</p>
<ul>
<li>Installation and setup guides</li>
<li>Configuration documentation</li>
<li>API references</li>
<li>User guides</li>
<li>Contributing guidelines</li>
<li>Changelog and release notes</li>
</ul>
<h2>External Links</h2>
<p>Our documentation may contain links to external resources such as:</p>
<ul>
<li>The LibreChat GitHub repository</li>
<li>Third-party service documentation</li>
<li>Community forums and discussions</li>
</ul>
<p>
We are not responsible for the privacy practices of these external sites. Please review
their privacy policies when visiting them.
</p>
<h2>Local Browser Storage</h2>
<p>While we don&apos;t collect any data, your browser may store:</p>
<ul>
<li>Theme preferences (light/dark mode) locally</li>
<li>Navigation state for your convenience</li>
</ul>
<p>
This information is stored only in your browser and is never transmitted to any server.
</p>
<h2>Open Source</h2>
<p>
LibreChat is an open-source project licensed under the MIT License. The source code is
publicly available at{' '}
<Link href="https://github.com/danny-avila/LibreChat">
github.com/danny-avila/LibreChat
</Link>
.
</p>
<h2>Changes to This Policy</h2>
<p>
We may update this privacy policy for clarity or to reflect changes in our practices.
Any updates will be posted on this page with a new &ldquo;Last updated&rdquo; date.
</p>
<h2>Contact</h2>
<p>
For questions about this privacy policy or the LibreChat project, please visit{' '}
<Link href="https://github.com/danny-avila/LibreChat">our GitHub repository</Link>.
</p>
<hr />
<p className="text-sm text-muted-foreground">
By using this documentation site, you acknowledge that no personal data is collected or
processed.
</p>
</article>
</main>
<div className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<FooterMenu />
</div>
</div>
</HomeLayout>
)
}

130
app/tos/page.tsx Normal file
View File

@@ -0,0 +1,130 @@
import Link from 'next/link'
import { HomeLayout } from 'fumadocs-ui/layouts/home'
import { baseOptions } from '@/app/layout.config'
import FooterMenu from '@/components/FooterMenu'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Terms of Service',
description: 'Terms of Service for the LibreChat documentation website.',
}
export default function TermsOfServicePage() {
return (
<HomeLayout {...baseOptions}>
<main className="min-h-screen px-4 py-16 sm:px-6 md:py-24 lg:px-8">
<article className="prose prose-neutral dark:prose-invert mx-auto max-w-3xl">
<header className="mb-12 not-prose text-center">
<p className="mb-4 text-sm font-medium uppercase tracking-widest text-muted-foreground">
Legal
</p>
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Terms of Service
</h1>
<p className="mt-4 text-sm text-muted-foreground">Last updated: June 13, 2025</p>
</header>
<p>
Welcome to the LibreChat documentation website, available at{' '}
<Link href="https://librechat.ai">librechat.ai</Link>. These Terms of Service govern
your use of our documentation website.
</p>
<h2>1. Purpose and Scope</h2>
<p>
This website provides documentation for LibreChat, an open-source AI chat platform. The
site is purely informational and includes:
</p>
<ul>
<li>Technical documentation</li>
<li>Installation and setup guides</li>
<li>Configuration references</li>
<li>API documentation</li>
<li>Contributing guidelines</li>
<li>Blog posts related to LibreChat</li>
</ul>
<h2>2. No Commercial Services</h2>
<p>This documentation site:</p>
<ul>
<li>Does not sell any products or services</li>
<li>Does not require payment for access</li>
<li>Does not collect user data or personal information</li>
<li>Does not require user registration or accounts</li>
</ul>
<h2>3. Open Source Project</h2>
<p>
LibreChat is an open-source project licensed under the MIT License. The source code is
available at{' '}
<Link href="https://github.com/danny-avila/LibreChat">
github.com/danny-avila/LibreChat
</Link>
.
</p>
<h2>4. Use of Documentation</h2>
<p>You may freely:</p>
<ul>
<li>Access and read all documentation</li>
<li>Share links to the documentation</li>
<li>Use the documentation to implement and configure LibreChat</li>
<li>Contribute improvements to the documentation via GitHub</li>
</ul>
<h2>5. No Warranty</h2>
<p>
This documentation is provided &ldquo;as is&rdquo; without warranty of any kind. While
we strive for accuracy, we make no guarantees about:
</p>
<ul>
<li>The completeness or accuracy of the documentation</li>
<li>The suitability of LibreChat for any particular purpose</li>
<li>The availability of this documentation site</li>
</ul>
<h2>6. External Resources</h2>
<p>
Our documentation may reference or link to third-party services, tools, or resources. We
are not responsible for:
</p>
<ul>
<li>The content or practices of external sites</li>
<li>The availability of external resources</li>
<li>Any issues arising from the use of third-party services</li>
</ul>
<h2>7. Intellectual Property</h2>
<p>
The documentation content is licensed under the MIT License, consistent with the
LibreChat project. You are free to use, modify, and distribute the documentation in
accordance with this license.
</p>
<h2>8. Changes to Terms</h2>
<p>
We may update these terms to reflect changes in the project or for clarity. Updates will
be posted on this page with a new effective date.
</p>
<h2>9. Contact</h2>
<p>
For questions about these terms or to contribute to the project, please visit{' '}
<Link href="https://github.com/danny-avila/LibreChat">our GitHub repository</Link>.
</p>
<hr />
<p className="text-sm text-muted-foreground">
By using this documentation site, you agree to these Terms of Service.
</p>
</article>
</main>
<div className="border-t border-border px-4 py-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<FooterMenu />
</div>
</div>
</HomeLayout>
)
}

View File

@@ -1,7 +1,6 @@
import Link from 'next/link'
import {
Container,
Plug,
Rocket,
Server,
Cloud,
Settings,
@@ -11,61 +10,82 @@ import {
FileText,
Map,
MessageSquare,
ArrowRight,
ChevronRight,
Terminal,
} from 'lucide-react'
const quickstart = [
const paths = [
{
icon: Container,
title: 'Local Setup',
description: 'Get LibreChat running on your machine in minutes using Docker.',
href: '/docs/quick_start/local_setup',
id: 'deploy',
title: 'Deploy',
items: [
{
icon: Rocket,
title: 'Quick Start',
description: 'Docker setup in 5 minutes',
href: '/docs/quick_start',
},
{
icon: Server,
title: 'Local Installation',
description: 'Docker, npm, and Helm Chart',
href: '/docs/local',
},
{
icon: Cloud,
title: 'Remote Hosting',
description: 'DigitalOcean, Railway, and more',
href: '/docs/remote',
},
],
},
{
icon: Plug,
title: 'Custom Endpoints',
description: 'Connect to OpenRouter, Ollama, Deepseek, Groq, and other AI providers.',
href: '/docs/quick_start/custom_endpoints',
},
]
const categories = [
{
icon: Server,
title: 'Local Installation',
description: 'Docker, npm, and Helm Chart deployment',
href: '/docs/local',
id: 'configure',
title: 'Configure',
items: [
{
icon: Settings,
title: 'Configuration',
description: 'Environment variables, YAML, and auth',
href: '/docs/configuration',
},
{
icon: Terminal,
title: 'Custom Endpoints',
description: 'Connect Ollama, Deepseek, Groq, and more',
href: '/docs/quick_start/custom_endpoints',
},
],
},
{
icon: Cloud,
title: 'Remote Hosting',
description: 'DigitalOcean, Railway, HuggingFace, and more',
href: '/docs/remote',
id: 'use',
title: 'Use',
items: [
{
icon: Sparkles,
title: 'Features',
description: 'MCP, Agents, Code Interpreter, Artifacts',
href: '/docs/features',
},
{
icon: BookOpen,
title: 'User Guides',
description: 'Presets, tips, and best practices',
href: '/docs/user_guides',
},
],
},
{
icon: Settings,
title: 'Configuration',
description: 'Environment variables, YAML, auth, and endpoints',
href: '/docs/configuration',
},
{
icon: Sparkles,
title: 'Features',
description: 'MCP, Agents, Code Interpreter, Artifacts, and more',
href: '/docs/features',
},
{
icon: BookOpen,
title: 'User Guides',
description: 'Presets, AI overview, tips, and best practices',
href: '/docs/user_guides',
},
{
icon: Code,
title: 'Development',
description: 'Contributing, architecture, and debugging',
href: '/docs/development',
id: 'contribute',
title: 'Contribute',
items: [
{
icon: Code,
title: 'Development',
description: 'Contributing, architecture, and debugging',
href: '/docs/development',
},
],
},
]
@@ -90,10 +110,13 @@ const resources = [
},
]
function SectionHeading({ children }: { children: React.ReactNode }) {
function SectionHeading({ children, id }: { children: React.ReactNode; id: string }) {
return (
<div className="mb-5 flex items-center gap-3">
<h2 className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground">
<div className="mb-4 flex items-center gap-3">
<h2
id={id}
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
{children}
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
@@ -104,82 +127,43 @@ function SectionHeading({ children }: { children: React.ReactNode }) {
export function DocsHub() {
return (
<nav className="not-prose space-y-10" aria-label="Documentation navigation">
{/* Quick Start - Primary actions, visually prominent */}
<section aria-labelledby="qs-heading">
<SectionHeading>
<span id="qs-heading">Quick Start</span>
</SectionHeading>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{quickstart.map((item) => {
const Icon = item.icon
return (
<Link
key={item.title}
href={item.href}
className="group relative rounded-xl border border-fd-border bg-fd-card p-6 transition-all duration-200 hover:border-fd-foreground/20 hover:shadow-lg hover:shadow-fd-foreground/[0.03]"
>
<div className="mb-4 inline-flex rounded-lg border border-fd-border bg-fd-background p-2.5 transition-colors group-hover:border-fd-foreground/20">
<Icon
className="size-5 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<h3 className="mb-1.5 text-base font-semibold text-fd-foreground">{item.title}</h3>
<p className="mb-4 text-sm leading-relaxed text-fd-muted-foreground">
{item.description}
</p>
<span
className="inline-flex items-center gap-1 text-sm font-medium text-fd-muted-foreground transition-all group-hover:gap-1.5 group-hover:text-fd-foreground"
aria-hidden="true"
{/* Intent-based path groups */}
{paths.map((path) => (
<section key={path.id} aria-labelledby={`${path.id}-heading`}>
<SectionHeading id={`${path.id}-heading`}>{path.title}</SectionHeading>
<div className="overflow-hidden rounded-xl border border-fd-border">
{path.items.map((item, i) => {
const Icon = item.icon
return (
<Link
key={item.title}
href={item.href}
className={`group flex items-center gap-4 px-5 py-4 transition-colors hover:bg-fd-accent${i < path.items.length - 1 ? ' border-b border-fd-border' : ''}`}
>
Get started
<ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
</Link>
)
})}
</div>
</section>
{/* Documentation - Clean navigation list */}
<section aria-labelledby="docs-heading">
<SectionHeading>
<span id="docs-heading">Documentation</span>
</SectionHeading>
<div className="overflow-hidden rounded-xl border border-fd-border">
{categories.map((item, i) => {
const Icon = item.icon
return (
<Link
key={item.title}
href={item.href}
className={`group flex items-center gap-4 px-5 py-4 transition-colors hover:bg-fd-accent${i < categories.length - 1 ? ' border-b border-fd-border' : ''}`}
>
<div className="shrink-0 rounded-md bg-fd-accent p-2 transition-colors group-hover:bg-fd-background">
<Icon
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
<div className="shrink-0 rounded-md bg-fd-accent p-2 transition-colors group-hover:bg-fd-background">
<Icon
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">{item.title}</span>
<p className="text-xs text-fd-muted-foreground">{item.description}</p>
</div>
<ChevronRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">{item.title}</span>
<p className="text-xs text-fd-muted-foreground">{item.description}</p>
</div>
<ChevronRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
)
})}
</div>
</section>
</Link>
)
})}
</div>
</section>
))}
{/* Resources - Compact links */}
{/* Resources */}
<section aria-labelledby="resources-heading">
<SectionHeading>
<span id="resources-heading">Resources</span>
</SectionHeading>
<SectionHeading id="resources-heading">Resources</SectionHeading>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{resources.map((item) => {
const Icon = item.icon

View File

@@ -23,6 +23,7 @@ import {
ShieldCheck,
ArrowRight,
ChevronRight,
Sparkles,
} from 'lucide-react'
type Feature = {
@@ -30,6 +31,16 @@ type Feature = {
title: string
description: string
href: string
tag?: string
}
const hero: Feature = {
icon: Cable,
title: 'Model Context Protocol',
description:
'Connect AI models to any external tool or service through MCP — the open standard for AI tool integration. LibreChat is an official MCP client.',
href: '/docs/features/mcp',
tag: 'Official MCP Client',
}
const highlights: Feature[] = [
@@ -40,67 +51,58 @@ const highlights: Feature[] = [
'Build custom AI assistants with tools, file handling, code execution, and API actions — no coding required.',
href: '/docs/features/agents',
},
{
icon: Cable,
title: 'Model Context Protocol',
description:
'Connect AI models to any tool or service through MCP, the universal standard for AI tool integration.',
href: '/docs/features/mcp',
},
{
icon: Terminal,
title: 'Code Interpreter',
description:
'Execute code in Python, JavaScript, Go, Rust, and more — securely sandboxed with zero setup.',
'Execute Python, JavaScript, Go, Rust, and more — securely sandboxed with zero setup.',
href: '/docs/features/code_interpreter',
},
{
icon: Code,
title: 'Artifacts',
description:
'Generate React components, HTML pages, and Mermaid diagrams directly inside chat.',
href: '/docs/features/artifacts',
},
{
icon: Brain,
title: 'Memory',
description:
'Persistent context across conversations so your AI remembers preferences and history.',
href: '/docs/features/memory',
},
{
icon: Globe,
title: 'Web Search',
description: 'Give any model live internet access with built-in search and reranking.',
href: '/docs/features/web_search',
},
{
icon: Shield,
title: 'Authentication',
description: 'Enterprise-ready SSO with OAuth2, SAML, LDAP, and two-factor authentication.',
href: '/docs/features/authentication',
},
]
type Category = {
title: string
id: string
layout: 'grid' | 'list'
items: Feature[]
}
const categories: Category[] = [
{
title: 'AI & Agents',
id: 'ai-agents',
items: [
{
icon: Cable,
title: 'MCP',
description: 'Universal AI tool integration via Model Context Protocol',
href: '/docs/features/mcp',
},
{
icon: Bot,
title: 'Agents',
description: 'Custom AI assistants with tools and actions',
href: '/docs/features/agents',
},
{
icon: Terminal,
title: 'Code Interpreter',
description: 'Secure code execution in 8+ languages',
href: '/docs/features/code_interpreter',
},
{
icon: Code,
title: 'Artifacts',
description: 'Generate React, HTML, and Mermaid diagrams in chat',
href: '/docs/features/artifacts',
},
],
},
{
title: 'Search & Knowledge',
id: 'search-knowledge',
layout: 'grid',
items: [
{
icon: Globe,
title: 'Web Search',
description: 'Search the web to enhance conversations',
description: 'Live internet access with built-in search and reranking',
href: '/docs/features/web_search',
},
{
@@ -132,6 +134,7 @@ const categories: Category[] = [
{
title: 'Media',
id: 'media',
layout: 'grid',
items: [
{
icon: ImageIcon,
@@ -150,6 +153,7 @@ const categories: Category[] = [
{
title: 'Chat',
id: 'chat',
layout: 'list',
items: [
{
icon: GitFork,
@@ -192,11 +196,12 @@ const categories: Category[] = [
{
title: 'Security',
id: 'security',
layout: 'list',
items: [
{
icon: Shield,
title: 'Authentication',
description: 'Multi-user auth with OAuth2, LDAP, and more',
description: 'Multi-user auth with OAuth2, SAML, LDAP, and more',
href: '/docs/features/authentication',
},
{
@@ -215,10 +220,13 @@ const categories: Category[] = [
},
]
function SectionHeading({ children }: { children: React.ReactNode }) {
function SectionHeading({ children, id }: { children: React.ReactNode; id: string }) {
return (
<div className="mb-5 flex items-center gap-3">
<h2 className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground">
<h2
id={id}
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
{children}
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
@@ -226,79 +234,135 @@ function SectionHeading({ children }: { children: React.ReactNode }) {
)
}
function FeatureCard({ feature }: { feature: Feature }) {
const Icon = feature.icon
return (
<Link
href={feature.href}
className="group flex flex-col rounded-xl border border-fd-border bg-fd-card p-5 transition-all duration-200 hover:border-fd-foreground/20 hover:shadow-lg hover:shadow-fd-foreground/[0.03]"
>
<div className="mb-3 inline-flex w-fit rounded-lg border border-fd-border bg-fd-background p-2 transition-colors group-hover:border-fd-foreground/20">
<Icon
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<h3 className="mb-1 text-sm font-semibold text-fd-foreground">{feature.title}</h3>
<p className="mb-3 flex-1 text-xs leading-relaxed text-fd-muted-foreground">
{feature.description}
</p>
<span
className="inline-flex items-center gap-1 text-xs font-medium text-fd-muted-foreground transition-all group-hover:gap-1.5 group-hover:text-fd-foreground"
aria-hidden="true"
>
Learn more
<ArrowRight className="size-3 transition-transform group-hover:translate-x-0.5" />
</span>
</Link>
)
}
function ListItem({ item, last }: { item: Feature; last: boolean }) {
const Icon = item.icon
return (
<Link
href={item.href}
className={`group flex items-center gap-4 px-5 py-3.5 transition-colors hover:bg-fd-accent${last ? '' : ' border-b border-fd-border'}`}
>
<div className="shrink-0 rounded-md bg-fd-accent p-1.5 transition-colors group-hover:bg-fd-background">
<Icon
className="size-3.5 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">{item.title}</span>
<p className="text-xs text-fd-muted-foreground">{item.description}</p>
</div>
<ChevronRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
)
}
export function FeaturesHub() {
const HeroIcon = hero.icon
return (
<nav className="not-prose space-y-10" aria-label="Features navigation">
{/* Highlights - 3 prominent feature cards */}
<section aria-labelledby="highlights-heading">
<SectionHeading>
<span id="highlights-heading">Highlights</span>
</SectionHeading>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{highlights.map((feature) => {
const Icon = feature.icon
return (
<Link
key={feature.title}
href={feature.href}
className="group relative flex flex-col rounded-xl border border-fd-border bg-fd-card p-5 transition-all duration-200 hover:border-fd-foreground/20 hover:shadow-lg hover:shadow-fd-foreground/[0.03]"
>
<div className="mb-3 inline-flex rounded-lg border border-fd-border bg-fd-background p-2 transition-colors group-hover:border-fd-foreground/20">
<Icon
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<h3 className="mb-1 text-sm font-semibold text-fd-foreground">{feature.title}</h3>
<p className="mb-3 flex-1 text-xs leading-relaxed text-fd-muted-foreground">
{feature.description}
</p>
<span
className="inline-flex items-center gap-1 text-xs font-medium text-fd-muted-foreground transition-all group-hover:gap-1.5 group-hover:text-fd-foreground"
aria-hidden="true"
>
Learn more
<ArrowRight className="size-3 transition-transform group-hover:translate-x-0.5" />
{/* Hero feature — MCP */}
<section aria-labelledby="hero-heading">
<SectionHeading id="hero-heading">Featured</SectionHeading>
<Link
href={hero.href}
className="group relative flex flex-col overflow-hidden rounded-xl border border-fd-foreground/15 bg-fd-card transition-all duration-200 hover:border-fd-foreground/25 hover:shadow-lg hover:shadow-fd-foreground/[0.04] sm:flex-row"
>
{/* Icon area */}
<div className="flex items-center justify-center border-b border-fd-border bg-fd-accent/50 p-8 sm:w-32 sm:border-b-0 sm:border-r">
<HeroIcon
className="size-10 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
{/* Content */}
<div className="flex flex-1 flex-col justify-center p-5 sm:p-6">
<div className="mb-2 flex flex-wrap items-center gap-2">
<h3 className="text-base font-semibold text-fd-foreground">{hero.title}</h3>
{hero.tag && (
<span className="rounded-full bg-fd-primary px-2.5 py-0.5 text-[10px] font-medium text-fd-primary-foreground">
{hero.tag}
</span>
</Link>
)
})}
)}
</div>
<p className="mb-3 text-sm leading-relaxed text-fd-muted-foreground">
{hero.description}
</p>
<span
className="inline-flex items-center gap-1.5 text-sm font-medium text-fd-foreground transition-colors group-hover:text-fd-foreground/80"
aria-hidden="true"
>
Read the docs
<ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
</div>
</Link>
</section>
{/* Highlights — 6 top features in a 3x2 grid */}
<section aria-labelledby="highlights-heading">
<SectionHeading id="highlights-heading">
<span className="inline-flex items-center gap-1.5">
<Sparkles className="size-3" aria-hidden="true" />
Core Features
</span>
</SectionHeading>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{highlights.map((feature) => (
<FeatureCard key={feature.title} feature={feature} />
))}
</div>
</section>
{/* Category sections */}
{/* Category sections — mixed layouts */}
{categories.map((category) => (
<section key={category.id} aria-labelledby={`${category.id}-heading`}>
<SectionHeading>
<span id={`${category.id}-heading`}>{category.title}</span>
</SectionHeading>
<div className="overflow-hidden rounded-xl border border-fd-border">
{category.items.map((item, i) => {
const Icon = item.icon
return (
<Link
key={item.title}
href={item.href}
className={`group flex items-center gap-4 px-5 py-3.5 transition-colors hover:bg-fd-accent${i < category.items.length - 1 ? ' border-b border-fd-border' : ''}`}
>
<div className="shrink-0 rounded-md bg-fd-accent p-1.5 transition-colors group-hover:bg-fd-background">
<Icon
className="size-3.5 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">{item.title}</span>
<p className="text-xs text-fd-muted-foreground">{item.description}</p>
</div>
<ChevronRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
)
})}
</div>
<SectionHeading id={`${category.id}-heading`}>{category.title}</SectionHeading>
{category.layout === 'grid' ? (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{category.items.map((item) => (
<FeatureCard key={item.title} feature={item} />
))}
</div>
) : (
<div className="overflow-hidden rounded-xl border border-fd-border">
{category.items.map((item, i) => (
<ListItem key={item.title} item={item} last={i === category.items.length - 1} />
))}
</div>
)}
</section>
))}
</nav>

147
components/Feedback.tsx Normal file
View File

@@ -0,0 +1,147 @@
'use client'
import { useEffect, useState, useTransition, type SyntheticEvent } from 'react'
import { ThumbsUp, ThumbsDown, Loader2 } from 'lucide-react'
import { usePathname } from 'next/navigation'
import { Collapsible, CollapsibleContent } from 'fumadocs-ui/components/ui/collapsible'
import { submitFeedback } from '@/app/actions/feedback'
type Opinion = 'good' | 'bad'
interface StoredFeedback {
opinion: Opinion
message: string
url: string
}
function useStoredFeedback(url: string) {
const key = `docs-feedback-${url}`
const [stored, setStored] = useState<StoredFeedback | null>(null)
useEffect(() => {
const item = localStorage.getItem(key)
if (!item) return
try {
const parsed = JSON.parse(item) as StoredFeedback
if (parsed.opinion && parsed.url) setStored(parsed)
} catch {
// ignore invalid data
}
}, [key])
function save(feedback: StoredFeedback | null) {
if (feedback) {
localStorage.setItem(key, JSON.stringify(feedback))
} else {
localStorage.removeItem(key)
}
setStored(feedback)
}
return { stored, save }
}
export function Feedback() {
const url = usePathname() ?? '/'
const { stored, save } = useStoredFeedback(url)
const [opinion, setOpinion] = useState<Opinion | null>(null)
const [message, setMessage] = useState('')
const [pending, startTransition] = useTransition()
function submit(e?: SyntheticEvent) {
if (!opinion) return
e?.preventDefault()
const payload = { opinion, message, url }
save(payload)
setMessage('')
setOpinion(null)
startTransition(async () => {
await submitFeedback(payload)
})
}
const activeOpinion = stored?.opinion ?? opinion
return (
<Collapsible
open={opinion !== null || stored !== null}
onOpenChange={(open) => {
if (!open) setOpinion(null)
}}
className="not-prose border-y border-fd-border py-3"
>
<div className="flex flex-row items-center gap-2">
<p className="pe-2 text-sm font-medium text-fd-foreground">How is this guide?</p>
<button
disabled={stored !== null}
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm font-medium transition-colors [&_svg]:size-4 ${
activeOpinion === 'good'
? 'border-fd-border bg-fd-accent text-fd-accent-foreground [&_svg]:fill-current'
: 'border-fd-border text-fd-muted-foreground hover:bg-fd-accent'
} disabled:cursor-not-allowed`}
onClick={() => setOpinion('good')}
aria-label="Good"
>
<ThumbsUp />
Good
</button>
<button
disabled={stored !== null}
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm font-medium transition-colors [&_svg]:size-4 ${
activeOpinion === 'bad'
? 'border-fd-border bg-fd-accent text-fd-accent-foreground [&_svg]:fill-current'
: 'border-fd-border text-fd-muted-foreground hover:bg-fd-accent'
} disabled:cursor-not-allowed`}
onClick={() => setOpinion('bad')}
aria-label="Bad"
>
<ThumbsDown />
Bad
</button>
</div>
<CollapsibleContent className="mt-3">
{stored ? (
<div className="flex flex-col items-center gap-3 rounded-xl bg-fd-card px-3 py-6 text-center text-sm text-fd-muted-foreground">
<p className="flex items-center gap-2">
{pending && <Loader2 className="size-3.5 animate-spin" />}
Thank you for your feedback!
</p>
<button
className="rounded-md border border-fd-border px-3 py-1.5 text-xs font-medium text-fd-muted-foreground transition-colors hover:bg-fd-accent"
onClick={() => {
setOpinion(stored.opinion)
save(null)
}}
>
Submit again
</button>
</div>
) : (
<form className="flex flex-col gap-3" onSubmit={submit}>
<textarea
autoFocus
value={message}
onChange={(e) => setMessage(e.target.value)}
className="resize-none rounded-lg border border-fd-border bg-fd-secondary p-3 text-sm text-fd-secondary-foreground placeholder:text-fd-muted-foreground focus-visible:outline-none"
placeholder="Any additional feedback? (optional)"
rows={3}
onKeyDown={(e) => {
if (!e.shiftKey && e.key === 'Enter') {
submit(e)
}
}}
/>
<button
type="submit"
className="w-fit rounded-md border border-fd-border px-3 py-1.5 text-sm font-medium text-fd-foreground transition-colors hover:bg-fd-accent"
>
Submit
</button>
</form>
)}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -0,0 +1,364 @@
import Link from 'next/link'
import {
ArrowRight,
CheckCircle2,
Clock,
Database,
FileSearch,
HardDrive,
Layers,
Package,
Server,
Shield,
} from 'lucide-react'
import type { ComponentProps } from 'react'
function DockerLogo(props: ComponentProps<'svg'>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="#2496ED"
d="M13.983 11.078h2.119a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.119a.186.186 0 0 0-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 0 0 .186-.186V3.574a.186.186 0 0 0-.186-.185h-2.118a.186.186 0 0 0-.185.185v1.888c0 .102.082.185.185.186m0 2.716h2.118a.187.187 0 0 0 .186-.186V6.29a.186.186 0 0 0-.186-.185h-2.118a.186.186 0 0 0-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 0 0 .184-.186V6.29a.186.186 0 0 0-.185-.185H8.1a.186.186 0 0 0-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 0 0 .185-.186V6.29a.186.186 0 0 0-.185-.185H5.136a.186.186 0 0 0-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.118a.186.186 0 0 0-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.186.186 0 0 0 .184-.185V9.006a.186.186 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.186.186 0 0 0 .185-.185V9.006a.186.186 0 0 0-.185-.186H5.136a.186.186 0 0 0-.186.185v1.888c0 .102.084.185.186.185m-2.92 0h2.12a.186.186 0 0 0 .184-.185V9.006a.186.186 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.184.186v1.887c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 0 0-.75.748 11.376 11.376 0 0 0 .692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 0 0 3.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"
/>
</svg>
)
}
function NpmLogo(props: ComponentProps<'svg'>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="#CB3837"
d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002z"
/>
</svg>
)
}
function HelmLogo(props: ComponentProps<'svg'>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="#0F1689"
d="M10.082 2.006a1.2 1.2 0 0 0-.59.196L2.47 6.56a1.213 1.213 0 0 0-.555.87l-.905 8.154c-.04.365.086.73.345.994l5.756 5.87c.259.264.617.4.98.37l8.14-.69c.362-.03.694-.214.907-.502l4.697-6.39c.213-.288.297-.653.23-1.013l-1.478-8.066a1.216 1.216 0 0 0-.616-.834L13.38 2.072a1.2 1.2 0 0 0-.538-.13h-.004a1.14 1.14 0 0 0-.144.008l-.004.001a1.2 1.2 0 0 0-.138.023h-.002l-.004.002-.009.002-.003.001-.022.006-.019.006h-.003l-.004.002-.018.005-.004.002-.003.001-.018.006-.004.002-.003.001-.018.006-.004.002-.003.001-.017.007-.004.002-.003.001-.017.007-.005.002-.002.001-.018.008-.004.002-.002.002-.017.008-.005.002-.002.002-.017.009-.005.002-.001.002-.018.01-.004.002-.002.002-.017.01-.004.003-.002.002-.017.01-.004.003-.002.002-.016.011-.005.003-.001.002-.017.012-.004.003-.002.002-.016.013-.004.003-.002.002-.015.013-.005.003-.001.003-.016.013-.004.004-.002.002-.015.014-.004.004-.002.002-.015.014-.004.004-.002.002-.014.015-.004.004-.002.002-.014.015-.004.004-.002.002-.013.016-.004.004-.002.003-.013.016-.004.004-.001.003-.013.016-.004.005-.002.002-.012.017-.003.005-.002.002-.012.017-.003.005-.002.003-.011.017-.004.005-.001.003-.011.017-.003.005-.002.003-.01.018-.004.005-.001.003-.01.018-.003.005-.001.003-.01.018-.003.006-.001.003-.009.018-.003.006-.001.003-.009.018-.003.006-.001.003-.008.019-.003.006-.001.003-.008.019-.002.006-.001.003-.007.019-.003.006 0 .004-.008.019-.002.006v.003l-.007.02-.003.006v.003l-.007.02-.002.006v.004l-.006.019-.002.007v.003l-.006.02-.002.006v.004l-.005.02-.002.006v.004l-.005.02-.002.007v.003l-.005.02v.007l-.001.004-.004.02v.007l-.001.004-.003.02-.001.007v.004l-.003.02v.007l-.001.004-.002.02v.007l-.001.005-.002.02v.007l-.001.004v.02l-.001.008v.074zm1.143 2.088v2.73l-2.364 1.365-2.364-1.365V4.095l2.364-1.365zm5.26 1.236 2.113 1.143v2.73l-2.362 1.365-2.364-1.364V6.473zm-10.52 0 2.364 1.143v2.73L5.965 10.57 3.6 9.204V6.473zM12 8.756l2.364 1.365v2.73L12 14.216l-2.364-1.365v-2.73zm-5.26 3.036 2.364 1.365v2.73l-2.364 1.365-2.363-1.365v-2.73zm10.52 0 2.364 1.365v2.73l-2.364 1.365-2.364-1.365v-2.73zM12 14.83l2.364 1.364v2.731L12 20.29l-2.364-1.365v-2.73z"
/>
</svg>
)
}
const services = [
{ name: 'MongoDB', icon: Database },
{ name: 'MeiliSearch', icon: FileSearch },
{ name: 'RAG API', icon: Layers },
{ name: 'Vector DB', icon: HardDrive },
]
const methods = [
{
id: 'docker',
icon: DockerLogo,
title: 'Docker Compose',
recommended: true,
href: '/docs/local/docker',
time: '~5 min',
difficulty: 'Beginner',
description:
'Everything runs in containers. MongoDB, MeiliSearch, RAG API, and Vector DB are all included automatically.',
prereqs: [
{ label: 'Git', href: 'https://git-scm.com/downloads' },
{ label: 'Docker Desktop', href: 'https://www.docker.com/products/docker-desktop/' },
],
commands: [
'git clone https://github.com/danny-avila/LibreChat.git',
'cd LibreChat',
'cp .env.example .env',
'docker compose up -d',
],
included: ['MongoDB', 'MeiliSearch', 'RAG API', 'Vector DB'],
},
{
id: 'npm',
icon: NpmLogo,
title: 'npm',
recommended: false,
href: '/docs/local/npm',
time: '~20 min',
difficulty: 'Intermediate',
description:
'Run LibreChat directly with Node.js. You manage external services like MongoDB and MeiliSearch yourself.',
prereqs: [
{ label: 'Node.js v20.19+', href: 'https://nodejs.org/en/download' },
{ label: 'Git', href: 'https://git-scm.com/downloads' },
{ label: 'MongoDB', href: '/docs/configuration/mongodb/mongodb_atlas' },
],
commands: [
'git clone https://github.com/danny-avila/LibreChat.git',
'cd LibreChat && npm ci',
'cp .env.example .env # edit MONGO_URI',
'npm run backend',
],
included: [],
},
{
id: 'helm',
icon: HelmLogo,
title: 'Helm Chart',
recommended: false,
href: '/docs/local/helm_chart',
time: '~15 min',
difficulty: 'Advanced',
description:
'Deploy on Kubernetes using Helm. Best for production clusters and infrastructure-as-code workflows.',
prereqs: [
{ label: 'Kubernetes cluster', href: null },
{ label: 'kubectl + Helm', href: null },
],
commands: [
'kubectl create secret generic librechat-credentials-env ...',
'helm install librechat oci://ghcr.io/danny-avila/librechat-chart/librechat',
],
included: [],
},
]
function DifficultyBadge({ level }: { level: string }) {
const colors: Record<string, string> = {
Beginner: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400',
Intermediate: 'bg-amber-500/10 text-amber-600 dark:text-amber-400',
Advanced: 'bg-rose-500/10 text-rose-600 dark:text-rose-400',
}
return (
<span className={`rounded-full px-2.5 py-0.5 text-[11px] font-medium ${colors[level] ?? ''}`}>
{level}
</span>
)
}
export function LocalInstallHub() {
return (
<div className="not-prose space-y-10">
{/* Services matrix — what Docker includes */}
<section aria-labelledby="included-heading">
<div className="mb-4 flex items-center gap-3">
<h2
id="included-heading"
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
Bundled with Docker
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
{services.map((svc) => {
const Icon = svc.icon
return (
<div
key={svc.name}
className="flex items-center gap-2.5 rounded-lg border border-fd-border bg-fd-card px-3.5 py-2.5"
>
<Icon className="size-4 shrink-0 text-fd-muted-foreground" aria-hidden="true" />
<span className="text-sm font-medium text-fd-foreground">{svc.name}</span>
</div>
)
})}
</div>
<p className="mt-3 text-xs leading-relaxed text-fd-muted-foreground">
Docker Compose handles all dependencies. With npm or Helm, you install and configure these
services separately.
</p>
</section>
{/* Installation methods */}
<section aria-labelledby="methods-heading">
<div className="mb-4 flex items-center gap-3">
<h2
id="methods-heading"
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
Choose a method
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<div className="space-y-4">
{methods.map((method) => {
const Icon = method.icon
return (
<Link
key={method.id}
href={method.href}
className={`group flex flex-col overflow-hidden rounded-xl border transition-all duration-200 hover:shadow-lg hover:shadow-fd-foreground/[0.03] ${
method.recommended
? 'border-fd-foreground/15 bg-fd-card'
: 'border-fd-border bg-fd-card'
} hover:border-fd-foreground/20`}
>
{/* Card header */}
<div className="flex items-start gap-4 border-b border-fd-border p-5">
<div
className={`shrink-0 rounded-lg border p-2.5 transition-colors ${
method.recommended
? 'border-fd-foreground/10 bg-fd-accent'
: 'border-fd-border bg-fd-background'
} group-hover:border-fd-foreground/20`}
>
<Icon className="size-5" aria-hidden="true" />
</div>
<div className="min-w-0 flex-1">
<div className="mb-1 flex flex-wrap items-center gap-2">
<h3 className="text-base font-semibold text-fd-foreground">{method.title}</h3>
{method.recommended && (
<span className="rounded-full bg-fd-primary px-2.5 py-0.5 text-[11px] font-medium text-fd-primary-foreground">
Recommended
</span>
)}
<DifficultyBadge level={method.difficulty} />
</div>
<p className="text-sm leading-relaxed text-fd-muted-foreground">
{method.description}
</p>
</div>
<div className="hidden shrink-0 items-center gap-1.5 text-xs text-fd-muted-foreground sm:flex">
<Clock className="size-3" aria-hidden="true" />
{method.time}
</div>
</div>
{/* Card body — prereqs + commands side by side */}
<div className="flex flex-col gap-4 p-5 sm:flex-row sm:gap-6">
{/* Prerequisites */}
<div className="sm:w-1/3">
<span className="mb-2 block text-[11px] font-semibold uppercase tracking-wider text-fd-muted-foreground">
Prerequisites
</span>
<ul className="space-y-1.5">
{method.prereqs.map((prereq) => (
<li
key={prereq.label}
className="flex items-center gap-2 text-sm text-fd-muted-foreground"
>
<CheckCircle2 className="size-3 shrink-0" aria-hidden="true" />
{prereq.label}
</li>
))}
</ul>
</div>
{/* Quick commands */}
<div className="flex-1">
<span className="mb-2 block text-[11px] font-semibold uppercase tracking-wider text-fd-muted-foreground">
Commands
</span>
<div className="overflow-hidden rounded-lg border border-fd-border bg-fd-background">
{method.commands.map((cmd, i) => (
<div
key={i}
className={`flex items-start gap-2 px-3 py-1.5 font-mono text-xs text-fd-muted-foreground${
i < method.commands.length - 1 ? ' border-b border-fd-border' : ''
}`}
>
<span
className="select-none text-fd-muted-foreground/40"
aria-hidden="true"
>
$
</span>
<span className="break-all">{cmd}</span>
</div>
))}
</div>
</div>
</div>
{/* Footer CTA */}
<div className="flex items-center justify-between border-t border-fd-border px-5 py-3">
{method.included.length > 0 ? (
<div className="flex flex-wrap items-center gap-1.5">
<Package className="size-3 text-fd-muted-foreground/60" aria-hidden="true" />
{method.included.map((name) => (
<span
key={name}
className="rounded border border-fd-border px-1.5 py-0.5 text-[10px] text-fd-muted-foreground"
>
{name}
</span>
))}
</div>
) : (
<span className="text-[11px] text-fd-muted-foreground/60">
External services required
</span>
)}
<span
className="inline-flex items-center gap-1.5 text-sm font-medium text-fd-foreground transition-colors group-hover:text-fd-foreground/80"
aria-hidden="true"
>
View full guide
<ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
</div>
</Link>
)
})}
</div>
</section>
{/* Remote hosting callout */}
<section aria-labelledby="remote-heading">
<div className="mb-4 flex items-center gap-3">
<h2
id="remote-heading"
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
Not running locally?
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<Link
href="/docs/remote"
className="group flex items-center gap-4 rounded-xl border border-fd-border px-5 py-4 transition-colors hover:bg-fd-accent"
>
<div className="shrink-0 rounded-md bg-fd-accent p-2 transition-colors group-hover:bg-fd-background">
<Server
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">Remote Hosting</span>
<p className="text-xs text-fd-muted-foreground">
DigitalOcean, Railway, Azure, and more
</p>
</div>
<ArrowRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
<Link
href="/docs/configuration/dotenv"
className="group flex items-center gap-4 rounded-xl border border-fd-border px-5 py-4 transition-colors hover:bg-fd-accent"
>
<div className="shrink-0 rounded-md bg-fd-accent p-2 transition-colors group-hover:bg-fd-background">
<Shield
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">.env Configuration</span>
<p className="text-xs text-fd-muted-foreground">
In-depth guide for environment variables
</p>
</div>
<ArrowRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
</div>
</section>
</div>
)
}

View File

@@ -1,126 +1,253 @@
import Link from 'next/link'
import { Container, Plug, Download, Settings, Play, FileCode, ArrowRight } from 'lucide-react'
import { Clock, CheckCircle2, ArrowRight, Plug, FileText, Map, MessageSquare } from 'lucide-react'
import type { ComponentProps } from 'react'
const guides = [
{
icon: Container,
title: 'Local Setup',
description: 'Get LibreChat running on your machine using Docker in just a few steps.',
href: '/docs/quick_start/local_setup',
steps: [
{ icon: Download, label: 'Download the project' },
{ icon: Container, label: 'Install Docker' },
{ icon: Settings, label: 'Configure environment' },
{ icon: Play, label: 'Start LibreChat' },
],
},
{
icon: Plug,
title: 'Custom Endpoints',
description:
'Connect to OpenRouter, Ollama, Deepseek, Groq, Mistral, Databricks, and other OpenAI-compatible services.',
href: '/docs/quick_start/custom_endpoints',
steps: [
{ icon: FileCode, label: 'Create Docker override' },
{ icon: Settings, label: 'Configure librechat.yaml' },
{ icon: Plug, label: 'Add provider endpoints' },
{ icon: Play, label: 'Restart and use' },
],
},
]
function SectionHeading({ children }: { children: React.ReactNode }) {
function DockerLogo(props: ComponentProps<'svg'>) {
return (
<div className="mb-5 flex items-center gap-3">
<h2 className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground">
{children}
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="#2496ED"
d="M13.983 11.078h2.119a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.119a.186.186 0 0 0-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 0 0 .186-.186V3.574a.186.186 0 0 0-.186-.185h-2.118a.186.186 0 0 0-.185.185v1.888c0 .102.082.185.185.186m0 2.716h2.118a.187.187 0 0 0 .186-.186V6.29a.186.186 0 0 0-.186-.185h-2.118a.186.186 0 0 0-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 0 0 .184-.186V6.29a.186.186 0 0 0-.185-.185H8.1a.186.186 0 0 0-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 0 0 .185-.186V6.29a.186.186 0 0 0-.185-.185H5.136a.186.186 0 0 0-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.118a.186.186 0 0 0-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.186.186 0 0 0 .184-.185V9.006a.186.186 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.186.186 0 0 0 .185-.185V9.006a.186.186 0 0 0-.185-.186H5.136a.186.186 0 0 0-.186.185v1.888c0 .102.084.185.186.185m-2.92 0h2.12a.186.186 0 0 0 .184-.185V9.006a.186.186 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.184.186v1.887c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 0 0-.75.748 11.376 11.376 0 0 0 .692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 0 0 3.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"
/>
</svg>
)
}
function NpmLogo(props: ComponentProps<'svg'>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="#CB3837"
d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002z"
/>
</svg>
)
}
function RailwayLogo({ className, ...props }: ComponentProps<'svg'>) {
return (
<svg
className={`text-[#100F13] dark:text-white ${className ?? ''}`}
viewBox="0 0 1024 1024"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M4.756 438.175A520.713 520.713 0 0 0 0 489.735h777.799c-2.716-5.306-6.365-10.09-10.045-14.772-132.97-171.791-204.498-156.896-306.819-161.26-34.114-1.403-57.249-1.967-193.037-1.967-72.677 0-151.688.185-228.628.39-9.96 26.884-19.566 52.942-24.243 74.14h398.571v51.909H4.756ZM783.93 541.696H.399c.82 13.851 2.112 27.517 3.978 40.999h723.39c32.248 0 50.299-18.297 56.162-40.999ZM45.017 724.306S164.941 1018.77 511.46 1024c207.112 0 385.071-123.006 465.907-299.694H45.017Z"
fill="currentColor"
/>
<path
d="M511.454 0C319.953 0 153.311 105.16 65.31 260.612c68.771-.144 202.704-.226 202.704-.226h.031v-.051c158.309 0 164.193.707 195.118 1.998l19.149.706c66.7 2.224 148.683 9.384 213.19 58.19 35.015 26.471 85.571 84.896 115.708 126.52 27.861 38.499 35.876 82.756 16.933 125.158-17.436 38.97-54.952 62.215-100.383 62.215H16.69s4.233 17.944 10.58 37.751h970.632A510.385 510.385 0 0 0 1024 512.218C1024.01 229.355 794.532 0 511.454 0Z"
fill="currentColor"
/>
</svg>
)
}
const methods = [
{
icon: DockerLogo,
title: 'Docker',
tag: 'Recommended',
description: 'Everything included — MongoDB, MeiliSearch, and RAG API run automatically.',
href: '/docs/local/docker',
time: '~5 min',
prereqs: ['Docker Desktop'],
steps: ['Clone the repository', 'Copy .env.example to .env', 'Run docker compose up'],
},
{
icon: NpmLogo,
title: 'npm',
description: 'Manual setup with Node.js. Requires separate MongoDB and MeiliSearch instances.',
href: '/docs/local/npm',
time: '~20 min',
prereqs: ['Node.js v20.19+', 'MongoDB instance'],
steps: [
'Clone and install dependencies',
'Configure .env and start MongoDB',
'Run npm run backend',
],
},
{
icon: RailwayLogo,
title: 'Railway',
tag: 'One-click',
description: 'Deploy to the cloud instantly. No local setup, no Docker, no servers to manage.',
href: '/docs/remote/railway',
time: '~3 min',
prereqs: ['Railway account', 'GitHub account'],
steps: ['Click the deploy button', 'Connect your GitHub', 'Set environment variables'],
},
]
const resources = [
{
icon: FileText,
title: 'Changelog',
description: 'Latest releases',
href: '/changelog',
},
{
icon: Map,
title: '2025 Roadmap',
description: "What's planned",
href: '/blog/2025-02-20_2025_roadmap',
},
{
icon: MessageSquare,
title: 'Discord',
description: 'Get help',
href: 'https://discord.librechat.ai',
},
]
export function QuickStartHub() {
return (
<nav className="not-prose space-y-10" aria-label="Quick start guides">
{/* Guides */}
<section aria-labelledby="guides-heading">
<SectionHeading>
<span id="guides-heading">Choose a Guide</span>
</SectionHeading>
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
{guides.map((guide) => {
const Icon = guide.icon
return (
<Link
key={guide.title}
href={guide.href}
className="group relative flex flex-col rounded-xl border border-fd-border bg-fd-card p-6 transition-all duration-200 hover:border-fd-foreground/20 hover:shadow-lg hover:shadow-fd-foreground/[0.03]"
>
<div className="mb-4 inline-flex rounded-lg border border-fd-border bg-fd-background p-2.5 transition-colors group-hover:border-fd-foreground/20">
<Icon
className="size-5 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
<nav className="not-prose space-y-8" aria-label="Quick start guides">
{/* Installation methods */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{methods.map((method) => {
const Icon = method.icon
return (
<Link
key={method.title}
href={method.href}
className="group flex flex-col rounded-xl border border-fd-border bg-fd-card transition-all duration-200 hover:border-fd-foreground/20 hover:shadow-lg hover:shadow-fd-foreground/[0.03]"
>
{/* Header */}
<div className="border-b border-fd-border p-5 pb-4">
<div className="mb-3 flex items-center justify-between">
<div className="inline-flex rounded-lg border border-fd-border bg-fd-background p-2 transition-colors group-hover:border-fd-foreground/20">
<Icon className="size-5" aria-hidden="true" />
</div>
<div className="flex items-center gap-2">
{method.tag && (
<span className="rounded-full border border-fd-border px-2 py-0.5 text-[10px] font-medium text-fd-muted-foreground">
{method.tag}
</span>
)}
<span className="inline-flex items-center gap-1 text-xs text-fd-muted-foreground">
<Clock className="size-3" aria-hidden="true" />
{method.time}
</span>
</div>
</div>
<h3 className="mb-1.5 text-base font-semibold text-fd-foreground">{guide.title}</h3>
<p className="mb-5 text-sm leading-relaxed text-fd-muted-foreground">
{guide.description}
<h3 className="mb-1 text-base font-semibold text-fd-foreground">{method.title}</h3>
<p className="text-sm leading-relaxed text-fd-muted-foreground">
{method.description}
</p>
</div>
{/* Step preview */}
<ol
className="mb-5 flex flex-1 flex-col gap-2.5"
aria-label={`${guide.title} steps`}
>
{guide.steps.map((step, i) => {
return (
<li key={step.label} className="flex items-center gap-3">
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-fd-accent text-[10px] font-semibold text-fd-muted-foreground">
{i + 1}
</span>
<span className="text-xs text-fd-muted-foreground">{step.label}</span>
</li>
)
})}
{/* Steps + prereqs */}
<div className="flex flex-1 flex-col justify-between p-5">
<ol className="mb-4 space-y-2" aria-label={`${method.title} steps`}>
{method.steps.map((step, i) => (
<li key={step} className="flex items-start gap-2.5">
<span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-fd-accent text-[10px] font-semibold tabular-nums text-fd-muted-foreground">
{i + 1}
</span>
<span className="text-sm leading-5 text-fd-muted-foreground">{step}</span>
</li>
))}
</ol>
<span
className="inline-flex items-center gap-1 text-sm font-medium text-fd-muted-foreground transition-all group-hover:gap-1.5 group-hover:text-fd-foreground"
<div>
<ul className="mb-4 space-y-1">
{method.prereqs.map((prereq) => (
<li
key={prereq}
className="flex items-center gap-2 text-xs text-fd-muted-foreground"
>
<CheckCircle2 className="size-3 shrink-0" aria-hidden="true" />
{prereq}
</li>
))}
</ul>
<span
className="inline-flex items-center gap-1.5 text-sm font-medium text-fd-foreground transition-colors group-hover:text-fd-foreground/80"
aria-hidden="true"
>
Get started
<ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
</div>
</div>
</Link>
)
})}
</div>
{/* Next step callout */}
<section aria-labelledby="next-step-heading">
<div className="flex items-center gap-3 mb-4">
<h2
id="next-step-heading"
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
After Installation
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<Link
href="/docs/quick_start/custom_endpoints"
className="group flex items-center gap-4 rounded-xl border border-fd-border px-5 py-4 transition-colors hover:bg-fd-accent"
>
<div className="shrink-0 rounded-md bg-fd-accent p-2 transition-colors group-hover:bg-fd-background">
<Plug
className="size-4 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-sm font-medium text-fd-foreground">Connect AI Providers</span>
<p className="text-xs text-fd-muted-foreground">
Add OpenRouter, Ollama, Deepseek, Groq, and other OpenAI-compatible services
</p>
</div>
<ArrowRight
className="size-4 shrink-0 text-fd-muted-foreground/40 transition-all group-hover:translate-x-0.5 group-hover:text-fd-foreground"
aria-hidden="true"
/>
</Link>
</section>
{/* Resources */}
<section aria-labelledby="resources-heading">
<div className="flex items-center gap-3 mb-4">
<h2
id="resources-heading"
className="shrink-0 text-xs font-semibold uppercase tracking-widest text-fd-muted-foreground"
>
Resources
</h2>
<div className="h-px flex-1 bg-fd-border" aria-hidden="true" />
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
{resources.map((item) => {
const Icon = item.icon
const isExternal = item.href.startsWith('http')
return (
<Link
key={item.title}
href={item.href}
{...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
className="group flex items-center gap-3 rounded-lg border border-fd-border px-4 py-3 transition-all hover:border-fd-foreground/20 hover:bg-fd-accent"
>
<Icon
className="size-4 shrink-0 text-fd-muted-foreground transition-colors group-hover:text-fd-foreground"
aria-hidden="true"
>
Start guide
<ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" />
</span>
/>
<div className="min-w-0">
<span className="text-sm font-medium text-fd-foreground">{item.title}</span>
<span className="ml-2 text-xs text-fd-muted-foreground">{item.description}</span>
</div>
</Link>
)
})}
</div>
</section>
{/* Tip */}
<section aria-labelledby="tip-heading">
<SectionHeading>
<span id="tip-heading">Not sure where to start?</span>
</SectionHeading>
<p className="text-sm leading-relaxed text-fd-muted-foreground">
If this is your first time, start with{' '}
<Link
href="/docs/quick_start/local_setup"
className="font-medium text-fd-foreground underline decoration-fd-border underline-offset-4 transition-colors hover:decoration-fd-foreground"
>
Local Setup
</Link>{' '}
to get LibreChat running with Docker. Once running, use{' '}
<Link
href="/docs/quick_start/custom_endpoints"
className="font-medium text-fd-foreground underline decoration-fd-border underline-offset-4 transition-colors hover:decoration-fd-foreground"
>
Custom Endpoints
</Link>{' '}
to connect additional AI providers beyond the defaults.
</p>
</section>
</nav>
)
}

View File

@@ -1,13 +1,15 @@
import { type ReactNode } from 'react'
import Image, { type StaticImageData } from 'next/image'
import {
GitFork,
BrainCog,
Code,
Bot,
Search,
Image as ImageIcon,
Terminal,
Settings2,
Code,
Search,
Plug,
Brain,
Globe,
ShieldCheck,
ArrowRight,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
@@ -17,7 +19,6 @@ import ArtifactsLight from './img/artifacts_light.png'
import ArtifactsDark from './img/artifacts_dark.png'
import AgentsLight from './img/agents_light.png'
import AgentsDark from './img/agents_dark.png'
import { cn } from '@/lib/utils'
import { Header } from '../Header'
import Link from 'next/link'
@@ -32,11 +33,11 @@ const BentoBgImage = ({
}) => (
<>
<Image
className="opacity-60 top-0 right-0 dark:hidden hidden md:block"
className="opacity-50 top-0 right-0 dark:hidden hidden md:block"
style={{
objectFit: 'contain',
objectPosition: 'top right',
maskImage: 'linear-gradient(to top, rgba(0,0,0,0) 15%, rgba(0,0,0,1))',
maskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 20%, rgba(0,0,0,0) 90%)',
}}
src={imgLight}
fill
@@ -44,11 +45,11 @@ const BentoBgImage = ({
sizes="(min-width: 1024px) 33vw, 100vw"
/>
<Image
className="opacity-60 top-0 right-0 hidden dark:md:block"
className="opacity-50 top-0 right-0 hidden dark:md:block"
style={{
objectFit: 'contain',
objectPosition: 'top right',
maskImage: 'linear-gradient(to top, rgba(0,0,0,0) 15%, rgba(0,0,0,1))',
maskImage: 'linear-gradient(to bottom, rgba(0,0,0,1) 20%, rgba(0,0,0,0) 90%)',
}}
src={imgDark}
fill
@@ -65,7 +66,6 @@ type Feature = {
href: string
cta: string
background: ReactNode | null
className: string
}
const features: Feature[] = [
@@ -74,71 +74,77 @@ const features: Feature[] = [
name: 'Agents',
description: 'Advanced agents with file handling, code interpretation, and API actions',
href: '/docs/features/agents',
cta: 'Meet the Agents!',
cta: 'Meet the Agents',
background: <BentoBgImage imgLight={AgentsLight} imgDark={AgentsDark} alt="Agents" />,
className: 'md:row-start-1 md:row-end-4 md:col-start-2 md:col-end-2',
},
{
Icon: Terminal,
name: 'Code Interpreter',
description:
'Execute code in multiple languages securely via API with zero setup - Python, JavaScript, TypeScript, Go, and more',
description: 'Execute code in multiple languages securely with zero setup',
href: '/docs/features/code_interpreter',
cta: 'Start Coding!',
cta: 'Start Coding',
background: (
<BentoBgImage imgLight={CodeInterpreter} imgDark={CodeInterpreter} alt="Artifacts" />
<BentoBgImage imgLight={CodeInterpreter} imgDark={CodeInterpreter} alt="Code Interpreter" />
),
className: 'md:col-start-1 md:col-end-2 md:row-start-1 md:row-end-3',
},
{
Icon: BrainCog,
Icon: Settings2,
name: 'Models',
description: 'AI model selection including Anthropic, AWS, OpenAI, Azure, and more',
href: '/docs/configuration/pre_configured_ai',
cta: 'Pick Your Brain!',
cta: 'Browse Models',
background: null,
className: 'md:col-start-1 md:col-end-2 md:row-start-3 md:row-end-4',
},
{
Icon: Code,
name: 'Artifacts',
description: 'Create React, HTML code, and Mermaid diagrams in chat',
href: '/docs/features/artifacts',
cta: 'Craft Some Code!',
cta: 'Craft Some Code',
background: <BentoBgImage imgLight={ArtifactsLight} imgDark={ArtifactsDark} alt="Artifacts" />,
className: 'md:col-start-3 md:col-end-3 md:row-start-1 md:row-end-2',
},
{
Icon: ImageIcon,
name: 'Multimodal',
description: 'Analyze images and chat with files using various endpoints',
href: '/docs/features',
cta: 'Image This!',
background: null,
className: 'md:col-start-3 md:col-end-3 md:row-start-2 md:row-end-3',
},
{
Icon: GitFork,
name: 'Fork',
description:
'Split messages to create multiple conversation threads for better context control',
href: '/docs/features/fork',
cta: 'Fork It Up!',
background: null,
className: 'md:col-start-3 md:col-end-3 md:row-start-3 md:row-end-4',
},
{
Icon: Search,
name: 'Search',
description: 'Search for messages, files, and code snippets in an instant',
href: '/docs/configuration/meilisearch',
cta: 'Find It Fast!',
cta: 'Find It Fast',
background: null,
},
{
Icon: Plug,
name: 'MCP',
description: 'Connect to any tool or service with Model Context Protocol support',
href: '/docs/features/mcp',
cta: 'Connect Tools',
background: null,
},
{
Icon: Brain,
name: 'Memory',
description: 'Persistent context across conversations so your AI remembers you',
href: '/docs/features/memory',
cta: 'Learn More',
background: null,
},
{
Icon: Globe,
name: 'Web Search',
description: 'Give any model live internet access with built-in search and reranking',
href: '/docs/features/web_search',
cta: 'Explore Search',
background: null,
},
{
Icon: ShieldCheck,
name: 'Authentication',
description: 'Enterprise-ready SSO with OAuth, SAML, LDAP, and two-factor auth',
href: '/docs/configuration/authentication',
cta: 'Secure Your Instance',
background: null,
className: 'md:col-start-3 md:col-end-3 md:row-start-3 md:row-end-4',
},
]
/** Homepage features showcase using a bento grid layout with linked feature cards. */
export default function Features() {
return (
<HomeSection>
@@ -147,36 +153,30 @@ export default function Features() {
description="Explore our unique and powerful features"
button={{ href: '/docs', text: 'Explore docs' }}
/>
<div className="grid w-full auto-rows-[13rem] grid-cols-3 gap-3">
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<Link
key={feature.name}
className={cn(
'group relative col-span-3 flex flex-col justify-between overflow-hidden rounded border',
'bg-white',
'transform-gpu dark:bg-transparent dark:backdrop-blur-md',
feature.className,
)}
className="group relative flex h-52 flex-col justify-between overflow-hidden rounded-xl border border-neutral-200 bg-white transition-colors dark:border-neutral-800 dark:bg-neutral-950"
href={feature.href}
>
{feature.background}
<div />
<div className="pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10">
<feature.Icon className="h-8 w-8 lg:h-12 lg:w-12 origin-left transform-gpu text-neutral-600 transition-all duration-300 ease-in-out group-hover:scale-75" />
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
<div className="pointer-events-none z-10 flex flex-col gap-1 p-5 transition-all duration-300 group-hover:-translate-y-8">
<feature.Icon className="mb-1 size-7 text-neutral-500 transition-all duration-300 group-hover:scale-90 dark:text-neutral-400" />
<h3 className="text-base font-semibold text-neutral-800 dark:text-neutral-200">
{feature.name}
</h3>
<p className="max-w-lg dark:text-neutral-400 text-neutral-500">
<p className="text-sm leading-relaxed text-neutral-500 dark:text-neutral-400">
{feature.description}
</p>
</div>
<div className="pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100">
<Button variant="ghost" size="sm" className="ml-2 pointer-events-auto">
<div className="pointer-events-none absolute bottom-0 flex w-full translate-y-10 items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100">
<Button variant="ghost" size="sm" className="pointer-events-auto">
{feature.cta}
<ArrowRight className="ml-2 h-4 w-4" />
<ArrowRight className="ml-1.5 size-3.5" />
</Button>
</div>
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/20" />
<div className="pointer-events-none absolute inset-0 transition-colors duration-300 group-hover:bg-black/[.02] dark:group-hover:bg-white/[.02]" />
</Link>
))}
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,7 +1,7 @@
---
title: Get Started
title: Quick Start
icon: Rocket
description: Everything you need to get up and running with LibreChat, the open-source AI chat platform.
description: Choose your setup method and get LibreChat running in minutes.
---
<DocsHub />
<QuickStartHub />

View File

@@ -1,23 +1,7 @@
---
title: Overview
icon: BookOpen
title: Local Installation
icon: Monitor
description: How to install LibreChat locally
---
**We recommend using `Docker Compose` to install LibreChat**, as it is the easiest, simplest, and most reliable method for getting started. With Docker, you can quickly spin up a container that includes everything you need to run LibreChat, including MongoDB, MeiliSearch, as well as the rag_api & vectordb for file support across all endpoints. This approach ensures consistency across different environments and eliminates the need for manual setup of dependencies, making it ideal for most use cases.
Alternatively, you can install LibreChat using the `npm` install method. However, this method requires manual setup of MongoDB, MeiliSearch, and the "rag_api + vectordb" dependencies, which can be time-consuming and error-prone.
Whether you choose the Docker, npm install method, or Helm chart for Kubernetes, we have detailed instructions to help you get started with LibreChat.
<Cards num={3}>
<Card title="Docker" href="/docs/local/docker" arrow>
The easiest and most reliable method. Includes MongoDB, MeiliSearch, and RAG API.
</Card>
<Card title="npm" href="/docs/local/npm" arrow>
Manual setup with npm. Requires separate MongoDB, MeiliSearch, and RAG API installation.
</Card>
<Card title="Helm Chart" href="/docs/local/helm_chart" arrow>
Deploy LibreChat on Kubernetes using Helm charts.
</Card>
</Cards>
<LocalInstallHub />

View File

@@ -1,11 +1,12 @@
{
"pages": [
"index",
"quick_start",
"features",
"---Deploy---",
"local",
"remote",
"configuration",
"---Learn---",
"features",
"user_guides",
"translation",
"---Contributing---",

View File

@@ -1,7 +1,7 @@
---
title: Overview
icon: BookOpen
description: Quick start guides to get up and running with LibreChat as quickly as possible.
title: Quick Start
icon: Rocket
description: Choose your setup method and get LibreChat running in minutes.
---
<QuickStartHub />

View File

@@ -2,11 +2,12 @@ import defaultMdxComponents from 'fumadocs-ui/mdx'
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tabs } from 'fumadocs-ui/components/tabs'
import { File, Folder, Files } from 'fumadocs-ui/components/files'
import { File as FumadocsFile, Folder, Files } from 'fumadocs-ui/components/files'
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
import { OptionTable } from '@/components/table'
import { Frame } from '@/components/Frame'
import { DocsHub } from '@/components/DocsHub'
import { LocalInstallHub } from '@/components/LocalInstallHub'
import { QuickStartHub } from '@/components/QuickStartHub'
import { FeaturesHub } from '@/components/FeaturesHub'
import Carousel from '@/components/carousel/Carousel'
@@ -169,7 +170,27 @@ function CardCompat({
function FileTreeCompat({ children, ...props }: { children?: ReactNode; [key: string]: any }) {
return <Files {...props}>{children}</Files>
}
;(FileTreeCompat as any).File = File
function FileCompat({
active,
className,
...props
}: {
active?: boolean
className?: string
name: string
icon?: ReactNode
[key: string]: any
}) {
return (
<FumadocsFile
className={
active ? `${className ?? ''} bg-fd-accent text-fd-accent-foreground`.trim() : className
}
{...props}
/>
)
}
;(FileTreeCompat as any).File = FileCompat
;(FileTreeCompat as any).Folder = Folder
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -197,7 +218,7 @@ export const mdxComponents = {
Cards: CardsCompat,
Card: CardCompat,
FileTree: FileTreeCompat,
File,
File: FileCompat,
Folder,
Files,
Accordion,
@@ -208,6 +229,7 @@ export const mdxComponents = {
DocsHub,
QuickStartHub,
FeaturesHub,
LocalInstallHub,
Button: ({ children, ...props }: { children?: ReactNode; [key: string]: any }) => (
<button className="rounded-md bg-fd-primary px-4 py-2 text-fd-primary-foreground" {...props}>
{children}

View File

@@ -1,155 +0,0 @@
---
title: About
description: 'LibreChat: Every AI for Everyone'
---
import LCLogo from '/components/lcLogo'
import Carousel from '/components/carousel/Carousel'
import Github from '@/components/icons/github'
import Discord from '@/components/icons/discord'
import { Mail } from 'lucide-react'
import Image from 'next/image'
<div style={{ marginBottom: '-10px', marginTop: '20px' }}>
<LCLogo />
</div>
# LibreChat
**Every AI for Everyone**
<p className="_flex _h-6 _mt-4 _gap-2">
<a href="https://github.com/danny-avila/LibreChat/blob/main/LICENSE" target="_blank">
<img
alt="license"
src="https://img.shields.io/github/license/danny-avila/LibreChat?style=for-the-badge&logo=github&logoColor=white&labelColor=000000"
loading="lazy"
decoding="async"
></img>
</a>
<a aria-label="Sponsors" href="https://github.com/sponsors/danny-avila" target="_blank">
<img
alt="sponsors"
src="https://img.shields.io/badge/SPONSORS-red.svg?style=for-the-badge&logo=github-sponsors&logoColor=white&labelColor=000000&logoWidth=20"
loading="lazy"
decoding="async"
></img>
</a>
</p>
**LibreChat** is a free, open-source AI chat platform that empowers you to harness the capabilities of cutting-edge language models from multiple providers in a unified interface. With its vast customization options, innovative enhancements, and seamless integration of AI services, LibreChat offers an unparalleled conversational experience.
<div>&nbsp;</div>
<div style={{ maxWidth: '100%', margin: 'auto' }}>
<Carousel autoplay animationDuration={4000} perView={1}>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/7fd1602d-6c93-487c-913c-1551e4e5a421" alt="l-login" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/68407d02-480c-493e-93b1-1c4e2753d4bb" alt="d-login" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/0f231e74-bca5-462a-9ed9-4a8245666ddd" alt="l-models" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/c11047fd-e297-4063-a3f9-cae7a5d01d59" alt="d-models" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/ca038204-61e7-44ac-9e6a-3a9758928171" alt="l-2cats" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/7b2552a6-8a00-4f4b-8b32-869f62a7841a" alt="d-2cats" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/7c6b6641-d303-473d-bbdc-befbd0453de3" alt="l-modelspecs" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/946c4801-07e4-4b87-922b-2152baa60fdf" alt="d-modelspecs" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/936f4823-6c52-449a-bc36-45aa0b8d5971" alt="l-graph" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/fd7010e6-5df5-4990-8eff-1521b6b02139" alt="d-graph" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/169a452c-32be-4b48-b68a-7571ef9e672c" alt="l-websearch" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/d235d81f-319e-4dbe-8f70-d53d6813771b" alt="d-websearch" width={320} height={512} />
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<div className="image-light-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/9473cec0-0b29-4785-b82d-c92cb58a970d" alt="l-banana" width={320} height={512} />
</div>
<div className="image-dark-theme">
<Image src="https://github.com/danny-avila/LibreChat/assets/32828263/3e8c742d-7ad1-4649-8fe9-3114bcfa6849" alt="d-banana" width={320} height={512} />
</div>
</div>
</Carousel>
</div>
## What is LibreChat?
LibreChat is an enhanced, open-source ChatGPT clone that brings together the latest advancements in AI technology. It serves as a centralized hub for all your AI conversations, providing a familiar, user-friendly interface enriched with advanced features and customization capabilities.
## Why LibreChat?
### 🔓 Open-Source and Free
LibreChat is a completely open-source project, allowing you to freely use, modify, and distribute the software without any restrictions or paid subscriptions.
### 🤖 Multi-Provider AI Support
LibreChat supports a wide range of AI providers, including OpenAI, Azure, Anthropic, Google, and more. You can seamlessly switch between different models and leverage their unique capabilities.
### 💻 Customizable and Extensible
With its modular design and extensive customization options, LibreChat can be tailored to meet your specific needs. You can create and share custom presets, integrate plugins, and even contribute to the project's development.
### 🌐 Multilingual Support
LibreChat's user interface is available in multiple languages, ensuring accessibility and ease of use for a global audience.
## Design Principles
LibreChat adheres to the following key design principles:
1. **User-Friendly Interface**: Inspired by the familiar ChatGPT UI, LibreChat offers a clean and intuitive layout, making it easy for users to engage with AI assistants.
2. **Multimodal Conversations**: LibreChat supports multimodal conversations, allowing you to upload and analyze images, chat with files, and leverage advanced agent capabilities powered by AI models like GPT-4 Claude and Gemini Vision.
3. **Extensibility**: With its plugin architecture and open-source nature, LibreChat encourages the development of custom extensions and integrations, enabling users to tailor the platform to their specific needs.
4. **Privacy and Security**: LibreChat prioritizes privacy and security by offering secure authentication, moderation tools, and the ability to self-host the application.
## License
Released under the MIT License.
## Contact us
<Cards num={3}>
<Cards.Card title="GitHub Discussions" href="/gh-discussions" icon={<Github />} />
<Cards.Card title="Discord" href="/discord" icon={<Discord />} />
<Cards.Card title="contact@librechat.ai" href="mailto:contact@librechat.ai" icon={<Mail />} />
</Cards>
## 💖 This project exists in its current state thanks to all the people who contribute
<a href="https://github.com/danny-avila/LibreChat/graphs/contributors">
<img src="https://contrib.rocks/image?repo=danny-avila/LibreChat" />
</a>

View File

@@ -1,60 +0,0 @@
---
title: Cookie Policy
searchable: false
---
# Cookie Policy for LibreChat Documentation
**Last updated: June 13, 2025**
## No Cookies Used
The LibreChat documentation website ([https://librechat.ai](https://librechat.ai)) **does not use cookies** or any similar tracking technologies.
## What This Means
- We don't set or read any cookies
- We don't track your browsing behavior
- We don't collect any personal information
- We don't use analytics or advertising cookies
- We don't use third-party tracking services
## Browser Storage
While we don't use cookies, this documentation site may use browser features like:
- **Local Storage**: To remember your theme preference (light/dark mode)
- **Session Storage**: To maintain navigation state during your visit
This data:
- Is stored only in your browser
- Is never transmitted to any server
- Can be cleared by clearing your browser data
- Does not identify you personally
## Third-Party Links
Our documentation may contain links to external websites that might use cookies. We are not responsible for the cookie practices of external sites. Please review their cookie policies when visiting them.
## Your Privacy
Since we don't use cookies:
- There's nothing to accept or decline
- No tracking occurs
- Your browsing is completely private
- No consent banner is needed
## Changes to This Policy
We may update this cookie policy for clarity. Any updates will be posted on this page with a new "Last updated" date. However, we do not anticipate adding cookies to this documentation site.
## Contact
For questions about this policy or the LibreChat project, please visit:
[https://github.com/danny-avila/LibreChat](https://github.com/danny-avila/LibreChat)
---
By using this documentation site, you acknowledge that no cookies or tracking technologies are employed.

View File

@@ -1,71 +0,0 @@
---
searchable: false
---
# Privacy Policy for LibreChat Documentation
**Last updated: June 13, 2025**
## Overview
This privacy policy describes how the LibreChat documentation website ([https://librechat.ai](https://librechat.ai)) handles user privacy.
**In short: We don't collect any personal data.**
## No Data Collection
The LibreChat documentation website:
- **Does not collect** any personal information
- **Does not use** cookies or tracking technologies
- **Does not require** user registration or accounts
- **Does not use** analytics or monitoring tools
- **Does not store** any user data
## Purpose of This Site
This website serves exclusively as documentation for the open-source LibreChat project. It provides:
- Installation and setup guides
- Configuration documentation
- API references
- User guides
- Contributing guidelines
- Changelog and release notes
## External Links
Our documentation may contain links to external resources such as:
- The LibreChat GitHub repository
- Third-party service documentation
- Community forums and discussions
We are not responsible for the privacy practices of these external sites. Please review their privacy policies when visiting them.
## Local Browser Storage
While we don't collect any data, your browser may store:
- Theme preferences (light/dark mode) locally
- Navigation state for your convenience
This information is stored only in your browser and is never transmitted to any server.
## Open Source
LibreChat is an open-source project licensed under the MIT License. The source code is publicly available at:
[https://github.com/danny-avila/LibreChat](https://github.com/danny-avila/LibreChat)
## Changes to This Policy
We may update this privacy policy for clarity or to reflect changes in our practices. Any updates will be posted on this page with a new "Last updated" date.
## Contact
For questions about this privacy policy or the LibreChat project, please visit our GitHub repository:
[https://github.com/danny-avila/LibreChat](https://github.com/danny-avila/LibreChat)
---
By using this documentation site, you acknowledge that no personal data is collected or processed.

View File

@@ -1,8 +0,0 @@
---
title: Terms of Services
searchable: false
---
import { TermsOfServices } from '@/components/policies'
<TermsOfServices />

File diff suppressed because one or more lines are too long