Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,59 @@ exports.onPostBuild = async ({ graphql, reporter }) => {
node {
document { title }
pageAttributes { pageid }
fields { markdownBody }
parent {
... on File {
sourceInstanceName
relativePath
}
}
}
}
}
}
`);

if (result.errors) {
reporter.warn(`llms.txt generation: GraphQL errors — ${JSON.stringify(result.errors)}`);
reporter.warn(`Build-time generation: GraphQL errors — ${JSON.stringify(result.errors)}`);
return;
}

const pageMap = {};
// pageData keyed by pageid: { title, docPath }
// docPath is the URL-path segment (e.g. '/getting-started', '/tutorials/intro')
// derived from getDocLinkFromEdge so tutorials with subdirectories resolve correctly.
const pageData = {};
let mdCount = 0;

result.data.allAsciidoc.edges.forEach(({ node }) => {
const pageid = node.pageAttributes?.pageid;
const title = node.document?.title;
if (pageid && title) pageMap[pageid] = title;
const markdownBody = node.fields?.markdownBody;
const relativePath = node.parent?.relativePath || '';
// Auto-generated per-symbol SDK reference pages (scripts/Converter/index.ts) —
// represented in llms.txt by the single curated VisualEmbedSdk entry, not individually.
const isTypedocGenerated = relativePath.startsWith('generated/typedoc/');

if (!pageid || pageid.startsWith('nav-')) return;

const docPath = getDocLinkFromEdge({ node }); // e.g. '/getting-started' or '/tutorials/category/page'
if (title) pageData[pageid] = { title, docPath, isTypedocGenerated };

// Write static .md file — serves at /docs<docPath>.md for agent crawlers
if (markdownBody) {
const header = `# ${title ?? pageid}\n\n> For the complete documentation index, see [llms.txt](${SITE_URL}/llms.txt)\n\nSource: ${SITE_URL}${docPath}\n\n`;
fsExtra.outputFileSync(
`${__dirname}/public${docPath}.md`,
header + markdownBody,
);
mdCount++;
}
});

reporter.info(`[md-gen] Wrote ${mdCount} .md files`);

// Generate llms.txt — curated sections first, then any remaining pages
const coveredIds = new Set();
const lines = [
'# ThoughtSpot Developer Documentation',
'',
Expand All @@ -104,21 +139,39 @@ exports.onPostBuild = async ({ graphql, reporter }) => {
];

for (const section of LLMS_SECTIONS) {
lines.push(`## ${section.label}`);
const sectionLines = [];
for (const pageId of section.pageIds) {
const title = pageMap[pageId];
if (title) lines.push(`- [${title}](${SITE_URL}/${pageId})`);
const data = pageData[pageId];
if (data) {
sectionLines.push(`- [${data.title}](${SITE_URL}${data.docPath}.md)`);
coveredIds.add(pageId);
}
}
if (sectionLines.length) {
lines.push(`## ${section.label}`);
lines.push(...sectionLines);
lines.push('');
}
}

// Add pages that exist as Asciidoc nodes but aren't in any LLMS_SECTIONS entry.
// Excludes typedoc-generated pages — those are covered by the curated VisualEmbedSdk entry.
const uncovered = Object.entries(pageData).filter(
([id, data]) => !coveredIds.has(id) && !data.isTypedocGenerated,
);
if (uncovered.length) {
lines.push('## Additional documentation');
uncovered.forEach(([, { title, docPath }]) => lines.push(`- [${title}](${SITE_URL}${docPath}.md)`));
lines.push('');
}

fsExtra.writeFileSync(
`${__dirname}/public/llms.txt`,
lines.join('\n'),
);
reporter.info(`llms.txt generated with ${Object.keys(pageMap).length} pages`);
reporter.info(`llms.txt: ${coveredIds.size} curated + ${uncovered.length} additional = ${coveredIds.size + uncovered.length} total pages`);
} catch (err) {
reporter.warn(`llms.txt generation failed: ${err.message}`);
reporter.warn(`Build-time generation failed: ${err.message}`);
}
};
exports.createPages = async function ({ actions, graphql }) {
Expand Down
36 changes: 36 additions & 0 deletions gatsby-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const React = require('react');
const { SITE_URL } = require('./src/configs/doc-configs');

exports.onRenderBody = ({ setHeadComponents, setPreBodyComponents }) => {
setHeadComponents([
React.createElement('link', {
key: 'llms-txt',
rel: 'llms-txt',
href: `${SITE_URL}/llms.txt`,
}),
]);

// Visually-hidden body element — picked up by agent crawlers that parse the DOM
// but ignore <head> link tags (Mintlify llms-txt-directive-html check).
setPreBodyComponents([
React.createElement(
'div',
{
key: 'llms-txt-directive',
style: {
position: 'absolute',
width: '1px',
height: '1px',
overflow: 'hidden',
clip: 'rect(0,0,0,0)',
whiteSpace: 'nowrap',
},
},
React.createElement(
'a',
{ href: `${SITE_URL}/llms.txt` },
'LLMs.txt: Complete documentation index for AI agents',
),
),
]);
};
6 changes: 1 addition & 5 deletions modules/ROOT/pages/common/nav-embedding.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,6 @@ include::generated/typedoc/CustomSideNav.adoc[]
[.sidebar-title]
Additional resources

* link:{{navprefix}}/embed-ts[About ThoughtSpot embedding]
* link:{{navprefix}}/get-started-tse[Embed licenses]
* link:{{navprefix}}/license-feature-matrix[Feature matrix]
* link:{{navprefix}}/faqs[FAQs]
* link:{{navprefix}}/code-samples[Code samples]
* link:https://codesandbox.io/s/big-tse-react-demo-i4g9xi[React CodeSandbox, window=_blank]
* link:https://codesandbox.io/s/graphqlcookieembed-wf4fk9?file=/src/App.js:418-426[GraphQL CodeSandbox, window=_blank]
* link:https://github.com/thoughtspot/developer-examples[Developer examples, window=_blank]
Loading