diff --git a/docs/src/lib/components/DocsMenu.svelte b/docs/src/lib/components/DocsMenu.svelte index 49b3b95cf..b06c8e2d5 100644 --- a/docs/src/lib/components/DocsMenu.svelte +++ b/docs/src/lib/components/DocsMenu.svelte @@ -7,6 +7,7 @@ import { sortFunc } from '@layerstack/utils'; import { cls } from '@layerstack/tailwind'; + import LucideBot from '~icons/lucide/bot'; import LucideCompass from '~icons/lucide/compass'; import LucideGalleryVertical from '~icons/lucide/gallery-vertical'; import LucideGalleryHorizontalEnd from '~icons/lucide/gallery-horizontal-end'; @@ -28,7 +29,8 @@ { name: 'Simplified charts', path: 'simplified-charts' }, { name: 'Scales', path: 'scales' }, { name: 'State', path: 'state' }, - { name: 'Styles', path: 'styles' } + { name: 'Styles', path: 'styles' }, + { name: 'LLMs', path: 'LLMs' } ]; const componentsByCategory = flatGroup(allComponents, (d) => d.category?.toLowerCase()) diff --git a/docs/src/lib/components/OpenWithButton.svelte b/docs/src/lib/components/OpenWithButton.svelte new file mode 100644 index 000000000..6ec8b09cc --- /dev/null +++ b/docs/src/lib/components/OpenWithButton.svelte @@ -0,0 +1,156 @@ + + + + + + + + + (openSourceModal = false)} + class="max-h-[98dvh] md:max-h-[90dvh] max-w-[98vw] md:max-w-[90vw] grid grid-rows-[auto_1fr_auto]" +> +
+
+
Source
+
{metadata?.sourceUrl}
+
+ + {#if metadata?.sourceUrl} + + {/if} +
+ +
+ +
+ +
+ +
+
diff --git a/docs/src/lib/markdown/components/Note.svelte b/docs/src/lib/markdown/components/Note.svelte index c74d80d7d..60bfeb5c1 100644 --- a/docs/src/lib/markdown/components/Note.svelte +++ b/docs/src/lib/markdown/components/Note.svelte @@ -25,7 +25,7 @@
p]:my-2', className diff --git a/docs/src/lib/markdown/utils.ts b/docs/src/lib/markdown/utils.ts index 2e012cf84..f1f037f35 100644 --- a/docs/src/lib/markdown/utils.ts +++ b/docs/src/lib/markdown/utils.ts @@ -153,3 +153,111 @@ export async function loadExamplesFromMarkdown( return examples; } + +/** + * Process content only outside of code blocks (``` ... ```) + * Preserves code block content unchanged while applying transformations elsewhere + */ +function processOutsideCodeBlocks(content: string, processor: (text: string) => string): string { + // Split by code blocks, preserving the delimiters + const parts = content.split(/(```[\s\S]*?```)/g); + + return parts + .map((part, index) => { + // Odd indices are code blocks (matched by the capture group) + if (index % 2 === 1) { + return part; // Keep code blocks unchanged + } + return processor(part); // Process non-code-block content + }) + .join(''); +} + +// Process markdown content for LLMs by removing custom syntax and converting to vanilla markdown +export function processMarkdownContent(content: string): string { + // Remove frontmatter (YAML between --- markers at start of file) + content = content.replace(/^---\n[\s\S]*?\n---\n*/, ''); + + // Remove Svelte script blocks and components ONLY outside of code blocks + content = processOutsideCodeBlocks(content, (text) => { + // Remove Svelte script blocks + text = text.replace(/]*>[\s\S]*?<\/script>\n*/g, ''); + // Remove Svelte components (self-closing and with content) + text = text.replace(/<[A-Z][a-zA-Z]*[^>]*\/>\n*/g, ''); + text = text.replace(/<[A-Z][a-zA-Z]*[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z]*>\n*/g, ''); + return text; + }); + + // Extract title from code blocks and add as "File:" line before (must run before tabs processing) + content = content.replace(/(```\w*)\s+([^\n]*title="[^"]+")[^\n]*$/gm, (_, lang, meta) => { + const titleMatch = meta.match(/title="([^"]+)"/); + if (titleMatch) { + return `File: ${titleMatch[1]} ${lang}`; + } + return lang; + }); + + // Process tabs - convert to table + content = content.replace( + /:::tabs\{key="([^"]+)"\}\s*([\s\S]*?)(?=\n:::(?:\s*$|\s*\n))\n:::/gm, + (_, key, tabsContent) => { + const tabs: { label: string; content: string }[] = []; + const tabRegex = /::tab\{label="([^"]+)"[^}]*\}\s*([\s\S]*?)\s*(?=\n\s*::(?:\s*$|\s+))\n\s*::/gm; + let match; + while ((match = tabRegex.exec(tabsContent)) !== null) { + tabs.push({ label: match[1], content: match[2].trim() }); + } + + if (tabs.length === 0) return ''; + + // Build table with capitalized key as header + const header = key.charAt(0).toUpperCase() + key.slice(1); + let table = `| ${header} | Details |\n|-----------|---------|`; + for (const tab of tabs) { + // Clean up content: remove :button syntax, convert to links, unwrap code blocks + const cleanContent = tab.content + .replace(/:button\{label="([^"]+)"\s+href="([^"]+)"[^}]*\}/g, '[$1]($2)') + .replace(/```\w*\n([\s\S]*?)```/g, '$1') // Remove code block fences, keep content + .replace(/\n/g, ' ') + .trim(); + table += `\n| ${tab.label} | ${cleanContent} |`; + } + return table; + } + ); + + // Convert ::note/:::note, ::tip/:::tip, etc. to blockquote (2 or 3 colons) + content = content.replace( + /:{2,3}(note|tip|warning|caution)\s*([\s\S]*?)(?=\n:{2,3}(?:\s*$|\s*\n))\n:{2,3}/gm, + (_, variant, noteContent) => { + return `> ${variant}: ${noteContent.trim()}\n`; + } + ); + + // Convert ::steps to numbered list (convert ## headings to numbered items) + content = content.replace(/::steps\s*([\s\S]*?)(?=\n::(?:\s*$|\s*\n))\n::/gm, (_, stepsContent: string) => { + let stepNum = 0; + return stepsContent.replace(/^## (.+)$/gm, (_match: string, heading: string) => { + stepNum++; + return `**${stepNum}. ${heading}**`; + }); + }); + + // Remove any remaining standalone :: + content = content.replace(/^::\s*$/gm, ''); + + // Remove :icon syntax, keep text if in brackets, otherwise just remove icon + content = content.replace(/\[:icon\{[^}]+\}\s*([^\]]+)\]/g, '$1'); + content = content.replace(/:icon\{[^}]+\}\s*/g, ''); + + // Convert :example to reference link + content = content.replace( + /:example\{component="([^"]+)"\s+name="([^"]+)"[^}]*\}/g, + 'See example: [$1/$2](https://layerchart.com/docs/components/$1/$2)' + ); + + // Clean up multiple blank lines + content = content.replace(/\n{3,}/g, '\n\n'); + + return content.trim(); +} \ No newline at end of file diff --git a/docs/src/routes/docs/components/[name]/+layout.svelte b/docs/src/routes/docs/components/[name]/+layout.svelte index 6d4b92132..e7ef7c25a 100644 --- a/docs/src/routes/docs/components/[name]/+layout.svelte +++ b/docs/src/routes/docs/components/[name]/+layout.svelte @@ -2,14 +2,13 @@ import { getSettings } from 'layerchart'; import { Button, Menu, Switch, Toggle, ToggleGroup, ToggleOption, Tooltip } from 'svelte-ux'; import { toTitleCase } from '@layerstack/utils'; + import OpenWithButton from '$lib/components/OpenWithButton.svelte'; - import ViewSourceButton from '$lib/components/ViewSourceButton.svelte'; import { examples } from '$lib/context.js'; import { loadExample } from '$lib/examples.js'; import { page } from '$app/state'; import LucideSettings from '~icons/lucide/settings'; - import LucideCode from '~icons/lucide/code'; import LucideChevronLeft from '~icons/lucide/chevron-left'; import LucideChevronRight from '~icons/lucide/chevron-right'; @@ -129,14 +128,7 @@
{metadata.description}
- {#if 'source' in metadata} - - {/if} + +{#if data.metadata.title !== 'LLMs'} +
+ +
+{/if} + + + {#snippet pending()} + loading... + {/snippet} + + {@render children()} + diff --git a/docs/src/routes/docs/guides/+layout.ts b/docs/src/routes/docs/guides/+layout.ts new file mode 100644 index 000000000..c4b1c5d61 --- /dev/null +++ b/docs/src/routes/docs/guides/+layout.ts @@ -0,0 +1,26 @@ +import { toTitleCase } from '@layerstack/utils'; + +// Dynamically import all guide markdown files to access their frontmatter +const modules = import.meta.glob('./**/+page.md', { eager: true }) as Record< + string, + { metadata?: Record } +>; + +export const load = async ({ url }) => { + // Get the last segment of the path (e.g., "features" from "/docs/guides/features") + const segments = url.pathname.split('/').filter(Boolean); + const name = segments[segments.length - 1]; + + // Get metadata from the markdown file's frontmatter + const modulePath = `./${name}/+page.md`; + const module = modules[modulePath]; + const frontmatter = module?.metadata ?? {}; + + // Use frontmatter title if available, otherwise derive from path + const title = + (frontmatter.title as string) ?? toTitleCase(name.replaceAll('-', ' ')); + + return { + metadata: { ...frontmatter, title } + }; +}; diff --git a/docs/src/routes/docs/guides/LLMs/+page.md b/docs/src/routes/docs/guides/LLMs/+page.md new file mode 100644 index 000000000..1fc3e3de5 --- /dev/null +++ b/docs/src/routes/docs/guides/LLMs/+page.md @@ -0,0 +1,41 @@ +--- +title: LLMs +--- + + + +The Layerchart documentation pages are designed to be accessible for humans developers using LLMs as well as large language models (LLMs) ingesting training data. + +## :icon{name="lucide:user" class="relative -top-1"} For the Humans + + + +At the top of each documentation page, and demonstrated above, you'll find a button which copies the content of the page's documentation in Markdown to the clipboard. The convenient dropdown gives you additional helpful options. + +::note +The option for `View Component Source` is only shown for component pages. +:: + +## :icon{name="lucide:bot" class="relative -top-1"} For the Bots + +LayerChart adopts the [llms.txt](https://llmstxt.org/) proposal standard, which provides a structured, machine-readable format optimized for LLMs. This enables developers, researchers, and AI systems to efficiently parse and utilize our documentation. + +::note +Not all pages may support the `/llms.txt` suffix (ie those deemed irrelevant to LLMs). +:: + +## LLM-friendly Documentation 3 Ways + +1. To access the LLM-friendly version of supported Layerchart documentation pages, simply append `/llms.txt` to the end of the page's URL. This will return the content in a plain-text, LLM-optimized format. This is the same text which is copied to the clipboard when you click the `View Page Markdown` button. + +:::tip +- **Standard Page**: The LineChart component documentation is available at [layerchart.com/docs/components/LineChart](/docs/components/LineChart). + +- **LLM-friendly Version**: is available at [layerchart.com/docs/components/Linechart/llms.txt](/docs/components/LineChart/llms.txt). +::: + +1. To explore all supported pages in LLM-friendly format, visit the root index at [layerchart.com/llms.txt](/llms.txt). This page provides a comprehensive list of available documentation endpoints compatible with the `llms.txt` standard. + +1. For a complete, consolidated view of the all the Layerchart documentation in an LLM-friendly format, navigate to [layerchart.com/docs/llms.txt](/docs/llms.txt). This single endpoint aggregates all documentation content into a machine-readable structure, ideal for bulk processing or ingestion into AI systems. diff --git a/docs/src/routes/docs/guides/[name]/llms.txt/+server.ts b/docs/src/routes/docs/guides/[name]/llms.txt/+server.ts new file mode 100644 index 000000000..ff4474ebe --- /dev/null +++ b/docs/src/routes/docs/guides/[name]/llms.txt/+server.ts @@ -0,0 +1,48 @@ +import { error } from '@sveltejs/kit'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import type { RequestHandler } from './$types'; +import { processMarkdownContent } from '$lib/markdown/utils.js'; + +export const GET: RequestHandler = async ({ params }) => { + const { name } = params; + + let content = ''; + try { + const mdPath = join(process.cwd(), `src/routes/docs/guides/${name}/+page.md`); + content = readFileSync(mdPath, 'utf-8'); + } catch (e) { + error(404, `Guide "${name}" not found`); + } + + // Extract title from frontmatter before processing + let title: string | undefined; + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n*/); + if (frontmatterMatch) { + const frontmatter = frontmatterMatch[1]; + const titleMatch = frontmatter.match(/^title:\s*(.+)$/m); + if (titleMatch) { + title = titleMatch[1].trim().replace(/^["']|["']$/g, ''); // Remove quotes if present + } + } + + // Process content (removes frontmatter) + content = processMarkdownContent(content); + + // Use frontmatter title if available, otherwise generate from name + if (!title) { + title = name + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } + + const markdown = `# ${title}\n\n${content}`; + + return new Response(markdown, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Content-Disposition': `inline; filename="${name}.md"` + } + }); +}; diff --git a/docs/src/routes/docs/guides/features/+page.md b/docs/src/routes/docs/guides/features/+page.md index 314bed042..b3c74ad62 100644 --- a/docs/src/routes/docs/guides/features/+page.md +++ b/docs/src/routes/docs/guides/features/+page.md @@ -1,4 +1,6 @@ -# Features +--- +title: Features +--- > WIP diff --git a/docs/src/routes/docs/guides/layers/+page.md b/docs/src/routes/docs/guides/layers/+page.md index 7f6cfe5de..dc38fc21d 100644 --- a/docs/src/routes/docs/guides/layers/+page.md +++ b/docs/src/routes/docs/guides/layers/+page.md @@ -1,11 +1,13 @@ +--- +title: Layers +--- + -# Layers - LayerChart provides first-class support for different types of layers including [Svg](/docs/components/Svg), [Html](/docs/components/Html), and [Canvas](/docs/components/Canvas) via [Layer](/docs/components/Layer) and [Primitive](/docs/guides/primitives) components. Each layer type provides unique and overlapping feature sets. LayerChart supports using layers of different types within the same chart to leverage a type's strengths or workaround a weakness. diff --git a/docs/src/routes/docs/guides/primitives/+page.md b/docs/src/routes/docs/guides/primitives/+page.md index 1b332b23f..96cee2a8f 100644 --- a/docs/src/routes/docs/guides/primitives/+page.md +++ b/docs/src/routes/docs/guides/primitives/+page.md @@ -1,3 +1,7 @@ +--- +title: Primitives +--- + -# Primitives - A collection of components which support rendering within different layer types including `Svg`, `Canvas`, or `Html`. Support for each layer type is dependent on the primitive's feature needs and capabilities of the layer type. diff --git a/docs/src/routes/docs/guides/scales/+page.md b/docs/src/routes/docs/guides/scales/+page.md index 4ed81aa27..ebe8db295 100644 --- a/docs/src/routes/docs/guides/scales/+page.md +++ b/docs/src/routes/docs/guides/scales/+page.md @@ -1,3 +1,7 @@ +--- +title: Scales +--- + -# Scales - ## What is a scale? At its essence, a scale is a function that maps data values (`domain`) to pixel or color values (`range`) on a per-dimension basis (`x`, `y`, `color`, etc). diff --git a/docs/src/routes/docs/guides/simplified-charts/+page.md b/docs/src/routes/docs/guides/simplified-charts/+page.md index efe17850c..9a2ae6981 100644 --- a/docs/src/routes/docs/guides/simplified-charts/+page.md +++ b/docs/src/routes/docs/guides/simplified-charts/+page.md @@ -1,3 +1,7 @@ +--- +title: Simplified Charts +--- + -# Simplified charts - The LayerChart project was written to offer options for both flexibility/complexity as well as approachablilty/simplicity. This brings us to a decision as you start your first LayerChart. ## Use `` or `Simple Chart`. diff --git a/docs/src/routes/docs/guides/state/+page.md b/docs/src/routes/docs/guides/state/+page.md index f48eca287..b24bf03c5 100644 --- a/docs/src/routes/docs/guides/state/+page.md +++ b/docs/src/routes/docs/guides/state/+page.md @@ -1,4 +1,6 @@ -# State / Context +--- +title: State / Context +--- > TODO diff --git a/docs/src/routes/docs/guides/styles/+page.md b/docs/src/routes/docs/guides/styles/+page.md index f84da58aa..8445269fa 100644 --- a/docs/src/routes/docs/guides/styles/+page.md +++ b/docs/src/routes/docs/guides/styles/+page.md @@ -1,4 +1,6 @@ -# Styling +--- +title: Styling +--- ## Colors diff --git a/docs/src/routes/docs/llms.txt/+server.ts b/docs/src/routes/docs/llms.txt/+server.ts new file mode 100644 index 000000000..96e27b2bf --- /dev/null +++ b/docs/src/routes/docs/llms.txt/+server.ts @@ -0,0 +1,237 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; +import type { RequestHandler } from './$types'; +import type { ComponentAPI } from '$lib/api-types.js'; +import { allComponents, allUtils } from 'content-collections'; + +const BASE_URL = 'https://layerchart.com'; + +const guides = [ + { slug: 'getting-started', name: 'Getting Started', description: 'Installation and setup guide for LayerChart' }, + { slug: 'guides/scales', name: 'Scales', description: 'Understanding scales, domains, and ranges' }, + { slug: 'guides/layers', name: 'Layers', description: 'Working with SVG, Canvas, and HTML layers' }, + { slug: 'guides/state', name: 'State', description: 'Managing chart state and reactivity' }, + { slug: 'guides/styles', name: 'Styles', description: 'Styling and theming your charts' }, + { slug: 'guides/primitives', name: 'Primitives', description: 'Low-level building blocks for custom charts' }, + { slug: 'guides/simplified-charts', name: 'Simplified Charts', description: 'Quick chart components for common use cases' }, + { slug: 'guides/features', name: 'Features', description: 'Overview of LayerChart features and capabilities' } +]; + +export const GET: RequestHandler = async () => { + const content = generateFullLlmsTxt(); + + return new Response(content, { + headers: { + 'Content-Type': 'text/plain; charset=utf-8' + } + }); +}; + +function generateFullLlmsTxt(): string { + const sections: string[] = []; + + // Header + sections.push(`# LayerChart Full Documentation for LLMs + +> LayerChart is a powerful, composable charting library for Svelte built on top of D3. + +This file contains the complete LLM-optimized documentation for all components and utilities.`); + + // General section (links only, as these are markdown pages) + sections.push(`## General + +${guides.map((g) => `- [${g.name}](${BASE_URL}/docs/${g.slug}): ${g.description}`).join('\n')}`); + + // Components section - full content + sections.push('---\n\n# Components'); + + const sortedComponents = allComponents + .filter((c) => c.slug && c.name) + .sort((a, b) => a.name.localeCompare(b.name)); + + for (const component of sortedComponents) { + const componentContent = generateComponentMarkdown(component); + sections.push(componentContent); + } + + // Utilities section - full content + sections.push('---\n\n# Utilities'); + + const sortedUtils = allUtils + .filter((u) => u.slug && u.name) + .sort((a, b) => a.name.localeCompare(b.name)); + + for (const util of sortedUtils) { + const utilContent = generateUtilMarkdown(util); + sections.push(utilContent); + } + + return sections.join('\n\n'); +} + +/** + * Trim code to remove module exports and data export statement + */ +function trimCode(code: string): string { + return code + .replace(/[\s\S]*?<\/script>\n*/g, '') + .replace(/\n*\s*export \{ data \};\s*\n*\s*<\/script>/gm, '\n') + .trim(); +} + +/** + * Escape special markdown characters in table cells + */ +function escapeMarkdown(text: string): string { + return text + .replace(/\|/g, '\\|') + .replace(/\n/g, ' ') + .replace(//g, '>'); +} + +/** + * Generate markdown API table from component properties + */ +function generateApiTable(api: ComponentAPI): string { + if (!api.properties || api.properties.length === 0) { + return ''; + } + + const rows = api.properties.map((prop) => { + const name = prop.required ? `**${prop.name}** (required)` : prop.name; + const type = `\`${escapeMarkdown(prop.type)}\``; + const defaultVal = prop.default ? `\`${escapeMarkdown(prop.default)}\`` : '-'; + const description = prop.description ? escapeMarkdown(prop.description) : '-'; + return `| ${name} | ${type} | ${defaultVal} | ${description} |`; + }); + + return `| Property | Type | Default | Description | +|----------|------|---------|-------------| +${rows.join('\n')}`; +} + +/** + * Generate markdown for a component + */ +function generateComponentMarkdown(component: (typeof allComponents)[number]): string { + const sections: string[] = []; + + // Title and description + sections.push(`## ${component.name}`); + if (component.description) { + sections.push(component.description); + } + + // Metadata + if (component.category) { + sections.push(`**Category:** ${component.category}`); + } + if (component.layers && component.layers.length > 0) { + sections.push(`**Supported Layers:** ${component.layers.join(', ')}`); + } + + // Documentation link + sections.push(`**Full Documentation:** [${component.name}](${BASE_URL}/docs/components/${component.slug})`); + + // Load example + let exampleSource = ''; + if (component.usageExample) { + try { + const examplePath = join( + process.cwd(), + `src/examples/components/${component.slug}/${component.usageExample}.svelte` + ); + exampleSource = readFileSync(examplePath, 'utf-8'); + exampleSource = trimCode(exampleSource); + } catch (e) { + // Example file may not exist + } + } + + if (exampleSource) { + sections.push('### Example'); + sections.push('```svelte\n' + exampleSource + '\n```'); + } + + // Load API + let api: ComponentAPI | null = null; + try { + const apiPath = join(process.cwd(), `src/generated/api/${component.slug}.json`); + const apiContent = readFileSync(apiPath, 'utf-8'); + api = JSON.parse(apiContent); + } catch (e) { + // API file may not exist + } + + if (api) { + sections.push('### API'); + const table = generateApiTable(api); + if (table) { + sections.push(table); + } + + if (api.extends && api.extends.length > 0) { + const extendsList = api.extends.map((e) => `\`${e.fullType}\``).join(', '); + sections.push(`**Extends:** ${extendsList}`); + } + } + + // Related + if (component.related && component.related.length > 0) { + sections.push('### Related'); + const relatedLinks = component.related + .map((r) => `- [${r}](${BASE_URL}/docs/components/${r})`) + .join('\n'); + sections.push(relatedLinks); + } + + return sections.join('\n\n'); +} + +/** + * Generate markdown for a utility + */ +function generateUtilMarkdown(util: (typeof allUtils)[number]): string { + const sections: string[] = []; + + // Title and description + sections.push(`## ${util.name}`); + if (util.description) { + sections.push(util.description); + } + + // Documentation link + sections.push(`**Full Documentation:** [${util.name}](${BASE_URL}/docs/utils/${util.slug})`); + + // Load example + let exampleSource = ''; + if (util.usageExample) { + try { + const examplePath = join( + process.cwd(), + `src/examples/utils/${util.slug}/${util.usageExample}.svelte` + ); + exampleSource = readFileSync(examplePath, 'utf-8'); + exampleSource = trimCode(exampleSource); + } catch (e) { + // Example file may not exist + } + } + + if (exampleSource) { + sections.push('### Example'); + sections.push('```svelte\n' + exampleSource + '\n```'); + } + + // Related + if (util.related && util.related.length > 0) { + sections.push('### Related'); + const relatedLinks = util.related + .map((r) => `- [${r}](${BASE_URL}/docs/utils/${r})`) + .join('\n'); + sections.push(relatedLinks); + } + + return sections.join('\n\n'); +} diff --git a/docs/src/routes/docs/utils/[name]/+layout.svelte b/docs/src/routes/docs/utils/[name]/+layout.svelte index ae00dddfe..9acb7e2a3 100644 --- a/docs/src/routes/docs/utils/[name]/+layout.svelte +++ b/docs/src/routes/docs/utils/[name]/+layout.svelte @@ -1,11 +1,10 @@ ') // remove data export statement + .trim(); +} + +/** + * Generate the full markdown document + */ +function generateMarkdown( + util: (typeof allUtils)[number], + exampleSource: string +): string { + const sections: string[] = []; + + // Title and description + sections.push(`# ${util.name}`); + if (util.description) { + sections.push(util.description); + } + + // Documentation link + sections.push(`**Full Documentation:** [${util.name}](https://layerchart.com/docs/utils/${util.slug})`); + + // Example + if (exampleSource) { + sections.push('## Example'); + sections.push('```svelte\n' + exampleSource + '\n```'); + } + + // Related + if (util.related && util.related.length > 0) { + sections.push('## Related'); + const relatedLinks = util.related + .map((r) => `- [${r}](https://layerchart.com/docs/utils/${r})`) + .join('\n'); + sections.push(relatedLinks); + } + + return sections.join('\n\n'); +} diff --git a/docs/src/routes/llms.txt/+server.ts b/docs/src/routes/llms.txt/+server.ts new file mode 100644 index 000000000..af7538c52 --- /dev/null +++ b/docs/src/routes/llms.txt/+server.ts @@ -0,0 +1,117 @@ +import { readdirSync } from 'fs'; +import { join } from 'path'; +import type { RequestHandler } from './$types'; +import { allComponents, allUtils } from 'content-collections'; + +const BASE_URL = 'https://layerchart.com'; + +const guides = [ + { slug: 'getting-started', name: 'Getting Started', description: 'Installation and setup guide for LayerChart' }, + { slug: 'guides/scales', name: 'Scales', description: 'Understanding scales, domains, and ranges' }, + { slug: 'guides/layers', name: 'Layers', description: 'Working with SVG, Canvas, and HTML layers' }, + { slug: 'guides/state', name: 'State', description: 'Managing chart state and reactivity' }, + { slug: 'guides/styles', name: 'Styles', description: 'Styling and theming your charts' }, + { slug: 'guides/primitives', name: 'Primitives', description: 'Low-level building blocks for custom charts' }, + { slug: 'guides/simplified-charts', name: 'Simplified Charts', description: 'Quick chart components for common use cases' }, + { slug: 'guides/features', name: 'Features', description: 'Overview of LayerChart features and capabilities' } +]; + +export const GET: RequestHandler = async () => { + const content = generateLlmsTxt(); + + return new Response(content, { + headers: { + 'Content-Type': 'text/plain; charset=utf-8' + } + }); +}; + +function generateLlmsTxt(): string { + const sections: string[] = []; + + // Header + sections.push(`# LayerChart Documentation for LLMs + +> LayerChart is a powerful, composable charting library for Svelte built on top of D3. + +This file contains links to LLM-optimized documentation in markdown format.`); + + // General section + sections.push(`## General + +${guides.map((g) => `- [${g.name}](${BASE_URL}/docs/${g.slug}): ${g.description}`).join('\n')}`); + + // Components section + const componentsList = allComponents + .filter((c) => c.slug && c.name) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((c) => { + const description = c.description || `Documentation for ${c.name} component`; + return `- [${c.name}](${BASE_URL}/docs/components/${c.slug}/llms.txt): ${description}`; + }) + .join('\n'); + + sections.push(`## Components + +${componentsList}`); + + // Utilities section + const utilsList = allUtils + .filter((u) => u.slug && u.name) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((u) => { + const description = u.description || `Documentation for ${u.name} utility`; + return `- [${u.name}](${BASE_URL}/docs/utils/${u.slug}/llms.txt): ${description}`; + }) + .join('\n'); + + sections.push(`## Utilities + +${utilsList}`); + + // Examples section + const examplesList = getExamplesList(); + sections.push(`## Examples + +${examplesList}`); + + return sections.join('\n\n'); +} + +/** + * Get list of all examples from the filesystem + */ +function getExamplesList(): string { + const examplesDir = join(process.cwd(), 'src/examples/components'); + const examples: string[] = []; + + try { + const componentDirs = readdirSync(examplesDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + .sort(); + + for (const componentName of componentDirs) { + const componentDir = join(examplesDir, componentName); + + try { + const exampleFiles = readdirSync(componentDir) + .filter((file) => file.endsWith('.svelte')) + .sort(); + + for (const exampleFile of exampleFiles) { + const exampleName = exampleFile.replace('.svelte', ''); + examples.push( + `- [${componentName}/${exampleName}](${BASE_URL}/docs/components/${componentName}/${exampleName}/llms.txt): Example code for ${componentName}` + ); + } + } catch (e) { + // Skip directories that can't be read + } + } + } catch (e) { + return 'Could not read examples directory.'; + } + + return examples.join('\n'); +}