botdocs 0.1.1 → 0.3.0
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/README.md +46 -1
- package/dist/src/builder/chunker.d.ts +2 -2
- package/dist/src/builder/chunker.js +9 -7
- package/dist/src/builder/index.js +25 -3
- package/dist/src/builder/site-generator.js +1 -0
- package/dist/src/builder/vector-db-builder.d.ts +13 -1
- package/dist/src/builder/vector-db-builder.js +110 -23
- package/dist/src/cli/index.js +3 -0
- package/dist/src/cli/options.d.ts +2 -0
- package/dist/src/cli/options.js +1 -0
- package/dist/src/types/config.d.ts +4 -3
- package/dist/src/types/config.js +3 -4
- package/dist/src/types/vector-db.d.ts +1 -0
- package/package.json +2 -2
- package/src/styles/main.css +23 -0
- package/src/styles/themes/classic.css +579 -0
- package/src/styles/themes/material.css +652 -0
- package/src/styles/themes/minimal.css +720 -0
- package/src/styles/themes/modern.css +770 -0
- package/src/styles/themes/slate.css +714 -0
- package/src/templates/layout.html +7 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Botdocs
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/botdocs)
|
|
4
|
+
|
|
3
5
|
Convert markdown documentation into beautiful static sites with AI-powered semantic search — no backend required.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
@@ -33,8 +35,35 @@ botdocs ./docs -o ./public
|
|
|
33
35
|
|
|
34
36
|
# Verbose logging
|
|
35
37
|
botdocs ./docs -v
|
|
38
|
+
|
|
39
|
+
# Use a specific theme
|
|
40
|
+
botdocs ./docs -t material
|
|
41
|
+
|
|
42
|
+
# Custom config file
|
|
43
|
+
botdocs ./docs -c ./my-config.json
|
|
44
|
+
|
|
45
|
+
# Combine multiple options
|
|
46
|
+
botdocs ./docs -o ./public -t slate -v
|
|
36
47
|
```
|
|
37
48
|
|
|
49
|
+
### CLI Options
|
|
50
|
+
|
|
51
|
+
| Option | Alias | Description | Default |
|
|
52
|
+
|--------|-------|-------------|---------|
|
|
53
|
+
| `--output <dir>` | `-o` | Output directory for generated site | `output` |
|
|
54
|
+
| `--no-chat` | | Disable AI chatbot functionality | `false` |
|
|
55
|
+
| `--config <file>` | `-c` | Path to config file | `botdocs.config.json` |
|
|
56
|
+
| `--theme <theme>` | `-t` | Theme to use | `classic` |
|
|
57
|
+
| `--verbose` | `-v` | Enable verbose logging | `false` |
|
|
58
|
+
|
|
59
|
+
### Available Themes
|
|
60
|
+
|
|
61
|
+
- **classic** - Clean, professional theme (default)
|
|
62
|
+
- **material** - Material Design theme
|
|
63
|
+
- **minimal** - Clean, minimalist theme
|
|
64
|
+
- **slate** - Dark slate theme
|
|
65
|
+
- **modern** - Modern documentation theme
|
|
66
|
+
|
|
38
67
|
## Configuration
|
|
39
68
|
|
|
40
69
|
Create `botdocs.config.json` in your docs directory:
|
|
@@ -43,15 +72,31 @@ Create `botdocs.config.json` in your docs directory:
|
|
|
43
72
|
{
|
|
44
73
|
"title": "My Documentation",
|
|
45
74
|
"description": "Project docs",
|
|
75
|
+
"theme": "classic",
|
|
76
|
+
"attribution": true,
|
|
46
77
|
"chat": { "enabled": true },
|
|
47
78
|
"build": {
|
|
48
79
|
"chunkSize": 500,
|
|
49
80
|
"chunkOverlap": 50,
|
|
50
|
-
"topK":
|
|
81
|
+
"topK": 3
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
84
|
```
|
|
54
85
|
|
|
86
|
+
### Configuration Options
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
|--------|------|---------|-------------|
|
|
90
|
+
| `title` | string | `"Documentation"` | Site title |
|
|
91
|
+
| `description` | string | `"Project documentation"` | Site description |
|
|
92
|
+
| `theme` | string | `"classic"` | Theme to use (classic, material, minimal, slate, modern) |
|
|
93
|
+
| `attribution` | boolean | `true` | Show "Built with Botdocs" footer link |
|
|
94
|
+
| `chat.enabled` | boolean | `true` | Enable AI chatbot |
|
|
95
|
+
| `chat.welcomeMessage` | string | `"Ask me anything about the docs!"` | Chatbot welcome message |
|
|
96
|
+
| `build.chunkSize` | number | `500` | Text chunk size for embeddings |
|
|
97
|
+
| `build.chunkOverlap` | number | `50` | Overlap between chunks |
|
|
98
|
+
| `build.topK` | number | `3` | Number of results to return |
|
|
99
|
+
|
|
55
100
|
## Front Matter
|
|
56
101
|
|
|
57
102
|
```markdown
|
|
@@ -17,7 +17,7 @@ export declare class Chunker {
|
|
|
17
17
|
/**
|
|
18
18
|
* Chunk a document into semantically meaningful pieces
|
|
19
19
|
*/
|
|
20
|
-
chunkDocument(doc: ProcessedDocument): TextChunk[];
|
|
20
|
+
chunkDocument(doc: ProcessedDocument, fileHash?: string): TextChunk[];
|
|
21
21
|
/**
|
|
22
22
|
* Create a chunk with metadata
|
|
23
23
|
*/
|
|
@@ -37,5 +37,5 @@ export declare class Chunker {
|
|
|
37
37
|
/**
|
|
38
38
|
* Chunk multiple documents
|
|
39
39
|
*/
|
|
40
|
-
chunkDocuments(documents: ProcessedDocument[]): TextChunk[];
|
|
40
|
+
chunkDocuments(documents: ProcessedDocument[], fileHashes?: Map<string, string>): TextChunk[];
|
|
41
41
|
}
|
|
@@ -12,7 +12,7 @@ export class Chunker {
|
|
|
12
12
|
/**
|
|
13
13
|
* Chunk a document into semantically meaningful pieces
|
|
14
14
|
*/
|
|
15
|
-
chunkDocument(doc) {
|
|
15
|
+
chunkDocument(doc, fileHash) {
|
|
16
16
|
const chunks = [];
|
|
17
17
|
const lines = doc.content.split('\n');
|
|
18
18
|
let currentChunk = [];
|
|
@@ -47,7 +47,7 @@ export class Chunker {
|
|
|
47
47
|
if (headingMatch) {
|
|
48
48
|
// Save current chunk if it exists
|
|
49
49
|
if (currentChunk.length > 0) {
|
|
50
|
-
chunks.push(this.createChunk(currentChunk.join('\n'), doc, currentHeading));
|
|
50
|
+
chunks.push(this.createChunk(currentChunk.join('\n'), doc, currentHeading, fileHash));
|
|
51
51
|
}
|
|
52
52
|
// Start new chunk with this heading
|
|
53
53
|
currentHeading = headingMatch[2];
|
|
@@ -60,7 +60,7 @@ export class Chunker {
|
|
|
60
60
|
if (tokenCount >= this.options.maxChunkSize) {
|
|
61
61
|
// Split chunk
|
|
62
62
|
const chunkText = currentChunk.join('\n');
|
|
63
|
-
chunks.push(this.createChunk(chunkText, doc, currentHeading));
|
|
63
|
+
chunks.push(this.createChunk(chunkText, doc, currentHeading, fileHash));
|
|
64
64
|
// Create overlap for next chunk
|
|
65
65
|
const overlapLines = this.getOverlapLines(currentChunk, this.options.chunkOverlap);
|
|
66
66
|
currentChunk = overlapLines;
|
|
@@ -69,14 +69,14 @@ export class Chunker {
|
|
|
69
69
|
}
|
|
70
70
|
// Add final chunk
|
|
71
71
|
if (currentChunk.length > 0) {
|
|
72
|
-
chunks.push(this.createChunk(currentChunk.join('\n'), doc, currentHeading));
|
|
72
|
+
chunks.push(this.createChunk(currentChunk.join('\n'), doc, currentHeading, fileHash));
|
|
73
73
|
}
|
|
74
74
|
return chunks.filter((chunk) => chunk.text.trim().length > 0);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Create a chunk with metadata
|
|
78
78
|
*/
|
|
79
|
-
createChunk(text, doc, heading) {
|
|
79
|
+
createChunk(text, doc, heading, fileHash) {
|
|
80
80
|
return {
|
|
81
81
|
text: text.trim(),
|
|
82
82
|
metadata: {
|
|
@@ -85,6 +85,7 @@ export class Chunker {
|
|
|
85
85
|
heading,
|
|
86
86
|
headingId: heading ? this.slugify(heading) : undefined,
|
|
87
87
|
url: doc.url,
|
|
88
|
+
fileHash,
|
|
88
89
|
},
|
|
89
90
|
};
|
|
90
91
|
}
|
|
@@ -128,10 +129,11 @@ export class Chunker {
|
|
|
128
129
|
/**
|
|
129
130
|
* Chunk multiple documents
|
|
130
131
|
*/
|
|
131
|
-
chunkDocuments(documents) {
|
|
132
|
+
chunkDocuments(documents, fileHashes) {
|
|
132
133
|
const allChunks = [];
|
|
133
134
|
for (const doc of documents) {
|
|
134
|
-
const
|
|
135
|
+
const fileHash = fileHashes?.get(doc.relativePath);
|
|
136
|
+
const chunks = this.chunkDocument(doc, fileHash);
|
|
135
137
|
allChunks.push(...chunks);
|
|
136
138
|
}
|
|
137
139
|
return allChunks;
|
|
@@ -13,7 +13,7 @@ const __dirname = dirname(__filename);
|
|
|
13
13
|
* Main build orchestrator
|
|
14
14
|
*/
|
|
15
15
|
export async function build(options) {
|
|
16
|
-
const { inputDir, outputDir, chatEnabled, configPath, verbose } = options;
|
|
16
|
+
const { inputDir, outputDir, chatEnabled, configPath, verbose, theme } = options;
|
|
17
17
|
// Validate input directory
|
|
18
18
|
if (!existsSync(inputDir)) {
|
|
19
19
|
throw new Error(`Input directory does not exist: ${inputDir}`);
|
|
@@ -25,6 +25,10 @@ export async function build(options) {
|
|
|
25
25
|
config.chat = config.chat || {};
|
|
26
26
|
config.chat.enabled = chatEnabled;
|
|
27
27
|
}
|
|
28
|
+
// Override theme if specified in CLI
|
|
29
|
+
if (theme) {
|
|
30
|
+
config.theme = theme;
|
|
31
|
+
}
|
|
28
32
|
if (verbose) {
|
|
29
33
|
console.log('Configuration:', JSON.stringify(config, null, 2));
|
|
30
34
|
}
|
|
@@ -107,9 +111,12 @@ export async function build(options) {
|
|
|
107
111
|
console.log('Copying styles...');
|
|
108
112
|
// From dist/src/builder, go to project root, then to src/styles
|
|
109
113
|
const stylesDir = resolve(__dirname, '../../../src/styles');
|
|
114
|
+
const themesDir = join(stylesDir, 'themes');
|
|
110
115
|
const outputCssDir = join(assetsDir, 'css');
|
|
111
|
-
//
|
|
112
|
-
const
|
|
116
|
+
// Determine which theme to use
|
|
117
|
+
const selectedTheme = config.theme || defaultConfig.theme || 'classic';
|
|
118
|
+
// Combine CSS files: base styles + selected theme + chat
|
|
119
|
+
const cssFiles = ['themes.css', 'chat.css'];
|
|
113
120
|
let bundledCss = '';
|
|
114
121
|
for (const cssFile of cssFiles) {
|
|
115
122
|
const cssPath = join(stylesDir, cssFile);
|
|
@@ -117,6 +124,21 @@ export async function build(options) {
|
|
|
117
124
|
bundledCss += readFileSync(cssPath, 'utf-8') + '\n\n';
|
|
118
125
|
}
|
|
119
126
|
}
|
|
127
|
+
// Add selected theme CSS
|
|
128
|
+
const themePath = join(themesDir, `${selectedTheme}.css`);
|
|
129
|
+
if (existsSync(themePath)) {
|
|
130
|
+
bundledCss += readFileSync(themePath, 'utf-8') + '\n\n';
|
|
131
|
+
if (verbose) {
|
|
132
|
+
console.log(`Using theme: ${selectedTheme}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.warn(`Theme '${selectedTheme}' not found, falling back to classic`);
|
|
137
|
+
const fallbackPath = join(themesDir, 'classic.css');
|
|
138
|
+
if (existsSync(fallbackPath)) {
|
|
139
|
+
bundledCss += readFileSync(fallbackPath, 'utf-8') + '\n\n';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
120
142
|
writeFileSync(join(outputCssDir, 'bundle.css'), bundledCss, 'utf-8');
|
|
121
143
|
if (verbose) {
|
|
122
144
|
console.log('Styles copied');
|
|
@@ -92,6 +92,7 @@ export class SiteGenerator {
|
|
|
92
92
|
content,
|
|
93
93
|
navigation: this.renderNavigation(navigation, doc.url),
|
|
94
94
|
chatEnabled: config.chat?.enabled,
|
|
95
|
+
attribution: config.attribution !== false, // defaults to true
|
|
95
96
|
});
|
|
96
97
|
// Write HTML file
|
|
97
98
|
const outputPath = join(outputDir, doc.relativePath.replace(/\.md$/, '.html'));
|
|
@@ -10,9 +10,21 @@ export declare class VectorDBBuilder {
|
|
|
10
10
|
private embedder;
|
|
11
11
|
constructor(options?: VectorDBBuilderOptions);
|
|
12
12
|
/**
|
|
13
|
-
* Build vector database from documents
|
|
13
|
+
* Build vector database from documents (with incremental build support)
|
|
14
14
|
*/
|
|
15
15
|
build(documents: ProcessedDocument[], outputDir: string, verbose?: boolean): Promise<VectorDatabase>;
|
|
16
|
+
/**
|
|
17
|
+
* Load existing vector database from disk
|
|
18
|
+
*/
|
|
19
|
+
private loadExistingDatabase;
|
|
20
|
+
/**
|
|
21
|
+
* Build a map of sourceFile -> chunks for quick lookup
|
|
22
|
+
*/
|
|
23
|
+
private buildCacheMap;
|
|
24
|
+
/**
|
|
25
|
+
* Hash file content for change detection
|
|
26
|
+
*/
|
|
27
|
+
private hashContent;
|
|
16
28
|
/**
|
|
17
29
|
* Generate unique ID for a chunk
|
|
18
30
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Chunker } from './chunker.js';
|
|
2
2
|
import { Embedder } from './embedder.js';
|
|
3
|
-
import { writeFileSync } from 'fs';
|
|
3
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
6
|
export class VectorDBBuilder {
|
|
@@ -14,43 +14,130 @@ export class VectorDBBuilder {
|
|
|
14
14
|
this.embedder = new Embedder(options.modelName);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Build vector database from documents
|
|
17
|
+
* Build vector database from documents (with incremental build support)
|
|
18
18
|
*/
|
|
19
19
|
async build(documents, outputDir, verbose = false) {
|
|
20
20
|
if (verbose) {
|
|
21
21
|
console.log('Building vector database...');
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
// Step 2:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
const outputPath = join(outputDir, 'vector-db.json');
|
|
24
|
+
// Step 1: Load existing vector database for incremental builds
|
|
25
|
+
const existingDB = this.loadExistingDatabase(outputPath, verbose);
|
|
26
|
+
const cachedChunks = this.buildCacheMap(existingDB);
|
|
27
|
+
// Step 2: Hash documents and determine what needs rebuilding
|
|
28
|
+
const fileHashes = new Map();
|
|
29
|
+
const changedDocs = [];
|
|
30
|
+
const unchangedDocs = [];
|
|
31
|
+
for (const doc of documents) {
|
|
32
|
+
const hash = this.hashContent(doc.content);
|
|
33
|
+
fileHashes.set(doc.relativePath, hash);
|
|
34
|
+
const cachedDocChunks = cachedChunks.get(doc.relativePath);
|
|
35
|
+
if (cachedDocChunks && cachedDocChunks[0]?.metadata.fileHash === hash) {
|
|
36
|
+
unchangedDocs.push(doc);
|
|
37
|
+
if (verbose) {
|
|
38
|
+
console.log(` ✓ Cached: ${doc.relativePath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
changedDocs.push(doc);
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.log(` ⟳ Changed: ${doc.relativePath}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
console.log(`Unchanged: ${unchangedDocs.length}, Changed: ${changedDocs.length}, Total: ${documents.length}`);
|
|
49
|
+
// Step 3: Reuse cached chunks for unchanged documents
|
|
50
|
+
const reusedChunks = [];
|
|
51
|
+
for (const doc of unchangedDocs) {
|
|
52
|
+
const chunks = cachedChunks.get(doc.relativePath);
|
|
53
|
+
if (chunks) {
|
|
54
|
+
reusedChunks.push(...chunks);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Step 4: Process changed documents only
|
|
58
|
+
let newChunks = [];
|
|
59
|
+
if (changedDocs.length > 0) {
|
|
60
|
+
console.log('Chunking changed documents...');
|
|
61
|
+
const chunks = this.chunker.chunkDocuments(changedDocs, fileHashes);
|
|
62
|
+
console.log(`Created ${chunks.length} chunks from ${changedDocs.length} changed documents`);
|
|
63
|
+
// Generate embeddings only for changed documents
|
|
64
|
+
console.log('Generating embeddings for changed documents...');
|
|
65
|
+
await this.embedder.initialize();
|
|
66
|
+
const texts = chunks.map((chunk) => chunk.text);
|
|
67
|
+
const embeddings = await this.embedder.embedBatch(texts, 10);
|
|
68
|
+
// Create document chunks with embeddings
|
|
69
|
+
newChunks = chunks.map((chunk, index) => ({
|
|
70
|
+
id: this.generateChunkId(chunk.text, chunk.metadata.sourceFile, index),
|
|
71
|
+
text: chunk.text,
|
|
72
|
+
embedding: embeddings[index],
|
|
73
|
+
metadata: chunk.metadata,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.log('No changes detected - reusing all cached embeddings');
|
|
78
|
+
}
|
|
79
|
+
// Step 5: Merge cached and new chunks
|
|
80
|
+
const allChunks = [...reusedChunks, ...newChunks];
|
|
81
|
+
// Step 6: Create vector database
|
|
40
82
|
const vectorDB = {
|
|
41
83
|
version: '1.0',
|
|
42
84
|
model: this.embedder.getModelName(),
|
|
43
85
|
dimension: this.embedder.getDimension(),
|
|
44
|
-
chunks:
|
|
86
|
+
chunks: allChunks,
|
|
45
87
|
};
|
|
46
|
-
// Step
|
|
47
|
-
const outputPath = join(outputDir, 'vector-db.json');
|
|
88
|
+
// Step 7: Write to file
|
|
48
89
|
writeFileSync(outputPath, JSON.stringify(vectorDB, null, 2), 'utf-8');
|
|
49
90
|
console.log(`Vector database saved: ${outputPath}`);
|
|
50
|
-
console.log(`Total chunks: ${vectorDB.chunks.length}`);
|
|
91
|
+
console.log(`Total chunks: ${vectorDB.chunks.length} (${reusedChunks.length} cached, ${newChunks.length} new)`);
|
|
51
92
|
console.log(`Embedding dimension: ${vectorDB.dimension}`);
|
|
52
93
|
return vectorDB;
|
|
53
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Load existing vector database from disk
|
|
97
|
+
*/
|
|
98
|
+
loadExistingDatabase(outputPath, verbose) {
|
|
99
|
+
if (!existsSync(outputPath)) {
|
|
100
|
+
if (verbose) {
|
|
101
|
+
console.log('No existing vector database found - performing full build');
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const content = readFileSync(outputPath, 'utf-8');
|
|
107
|
+
const db = JSON.parse(content);
|
|
108
|
+
if (verbose) {
|
|
109
|
+
console.log(`Loaded existing vector database with ${db.chunks.length} chunks`);
|
|
110
|
+
}
|
|
111
|
+
return db;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.warn('Failed to load existing vector database - performing full build');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build a map of sourceFile -> chunks for quick lookup
|
|
120
|
+
*/
|
|
121
|
+
buildCacheMap(db) {
|
|
122
|
+
const cache = new Map();
|
|
123
|
+
if (!db) {
|
|
124
|
+
return cache;
|
|
125
|
+
}
|
|
126
|
+
for (const chunk of db.chunks) {
|
|
127
|
+
const sourceFile = chunk.metadata.sourceFile;
|
|
128
|
+
if (!cache.has(sourceFile)) {
|
|
129
|
+
cache.set(sourceFile, []);
|
|
130
|
+
}
|
|
131
|
+
cache.get(sourceFile).push(chunk);
|
|
132
|
+
}
|
|
133
|
+
return cache;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Hash file content for change detection
|
|
137
|
+
*/
|
|
138
|
+
hashContent(content) {
|
|
139
|
+
return createHash('md5').update(content).digest('hex');
|
|
140
|
+
}
|
|
54
141
|
/**
|
|
55
142
|
* Generate unique ID for a chunk
|
|
56
143
|
*/
|
package/dist/src/cli/index.js
CHANGED
|
@@ -18,6 +18,7 @@ program
|
|
|
18
18
|
.option('-o, --output <dir>', 'Output directory for generated site', defaultOptions.output)
|
|
19
19
|
.option('--no-chat', 'Disable AI chatbot functionality')
|
|
20
20
|
.option('-c, --config <file>', 'Path to config file (botdocs.config.json)')
|
|
21
|
+
.option('-t, --theme <theme>', 'Theme to use (classic, material, minimal, slate, modern)', defaultOptions.theme)
|
|
21
22
|
.option('-v, --verbose', 'Enable verbose logging')
|
|
22
23
|
.action(async (input, options) => {
|
|
23
24
|
try {
|
|
@@ -28,6 +29,7 @@ program
|
|
|
28
29
|
console.log(`Input: ${inputDir}`);
|
|
29
30
|
console.log(`Output: ${outputDir}`);
|
|
30
31
|
console.log(`Chat enabled: ${!options.noChat}`);
|
|
32
|
+
console.log(`Theme: ${options.theme || defaultOptions.theme}`);
|
|
31
33
|
}
|
|
32
34
|
await build({
|
|
33
35
|
inputDir,
|
|
@@ -35,6 +37,7 @@ program
|
|
|
35
37
|
chatEnabled: !options.noChat,
|
|
36
38
|
configPath: options.config,
|
|
37
39
|
verbose: options.verbose || false,
|
|
40
|
+
theme: options.theme,
|
|
38
41
|
});
|
|
39
42
|
console.log('Build complete!');
|
|
40
43
|
console.log(`Site generated at: ${outputDir}`);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export type Theme = 'classic' | 'material' | 'minimal' | 'slate' | 'modern';
|
|
1
2
|
export interface CliOptions {
|
|
2
3
|
output?: string;
|
|
3
4
|
noChat?: boolean;
|
|
4
5
|
config?: string;
|
|
5
6
|
verbose?: boolean;
|
|
7
|
+
theme?: Theme;
|
|
6
8
|
}
|
|
7
9
|
export declare const defaultOptions: Partial<CliOptions>;
|
package/dist/src/cli/options.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
export type Theme = 'classic' | 'material' | 'minimal' | 'slate' | 'modern';
|
|
1
2
|
export interface BotdocsConfig {
|
|
2
3
|
title?: string;
|
|
3
4
|
description?: string;
|
|
4
|
-
theme?:
|
|
5
|
-
|
|
6
|
-
};
|
|
5
|
+
theme?: Theme;
|
|
6
|
+
attribution?: boolean;
|
|
7
7
|
chat?: {
|
|
8
8
|
enabled?: boolean;
|
|
9
9
|
welcomeMessage?: string;
|
|
@@ -20,5 +20,6 @@ export interface BuildOptions {
|
|
|
20
20
|
chatEnabled: boolean;
|
|
21
21
|
configPath?: string;
|
|
22
22
|
verbose: boolean;
|
|
23
|
+
theme?: Theme;
|
|
23
24
|
}
|
|
24
25
|
export declare const defaultConfig: BotdocsConfig;
|
package/dist/src/types/config.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
2
|
title: 'Documentation',
|
|
3
3
|
description: 'Project documentation',
|
|
4
|
-
theme:
|
|
5
|
-
|
|
6
|
-
},
|
|
4
|
+
theme: 'classic',
|
|
5
|
+
attribution: true,
|
|
7
6
|
chat: {
|
|
8
7
|
enabled: true,
|
|
9
8
|
welcomeMessage: 'Ask me anything about the docs!',
|
|
@@ -11,6 +10,6 @@ export const defaultConfig = {
|
|
|
11
10
|
build: {
|
|
12
11
|
chunkSize: 500,
|
|
13
12
|
chunkOverlap: 50,
|
|
14
|
-
topK:
|
|
13
|
+
topK: 3,
|
|
15
14
|
},
|
|
16
15
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botdocs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI tool that converts markdown documentation into a static website with an optional client-side AI chatbot",
|
|
5
5
|
"author": "usr-wwelsh <https://wwel.sh>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
package/src/styles/main.css
CHANGED
|
@@ -503,6 +503,29 @@ img {
|
|
|
503
503
|
font-size: 0.875rem;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
/* Footer Attribution */
|
|
507
|
+
.botdocs-attribution {
|
|
508
|
+
margin-top: 4rem;
|
|
509
|
+
padding-top: 2rem;
|
|
510
|
+
border-top: 1px solid var(--border-color);
|
|
511
|
+
text-align: center;
|
|
512
|
+
font-size: 0.875rem;
|
|
513
|
+
color: var(--text-secondary);
|
|
514
|
+
opacity: 0.8;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.botdocs-attribution a {
|
|
518
|
+
color: var(--text-secondary);
|
|
519
|
+
text-decoration: none;
|
|
520
|
+
font-weight: 500;
|
|
521
|
+
transition: color 0.2s;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.botdocs-attribution a:hover {
|
|
525
|
+
color: var(--link-color);
|
|
526
|
+
text-decoration: none;
|
|
527
|
+
}
|
|
528
|
+
|
|
506
529
|
/* Responsive */
|
|
507
530
|
@media (max-width: 768px) {
|
|
508
531
|
.sidebar {
|