1+ import { resolve , join , dirname } from 'path' ;
2+ import { readFileSync , readdirSync , statSync , mkdirSync , existsSync , writeFileSync } from 'fs' ;
3+ import type { Plugin } from 'vite' ;
4+
5+ export interface coolbotPluginOptions {
6+ /**
7+ * Write the raw txt output to the converted directory
8+ * @default false
9+ */
10+ writeRawOutput ?: boolean ;
11+ /**
12+ * Directory containing markdown files to convert
13+ * @default 'docs'
14+ */
15+ docsDir ?: string ;
16+ /**
17+ * Webhook URL to send the file map to
18+ * @default '' || process.env.COOLBOT_WEBHOOK_URL
19+ */
20+ webhookUrl ?: string ;
21+ /**
22+ * Ignore files in the docs directory
23+ * @default []
24+ */
25+ ignoreFolders ?: string [ ] ;
26+ }
27+
28+ interface FileMap {
29+ [ key : string ] : string ; // path: content mapping
30+ }
31+
32+ /**
33+ * Plugin to convert markdown files to text files, RAG chain.
34+ */
35+ export default function coolbotPlugin ( options : coolbotPluginOptions = { } ) : Plugin {
36+ const docsDir = options . docsDir || 'docs' ;
37+ const webhookUrl = process . env . COOLBOT_WEBHOOK_URL || '' ;
38+
39+ const ensureDirectoryExists = ( filePath : string ) => {
40+ const dir = dirname ( filePath ) ;
41+ if ( ! existsSync ( dir ) ) {
42+ mkdirSync ( dir , { recursive : true } ) ;
43+ }
44+ } ;
45+
46+ return {
47+ name : 'vitepress-plugin-CoolBot' ,
48+
49+ async closeBundle ( ) {
50+ try {
51+ const docsPath = resolve ( process . cwd ( ) , docsDir ) ;
52+ const markdownFiles = getAllMarkdownFiles ( docsPath ) ;
53+ const convertedDir = resolve ( docsPath , '.vitepress/dist/' ) ;
54+ const fileMap : FileMap = { } ;
55+
56+ // Ensure converted directory exists
57+ if ( ! existsSync ( convertedDir ) ) {
58+ mkdirSync ( convertedDir , { recursive : true } ) ;
59+ }
60+
61+ for ( const file of markdownFiles ) {
62+ try {
63+ const relativePath = file . replace ( docsPath , '' ) ;
64+
65+ if ( ! relativePath . startsWith ( '\\' ) && ! relativePath . startsWith ( '/' ) ) {
66+ console . warn ( `CoolBot: Skipping file with invalid path: ${ file } ` ) ;
67+ continue ;
68+ }
69+
70+ const outputPath = resolve ( convertedDir , relativePath . slice ( 1 ) . replace ( '.md' , '.txt' ) ) ;
71+ const content = readFileSync ( file , 'utf-8' ) ;
72+ const convertedContent = convertMarkdownToText ( content ) ;
73+
74+ // Store in map using normalized path without .txt extension
75+ const normalizedPath = relativePath . slice ( 1 ) . replace ( '.md' , '' ) . replace ( / \\ / g, '/' ) ;
76+ fileMap [ normalizedPath ] = convertedContent ;
77+
78+ if ( options . writeRawOutput ) {
79+ // Write the converted file
80+ ensureDirectoryExists ( outputPath ) ;
81+ writeFileSync ( outputPath , convertedContent , 'utf-8' ) ;
82+ console . log ( `CoolBot: ${ relativePath } -> .vitepress/dist/${ relativePath . replace ( '.md' , '.txt' ) } ` ) ;
83+ }
84+
85+ } catch ( fileError ) {
86+ console . error ( `CoolBot: Error processing file ${ file } :` , fileError ) ;
87+ }
88+ }
89+
90+
91+ // Write the file map
92+ const mapPath = resolve ( convertedDir , 'kvmap.json' ) ;
93+ writeFileSync ( mapPath , JSON . stringify ( fileMap , null , 2 ) , 'utf-8' ) ;
94+
95+ if ( webhookUrl ) {
96+ // Send POST request to webhook
97+ try {
98+ const response = await fetch ( webhookUrl , {
99+ method : 'POST' ,
100+ headers : {
101+ 'Content-Type' : 'application/json' ,
102+ } ,
103+ body : JSON . stringify ( fileMap ) ,
104+ } ) ;
105+
106+ if ( ! response . ok ) {
107+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
108+ }
109+
110+ console . log ( '\nCoolBot: Successfully sent file map to webhook' ) ;
111+ } catch ( webhookError ) {
112+ console . error ( 'CoolBot: Error sending file map to webhook:' , webhookError ) ;
113+ }
114+ }
115+
116+ console . log ( '\nCoolBot: Generated file map at .vitepress/dist/public/kvmap.json' ) ;
117+ console . log ( '\nCoolBot: Conversion complete' ) ;
118+ } catch ( error ) {
119+ console . error ( 'CoolBot: Error during conversion:' , error ) ;
120+ }
121+ }
122+ } ;
123+ }
124+
125+ /**
126+ * Recursively find all markdown files in a directory
127+ * @param {string } dir - Directory to search
128+ * @returns {string[] } Array of absolute file paths
129+ */
130+ function getAllMarkdownFiles ( dir : string ) : string [ ] {
131+ let results : string [ ] = [ ] ;
132+ try {
133+ const files = readdirSync ( dir ) ;
134+ const defaultIgnored = [ 'node_modules' , '.vitepress' , 'dist' , 'api-reference' ] ;
135+
136+ for ( const file of files ) {
137+ const filePath = join ( dir , file ) ;
138+ const stat = statSync ( filePath ) ;
139+
140+ if ( defaultIgnored . includes ( file ) ) {
141+ continue ;
142+ }
143+
144+ if ( stat . isDirectory ( ) ) {
145+ results = results . concat ( getAllMarkdownFiles ( filePath ) ) ;
146+ } else if ( file . endsWith ( '.md' ) ) {
147+ results . push ( filePath ) ;
148+ }
149+ }
150+ } catch ( error ) {
151+ console . error ( 'CoolBot: Error reading directory:' , error ) ;
152+ }
153+
154+ return results ;
155+ }
156+
157+ /**
158+ * Convert markdown content to plain text
159+ * @param {string } markdown - Markdown content
160+ * @returns {string } Plain text content
161+ */
162+ function convertMarkdownToText ( markdown : string ) : string {
163+ return markdown
164+ // 1. Remove metadata and containers
165+ . replace ( / ^ - - - [ \s \S ] * ?- - - / , '\n' ) // Remove YAML frontmatter
166+ . replace ( / < [ ^ > ] + > / g, '' ) // Remove HTML tags but preserve content
167+ . replace ( / : : : \s * \w + \s * / g, '\n' ) // Remove VitePress container starts
168+ . replace ( / : : : / g, '\n' ) // Remove VitePress container ends
169+
170+ // 2. Add spacing around structural elements
171+ . replace ( / \n ( # \s [ ^ \n ] * ) / g, '\n\n\n\n$1\n' ) // Add extra space around h1
172+ . replace ( / \n ( # # \s [ ^ \n ] * ) / g, '\n\n\n$1\n' ) // Add extra space around h2
173+ . replace ( / \n ( # # # \s [ ^ \n ] * ) / g, '\n\n$1\n' ) // Add extra space around h3
174+ . replace ( / \n ( # # # # .* ) / g, '\n\n$1\n' ) // Add space around h4+
175+ . replace ( / \n ( ` ` ` [ ^ \n ] * ) / g, '\n\n$1' ) // Add double space before code blocks
176+ . replace ( / ( ` ` ` ) \s * \n / g, '$1\n\n\n' ) // Add triple space after code blocks
177+
178+ // 3. Preserve and normalize line breaks
179+ . split ( '\n' ) // Split into lines
180+ . map ( line => line . trim ( ) ) // Trim each line
181+ . join ( '\n' ) // Rejoin with line breaks
182+ . replace ( / \n { 5 , } / g, '\n\n\n\n' ) // Max 4 consecutive line breaks
183+ . replace ( / \s { 2 , } / g, ' ' ) // Collapse multiple spaces to single
184+ . trim ( ) ;
185+ }
0 commit comments