@farming-labs/theme 0.0.2-beta.22 → 0.0.2-beta.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs-api.d.mts +7 -6
- package/dist/docs-api.mjs +73 -4
- package/dist/docs-layout.mjs +2 -0
- package/dist/docs-page-client.d.mts +3 -0
- package/dist/docs-page-client.mjs +25 -6
- package/dist/search.d.mts +1 -1
- package/package.json +2 -2
- package/styles/base.css +23 -0
- package/styles/pixel-border.css +23 -0
package/dist/docs-api.d.mts
CHANGED
|
@@ -38,12 +38,14 @@ interface DocsAPIOptions {
|
|
|
38
38
|
* Create a unified docs API route handler.
|
|
39
39
|
*
|
|
40
40
|
* Returns `{ GET, POST }` for use in a Next.js route handler:
|
|
41
|
-
* - **GET
|
|
42
|
-
* - **
|
|
41
|
+
* - **GET ?query=…** → full-text search
|
|
42
|
+
* - **GET ?format=llms** → llms.txt (concise page listing)
|
|
43
|
+
* - **GET ?format=llms-full** → llms-full.txt (full page content)
|
|
44
|
+
* - **POST** → AI-powered chat with RAG
|
|
43
45
|
*
|
|
44
46
|
* @example
|
|
45
47
|
* ```ts
|
|
46
|
-
* // app/api/docs/route.ts
|
|
48
|
+
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
47
49
|
* import { createDocsAPI } from "@farming-labs/theme/api";
|
|
48
50
|
* export const { GET, POST } = createDocsAPI();
|
|
49
51
|
* export const revalidate = false;
|
|
@@ -53,10 +55,9 @@ interface DocsAPIOptions {
|
|
|
53
55
|
*/
|
|
54
56
|
declare function createDocsAPI(options?: DocsAPIOptions): {
|
|
55
57
|
/**
|
|
56
|
-
* GET handler — full
|
|
57
|
-
* Query: `?query=search+term`
|
|
58
|
+
* GET handler — search, llms.txt, or llms-full.txt depending on query params.
|
|
58
59
|
*/
|
|
59
|
-
GET
|
|
60
|
+
GET(request: Request): Response | Promise<Response>;
|
|
60
61
|
/**
|
|
61
62
|
* POST handler — AI chat with RAG.
|
|
62
63
|
* Body: `{ messages: [{ role: "user", content: "How do I …?" }] }`
|
package/dist/docs-api.mjs
CHANGED
|
@@ -163,16 +163,64 @@ async function handleAskAI(request, indexes, searchServer, aiConfig) {
|
|
|
163
163
|
Connection: "keep-alive"
|
|
164
164
|
} });
|
|
165
165
|
}
|
|
166
|
+
function readLlmsTxtConfig(root) {
|
|
167
|
+
for (const ext of FILE_EXTS) {
|
|
168
|
+
const configPath = path.join(root, `docs.config.${ext}`);
|
|
169
|
+
if (fs.existsSync(configPath)) try {
|
|
170
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
171
|
+
if (!content.includes("llmsTxt")) return { enabled: false };
|
|
172
|
+
if (/llmsTxt\s*:\s*true/.test(content)) return { enabled: true };
|
|
173
|
+
const enabledMatch = content.match(/llmsTxt\s*:\s*\{[^}]*enabled\s*:\s*(true|false)/s);
|
|
174
|
+
if (enabledMatch && enabledMatch[1] === "false") return { enabled: false };
|
|
175
|
+
const baseUrlMatch = content.match(/llmsTxt\s*:\s*\{[^}]*baseUrl\s*:\s*["']([^"']+)["']/s);
|
|
176
|
+
const siteTitleMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteTitle\s*:\s*["']([^"']+)["']/s);
|
|
177
|
+
const siteDescMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteDescription\s*:\s*["']([^"']+)["']/s);
|
|
178
|
+
const navTitleMatch = content.match(/nav\s*:\s*\{[^}]*title\s*:\s*["']([^"']+)["']/s);
|
|
179
|
+
return {
|
|
180
|
+
enabled: true,
|
|
181
|
+
baseUrl: baseUrlMatch?.[1],
|
|
182
|
+
siteTitle: siteTitleMatch?.[1] ?? navTitleMatch?.[1],
|
|
183
|
+
siteDescription: siteDescMatch?.[1]
|
|
184
|
+
};
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
return { enabled: false };
|
|
188
|
+
}
|
|
189
|
+
function generateLlmsTxt(indexes, options) {
|
|
190
|
+
const { siteTitle = "Documentation", siteDescription, baseUrl = "" } = options;
|
|
191
|
+
let llmsTxt = `# ${siteTitle}\n\n`;
|
|
192
|
+
if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
|
|
193
|
+
llmsTxt += `## Pages\n\n`;
|
|
194
|
+
for (const page of indexes) {
|
|
195
|
+
llmsTxt += `- [${page.title}](${baseUrl}${page.url})`;
|
|
196
|
+
if (page.description) llmsTxt += `: ${page.description}`;
|
|
197
|
+
llmsTxt += `\n`;
|
|
198
|
+
}
|
|
199
|
+
let llmsFullTxt = `# ${siteTitle}\n\n`;
|
|
200
|
+
if (siteDescription) llmsFullTxt += `> ${siteDescription}\n\n`;
|
|
201
|
+
for (const page of indexes) {
|
|
202
|
+
llmsFullTxt += `## ${page.title}\n\n`;
|
|
203
|
+
llmsFullTxt += `URL: ${baseUrl}${page.url}\n\n`;
|
|
204
|
+
if (page.description) llmsFullTxt += `${page.description}\n\n`;
|
|
205
|
+
llmsFullTxt += `${page.content}\n\n---\n\n`;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
llmsTxt,
|
|
209
|
+
llmsFullTxt
|
|
210
|
+
};
|
|
211
|
+
}
|
|
166
212
|
/**
|
|
167
213
|
* Create a unified docs API route handler.
|
|
168
214
|
*
|
|
169
215
|
* Returns `{ GET, POST }` for use in a Next.js route handler:
|
|
170
|
-
* - **GET
|
|
171
|
-
* - **
|
|
216
|
+
* - **GET ?query=…** → full-text search
|
|
217
|
+
* - **GET ?format=llms** → llms.txt (concise page listing)
|
|
218
|
+
* - **GET ?format=llms-full** → llms-full.txt (full page content)
|
|
219
|
+
* - **POST** → AI-powered chat with RAG
|
|
172
220
|
*
|
|
173
221
|
* @example
|
|
174
222
|
* ```ts
|
|
175
|
-
* // app/api/docs/route.ts
|
|
223
|
+
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
176
224
|
* import { createDocsAPI } from "@farming-labs/theme/api";
|
|
177
225
|
* export const { GET, POST } = createDocsAPI();
|
|
178
226
|
* export const revalidate = false;
|
|
@@ -186,13 +234,34 @@ function createDocsAPI(options) {
|
|
|
186
234
|
const docsDir = path.join(root, "app", entry);
|
|
187
235
|
const language = options?.language ?? "english";
|
|
188
236
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
237
|
+
const llmsConfig = readLlmsTxtConfig(root);
|
|
189
238
|
const indexes = scanDocsDir(docsDir, entry);
|
|
239
|
+
let _llmsCache = null;
|
|
240
|
+
function getLlmsContent() {
|
|
241
|
+
if (!_llmsCache) _llmsCache = generateLlmsTxt(indexes, {
|
|
242
|
+
siteTitle: llmsConfig.siteTitle ?? "Documentation",
|
|
243
|
+
siteDescription: llmsConfig.siteDescription,
|
|
244
|
+
baseUrl: llmsConfig.baseUrl ?? ""
|
|
245
|
+
});
|
|
246
|
+
return _llmsCache;
|
|
247
|
+
}
|
|
190
248
|
const searchAPI = createSearchAPI("simple", {
|
|
191
249
|
language,
|
|
192
250
|
indexes
|
|
193
251
|
});
|
|
194
252
|
return {
|
|
195
|
-
GET
|
|
253
|
+
GET(request) {
|
|
254
|
+
const format = new URL(request.url).searchParams.get("format");
|
|
255
|
+
if (format === "llms") return new Response(getLlmsContent().llmsTxt, { headers: {
|
|
256
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
257
|
+
"Cache-Control": "public, max-age=3600"
|
|
258
|
+
} });
|
|
259
|
+
if (format === "llms-full") return new Response(getLlmsContent().llmsFullTxt, { headers: {
|
|
260
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
261
|
+
"Cache-Control": "public, max-age=3600"
|
|
262
|
+
} });
|
|
263
|
+
return searchAPI.GET(request);
|
|
264
|
+
},
|
|
196
265
|
async POST(request) {
|
|
197
266
|
if (!aiConfig.enabled) return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
|
|
198
267
|
return handleAskAI(request, indexes, searchAPI, aiConfig);
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -357,6 +357,7 @@ function createDocsLayout(config) {
|
|
|
357
357
|
const lastUpdatedRaw = config.lastUpdated;
|
|
358
358
|
const lastUpdatedEnabled = lastUpdatedRaw !== false && (typeof lastUpdatedRaw !== "object" || lastUpdatedRaw.enabled !== false);
|
|
359
359
|
const lastUpdatedPosition = typeof lastUpdatedRaw === "object" ? lastUpdatedRaw.position ?? "footer" : "footer";
|
|
360
|
+
const llmsTxtEnabled = resolveBool(config.llmsTxt);
|
|
360
361
|
const openDocsProviders = (typeof pageActions?.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((p) => ({
|
|
361
362
|
name: p.name,
|
|
362
363
|
urlTemplate: p.urlTemplate,
|
|
@@ -418,6 +419,7 @@ function createDocsLayout(config) {
|
|
|
418
419
|
lastModifiedMap,
|
|
419
420
|
lastUpdatedEnabled,
|
|
420
421
|
lastUpdatedPosition,
|
|
422
|
+
llmsTxtEnabled,
|
|
421
423
|
descriptionMap,
|
|
422
424
|
children
|
|
423
425
|
})
|
|
@@ -33,6 +33,8 @@ interface DocsPageClientProps {
|
|
|
33
33
|
lastUpdatedEnabled?: boolean;
|
|
34
34
|
/** Where to show the "Last updated" date: "footer" (next to Edit on GitHub) or "below-title" */
|
|
35
35
|
lastUpdatedPosition?: "footer" | "below-title";
|
|
36
|
+
/** Whether llms.txt is enabled — shows links in footer */
|
|
37
|
+
llmsTxtEnabled?: boolean;
|
|
36
38
|
/** Map of pathname → frontmatter description */
|
|
37
39
|
descriptionMap?: Record<string, string>;
|
|
38
40
|
/** Frontmatter description to display below the page title (overrides descriptionMap) */
|
|
@@ -55,6 +57,7 @@ declare function DocsPageClient({
|
|
|
55
57
|
lastModifiedMap,
|
|
56
58
|
lastUpdatedEnabled,
|
|
57
59
|
lastUpdatedPosition,
|
|
60
|
+
llmsTxtEnabled,
|
|
58
61
|
descriptionMap,
|
|
59
62
|
description,
|
|
60
63
|
children
|
|
@@ -63,7 +63,7 @@ function buildGithubFileUrl(githubUrl, branch, pathname, directory) {
|
|
|
63
63
|
const segments = pathname.replace(/^\//, "").replace(/\/$/, "");
|
|
64
64
|
return `${githubUrl}/tree/${branch}/${directory ? `${directory}/` : ""}app/${segments}/page.mdx`;
|
|
65
65
|
}
|
|
66
|
-
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", descriptionMap, description, children }) {
|
|
66
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, children }) {
|
|
67
67
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
68
68
|
const [toc, setToc] = useState([]);
|
|
69
69
|
const pathname = usePathname();
|
|
@@ -109,7 +109,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
109
109
|
const lastModified = lastUpdatedEnabled ? lastModifiedMap?.[normalizedPath] : void 0;
|
|
110
110
|
const showLastUpdatedBelowTitle = !!lastModified && lastUpdatedPosition === "below-title";
|
|
111
111
|
const showLastUpdatedInFooter = !!lastModified && lastUpdatedPosition === "footer";
|
|
112
|
-
const showFooter = !!githubFileUrl || showLastUpdatedInFooter;
|
|
112
|
+
const showFooter = !!githubFileUrl || showLastUpdatedInFooter || llmsTxtEnabled;
|
|
113
113
|
const needsBelowTitleBlock = showLastUpdatedBelowTitle || showActions;
|
|
114
114
|
useEffect(() => {
|
|
115
115
|
if (!needsBelowTitleBlock) return;
|
|
@@ -188,10 +188,29 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
188
188
|
children
|
|
189
189
|
}), showFooter && /* @__PURE__ */ jsxs("div", {
|
|
190
190
|
className: "not-prose fd-page-footer",
|
|
191
|
-
children: [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
children: [
|
|
192
|
+
githubFileUrl && /* @__PURE__ */ jsx(EditOnGitHub, { href: githubFileUrl }),
|
|
193
|
+
llmsTxtEnabled && /* @__PURE__ */ jsxs("span", {
|
|
194
|
+
className: "fd-llms-txt-links",
|
|
195
|
+
children: [/* @__PURE__ */ jsx("a", {
|
|
196
|
+
href: "/api/docs?format=llms",
|
|
197
|
+
target: "_blank",
|
|
198
|
+
rel: "noopener noreferrer",
|
|
199
|
+
className: "fd-llms-txt-link",
|
|
200
|
+
children: "llms.txt"
|
|
201
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
202
|
+
href: "/api/docs?format=llms-full",
|
|
203
|
+
target: "_blank",
|
|
204
|
+
rel: "noopener noreferrer",
|
|
205
|
+
className: "fd-llms-txt-link",
|
|
206
|
+
children: "llms-full.txt"
|
|
207
|
+
})]
|
|
208
|
+
}),
|
|
209
|
+
showLastUpdatedInFooter && lastModified && /* @__PURE__ */ jsxs("span", {
|
|
210
|
+
className: "fd-last-updated-footer",
|
|
211
|
+
children: ["Last updated ", lastModified]
|
|
212
|
+
})
|
|
213
|
+
]
|
|
195
214
|
})]
|
|
196
215
|
})
|
|
197
216
|
]
|
package/dist/search.d.mts
CHANGED
|
@@ -27,7 +27,7 @@ declare function createDocsSearchAPI(options?: {
|
|
|
27
27
|
entry?: string;
|
|
28
28
|
language?: string;
|
|
29
29
|
}): {
|
|
30
|
-
GET
|
|
30
|
+
GET(request: Request): Response | Promise<Response>;
|
|
31
31
|
POST(request: Request): Promise<Response>;
|
|
32
32
|
};
|
|
33
33
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/theme",
|
|
3
|
-
"version": "0.0.2-beta.
|
|
3
|
+
"version": "0.0.2-beta.23",
|
|
4
4
|
"description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"next": ">=14.0.0",
|
|
99
99
|
"tsdown": "^0.20.3",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
|
-
"@farming-labs/docs": "0.0.2-beta.
|
|
101
|
+
"@farming-labs/docs": "0.0.2-beta.23"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
104
|
"@farming-labs/docs": ">=0.0.1",
|
package/styles/base.css
CHANGED
|
@@ -409,6 +409,29 @@ figure.shiki:has(figcaption) figcaption {
|
|
|
409
409
|
margin-left: auto;
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
+
.fd-llms-txt-links {
|
|
413
|
+
display: inline-flex;
|
|
414
|
+
align-items: center;
|
|
415
|
+
gap: 0.5rem;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.fd-llms-txt-link {
|
|
419
|
+
color: var(--color-fd-muted-foreground, hsl(0 0% 45%));
|
|
420
|
+
font-size: 0.75rem;
|
|
421
|
+
font-family: var(--fd-font-mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace);
|
|
422
|
+
text-decoration: none;
|
|
423
|
+
padding: 0.125rem 0.375rem;
|
|
424
|
+
border-radius: 0.25rem;
|
|
425
|
+
border: 1px solid var(--color-fd-border, hsl(0 0% 80% / 50%));
|
|
426
|
+
transition: color 150ms, border-color 150ms;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.fd-llms-txt-link:hover {
|
|
430
|
+
/* color: var(--color-fd-foreground, hsl(0 0% 10%));
|
|
431
|
+
border-color: var(--color-fd-foreground, hsl(0 0% 10%)); */
|
|
432
|
+
text-decoration: none;
|
|
433
|
+
}
|
|
434
|
+
|
|
412
435
|
/* ─── Code block copy button: show on hover ────────────────────────── */
|
|
413
436
|
|
|
414
437
|
figure.shiki > button,
|
package/styles/pixel-border.css
CHANGED
|
@@ -425,6 +425,29 @@ figure.shiki > div:first-child {
|
|
|
425
425
|
font-family: var(--fd-font-mono, var(--font-geist-mono, ui-monospace, monospace));
|
|
426
426
|
text-transform: uppercase;
|
|
427
427
|
}
|
|
428
|
+
/* llms.txt links */
|
|
429
|
+
.fd-llms-txt-links {
|
|
430
|
+
display: inline-flex;
|
|
431
|
+
align-items: center;
|
|
432
|
+
gap: 0.5rem;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.fd-llms-txt-link {
|
|
436
|
+
color: var(--color-fd-muted-foreground, hsl(0 0% 45%));
|
|
437
|
+
font-size: 0.65rem !important;
|
|
438
|
+
font-family: var(--fd-font-mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace);
|
|
439
|
+
text-decoration: none;
|
|
440
|
+
padding: 0.015rem 0.4rem !important;
|
|
441
|
+
border-radius: 0px !important;
|
|
442
|
+
border: 0.5px solid var(--color-fd-border, hsl(0 0% 80% / 50%));
|
|
443
|
+
transition: color 150ms, border-color 150ms;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.fd-llms-txt-link:hover {
|
|
447
|
+
color: var(--color-fd-foreground, hsl(0 0% 10%)) !important;
|
|
448
|
+
border: 0.5px solid var(--color-fd-muted-foreground, hsl(0 0% 10% / 10%)) !important;
|
|
449
|
+
text-decoration: none;
|
|
450
|
+
}
|
|
428
451
|
|
|
429
452
|
/* ─── Page Actions (pixel-border overrides) ───────────────────────── */
|
|
430
453
|
|