Enhance LLM export with llms.txt index and copyToLLM styling#336
Conversation
There was a problem hiding this comment.
Code Review
This pull request enhances the LLM-friendly markdown generation by introducing llms.txt and llms-full.txt indexes, supporting <llm-only> custom elements to hide web-only content, and refactoring the copy-to-LLM UI component to use external CSS and support keyboard/click-away interactions. The review feedback suggests three key improvements: using relative paths in llms.txt for better portability, stripping custom VitePress anchors from headings when parsing titles, and updating the <llm-only> regex to handle potential HTML attributes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const links = pages | ||
| .map((page) => { | ||
| const title = firstHeading(page.markdown) ?? page.relativePath; | ||
| return `- [${title}](${ORIGIN}/llms/${page.relativePath})`; |
There was a problem hiding this comment.
Using relative paths in llms.txt is more robust and portable than hardcoding ORIGIN. It allows the index to work correctly on local previews, staging environments, or if the site is hosted under a different domain or base path. LLM clients will resolve these relative links against the URL of the llms.txt file itself.
| return `- [${title}](${ORIGIN}/llms/${page.relativePath})`; | |
| return "- [" + title + "](llms/" + page.relativePath + ")"; |
| function firstHeading(markdown: string): string | null { | ||
| return markdown.match(/^#\s+(.+)$/m)?.[1].trim() ?? null; | ||
| } |
There was a problem hiding this comment.
VitePress headings often contain custom anchors like {#custom-id} (e.g., # Zig 语言圣经 {#zig-bible}). The current regex will include these anchors in the title, which looks messy in the llms.txt index. Stripping them out ensures a clean title.
function firstHeading(markdown: string): string | null {
const match = markdown.match(/^#\\s+(.+)$/m);
if (!match) return null;
return match[1].replace(/\\{#[\\w-]+\\\}\\s*$/, "").trim();
}| function unwrapLlmOnly(markdown: string): string { | ||
| return markdown.replace(/<\/?llm-only\s*>/gi, ""); | ||
| } |
There was a problem hiding this comment.
If the <llm-only> tag has any attributes (such as classes, styles, or Vue directives) or multiple spaces, the current regex /<\\/?llm-only\\s*>/gi will fail to match and strip it. Updating the regex to support optional attributes ensures that the tags are always stripped correctly from the LLM markdown output.
function unwrapLlmOnly(markdown: string): string {
return markdown.replace(/<\\/?llm-only(?:\\s+[^>]*)?>/gi, "");
}
Summary
llms.txtandllms-full.txtindex generation following llmstxt.org standard for LLM-friendly content discovery<llm-only>as a custom Vue element, hidden on web but included in LLM exportsChanges
config.mts: Configure Vue to recognize
<llm-only>elements; pass title/description to pluginllmMarkdown.ts: Generate
llms.txt(linked index) andllms-full.txt(concatenated content); implementunwrapLlmOnly()to strip tags for LLM output while keeping them for web displaycopyToLLM.ts: Move inline styles to CSS classes; add keyboard/click event handlers for menu closure; add ARIA attributes for accessibility
copyToLLM.css (new): Style copy menu, trigger button, and hide
<llm-only>blocks on webindex.ts: Import new stylesheet