feat(docs): add documentation site and demo features

- Introduced a new documentation site for Cursor BYOK using Next.js and Fumadocs.
- Added a product demo page with a corresponding Vite configuration.
- Implemented a demo API to simulate LLM calls and responses.
- Enhanced the Makefile to include new build and development commands for the documentation.
- Updated package.json scripts for building and running the documentation site.
- Created various components and layouts for the documentation structure, including blog and user documentation sections.
- Added styling for the new components and layouts to ensure a cohesive design.
- Included a README and other necessary files for local development and deployment.
This commit is contained in:
leokun
2026-08-27 21:45:28 +08:00
parent ee915ee760
commit c5d578c5b1
70 changed files with 13299 additions and 567 deletions
+35
View File
@@ -0,0 +1,35 @@
import { loader } from 'fumadocs-core/source';
import { pageSchema } from 'fumadocs-core/source/schema';
import { defineCollections } from 'fumadocs-mdx/macro';
const blog = defineCollections({
type: 'doc',
dir: 'content/blog',
schema: pageSchema,
});
export const blogSource = loader({
baseUrl: '/blog',
source: blog.toFumadocsSource(),
});
export function getBlogDate(path: string): Date {
const fileName = path.split('/').at(-1) ?? '';
const match = /^(\d{4}-\d{2}-\d{2})/.exec(fileName);
return match ? new Date(`${match[1]}T00:00:00Z`) : new Date(0);
}
export function formatBlogDate(path: string): string {
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC',
}).format(getBlogDate(path));
}
export function sortBlogPages<T extends { path: string }>(pages: T[]): T[] {
return [...pages].sort((left, right) => {
return getBlogDate(right.path).getTime() - getBlogDate(left.path).getTime();
});
}
+1
View File
@@ -0,0 +1 @@
export { cn } from 'cnfast';
+31
View File
@@ -0,0 +1,31 @@
import { zhCN } from '@fumadocs/language/zh-cn';
import { defineTranslations } from 'fumadocs-core/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
import { uiTranslations } from 'fumadocs-ui/i18n';
import { appName, gitConfig, releaseUrl } from './shared';
export const translations = defineTranslations().extend(uiTranslations()).preset(zhCN());
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: appName,
},
links: [
{
text: '文档',
url: '/docs',
},
{
text: '开发者博客',
url: '/blog',
},
{
text: '下载',
url: releaseUrl,
external: true,
},
],
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
};
}
+13
View File
@@ -0,0 +1,13 @@
export const appName = 'cursor-byok';
export const appDescription = 'Cursor 服务端的开源替代';
export const docsRoute = '/docs';
export const docsImageRoute = '/og/docs';
export const docsContentRoute = '/llms.mdx/docs';
export const repositoryUrl = 'https://github.com/leookun/cursor-byok';
export const releaseUrl = `${repositoryUrl}/releases/latest`;
export const gitConfig = {
user: 'leookun',
repo: 'cursor-byok',
branch: 'main',
};
+51
View File
@@ -0,0 +1,51 @@
import { loader } from 'fumadocs-core/source';
import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons';
import { docsContentRoute, docsImageRoute, docsRoute } from './shared';
import { defineDocs } from 'fumadocs-mdx/macro';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
const docs = defineDocs({
dir: 'content/docs',
docs: {
schema: pageSchema,
postprocess: {
includeProcessedMarkdown: true,
},
},
meta: {
schema: metaSchema,
},
});
// See https://fumadocs.dev/docs/headless/source-api for more info
export const source = loader({
baseUrl: docsRoute,
source: docs.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
});
export function getPageImageUrl(page: (typeof source)['$inferPage']) {
const segments = [...page.slugs, 'image.png'];
return {
segments,
url: '/' + [page.locale, ...docsImageRoute.split('/'), ...segments].filter(Boolean).join('/'),
};
}
export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) {
const segments = [...page.slugs, 'content.md'];
return {
segments,
url: '/' + [page.locale, ...docsContentRoute.split('/'), ...segments].filter(Boolean).join('/'),
};
}
export async function getLLMText(page: (typeof source)['$inferPage']) {
const processed = await page.data.getText('processed');
return `# ${page.data.title} (${page.url})
${processed}`;
}