Skip to content

Commit 86d940a

Browse files
committed
Update Geistdocs
1 parent bc87126 commit 86d940a

5 files changed

Lines changed: 220 additions & 16 deletions

File tree

apps/docs/app/[lang]/(home)/components/centered-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const CenteredSection = ({
1212
children,
1313
}: CenteredSectionProps) => (
1414
<div className="grid items-center gap-10 overflow-hidden px-4 py-8 sm:px-12 sm:py-12">
15-
<div className="mx-auto grid max-w-xl gap-4 text-center">
15+
<div className="mx-auto grid max-w-lg gap-4 text-center">
1616
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl md:text-3xl lg:text-[40px]">
1717
{title}
1818
</h2>

apps/docs/app/[lang]/llms.txt/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ export const GET = async (
1313
const scan = pages.map((page) => getLLMText(page));
1414
const scanned = await Promise.all(scan);
1515

16-
return new Response(scanned.join("\n\n"));
16+
return new Response(scanned.join("\n\n"), {
17+
headers: {
18+
"Content-Type": "text/markdown; charset=utf-8",
19+
},
20+
});
1721
};
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import type { NextRequest } from "next/server";
2+
import { source } from "@/lib/geistdocs/source";
3+
4+
export const revalidate = false;
5+
6+
const DOCS_PREFIX_PATTERN = /^\/docs\/?/;
7+
const WHITESPACE_PATTERN = /\s+/;
8+
9+
type PageNode = {
10+
title: string;
11+
description: string;
12+
url: string;
13+
type?: string;
14+
summary?: string;
15+
prerequisites?: string[];
16+
product?: string;
17+
lastmod?: string;
18+
children: PageNode[];
19+
};
20+
21+
function buildTree(
22+
pages: Array<{
23+
url: string;
24+
data: {
25+
title: string;
26+
description?: string;
27+
type?: string;
28+
summary?: string;
29+
prerequisites?: string[];
30+
product?: string;
31+
lastModified?: Date;
32+
};
33+
}>
34+
): PageNode[] {
35+
const root: PageNode[] = [];
36+
const map = new Map<string, PageNode>();
37+
38+
const sorted = [...pages].sort((a, b) => a.url.localeCompare(b.url));
39+
40+
for (const page of sorted) {
41+
const node: PageNode = {
42+
title: page.data.title,
43+
description: page.data.description ?? "",
44+
url: page.url,
45+
type: page.data.type,
46+
summary: page.data.summary,
47+
prerequisites: page.data.prerequisites,
48+
product: page.data.product,
49+
lastmod: page.data.lastModified
50+
? page.data.lastModified.toISOString().split("T")[0]
51+
: undefined,
52+
children: [],
53+
};
54+
map.set(page.url, node);
55+
56+
const segments = page.url.split("/").filter(Boolean);
57+
if (segments.length <= 1) {
58+
root.push(node);
59+
} else {
60+
const parentUrl = `/${segments.slice(0, -1).join("/")}`;
61+
const parent = map.get(parentUrl);
62+
if (parent) {
63+
parent.children.push(node);
64+
} else {
65+
root.push(node);
66+
}
67+
}
68+
}
69+
70+
return root;
71+
}
72+
73+
function inferDocType(url: string, explicitType?: string): string {
74+
if (explicitType) {
75+
return explicitType.charAt(0).toUpperCase() + explicitType.slice(1);
76+
}
77+
if (url.includes("/getting-started")) {
78+
return "Guide";
79+
}
80+
if (url.includes("/reference")) {
81+
return "Reference";
82+
}
83+
if (url.includes("/guides/")) {
84+
return "Guide";
85+
}
86+
return "Conceptual";
87+
}
88+
89+
function extractTopics(url: string, product?: string): string[] {
90+
const topics: string[] = [];
91+
if (product) {
92+
topics.push(product);
93+
}
94+
95+
const segments = url
96+
.replace(DOCS_PREFIX_PATTERN, "")
97+
.split("/")
98+
.filter(Boolean);
99+
100+
for (const segment of segments) {
101+
if (!topics.includes(segment)) {
102+
topics.push(segment);
103+
}
104+
if (topics.length >= 3) {
105+
break;
106+
}
107+
}
108+
109+
return topics.slice(0, 3);
110+
}
111+
112+
function truncateToWords(text: string, maxWords: number): string {
113+
const words = text.split(WHITESPACE_PATTERN);
114+
if (words.length <= maxWords) {
115+
return text;
116+
}
117+
return `${words.slice(0, maxWords).join(" ")}...`;
118+
}
119+
120+
function renderNode(
121+
node: PageNode,
122+
indent: number,
123+
parentTitle?: string
124+
): string {
125+
const prefix = " ".repeat(indent);
126+
const lines: string[] = [];
127+
128+
const segments: string[] = [];
129+
segments.push(`Type: ${inferDocType(node.url, node.type)}`);
130+
131+
if (node.lastmod) {
132+
segments.push(`Lastmod: ${node.lastmod}`);
133+
}
134+
135+
const summary = node.summary || node.description;
136+
if (summary) {
137+
segments.push(`Summary: ${truncateToWords(summary, 100)}`);
138+
}
139+
140+
const prereqs =
141+
node.prerequisites && node.prerequisites.length > 0
142+
? node.prerequisites.join(", ")
143+
: parentTitle;
144+
if (prereqs) {
145+
segments.push(`Prerequisites: ${prereqs}`);
146+
}
147+
148+
const topics = extractTopics(node.url, node.product);
149+
if (topics.length > 0) {
150+
segments.push(`Topics: ${topics.join(", ")}`);
151+
}
152+
153+
lines.push(
154+
`${prefix}- [${node.title}](${node.url}) | ${segments.join(" | ")}`
155+
);
156+
157+
for (const child of node.children) {
158+
lines.push("");
159+
lines.push(renderNode(child, indent + 1, node.title));
160+
}
161+
162+
return lines.join("\n");
163+
}
164+
165+
export const GET = async (
166+
_req: NextRequest,
167+
{ params }: RouteContext<"/[lang]/sitemap.md">
168+
) => {
169+
const { lang } = await params;
170+
const pages = source.getPages(lang);
171+
172+
const tree = buildTree(pages);
173+
174+
const header = `# Documentation Sitemap
175+
176+
## Purpose
177+
178+
This file is a high-level semantic index of the documentation.
179+
It is intended for:
180+
181+
- LLM-assisted navigation (ChatGPT, Claude, etc.)
182+
- Quick orientation for contributors
183+
- Identifying relevant documentation areas during development
184+
185+
It is not intended to replace individual docs.
186+
187+
---
188+
189+
`;
190+
191+
const body = tree.map((node) => renderNode(node, 0)).join("\n\n");
192+
193+
return new Response(header + body, {
194+
headers: {
195+
"Content-Type": "text/markdown",
196+
},
197+
});
198+
};
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,39 @@ import { siteId } from "@/geistdocs";
33
const PLATFORM_URL = "https://geistdocs.com/md-tracking";
44

55
interface TrackMdRequestParams {
6+
acceptHeader: string | null;
67
path: string;
7-
userAgent: string | null;
88
referer: string | null;
9-
acceptHeader: string | null;
109
/** How the markdown was requested: 'md-url' for direct .md URLs, 'header-negotiated' for Accept header */
1110
requestType?: "md-url" | "header-negotiated";
11+
userAgent: string | null;
1212
}
1313

1414
/**
1515
* Track a markdown page request via the geistdocs platform.
1616
* Fire-and-forget: errors are logged but don't affect the response.
1717
*/
18-
export const trackMdRequest = async ({
18+
export async function trackMdRequest({
1919
path,
2020
userAgent,
2121
referer,
2222
acceptHeader,
2323
requestType,
24-
}: TrackMdRequestParams): Promise<void> => {
24+
}: TrackMdRequestParams): Promise<void> {
2525
try {
2626
const response = await fetch(PLATFORM_URL, {
27+
method: "POST",
28+
headers: {
29+
"Content-Type": "application/json",
30+
},
2731
body: JSON.stringify({
28-
acceptHeader,
2932
path,
30-
referer,
31-
requestType,
3233
siteId: siteId ?? "geistdocs-unknown",
3334
userAgent,
35+
referer,
36+
acceptHeader,
37+
requestType,
3438
}),
35-
headers: {
36-
"Content-Type": "application/json",
37-
},
38-
method: "POST",
3939
});
4040

4141
if (!response.ok) {
@@ -48,4 +48,4 @@ export const trackMdRequest = async ({
4848
} catch (error) {
4949
console.error("MD tracking error:", error);
5050
}
51-
};
51+
}

apps/docs/proxy.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import {
66
NextResponse,
77
} from "next/server";
88
import { i18n } from "@/lib/geistdocs/i18n";
9-
import { trackMdRequest } from "@/lib/md-tracking";
9+
import { trackMdRequest } from "@/lib/geistdocs/md-tracking";
1010

1111
const { rewrite: rewriteLLM } = rewritePath(
1212
"/*path",
1313
`/${i18n.defaultLanguage}/llms.mdx/*path`
1414
);
1515

16+
const MDX_EXTENSION_PATTERN = /\.mdx?$/;
17+
1618
const internationalizer = createI18nMiddleware(i18n);
1719

1820
const proxy = (request: NextRequest, context: NextFetchEvent) => {
@@ -37,7 +39,7 @@ const proxy = (request: NextRequest, context: NextFetchEvent) => {
3739
pathname.startsWith("/")) &&
3840
(pathname.endsWith(".md") || pathname.endsWith(".mdx"))
3941
) {
40-
const stripped = pathname.replace(/\.mdx?$/, "");
42+
const stripped = pathname.replace(MDX_EXTENSION_PATTERN, "");
4143
const result =
4244
stripped === ""
4345
? `/${i18n.defaultLanguage}/llms.mdx`

0 commit comments

Comments
 (0)