generated from polymech/site-template
llms.txt 1/2
This commit is contained in:
parent
5ecda8cd06
commit
cf0eb86e1e
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-12-29T08:42:36.086Z"
|
||||
"default": "2025-12-29T09:41:13.779Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
File diff suppressed because one or more lines are too long
35
app.log
35
app.log
@ -49,3 +49,38 @@
|
||||
{"level":"INFO","time":"2025-12-29T08:42:33.104Z","pid":12492,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:42:34.907Z","pid":12492,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:42:34.921Z","pid":12492,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:43:34.721Z","pid":18444,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:43:35.645Z","pid":18444,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:43:57.484Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:01.084Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:02.837Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:02.851Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:28.738Z","pid":27612,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:29.878Z","pid":27612,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:31.298Z","pid":27612,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:44:31.309Z","pid":27612,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:45:49.050Z","pid":15064,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:45:49.906Z","pid":15064,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:45:51.330Z","pid":15064,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T08:45:51.341Z","pid":15064,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:41.945Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:46.614Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:47.646Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:47.662Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:55.591Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:56.126Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:39:56.141Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:28.111Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:32.018Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:33.082Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:33.097Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:40.609Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:41.182Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:40:41.199Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:00.791Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:04.597Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:05.654Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:05.672Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:13.146Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:13.700Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-29T09:41:13.715Z","pid":24528,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
// Force restart
|
||||
import { imagetools } from "imagetools"
|
||||
import react from "@astrojs/react"
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
116
docs/llms.md
Normal file
116
docs/llms.md
Normal file
@ -0,0 +1,116 @@
|
||||
# Plan: Implementing `llms.txt` (Push/Aggregation Model)
|
||||
|
||||
## Objective
|
||||
Implement `llms.txt` by aggregating content summaries from disparate data loaders ("models") into a central, global hub during the build process, and then writing this aggregated data to a static file.
|
||||
|
||||
## Rationale
|
||||
The user prefers a "Push" model where data loaders (like `howto.ts` and `component.ts`) actively "emit" their simplified LLM representation to a central aggregator. This decoupling allows the loaders to own their data transformation logic while the aggregator simply handles storage and final output.
|
||||
|
||||
## Architecture
|
||||
|
||||
1. **Central Aggregator (`LLMRegistry`)**:
|
||||
* A singleton or global store (resilient to Astro module isolation) that holds the text segments.
|
||||
* Exposes an `emit(section: string, item: LLMItem)` method.
|
||||
2. **Data Emitters (Loaders)**:
|
||||
* **`howto.ts`**: Inside `onStoreItem`, transform the processed item into an LLM-friendly summary and emit to `LLMRegistry`.
|
||||
* **`component.ts`**: Inside `onItem` / `loader`, do the same for products.
|
||||
3. **Output Generator**:
|
||||
* **`src/pages/llms.txt.ts`**: An Astro endpoint that reads the fully populated `LLMRegistry` and returns the plain text response. Since Astro runs loaders before generating pages, the registry will be populated by the time this endpoint is executed.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Create `LLMRegistry` in `packages/polymech/src/registry.ts`
|
||||
|
||||
Extending the existing `globalThis` pattern used by `PolymechRegistry`:
|
||||
|
||||
```typescript
|
||||
// Define the shape of an LLM item
|
||||
export interface LLMItem {
|
||||
title: string;
|
||||
url: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Global store initialization
|
||||
const G = globalThis as any;
|
||||
if (!G.__LLM_REGISTRY__) {
|
||||
G.__LLM_REGISTRY__ = new Map<string, LLMItem[]>();
|
||||
}
|
||||
|
||||
export class LLMRegistry {
|
||||
static add(section: string, item: LLMItem) {
|
||||
const store = G.__LLM_REGISTRY__ as Map<string, LLMItem[]>;
|
||||
if (!store.has(section)) store.set(section, []);
|
||||
store.get(section).push(item);
|
||||
}
|
||||
|
||||
static getAll(): Map<string, LLMItem[]> {
|
||||
return G.__LLM_REGISTRY__;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Update `howto.ts` (Model)
|
||||
|
||||
Modify `src/model/howto/howto.ts` to emit data.
|
||||
|
||||
```typescript
|
||||
import { LLMRegistry } from '@/registry'; // Adjust import path
|
||||
|
||||
// In onStoreItem or complete() function:
|
||||
const llmSummary = {
|
||||
title: item.title,
|
||||
url: `/howtos/${item.slug}`,
|
||||
description: item.description // Already filtered/summarized
|
||||
};
|
||||
LLMRegistry.add("How-To Guides", llmSummary);
|
||||
```
|
||||
|
||||
### 3. Update `component.ts` (Model)
|
||||
|
||||
Modify `src/model/component.ts`:
|
||||
|
||||
```typescript
|
||||
import { LLMRegistry } from '@/registry';
|
||||
|
||||
// In onItem function:
|
||||
const llmSummary = {
|
||||
title: item.data.title,
|
||||
url: `/products/${item.data.slug}`, // Verify URL structure
|
||||
description: item.data.description || item.data.short_description
|
||||
};
|
||||
LLMRegistry.add("Products and Components", llmSummary);
|
||||
```
|
||||
|
||||
### 4. Create Output Endpoint
|
||||
|
||||
Create `library.polymech/src/pages/llms.txt.ts`:
|
||||
|
||||
```typescript
|
||||
import { LLMRegistry } from '@polymech/astro-base/registry'; // Adjust import
|
||||
|
||||
export const GET = async () => {
|
||||
const registry = LLMRegistry.getAll();
|
||||
let content = "# Polymech Documentation Index\n\n";
|
||||
|
||||
// Sort sections for deterministic output
|
||||
const sections = Array.from(registry.keys()).sort();
|
||||
|
||||
for (const section of sections) {
|
||||
content += `## ${section}\n\n`;
|
||||
const items = registry.get(section) || [];
|
||||
for (const item of items) {
|
||||
content += `- [${item.title}](${item.url}): ${item.description}\n`;
|
||||
}
|
||||
content += "\n";
|
||||
}
|
||||
|
||||
return new Response(content.trim(), {
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Verification
|
||||
- Run `npm run build` or `npm run dev` and access `/llms.txt`.
|
||||
- Check if all sections ("How-To Guides", "Products") are present.
|
||||
@ -427,16 +427,29 @@ export function loader(): Loader {
|
||||
item
|
||||
}
|
||||
|
||||
let storeItem = {
|
||||
const storeItem = {
|
||||
digest: await generateDigest(data),
|
||||
filePath: id,
|
||||
id: `${item.slug}`,
|
||||
data: data
|
||||
}
|
||||
|
||||
storeItem = await onStoreItem(storeItem)
|
||||
storeItem.data['config'] = JSON.stringify(storeItem.data, null, 2)
|
||||
store.set(storeItem)
|
||||
const storeItemProcessed = await onStoreItem(storeItem)
|
||||
|
||||
// Emit to LLM Registry
|
||||
try {
|
||||
const { LLMRegistry } = await import('@polymech/astro-base/registry')
|
||||
LLMRegistry.add("How-To Guides", {
|
||||
title: data.title,
|
||||
url: `/howtos/${data.slug}`,
|
||||
description: storeItemProcessed.data.item.description || ""
|
||||
})
|
||||
} catch (e) {
|
||||
console.error("Failed to emit to LLM Registry", e)
|
||||
}
|
||||
|
||||
storeItemProcessed.data['config'] = JSON.stringify(storeItemProcessed.data, null, 2)
|
||||
store.set(storeItemProcessed)
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@ -1,115 +1,3 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
Astro.redirect("/en/home");
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<section>
|
||||
<div
|
||||
class="flex flex-col gap-12 h-full justify-between p-4 text-center py-20"
|
||||
>
|
||||
<div class="max-w-xl mx-auto">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance"
|
||||
>
|
||||
404 Page not found
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500">
|
||||
The page you are looking for does not exist. Please try again. If the
|
||||
problem persists, please contact us.
|
||||
</p>
|
||||
<div class="gap-2 flex flex-col h-full justify-end mt-12">
|
||||
<a
|
||||
href="/"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-white hover:bg-neutral-200 duration-300 rounded-xl w-full justify-between"
|
||||
>
|
||||
<span class="relative uppercase text-xs text-orange-600"
|
||||
>Go home</span
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="w-12 text-orange-600 transition duration-300 -translate-y-7 group-hover:translate-y-7"
|
||||
>
|
||||
<div class="h-14 flex">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 m-auto fill-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="h-14 flex">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 m-auto fill-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="/forms/contact"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-orange-500 hover:bg-black duration-300 rounded-xl w-full justify-between"
|
||||
>
|
||||
<span class="relative uppercase text-xs text-white">Contact us</span
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="w-12 text-white transition duration-300 -translate-y-7 group-hover:translate-y-7"
|
||||
>
|
||||
<div class="h-14 flex">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 m-auto fill-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="h-14 flex">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 m-auto fill-white"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
40
src/pages/[locale]/howtos/[...path].md.ts
Normal file
40
src/pages/[locale]/howtos/[...path].md.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export const GET = async ({ params, props }) => {
|
||||
const { item } = props;
|
||||
|
||||
if (!item) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
const content = `
|
||||
# ${item.data.title}
|
||||
|
||||
${item.data.description}
|
||||
|
||||
[View Original](${import.meta.env.SITE || ''}/${params.locale}/howtos/${item.slug})
|
||||
|
||||
---
|
||||
|
||||
${item.data.item.content || 'Content not available in markdown format.'}
|
||||
`.trim();
|
||||
|
||||
return new Response(content, {
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
});
|
||||
};
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const howtos = await getCollection('howtos');
|
||||
const locales = ['en'];
|
||||
|
||||
return locales.flatMap(locale =>
|
||||
howtos.map(item => ({
|
||||
params: {
|
||||
locale,
|
||||
path: item.slug
|
||||
},
|
||||
props: { item },
|
||||
}))
|
||||
);
|
||||
}
|
||||
40
src/pages/[locale]/library/[...path].md.ts
Normal file
40
src/pages/[locale]/library/[...path].md.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
export const GET = async ({ params, props }) => {
|
||||
const { item } = props;
|
||||
|
||||
if (!item) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
const content = `
|
||||
# ${item.data.title}
|
||||
|
||||
${item.data.description || ''}
|
||||
|
||||
[View Original](${import.meta.env.SITE || ''}/${params.locale}/library/${item.id})
|
||||
|
||||
---
|
||||
|
||||
${item.data.readme || item.data.content || 'Content not available in markdown format.'}
|
||||
`.trim();
|
||||
|
||||
return new Response(content, {
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
});
|
||||
};
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const products = await getCollection('store');
|
||||
const locales = ['en'];
|
||||
|
||||
return locales.flatMap(locale =>
|
||||
products.map(item => ({
|
||||
params: {
|
||||
locale,
|
||||
path: item.id
|
||||
},
|
||||
props: { item },
|
||||
}))
|
||||
);
|
||||
}
|
||||
25
src/pages/llms.txt.ts
Normal file
25
src/pages/llms.txt.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { LLMRegistry } from '@polymech/astro-base/registry';
|
||||
|
||||
export const GET = async () => {
|
||||
const registry = LLMRegistry.getAll();
|
||||
let content = "# Polymech Documentation Index\n\n";
|
||||
|
||||
// Sort sections for deterministic output
|
||||
const sections = Array.from(registry.keys()).sort();
|
||||
|
||||
for (const section of sections) {
|
||||
content += `## ${section}\n\n`;
|
||||
const items = registry.get(section) || [];
|
||||
// Sort items by title for deterministic output
|
||||
items.sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
for (const item of items) {
|
||||
content += `- [${item.title}](${item.url}): ${item.description}\n`;
|
||||
}
|
||||
content += "\n";
|
||||
}
|
||||
|
||||
return new Response(content.trim(), {
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
||||
});
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user