Skip to content

Commit 810437f

Browse files
Add llms.txt file (#518)
* Add llms.txt file * Add llms-full.txt file * feat: llms generation --------- Co-authored-by: Vlad Stohnii <[email protected]>
1 parent b637b1b commit 810437f

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

src/data/llms.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const title = 'Drizzle';
2+
export const description = 'Drizzle is a modern TypeScript ORM developers wanna use in their next project. It is lightweight at only ~7.4kb minified+gzipped, and it\'s tree shakeable with exactly 0 dependencies. It supports every PostgreSQL, MySQL, SQLite and SingleStore database and is serverless-ready by design.';

src/pages/llms-full.txt.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { description, title } from "@/data/llms";
2+
import type { APIRoute } from "astro";
3+
import { getCollection } from "astro:content";
4+
5+
export const GET: APIRoute = async ({ url }) => {
6+
let llms = `# ${title}\n\n> ${description}\n\n`;
7+
8+
const docCollection = await getCollection("docs", (entry) => {
9+
return {
10+
slug: entry.slug,
11+
body: entry.body,
12+
}
13+
});
14+
15+
docCollection.filter((entry) => {
16+
if (entry.slug.includes("latest-releases") || entry.slug.includes("migrate/")) {
17+
return false;
18+
}
19+
return true;
20+
}).forEach((doc) => {
21+
if (doc.slug.includes("tutorials/")) {
22+
const tutorialSlug = doc.slug.split("/").at(-1);
23+
llms += `Source: ${url.origin}/docs/tutorials/${tutorialSlug}\n\n${doc.body}\n\n`;
24+
} else {
25+
llms += `Source: ${url.origin}/docs/${doc.slug}\n\n${doc.body}\n\n`;
26+
}
27+
});
28+
29+
return new Response(llms);
30+
};

src/pages/llms.txt.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// https://llmstxthub.com/guides/getting-started-llms-txt
2+
import type { APIRoute } from "astro";
3+
import { getCollection } from "astro:content";
4+
5+
import {
6+
description,
7+
title,
8+
} from "@/data/llms";
9+
10+
export const GET: APIRoute = async ({ url }) => {
11+
// Get the URL
12+
const getUrl = (path: string) => `${url.origin}/docs/${path}`;
13+
14+
// Create the LLMS
15+
let llms = `# ${title}\n\n> ${description}\n`;
16+
17+
const docCollection = await getCollection("docs", (entry) => {
18+
return {
19+
slug: entry.slug,
20+
body: entry.body,
21+
data: entry.data,
22+
};
23+
});
24+
25+
// Get all meta files
26+
const metaFiles = import.meta.glob("../content/**/_meta.json");
27+
28+
// Get all slugs from meta files
29+
const metaFilesSlugs = await Promise.all(
30+
Object.keys(metaFiles).map(async (meta) => {
31+
const metaFilePath = meta.replace("/_meta.json", "");
32+
const directoryName = metaFilePath.split("/")[3];
33+
// Add directory name as prefix to the slug
34+
const slugPrefix = directoryName ? `${directoryName}/` : "";
35+
36+
const { default: parsed } = (await metaFiles[meta]()) as {
37+
default: string[] | string[][];
38+
};
39+
return parsed.map((slug) => {
40+
if (Array.isArray(slug)) {
41+
return slug.map((subSlug) => `${slugPrefix}${subSlug}`);
42+
}
43+
return `${slugPrefix}${slug}`;
44+
});
45+
}),
46+
);
47+
48+
// Remove slugs with "---" and replace "::"(collapse block)
49+
const slugs = metaFilesSlugs
50+
.flat()
51+
.filter((slug) => !slug.includes("---"))
52+
.map((slug) => (typeof slug === "string" ? slug.replace("::", "") : slug));
53+
54+
// Reorder the array (grouped by prefix)
55+
const reorderArray = (arr: string[]) => {
56+
const grouped: Record<string, string[]> = {};
57+
const order: string[] = [];
58+
59+
arr.forEach((item) => {
60+
const prefix = item.includes("/") ? item.split("/")[0] : item;
61+
if (!grouped[prefix]) {
62+
grouped[prefix] = [];
63+
order.push(prefix);
64+
}
65+
grouped[prefix].push(item);
66+
});
67+
68+
return order.flatMap((prefix) => grouped[prefix]);
69+
}
70+
71+
// Get slugs without array
72+
const slugsWithOneValue = slugs.map((slug) => {
73+
if (Array.isArray(slug)) {
74+
return slug[0];
75+
}
76+
return slug;
77+
});
78+
79+
const sortedSlugs = reorderArray(slugsWithOneValue);
80+
81+
const formatedSlugs = sortedSlugs.map((slug) => {
82+
const slugArray = slugs.find((s) => s[0] === slug) as string[] | undefined;
83+
if (slugArray) {
84+
return slugArray;
85+
}
86+
return slug;
87+
});
88+
89+
// Add the slugs to the LLMS
90+
formatedSlugs.forEach((slug) => {
91+
if (typeof slug === "string") {
92+
llms += `\n## ${slug}\n\n`;
93+
}
94+
95+
// if the slug is an array, it means its a section
96+
if (Array.isArray(slug)) {
97+
const collectionEntry = docCollection.find(
98+
(entry) => slug[0] === entry.slug,
99+
);
100+
// Find title in the body
101+
const findTitle = collectionEntry?.body.match(/^# (.+)/gm);
102+
if (findTitle) {
103+
const sectionTitle = findTitle[0].replace("# ", "");
104+
llms += `- [${sectionTitle}](${getUrl(slug[0])})\n`;
105+
} else {
106+
// If title is not found in the body, use the title from the meta file
107+
const entryTitle = collectionEntry?.data.title;
108+
if (entryTitle) {
109+
llms += `- [${entryTitle}](${getUrl(slug[0])})\n`;
110+
}
111+
}
112+
}
113+
});
114+
115+
// Guides
116+
const mapFiles = import.meta.glob("../content/**/_map.json");
117+
const guidesSlugs = [];
118+
for (let map in mapFiles) {
119+
const { default: parsed } = (await mapFiles[map]()) as {
120+
default: string[][];
121+
};
122+
// Add the parsed slugs to the guidesSlugs array
123+
guidesSlugs.push(...parsed);
124+
}
125+
126+
llms += `\n## Guides\n\n`;
127+
128+
// Main Guides page
129+
llms += `- [Guides](${getUrl("guides")})\n`;
130+
guidesSlugs.forEach((slug) => {
131+
const collectionEntry = docCollection.find(
132+
(entry) => slug[0] === entry.slug,
133+
);
134+
const guideTitle = collectionEntry?.data.title;
135+
const guideSlug = collectionEntry?.data.slug || slug[0];
136+
if (guideTitle) {
137+
llms += `- [${guideTitle}](` + getUrl(`guides/${guideSlug}`) + `)\n`;
138+
}
139+
});
140+
141+
// Tutorials
142+
llms += `\n## Tutorials\n\n`;
143+
144+
const tutorialsEntries = docCollection.filter((entry) => entry.slug.includes("tutorials"));
145+
146+
// Main Tutorials page
147+
llms += `- [Tutorials](${getUrl("tutorials")})\n`;
148+
149+
tutorialsEntries.forEach((entry) => {
150+
const tutorialTitle = entry.data.title;
151+
// Tutorial slug has group name as prefix, so we need to remove it to get the correct slug
152+
const tutorialsSlug = "tutorials/" + entry.slug.split("/").at(-1);
153+
if (tutorialTitle) {
154+
llms += `- [${tutorialTitle}](` + getUrl(tutorialsSlug) + `)\n`;
155+
}
156+
});
157+
158+
return new Response(llms);
159+
};

0 commit comments

Comments
 (0)