doccupine 0.0.89 → 0.0.90
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/index.js +127 -1
- package/dist/templates/components/layout/Button.d.ts +1 -1
- package/dist/templates/components/layout/Button.js +9 -0
- package/dist/templates/components/layout/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +1 -1
- package/dist/templates/llms/llmsFull.d.ts +12 -0
- package/dist/templates/llms/llmsFull.js +59 -0
- package/dist/templates/llms/llmsIndex.d.ts +9 -0
- package/dist/templates/llms/llmsIndex.js +105 -0
- package/dist/templates/llms/llmsPage.d.ts +2 -0
- package/dist/templates/llms/llmsPage.js +20 -0
- package/dist/templates/mdx/accordion.mdx.d.ts +1 -1
- package/dist/templates/mdx/accordion.mdx.js +21 -16
- package/dist/templates/mdx/ai-assistant.mdx.d.ts +1 -1
- package/dist/templates/mdx/ai-assistant.mdx.js +22 -5
- package/dist/templates/mdx/analytics.mdx.d.ts +1 -1
- package/dist/templates/mdx/analytics.mdx.js +15 -4
- package/dist/templates/mdx/buttons.mdx.d.ts +1 -1
- package/dist/templates/mdx/buttons.mdx.js +10 -2
- package/dist/templates/mdx/callouts.mdx.d.ts +1 -1
- package/dist/templates/mdx/callouts.mdx.js +10 -17
- package/dist/templates/mdx/cards.mdx.d.ts +1 -1
- package/dist/templates/mdx/cards.mdx.js +10 -5
- package/dist/templates/mdx/code.mdx.d.ts +1 -1
- package/dist/templates/mdx/code.mdx.js +7 -3
- package/dist/templates/mdx/color-swatches.mdx.d.ts +1 -1
- package/dist/templates/mdx/color-swatches.mdx.js +7 -4
- package/dist/templates/mdx/columns.mdx.d.ts +1 -1
- package/dist/templates/mdx/columns.mdx.js +3 -0
- package/dist/templates/mdx/commands.mdx.d.ts +1 -1
- package/dist/templates/mdx/commands.mdx.js +7 -4
- package/dist/templates/mdx/components.mdx.d.ts +1 -1
- package/dist/templates/mdx/components.mdx.js +1 -0
- package/dist/templates/mdx/deployment-and-hosting.mdx.d.ts +1 -1
- package/dist/templates/mdx/deployment-and-hosting.mdx.js +6 -0
- package/dist/templates/mdx/fields.mdx.d.ts +1 -1
- package/dist/templates/mdx/fields.mdx.js +3 -0
- package/dist/templates/mdx/fonts.mdx.d.ts +1 -1
- package/dist/templates/mdx/fonts.mdx.js +13 -2
- package/dist/templates/mdx/footer-links.mdx.d.ts +1 -1
- package/dist/templates/mdx/footer-links.mdx.js +5 -0
- package/dist/templates/mdx/globals.mdx.d.ts +1 -1
- package/dist/templates/mdx/globals.mdx.js +16 -13
- package/dist/templates/mdx/headers-and-text.mdx.d.ts +1 -1
- package/dist/templates/mdx/headers-and-text.mdx.js +22 -2
- package/dist/templates/mdx/icons.mdx.d.ts +1 -1
- package/dist/templates/mdx/icons.mdx.js +3 -0
- package/dist/templates/mdx/image-and-embeds.mdx.d.ts +1 -1
- package/dist/templates/mdx/image-and-embeds.mdx.js +19 -10
- package/dist/templates/mdx/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/index.mdx.js +2 -2
- package/dist/templates/mdx/lists-and-tables.mdx.d.ts +1 -1
- package/dist/templates/mdx/lists-and-tables.mdx.js +8 -2
- package/dist/templates/mdx/media-and-assets.mdx.d.ts +1 -1
- package/dist/templates/mdx/media-and-assets.mdx.js +14 -5
- package/dist/templates/mdx/model-context-protocol.mdx.d.ts +1 -1
- package/dist/templates/mdx/model-context-protocol.mdx.js +31 -15
- package/dist/templates/mdx/navigation.mdx.d.ts +1 -1
- package/dist/templates/mdx/navigation.mdx.js +9 -0
- package/dist/templates/mdx/platform/ai-assistant.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/ai-assistant.mdx.js +7 -0
- package/dist/templates/mdx/platform/analytics.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/analytics.mdx.js +7 -0
- package/dist/templates/mdx/platform/billing.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/billing.mdx.js +8 -0
- package/dist/templates/mdx/platform/build-and-deploy.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/build-and-deploy.mdx.js +6 -0
- package/dist/templates/mdx/platform/creating-a-project.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/creating-a-project.mdx.js +7 -0
- package/dist/templates/mdx/platform/custom-domains.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/custom-domains.mdx.js +5 -0
- package/dist/templates/mdx/platform/external-links.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/external-links.mdx.js +5 -0
- package/dist/templates/mdx/platform/file-editor.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/file-editor.mdx.js +7 -0
- package/dist/templates/mdx/platform/fonts-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/fonts-settings.mdx.js +5 -0
- package/dist/templates/mdx/platform/index.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/index.mdx.js +5 -0
- package/dist/templates/mdx/platform/navigation-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/navigation-settings.mdx.js +20 -4
- package/dist/templates/mdx/platform/project-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/project-settings.mdx.js +4 -0
- package/dist/templates/mdx/platform/publishing.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/publishing.mdx.js +6 -0
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +8 -0
- package/dist/templates/mdx/platform/team-members.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/team-members.mdx.js +8 -0
- package/dist/templates/mdx/platform/theme-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/theme-settings.mdx.js +7 -0
- package/dist/templates/mdx/sections.mdx.d.ts +1 -1
- package/dist/templates/mdx/sections.mdx.js +22 -1
- package/dist/templates/mdx/steps.mdx.d.ts +1 -1
- package/dist/templates/mdx/steps.mdx.js +7 -5
- package/dist/templates/mdx/tabs.mdx.d.ts +1 -1
- package/dist/templates/mdx/tabs.mdx.js +7 -2
- package/dist/templates/mdx/theme.mdx.d.ts +1 -1
- package/dist/templates/mdx/theme.mdx.js +10 -0
- package/dist/templates/mdx/update.mdx.d.ts +1 -1
- package/dist/templates/mdx/update.mdx.js +17 -14
- package/dist/templates/package.js +14 -14
- package/dist/templates/pnpmWorkspace.d.ts +1 -0
- package/dist/templates/pnpmWorkspace.js +7 -0
- package/package.json +6 -7
package/dist/index.js
CHANGED
|
@@ -12,9 +12,13 @@ import { ConfigManager } from "./lib/config-manager.js";
|
|
|
12
12
|
import { findAvailablePort, generateSlug, getFullSlug, escapeTemplateContent, } from "./lib/utils.js";
|
|
13
13
|
import { generateMetadataBlock, generateRuntimeOnlyMetadataBlock, generateJsonLdScript, } from "./lib/metadata.js";
|
|
14
14
|
import { nextConfigTemplate } from "./templates/next.config.js";
|
|
15
|
+
import { pnpmWorkspaceTemplate } from "./templates/pnpmWorkspace.js";
|
|
15
16
|
import { proxyTemplate } from "./templates/proxy.js";
|
|
16
17
|
import { robotsTemplate } from "./templates/app/robots.js";
|
|
17
18
|
import { sitemapTemplate } from "./templates/app/sitemap.js";
|
|
19
|
+
import { llmsIndexTemplate } from "./templates/llms/llmsIndex.js";
|
|
20
|
+
import { llmsFullTemplate, } from "./templates/llms/llmsFull.js";
|
|
21
|
+
import { llmsPageTemplate } from "./templates/llms/llmsPage.js";
|
|
18
22
|
export { generateSlug, getFullSlug, escapeTemplateContent, } from "./lib/utils.js";
|
|
19
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
24
|
const __dirname = path.dirname(__filename);
|
|
@@ -43,6 +47,8 @@ class MDXToNextJSGenerator {
|
|
|
43
47
|
sectionsConfig = null;
|
|
44
48
|
/** Guards against recursive reprocessing when maybeUpdateSections() triggers processAllMDXFiles() */
|
|
45
49
|
isReprocessing = false;
|
|
50
|
+
/** Tracks per-page .md files written under public/ so we can clean up stale ones on rename/delete */
|
|
51
|
+
generatedLlmsPagePaths = new Set();
|
|
46
52
|
constructor(watchDir, outputDir) {
|
|
47
53
|
this.watchDir = path.resolve(watchDir);
|
|
48
54
|
this.outputDir = path.resolve(outputDir);
|
|
@@ -78,6 +84,7 @@ class MDXToNextJSGenerator {
|
|
|
78
84
|
const structure = {
|
|
79
85
|
...appStructure,
|
|
80
86
|
"next.config.ts": nextConfigTemplate(this.analyticsConfig),
|
|
87
|
+
"pnpm-workspace.yaml": pnpmWorkspaceTemplate,
|
|
81
88
|
"proxy.ts": proxyTemplate(this.analyticsConfig),
|
|
82
89
|
"analytics.json": `{}\n`,
|
|
83
90
|
"config.json": `{}\n`,
|
|
@@ -94,6 +101,7 @@ class MDXToNextJSGenerator {
|
|
|
94
101
|
await fs.writeFile(fullPath, String(await content), "utf8");
|
|
95
102
|
}
|
|
96
103
|
await this.updateSitemap();
|
|
104
|
+
await this.updateLlmsFiles();
|
|
97
105
|
}
|
|
98
106
|
async createStartingDocs() {
|
|
99
107
|
const structure = startingDocsStructure;
|
|
@@ -334,6 +342,7 @@ class MDXToNextJSGenerator {
|
|
|
334
342
|
if (fileName === "config.json") {
|
|
335
343
|
await this.updateSitemap();
|
|
336
344
|
await this.updateRobots();
|
|
345
|
+
await this.updateLlmsFiles();
|
|
337
346
|
}
|
|
338
347
|
}
|
|
339
348
|
catch (error) {
|
|
@@ -356,6 +365,7 @@ class MDXToNextJSGenerator {
|
|
|
356
365
|
if (fileName === "config.json") {
|
|
357
366
|
await this.updateSitemap();
|
|
358
367
|
await this.updateRobots();
|
|
368
|
+
await this.updateLlmsFiles();
|
|
359
369
|
}
|
|
360
370
|
}
|
|
361
371
|
catch (error) {
|
|
@@ -681,6 +691,7 @@ class MDXToNextJSGenerator {
|
|
|
681
691
|
await this.updatePagesIndex();
|
|
682
692
|
await this.updateRootLayout();
|
|
683
693
|
await this.updateSitemap();
|
|
694
|
+
await this.updateLlmsFiles();
|
|
684
695
|
await this.generateSectionIndexPages();
|
|
685
696
|
console.log(chalk.green(`✅ Generated page for: ${filePath}`));
|
|
686
697
|
await this.maybeUpdateSections();
|
|
@@ -705,6 +716,7 @@ class MDXToNextJSGenerator {
|
|
|
705
716
|
await this.updatePagesIndex();
|
|
706
717
|
await this.updateRootLayout();
|
|
707
718
|
await this.updateSitemap();
|
|
719
|
+
await this.updateLlmsFiles();
|
|
708
720
|
console.log(chalk.green(`✅ Removed page for: ${filePath}`));
|
|
709
721
|
await this.maybeUpdateSections();
|
|
710
722
|
}
|
|
@@ -1018,6 +1030,120 @@ export default function Page() {
|
|
|
1018
1030
|
? `🤖 Regenerated robots.ts with sitemap link`
|
|
1019
1031
|
: `🤖 Regenerated robots.ts (no sitemap link)`));
|
|
1020
1032
|
}
|
|
1033
|
+
async loadSiteMetadata() {
|
|
1034
|
+
const configPath = path.join(this.rootDir, "config.json");
|
|
1035
|
+
let url = null;
|
|
1036
|
+
let name = "Documentation";
|
|
1037
|
+
let description = "";
|
|
1038
|
+
try {
|
|
1039
|
+
if (await fs.pathExists(configPath)) {
|
|
1040
|
+
const content = await fs.readFile(configPath, "utf8");
|
|
1041
|
+
const parsed = JSON.parse(content);
|
|
1042
|
+
if (typeof parsed.url === "string" && parsed.url.trim() !== "") {
|
|
1043
|
+
url = parsed.url.trim().replace(/\/$/, "");
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof parsed.name === "string" && parsed.name.trim() !== "") {
|
|
1046
|
+
name = parsed.name.trim();
|
|
1047
|
+
}
|
|
1048
|
+
else if (typeof parsed.title === "string" &&
|
|
1049
|
+
parsed.title.trim() !== "") {
|
|
1050
|
+
name = parsed.title.trim();
|
|
1051
|
+
}
|
|
1052
|
+
if (typeof parsed.description === "string" &&
|
|
1053
|
+
parsed.description.trim() !== "") {
|
|
1054
|
+
description = parsed.description.trim();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
catch (error) {
|
|
1059
|
+
console.warn(chalk.yellow("⚠️ Error reading config.json for llms metadata"), error);
|
|
1060
|
+
}
|
|
1061
|
+
return { url, name, description };
|
|
1062
|
+
}
|
|
1063
|
+
async readPageWithBody(page) {
|
|
1064
|
+
const fullPath = path.join(this.watchDir, page.path);
|
|
1065
|
+
const raw = await fs.readFile(fullPath, "utf8");
|
|
1066
|
+
const { content: body } = matter(raw);
|
|
1067
|
+
return { ...page, body };
|
|
1068
|
+
}
|
|
1069
|
+
llmsManifestPath() {
|
|
1070
|
+
return path.join(this.outputDir, ".doccupine-llms-manifest.json");
|
|
1071
|
+
}
|
|
1072
|
+
async readLlmsManifest() {
|
|
1073
|
+
const manifestPath = this.llmsManifestPath();
|
|
1074
|
+
try {
|
|
1075
|
+
if (await fs.pathExists(manifestPath)) {
|
|
1076
|
+
const raw = await fs.readFile(manifestPath, "utf8");
|
|
1077
|
+
const parsed = JSON.parse(raw);
|
|
1078
|
+
if (Array.isArray(parsed.pageFiles)) {
|
|
1079
|
+
return new Set(parsed.pageFiles.filter((entry) => typeof entry === "string"));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
catch {
|
|
1084
|
+
// ignore corrupted manifest
|
|
1085
|
+
}
|
|
1086
|
+
return new Set();
|
|
1087
|
+
}
|
|
1088
|
+
async writeLlmsManifest(pageFiles) {
|
|
1089
|
+
const manifestPath = this.llmsManifestPath();
|
|
1090
|
+
const payload = { pageFiles: Array.from(pageFiles).sort() };
|
|
1091
|
+
const json = JSON.stringify(payload, null, 2) + "\n";
|
|
1092
|
+
await fs.writeFile(manifestPath, json, "utf8");
|
|
1093
|
+
}
|
|
1094
|
+
async updateLlmsFiles() {
|
|
1095
|
+
const publicDir = path.join(this.outputDir, "public");
|
|
1096
|
+
await fs.ensureDir(publicDir);
|
|
1097
|
+
const { url: baseUrl, name, description } = await this.loadSiteMetadata();
|
|
1098
|
+
const pages = await this.buildAllPagesMeta();
|
|
1099
|
+
const pagesWithBodies = await Promise.all(pages.map((page) => this.readPageWithBody(page)));
|
|
1100
|
+
const indexContent = llmsIndexTemplate({
|
|
1101
|
+
siteName: name,
|
|
1102
|
+
siteDescription: description,
|
|
1103
|
+
baseUrl,
|
|
1104
|
+
pages,
|
|
1105
|
+
sectionsConfig: this.sectionsConfig,
|
|
1106
|
+
});
|
|
1107
|
+
const fullContent = llmsFullTemplate({
|
|
1108
|
+
siteName: name,
|
|
1109
|
+
siteDescription: description,
|
|
1110
|
+
baseUrl,
|
|
1111
|
+
pages: pagesWithBodies,
|
|
1112
|
+
sectionsConfig: this.sectionsConfig,
|
|
1113
|
+
});
|
|
1114
|
+
await fs.writeFile(path.join(publicDir, "llms.txt"), indexContent, "utf8");
|
|
1115
|
+
await fs.writeFile(path.join(publicDir, "llms-full.txt"), fullContent, "utf8");
|
|
1116
|
+
const nextRelativePaths = new Set();
|
|
1117
|
+
await Promise.all(pagesWithBodies.map(async (page) => {
|
|
1118
|
+
if (page.slug === "")
|
|
1119
|
+
return;
|
|
1120
|
+
const relPath = `${page.slug}.md`;
|
|
1121
|
+
const targetPath = path.join(publicDir, relPath);
|
|
1122
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
1123
|
+
await fs.writeFile(targetPath, llmsPageTemplate(page, baseUrl), "utf8");
|
|
1124
|
+
nextRelativePaths.add(relPath);
|
|
1125
|
+
}));
|
|
1126
|
+
const previousRelativePaths = new Set([
|
|
1127
|
+
...this.generatedLlmsPagePaths,
|
|
1128
|
+
...(await this.readLlmsManifest()),
|
|
1129
|
+
]);
|
|
1130
|
+
for (const stale of previousRelativePaths) {
|
|
1131
|
+
if (!nextRelativePaths.has(stale)) {
|
|
1132
|
+
try {
|
|
1133
|
+
const stalePath = path.join(publicDir, stale);
|
|
1134
|
+
if (await fs.pathExists(stalePath)) {
|
|
1135
|
+
await fs.remove(stalePath);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
// ignore
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
this.generatedLlmsPagePaths = nextRelativePaths;
|
|
1144
|
+
await this.writeLlmsManifest(nextRelativePaths);
|
|
1145
|
+
console.log(chalk.green(`🤖 Generated llms.txt and llms-full.txt with ${pages.length} page(s)${baseUrl ? ` using ${baseUrl}` : " (relative URLs)"}`));
|
|
1146
|
+
}
|
|
1021
1147
|
async stop() {
|
|
1022
1148
|
if (this.watcher) {
|
|
1023
1149
|
await this.watcher.close();
|
|
@@ -1077,7 +1203,7 @@ program
|
|
|
1077
1203
|
console.log(chalk.blue(`📦 Using ${packageManager}...`));
|
|
1078
1204
|
const install = spawn(packageManager, ["install"], {
|
|
1079
1205
|
cwd: config.outputDir,
|
|
1080
|
-
stdio: "
|
|
1206
|
+
stdio: "inherit",
|
|
1081
1207
|
});
|
|
1082
1208
|
await new Promise((resolve, reject) => {
|
|
1083
1209
|
install.on("close", (code) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\n// Cherry's buttonStyles picks filled-button text via `isDark ? colors.dark : colors.light`.\n// Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so\n// the fallback resolves to --color-light (black in dark mode). Re-pin to `surface`,\n// which resolves to white in both modes, for the filled, non-disabled case.\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
|
|
1
|
+
export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n rel?: string;\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n theme?: typeof localTheme;\n}\n\n// Cherry's buttonStyles picks filled-button text via `isDark ? colors.dark : colors.light`.\n// Our theme's isDark is a stub (false) because mode switching lives in CSS vars, so\n// the fallback resolves to --color-light (black in dark mode). Re-pin to `surface`,\n// which resolves to white in both modes, for the filled, non-disabled case.\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n ${({ theme, $outline, disabled }) =>\n !disabled && !$outline && `color: ${theme.colors.surface};`}\n\n & p {\n color: inherit !important;\n }\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme: _theme = localTheme,\n href,\n target,\n rel,\n ...props\n}: LinkButtonProps) {\n const isExternal = typeof href === \"string\" && /^(https?:)?\\/\\//i.test(href);\n const resolvedTarget = target ?? (isExternal ? \"_blank\" : undefined);\n const resolvedRel =\n rel ?? (resolvedTarget === \"_blank\" ? \"noopener noreferrer\" : undefined);\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n target={resolvedTarget}\n rel={resolvedRel}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
|
|
@@ -11,6 +11,7 @@ import { Icon } from "@/components/layout/Icon";
|
|
|
11
11
|
interface LinkButtonProps extends ButtonProps {
|
|
12
12
|
href?: string;
|
|
13
13
|
target?: "_blank" | "_self" | "_parent" | "_top";
|
|
14
|
+
rel?: string;
|
|
14
15
|
variant?: "primary" | "secondary" | "tertiary";
|
|
15
16
|
size?: "default" | "big";
|
|
16
17
|
outline?: boolean;
|
|
@@ -69,13 +70,21 @@ function Button({
|
|
|
69
70
|
iconPosition = "left",
|
|
70
71
|
theme: _theme = localTheme,
|
|
71
72
|
href,
|
|
73
|
+
target,
|
|
74
|
+
rel,
|
|
72
75
|
...props
|
|
73
76
|
}: LinkButtonProps) {
|
|
77
|
+
const isExternal = typeof href === "string" && /^(https?:)?\\/\\//i.test(href);
|
|
78
|
+
const resolvedTarget = target ?? (isExternal ? "_blank" : undefined);
|
|
79
|
+
const resolvedRel =
|
|
80
|
+
rel ?? (resolvedTarget === "_blank" ? "noopener noreferrer" : undefined);
|
|
74
81
|
return href ? (
|
|
75
82
|
<div>
|
|
76
83
|
<StyledLinkButton
|
|
77
84
|
{...props}
|
|
78
85
|
href={href}
|
|
86
|
+
target={resolvedTarget}
|
|
87
|
+
rel={resolvedRel}
|
|
79
88
|
$variant={variant}
|
|
80
89
|
$size={size}
|
|
81
90
|
$outline={outline}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n\n &:hover {\n color: ${({ theme }) => theme.colors.
|
|
1
|
+
export declare const footerTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { Space, styledSmall } from \"cherry-styled-components\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { GitHubLogo } from \"@/components/layout/Pictograms\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledFooter = styled.footer<{\n theme: Theme;\n $isChatOpen?: boolean;\n $hasLinks?: boolean;\n}>`\n padding: 0 20px;\n transition: all 0.3s ease;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n margin-top: 20px;\n `}\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 0 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 0 300px;\n `}\n }\n`;\n\nconst StyledFooterInner = styled.div<{ theme: Theme }>`\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n max-width: 640px;\n margin: 0 auto;\n padding: 28px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n\n ${mq(\"lg\")} {\n padding: 20px 0;\n }\n\n & a {\n font-weight: 700;\n color: ${({ theme }) => theme.colors.accent};\n text-decoration: none;\n transition: all 0.3s ease;\n display: inline-flex;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primary};\n }\n\n & svg {\n width: 18px;\n height: 18px;\n }\n }\n`;\n\nconst StyledFooterFlex = styled.div`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n gap: 20px;\n\n ${mq(\"lg\")} {\n justify-content: space-between;\n }\n`;\n\nfunction Footer({ hideBranding }: { hideBranding?: boolean }) {\n const { isOpen } = useContext(ChatContext);\n\n if (hideBranding) return <Space $xs={80} $lg=\"none\" />;\n\n return (\n <StyledFooter $isChatOpen={isOpen} $hasLinks={links.length > 0}>\n <StyledFooterInner>\n <StyledFooterFlex>\n <span>\n Powered by <a href=\"https://doccupine.com\">Doccupine</a>\n </span>\n <a\n href=\"https://github.com/doccupine/cli\"\n target=\"_blank\"\n aria-label=\"Doccupine on GitHub\"\n >\n <GitHubLogo />\n </a>\n </StyledFooterFlex>\n </StyledFooterInner>\n </StyledFooter>\n );\n}\n\nexport { Footer };\n";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PageMeta, SectionConfig } from "../../lib/types.js";
|
|
2
|
+
export interface PageWithBody extends PageMeta {
|
|
3
|
+
body: string;
|
|
4
|
+
}
|
|
5
|
+
export interface LlmsFullArgs {
|
|
6
|
+
siteName: string;
|
|
7
|
+
siteDescription?: string;
|
|
8
|
+
baseUrl: string | null;
|
|
9
|
+
pages: PageWithBody[];
|
|
10
|
+
sectionsConfig: SectionConfig[] | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function llmsFullTemplate(args: LlmsFullArgs): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function pageUrl(slug, baseUrl) {
|
|
2
|
+
if (baseUrl) {
|
|
3
|
+
return slug === "" ? `${baseUrl}/` : `${baseUrl}/${slug}`;
|
|
4
|
+
}
|
|
5
|
+
return slug === "" ? "/" : `/${slug}`;
|
|
6
|
+
}
|
|
7
|
+
function orderedPages(pages, sectionsConfig) {
|
|
8
|
+
const sectionOrderIndex = new Map();
|
|
9
|
+
sectionOrderIndex.set("", 0);
|
|
10
|
+
if (sectionsConfig) {
|
|
11
|
+
sectionsConfig.forEach((section, idx) => {
|
|
12
|
+
sectionOrderIndex.set(section.slug, idx + 1);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return [...pages].sort((a, b) => {
|
|
16
|
+
const sa = sectionOrderIndex.get(a.section) ?? 999;
|
|
17
|
+
const sb = sectionOrderIndex.get(b.section) ?? 999;
|
|
18
|
+
if (sa !== sb)
|
|
19
|
+
return sa - sb;
|
|
20
|
+
if (a.categoryOrder !== b.categoryOrder)
|
|
21
|
+
return a.categoryOrder - b.categoryOrder;
|
|
22
|
+
if (a.category !== b.category)
|
|
23
|
+
return a.category.localeCompare(b.category);
|
|
24
|
+
if (a.order !== b.order)
|
|
25
|
+
return a.order - b.order;
|
|
26
|
+
return a.title.localeCompare(b.title);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function llmsFullTemplate(args) {
|
|
30
|
+
const { siteName, siteDescription, baseUrl, pages, sectionsConfig } = args;
|
|
31
|
+
const lines = [];
|
|
32
|
+
lines.push(`# ${siteName}`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push(`> Full documentation for ${siteName}.`);
|
|
35
|
+
if (siteDescription && siteDescription.trim() !== "") {
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push(siteDescription.trim());
|
|
38
|
+
}
|
|
39
|
+
lines.push("");
|
|
40
|
+
if (pages.length === 0) {
|
|
41
|
+
return lines.join("\n") + "\n";
|
|
42
|
+
}
|
|
43
|
+
const sorted = orderedPages(pages, sectionsConfig);
|
|
44
|
+
for (const page of sorted) {
|
|
45
|
+
lines.push("---");
|
|
46
|
+
lines.push("");
|
|
47
|
+
lines.push(`# ${page.title}`);
|
|
48
|
+
lines.push("");
|
|
49
|
+
if (page.description && page.description.trim() !== "") {
|
|
50
|
+
lines.push(`> ${page.description.trim()}`);
|
|
51
|
+
lines.push("");
|
|
52
|
+
}
|
|
53
|
+
lines.push(`Source: ${pageUrl(page.slug, baseUrl)}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push(page.body.trim());
|
|
56
|
+
lines.push("");
|
|
57
|
+
}
|
|
58
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
59
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PageMeta, SectionConfig } from "../../lib/types.js";
|
|
2
|
+
export interface LlmsIndexArgs {
|
|
3
|
+
siteName: string;
|
|
4
|
+
siteDescription?: string;
|
|
5
|
+
baseUrl: string | null;
|
|
6
|
+
pages: PageMeta[];
|
|
7
|
+
sectionsConfig: SectionConfig[] | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function llmsIndexTemplate(args: LlmsIndexArgs): string;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function pageUrl(slug, baseUrl) {
|
|
2
|
+
if (baseUrl) {
|
|
3
|
+
return slug === "" ? `${baseUrl}/` : `${baseUrl}/${slug}`;
|
|
4
|
+
}
|
|
5
|
+
return slug === "" ? "/" : `/${slug}`;
|
|
6
|
+
}
|
|
7
|
+
function comparePages(a, b) {
|
|
8
|
+
if (a.order !== b.order)
|
|
9
|
+
return a.order - b.order;
|
|
10
|
+
return a.title.localeCompare(b.title);
|
|
11
|
+
}
|
|
12
|
+
function buildSectionGroups(pages, sectionsConfig) {
|
|
13
|
+
const sectionOrderIndex = new Map();
|
|
14
|
+
sectionOrderIndex.set("", 0);
|
|
15
|
+
if (sectionsConfig) {
|
|
16
|
+
sectionsConfig.forEach((section, idx) => {
|
|
17
|
+
sectionOrderIndex.set(section.slug, idx + 1);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const sectionLabelByslug = new Map();
|
|
21
|
+
if (sectionsConfig) {
|
|
22
|
+
for (const section of sectionsConfig) {
|
|
23
|
+
sectionLabelByslug.set(section.slug, section.label);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const bySection = new Map();
|
|
27
|
+
for (const page of pages) {
|
|
28
|
+
const key = page.section || "";
|
|
29
|
+
const list = bySection.get(key) ?? [];
|
|
30
|
+
list.push(page);
|
|
31
|
+
bySection.set(key, list);
|
|
32
|
+
}
|
|
33
|
+
const groups = [];
|
|
34
|
+
for (const [sectionSlug, sectionPages] of bySection) {
|
|
35
|
+
const byCategory = new Map();
|
|
36
|
+
for (const page of sectionPages) {
|
|
37
|
+
const key = page.category || "";
|
|
38
|
+
const list = byCategory.get(key) ?? [];
|
|
39
|
+
list.push(page);
|
|
40
|
+
byCategory.set(key, list);
|
|
41
|
+
}
|
|
42
|
+
const categories = [];
|
|
43
|
+
for (const [category, catPages] of byCategory) {
|
|
44
|
+
catPages.sort(comparePages);
|
|
45
|
+
const minCategoryOrder = catPages.reduce((min, p) => Math.min(min, p.categoryOrder), Number.POSITIVE_INFINITY);
|
|
46
|
+
categories.push({
|
|
47
|
+
category,
|
|
48
|
+
minCategoryOrder: Number.isFinite(minCategoryOrder)
|
|
49
|
+
? minCategoryOrder
|
|
50
|
+
: 0,
|
|
51
|
+
pages: catPages,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
categories.sort((a, b) => {
|
|
55
|
+
if (a.minCategoryOrder !== b.minCategoryOrder) {
|
|
56
|
+
return a.minCategoryOrder - b.minCategoryOrder;
|
|
57
|
+
}
|
|
58
|
+
return a.category.localeCompare(b.category);
|
|
59
|
+
});
|
|
60
|
+
groups.push({
|
|
61
|
+
sectionSlug,
|
|
62
|
+
sectionLabel: sectionLabelByslug.get(sectionSlug) ??
|
|
63
|
+
(sectionSlug === "" ? "Documentation" : sectionSlug),
|
|
64
|
+
sectionOrder: sectionOrderIndex.get(sectionSlug) ?? 999,
|
|
65
|
+
categories,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
groups.sort((a, b) => a.sectionOrder - b.sectionOrder);
|
|
69
|
+
return groups;
|
|
70
|
+
}
|
|
71
|
+
export function llmsIndexTemplate(args) {
|
|
72
|
+
const { siteName, siteDescription, baseUrl, pages, sectionsConfig } = args;
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(`# ${siteName}`);
|
|
75
|
+
lines.push("");
|
|
76
|
+
if (siteDescription && siteDescription.trim() !== "") {
|
|
77
|
+
lines.push(`> ${siteDescription.trim()}`);
|
|
78
|
+
lines.push("");
|
|
79
|
+
}
|
|
80
|
+
if (pages.length === 0) {
|
|
81
|
+
return lines.join("\n") + "\n";
|
|
82
|
+
}
|
|
83
|
+
const groups = buildSectionGroups(pages, sectionsConfig);
|
|
84
|
+
for (const group of groups) {
|
|
85
|
+
lines.push(`## ${group.sectionLabel}`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
const useCategoryHeadings = group.categories.length > 1 ||
|
|
88
|
+
(group.categories.length === 1 && group.categories[0].category !== "");
|
|
89
|
+
for (const cat of group.categories) {
|
|
90
|
+
if (useCategoryHeadings && cat.category !== "") {
|
|
91
|
+
lines.push(`### ${cat.category}`);
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
94
|
+
for (const page of cat.pages) {
|
|
95
|
+
const url = pageUrl(page.slug, baseUrl);
|
|
96
|
+
const desc = page.description && page.description.trim() !== ""
|
|
97
|
+
? `: ${page.description.trim()}`
|
|
98
|
+
: "";
|
|
99
|
+
lines.push(`- [${page.title}](${url})${desc}`);
|
|
100
|
+
}
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
105
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function pageUrl(slug, baseUrl) {
|
|
2
|
+
if (baseUrl) {
|
|
3
|
+
return slug === "" ? `${baseUrl}/` : `${baseUrl}/${slug}`;
|
|
4
|
+
}
|
|
5
|
+
return slug === "" ? "/" : `/${slug}`;
|
|
6
|
+
}
|
|
7
|
+
export function llmsPageTemplate(page, baseUrl) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`# ${page.title}`);
|
|
10
|
+
lines.push("");
|
|
11
|
+
if (page.description && page.description.trim() !== "") {
|
|
12
|
+
lines.push(`> ${page.description.trim()}`);
|
|
13
|
+
lines.push("");
|
|
14
|
+
}
|
|
15
|
+
lines.push(`Source: ${pageUrl(page.slug, baseUrl)}`);
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push(page.body.trim());
|
|
18
|
+
lines.push("");
|
|
19
|
+
return lines.join("\n");
|
|
20
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const accordionMdxTemplate = "---\ntitle: \"Accordion\"\ndescription: \"Interactive panels for toggling visibility of content.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 4\n---\n# Accordion\nInteractive panels for toggling visibility of content.\n\nAccordion elements help organize information by letting users show or hide sections as needed. They\u2019re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.\n\n## Accordion Usage\nYou can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:\n\n
|
|
1
|
+
export declare const accordionMdxTemplate = "---\ntitle: \"Accordion\"\ndescription: \"Interactive panels for toggling visibility of content.\"\ndate: \"2026-02-19\"\ncategory: \"Components\"\ncategoryOrder: 1\norder: 4\n---\n\n# Accordion\n\nInteractive panels for toggling visibility of content.\n\nAccordion elements help organize information by letting users show or hide sections as needed. They\u2019re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.\n\n## Accordion Usage\n\nYou can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:\n\n````mdx\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n```\n\n</Accordion>\n````\n\n<Accordion title=\"What is MDX?\">\n You can put any content in here, including other components, like code:\n\n```java HelloWorld.java\n class HelloWorld {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n }\n }\n```\n\n</Accordion>\n\n## Properties\n\n<Field value=\"title\" type=\"string\" required>\n The title of the accordion.\n</Field>\n\n<Field value=\"children\" type=\"node\" required>\n The content of the accordion.\n</Field>";
|
|
@@ -6,38 +6,43 @@ category: "Components"
|
|
|
6
6
|
categoryOrder: 1
|
|
7
7
|
order: 4
|
|
8
8
|
---
|
|
9
|
+
|
|
9
10
|
# Accordion
|
|
11
|
+
|
|
10
12
|
Interactive panels for toggling visibility of content.
|
|
11
13
|
|
|
12
14
|
Accordion elements help organize information by letting users show or hide sections as needed. They’re an effective way to manage progressive disclosure and simplify navigation through dense or optional content.
|
|
13
15
|
|
|
14
16
|
## Accordion Usage
|
|
17
|
+
|
|
15
18
|
You can use the Accordion component directly within your MDX files without any import. The following example shows a basic usage:
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
\`\`\`\`mdx
|
|
18
21
|
<Accordion title="What is MDX?">
|
|
19
22
|
You can put any content in here, including other components, like code:
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
\`\`\`java HelloWorld.java
|
|
25
|
+
class HelloWorld {
|
|
26
|
+
public static void main(String[] args) {
|
|
27
|
+
System.out.println("Hello, World!");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
28
32
|
</Accordion>
|
|
29
|
-
|
|
33
|
+
\`\`\`\`
|
|
30
34
|
|
|
31
35
|
<Accordion title="What is MDX?">
|
|
32
36
|
You can put any content in here, including other components, like code:
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
\`\`\`java HelloWorld.java
|
|
39
|
+
class HelloWorld {
|
|
40
|
+
public static void main(String[] args) {
|
|
41
|
+
System.out.println("Hello, World!");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
41
46
|
</Accordion>
|
|
42
47
|
|
|
43
48
|
## Properties
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const aiAssistantMdxTemplate = "---\ntitle: \"AI Assistant\"\ndescription: \"Integrate AI capabilities into your Doccupine documentation using OpenAI, Anthropic, or Google Gemini.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 8\n---\n# AI Assistant\nDoccupine supports AI integration to enhance your documentation experience. You can use OpenAI, Anthropic, or Google Gemini to power AI features in your documentation site. The AI assistant uses your documentation content as context, allowing users to ask questions about your docs and receive accurate answers based on the documentation.\n\n## Setup\nTo enable AI features, create an `.env` file in the directory where your website is generated. By default, this is the `nextjs-app/` directory.\n\n## Configuration\nCreate an `.env` file with the following configuration options:\n\n```env\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model (see your provider's docs for available models)\n# LLM_CHAT_MODEL=your-model-id\n\n# Optional: Override default embedding model (see your provider's docs for available models)\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=your-embedding-model-id\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n```\n\n## Provider Selection\nSet `LLM_PROVIDER` to one of the following values:\n- `openai` - Use OpenAI's models\n- `anthropic` - Use Anthropic's models\n- `google` - Use Google's models\n\n## API Keys\nYou need to set the API key that matches your chosen provider:\n- For OpenAI: Set `OPENAI_API_KEY`\n- For Anthropic: Set `ANTHROPIC_API_KEY`\n- For Google: Set `GOOGLE_API_KEY`\n\n<Callout type=\"warning\">\n Keep your API keys secure. Never commit your `.env` file to version control.\n</Callout>\n\n<Callout type=\"note\">\n Doccupine automatically adds `.env` to your `.gitignore` file.\n</Callout>\n\n## Using Anthropic with OpenAI\nIf you want to use Anthropic as your LLM provider, you must also have an OpenAI API key set. Here's why:\n\n### The Situation\nAnthropic (Claude) does not provide an embeddings API. They only offer chat/completion models, not text embeddings.\n\nYour RAG (Retrieval-Augmented Generation) system has two components:\n- **Chat/Completion** - Generates answers, works with Anthropic.\n- **Embeddings** - Creates vector representations of text for search, Anthropic doesn't provide this.\n\nWhen using Anthropic as your `LLM_PROVIDER`, Doccupine will use Anthropic for chat/completion tasks, but will automatically fallback to OpenAI for embeddings. This means you need both API keys configured:\n\n```env\nLLM_PROVIDER=anthropic\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nOPENAI_API_KEY=your_openai_api_key_here\n```\n\nThis hybrid approach allows you to leverage Anthropic's powerful chat models while still having access to embeddings functionality through OpenAI.\n\n## Default models\n\n| Provider
|
|
1
|
+
export declare const aiAssistantMdxTemplate = "---\ntitle: \"AI Assistant\"\ndescription: \"Integrate AI capabilities into your Doccupine documentation using OpenAI, Anthropic, or Google Gemini.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 8\n---\n\n# AI Assistant\n\nDoccupine supports AI integration to enhance your documentation experience. You can use OpenAI, Anthropic, or Google Gemini to power AI features in your documentation site. The AI assistant uses your documentation content as context, allowing users to ask questions about your docs and receive accurate answers based on the documentation.\n\n## Setup\n\nTo enable AI features, create an `.env` file in the directory where your website is generated. By default, this is the `nextjs-app/` directory.\n\n## Configuration\n\nCreate an `.env` file with the following configuration options:\n\n```env\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model (see your provider's docs for available models)\n# LLM_CHAT_MODEL=your-model-id\n\n# Optional: Override default embedding model (see your provider's docs for available models)\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=your-embedding-model-id\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n```\n\n## Provider Selection\n\nSet `LLM_PROVIDER` to one of the following values:\n\n- `openai` - Use OpenAI's models\n- `anthropic` - Use Anthropic's models\n- `google` - Use Google's models\n\n## API Keys\n\nYou need to set the API key that matches your chosen provider:\n\n- For OpenAI: Set `OPENAI_API_KEY`\n- For Anthropic: Set `ANTHROPIC_API_KEY`\n- For Google: Set `GOOGLE_API_KEY`\n\n<Callout type=\"warning\">\n Keep your API keys secure. Never commit your `.env` file to version control.\n</Callout>\n\n<Callout type=\"note\">\n Doccupine automatically adds `.env` to your `.gitignore` file.\n</Callout>\n\n## Using Anthropic with OpenAI\n\nIf you want to use Anthropic as your LLM provider, you must also have an OpenAI API key set. Here's why:\n\n### The Situation\n\nAnthropic (Claude) does not provide an embeddings API. They only offer chat/completion models, not text embeddings.\n\nYour RAG (Retrieval-Augmented Generation) system has two components:\n\n- **Chat/Completion** - Generates answers, works with Anthropic.\n- **Embeddings** - Creates vector representations of text for search, Anthropic doesn't provide this.\n\nWhen using Anthropic as your `LLM_PROVIDER`, Doccupine will use Anthropic for chat/completion tasks, but will automatically fallback to OpenAI for embeddings. This means you need both API keys configured:\n\n```env\nLLM_PROVIDER=anthropic\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nOPENAI_API_KEY=your_openai_api_key_here\n```\n\nThis hybrid approach allows you to leverage Anthropic's powerful chat models while still having access to embeddings functionality through OpenAI.\n\n## Default models\n\n| Provider | Chat model | Embedding model |\n| --------- | ---------------------------- | ------------------------ |\n| OpenAI | `gpt-4.1-nano` | `text-embedding-3-small` |\n| Anthropic | `claude-sonnet-4-5-20250929` | OpenAI fallback |\n| Google | `gemini-2.5-flash-lite` | `gemini-embedding-001` |\n\n## Optional Settings\n\n### Chat Model\n\nOverride the default chat model by uncommenting and setting `LLM_CHAT_MODEL`. You can use any available model from your chosen provider. For a complete list of available models, refer to the official documentation:\n\n- [OpenAI Models](https://platform.openai.com/docs/models)\n- [Anthropic Models](https://docs.anthropic.com/claude/docs/models-overview)\n- [Google Gemini Models](https://ai.google.dev/models/gemini)\n\n### Embedding Model\n\nOverride the default embedding model by uncommenting and setting `LLM_EMBEDDING_MODEL`. For a complete list of available embedding models, refer to the official documentation:\n\n- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)\n- [Google Gemini Embeddings](https://ai.google.dev/gemini-api/docs/embeddings)\n- **Anthropic**: Anthropic doesn't provide embeddings. If you use Anthropic as your provider, Doccupine will fallback to OpenAI for embeddings.\n\n### Temperature\n\nControl the randomness of AI responses by setting `LLM_TEMPERATURE` to a value between 0 and 1:\n\n- `0` - More deterministic and focused responses (default)\n- `1` - More creative and varied responses";
|