|
| 1 | +#!/usr/bin/env node |
| 2 | + |
| 3 | +/** |
| 4 | + * Knowledge Base Indexer |
| 5 | + * Indexes documentation and code into the RAG system |
| 6 | + * |
| 7 | + * Usage: |
| 8 | + * node scripts/index-knowledge.js |
| 9 | + * node scripts/index-knowledge.js --category=docs |
| 10 | + * node scripts/index-knowledge.js --clear |
| 11 | + */ |
| 12 | + |
| 13 | +import fs from 'fs' |
| 14 | +import path from 'path' |
| 15 | +import { ragService } from '../lib/services/rag-service.js' |
| 16 | + |
| 17 | +// Configuration |
| 18 | +const DOCS_DIR = path.join(process.cwd(), 'docs') |
| 19 | +const CHUNK_SIZE = 1000 // Characters per chunk |
| 20 | +const CHUNK_OVERLAP = 200 // Overlap between chunks |
| 21 | + |
| 22 | +/** |
| 23 | + * Split text into chunks with overlap |
| 24 | + */ |
| 25 | +function chunkText(text, size = CHUNK_SIZE, overlap = CHUNK_OVERLAP) { |
| 26 | + const chunks = [] |
| 27 | + let start = 0 |
| 28 | + |
| 29 | + while (start < text.length) { |
| 30 | + const end = Math.min(start + size, text.length) |
| 31 | + const chunk = text.slice(start, end) |
| 32 | + |
| 33 | + // Only add non-empty chunks |
| 34 | + if (chunk.trim()) { |
| 35 | + chunks.push(chunk.trim()) |
| 36 | + } |
| 37 | + |
| 38 | + start += size - overlap |
| 39 | + } |
| 40 | + |
| 41 | + return chunks |
| 42 | +} |
| 43 | + |
| 44 | +/** |
| 45 | + * Extract title from markdown file |
| 46 | + */ |
| 47 | +function extractTitle(content) { |
| 48 | + const titleMatch = content.match(/^#\s+(.+)$/m) |
| 49 | + return titleMatch ? titleMatch[1].trim() : null |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * Process a markdown file |
| 54 | + */ |
| 55 | +function processMarkdownFile(filePath) { |
| 56 | + const content = fs.readFileSync(filePath, 'utf-8') |
| 57 | + const relativePath = path.relative(process.cwd(), filePath) |
| 58 | + const title = extractTitle(content) || path.basename(filePath, '.md') |
| 59 | + |
| 60 | + // Split into chunks |
| 61 | + const chunks = chunkText(content) |
| 62 | + |
| 63 | + return chunks.map((chunk, index) => ({ |
| 64 | + content: chunk, |
| 65 | + category: 'documentation', |
| 66 | + source: relativePath, |
| 67 | + title: chunks.length > 1 ? `${title} (Part ${index + 1}/${chunks.length})` : title, |
| 68 | + metadata: { |
| 69 | + fileType: 'markdown', |
| 70 | + chunkIndex: index, |
| 71 | + totalChunks: chunks.length, |
| 72 | + }, |
| 73 | + })) |
| 74 | +} |
| 75 | + |
| 76 | +/** |
| 77 | + * Get all markdown files in a directory |
| 78 | + */ |
| 79 | +function getMarkdownFiles(dir) { |
| 80 | + const files = [] |
| 81 | + |
| 82 | + function traverse(currentDir) { |
| 83 | + const entries = fs.readdirSync(currentDir, { withFileTypes: true }) |
| 84 | + |
| 85 | + for (const entry of entries) { |
| 86 | + const fullPath = path.join(currentDir, entry.name) |
| 87 | + |
| 88 | + if (entry.isDirectory()) { |
| 89 | + traverse(fullPath) |
| 90 | + } else if (entry.isFile() && entry.name.endsWith('.md')) { |
| 91 | + files.push(fullPath) |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + traverse(dir) |
| 97 | + return files |
| 98 | +} |
| 99 | + |
| 100 | +/** |
| 101 | + * Index all documentation |
| 102 | + */ |
| 103 | +async function indexDocumentation() { |
| 104 | + console.log('📚 Starting documentation indexing...\n') |
| 105 | + |
| 106 | + try { |
| 107 | + // Get all markdown files |
| 108 | + const markdownFiles = getMarkdownFiles(DOCS_DIR) |
| 109 | + console.log(`Found ${markdownFiles.length} markdown files`) |
| 110 | + |
| 111 | + let totalDocuments = 0 |
| 112 | + |
| 113 | + // Process each file |
| 114 | + for (const filePath of markdownFiles) { |
| 115 | + const relativePath = path.relative(process.cwd(), filePath) |
| 116 | + console.log(`Processing: ${relativePath}`) |
| 117 | + |
| 118 | + const documents = processMarkdownFile(filePath) |
| 119 | + await ragService.addDocuments(documents) |
| 120 | + |
| 121 | + totalDocuments += documents.length |
| 122 | + console.log(` ✅ Added ${documents.length} chunks\n`) |
| 123 | + } |
| 124 | + |
| 125 | + console.log(`\n✨ Indexing complete!`) |
| 126 | + console.log(` Total files: ${markdownFiles.length}`) |
| 127 | + console.log(` Total chunks: ${totalDocuments}`) |
| 128 | + } catch (error) { |
| 129 | + console.error('❌ Error indexing documentation:', error) |
| 130 | + process.exit(1) |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +/** |
| 135 | + * Add sample platform features |
| 136 | + */ |
| 137 | +async function indexPlatformFeatures() { |
| 138 | + console.log('📋 Adding platform features...\n') |
| 139 | + |
| 140 | + const features = [ |
| 141 | + { |
| 142 | + content: 'Lab68 Dev Platform is a comprehensive project management and collaboration platform. It includes features like project management, team collaboration, AI-powered tools, resume builder, diagrams, whiteboard, wiki, and real-time chat.', |
| 143 | + category: 'feature', |
| 144 | + source: 'platform-overview', |
| 145 | + title: 'Platform Overview', |
| 146 | + metadata: { type: 'overview' }, |
| 147 | + }, |
| 148 | + { |
| 149 | + content: 'The AI Tools feature allows users to chat with AI assistants powered by Ollama. It supports local models like DeepSeek R1, Llama, and Mistral. The AI can help with coding, planning, and answering questions about the platform.', |
| 150 | + category: 'feature', |
| 151 | + source: 'ai-tools', |
| 152 | + title: 'AI Tools Feature', |
| 153 | + metadata: { type: 'feature', module: 'ai-tools' }, |
| 154 | + }, |
| 155 | + { |
| 156 | + content: 'Project Management includes Kanban boards, sprint planning, issue tracking, and team collaboration. It supports epics, stories, tasks, and subtasks with priority levels and labels.', |
| 157 | + category: 'feature', |
| 158 | + source: 'projects', |
| 159 | + title: 'Project Management', |
| 160 | + metadata: { type: 'feature', module: 'projects' }, |
| 161 | + }, |
| 162 | + { |
| 163 | + content: 'The Resume Builder allows users to create professional resumes with templates, export to PDF, and manage multiple resume versions. It includes sections for experience, education, skills, and projects.', |
| 164 | + category: 'feature', |
| 165 | + source: 'resume-builder', |
| 166 | + title: 'Resume Builder', |
| 167 | + metadata: { type: 'feature', module: 'resume' }, |
| 168 | + }, |
| 169 | + { |
| 170 | + content: 'Diagram feature supports creating flowcharts, sequence diagrams, class diagrams, and more using Mermaid.js. Users can export diagrams as PNG or SVG.', |
| 171 | + category: 'feature', |
| 172 | + source: 'diagrams', |
| 173 | + title: 'Diagram Creator', |
| 174 | + metadata: { type: 'feature', module: 'diagrams' }, |
| 175 | + }, |
| 176 | + { |
| 177 | + content: 'Real-time collaboration with Socket.io enables live chat, notifications, and presence indicators. Users can see who is online and communicate in real-time.', |
| 178 | + category: 'feature', |
| 179 | + source: 'collaboration', |
| 180 | + title: 'Real-time Collaboration', |
| 181 | + metadata: { type: 'feature', module: 'collaboration' }, |
| 182 | + }, |
| 183 | + { |
| 184 | + content: 'The platform uses Supabase for database, authentication, and real-time features. It supports row-level security, user management, and secure data storage.', |
| 185 | + category: 'technical', |
| 186 | + source: 'tech-stack', |
| 187 | + title: 'Technical Stack - Supabase', |
| 188 | + metadata: { type: 'technical', component: 'database' }, |
| 189 | + }, |
| 190 | + { |
| 191 | + content: 'Built with Next.js 15, React 19, TypeScript, and Tailwind CSS. Uses shadcn/ui components for a consistent design system.', |
| 192 | + category: 'technical', |
| 193 | + source: 'tech-stack', |
| 194 | + title: 'Technical Stack - Frontend', |
| 195 | + metadata: { type: 'technical', component: 'frontend' }, |
| 196 | + }, |
| 197 | + ] |
| 198 | + |
| 199 | + try { |
| 200 | + const ids = await ragService.addDocuments(features) |
| 201 | + console.log(`✅ Added ${ids.length} platform features\n`) |
| 202 | + } catch (error) { |
| 203 | + console.error('❌ Error adding features:', error) |
| 204 | + process.exit(1) |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +/** |
| 209 | + * Clear knowledge base |
| 210 | + */ |
| 211 | +async function clearKnowledgeBase(category) { |
| 212 | + console.log(`🗑️ Clearing knowledge base${category ? ` (category: ${category})` : ''}...`) |
| 213 | + |
| 214 | + try { |
| 215 | + await ragService.clearAllDocuments(category) |
| 216 | + console.log('✅ Knowledge base cleared\n') |
| 217 | + } catch (error) { |
| 218 | + console.error('❌ Error clearing knowledge base:', error) |
| 219 | + process.exit(1) |
| 220 | + } |
| 221 | +} |
| 222 | + |
| 223 | +/** |
| 224 | + * Main function |
| 225 | + */ |
| 226 | +async function main() { |
| 227 | + const args = process.argv.slice(2) |
| 228 | + const clearFlag = args.includes('--clear') |
| 229 | + const categoryArg = args.find((arg) => arg.startsWith('--category=')) |
| 230 | + const category = categoryArg ? categoryArg.split('=')[1] : null |
| 231 | + |
| 232 | + console.log('🚀 Lab68 Knowledge Base Indexer\n') |
| 233 | + |
| 234 | + // Clear if requested |
| 235 | + if (clearFlag) { |
| 236 | + await clearKnowledgeBase(category) |
| 237 | + } |
| 238 | + |
| 239 | + // Index documentation |
| 240 | + if (!category || category === 'docs' || category === 'documentation') { |
| 241 | + await indexDocumentation() |
| 242 | + } |
| 243 | + |
| 244 | + // Index platform features |
| 245 | + if (!category || category === 'features') { |
| 246 | + await indexPlatformFeatures() |
| 247 | + } |
| 248 | + |
| 249 | + console.log('\n✨ All done! Knowledge base is ready.\n') |
| 250 | +} |
| 251 | + |
| 252 | +// Run the script |
| 253 | +main().catch((error) => { |
| 254 | + console.error('Fatal error:', error) |
| 255 | + process.exit(1) |
| 256 | +}) |
0 commit comments