Kraft102's picture
Deploy backend fix v2.1.0
1d28c11
// 🧠 THE BRAIN: NeuralCompiler.ts
// Ansvarlig for at æde filer og gøre dem til struktureret viden.
// Renamed from KnowledgeCompiler to avoid conflict with existing service.
import fs from 'fs/promises';
import path from 'path';
import crypto from 'crypto';
import chokidar from 'chokidar';
import { vectorService } from './VectorService';
// Conditional imports for optional services
let neo4jService: any = null;
let metricsService: any = null;
export interface CompiledDocument {
id: string;
name: string;
path: string;
extension: string;
size: number;
content: string;
embedding: number[];
lastModified: Date;
tags: string[];
}
export class NeuralCompiler {
private static instance: NeuralCompiler;
private watcher: chokidar.FSWatcher | null = null;
private isProcessing = false;
private documentCache = new Map<string, CompiledDocument>();
private eventQueue: { type: string; path: string }[] = [];
private processingInterval: NodeJS.Timeout | null = null;
private constructor() {
console.log('📚 [KNOWLEDGE] Compiler Initialized');
this.initServices();
}
private async initServices() {
try {
const neo4j = await import('./Neo4jService').catch(() => null);
if (neo4j) neo4jService = neo4j.neo4jService;
const metrics = await import('./MetricsService').catch(() => null);
if (metrics) metricsService = metrics.metricsService;
} catch {
console.log('📚 [KNOWLEDGE] Running in standalone mode');
}
}
public static getInstance(): NeuralCompiler {
if (!NeuralCompiler.instance) {
NeuralCompiler.instance = new NeuralCompiler();
}
return NeuralCompiler.instance;
}
public async startWatching(dirPath: string): Promise<void> {
// Ensure directory exists
await fs.mkdir(dirPath, { recursive: true }).catch(() => {});
console.log(`👁️ [KNOWLEDGE] Watching directory: ${dirPath}`);
this.watcher = chokidar.watch(dirPath, {
ignored: /(^|[\/\\])\../, // Ignore dotfiles
persistent: true,
depth: 5,
awaitWriteFinish: {
stabilityThreshold: 1000,
pollInterval: 100
}
});
this.watcher
.on('add', p => this.queueEvent('ADD', p))
.on('change', p => this.queueEvent('MODIFY', p))
.on('unlink', p => this.queueEvent('DELETE', p))
.on('error', err => console.error('👁️ [KNOWLEDGE] Watcher error:', err));
// Process queue every 500ms to batch events
this.processingInterval = setInterval(() => this.processQueue(), 500);
}
private queueEvent(type: string, filePath: string) {
this.eventQueue.push({ type, path: filePath });
}
private async processQueue() {
if (this.isProcessing || this.eventQueue.length === 0) return;
this.isProcessing = true;
const events = [...this.eventQueue];
this.eventQueue = [];
for (const event of events) {
await this.handleFileEvent(event.type as 'ADD' | 'MODIFY' | 'DELETE', event.path);
}
this.isProcessing = false;
}
private async handleFileEvent(
eventType: 'ADD' | 'MODIFY' | 'DELETE',
filePath: string
): Promise<void> {
try {
const fileId = this.generateFileId(filePath);
const filename = path.basename(filePath);
const ext = path.extname(filePath).toLowerCase();
// Skip unsupported files
const supportedExts = ['.txt', '.md', '.json', '.ts', '.js', '.py', '.html', '.css', '.yaml', '.yml', '.xml', '.csv'];
if (!supportedExts.includes(ext)) return;
if (eventType === 'DELETE') {
this.documentCache.delete(fileId);
if (neo4jService) {
await neo4jService.write(
`MATCH (f:File {id: $id}) DETACH DELETE f`,
{ id: fileId }
);
}
console.log(`🗑️ [KNOWLEDGE] Forgot file: ${filename}`);
return;
}
// 1. Read Content
const content = await fs.readFile(filePath, 'utf-8');
// 2. Generate Vector Embedding (Cognitive Dark Matter)
const textForEmbedding = content.substring(0, 2000); // Limit for speed
const embedding = await vectorService.embedText(textForEmbedding);
// 3. Create compiled document
const doc: CompiledDocument = {
id: fileId,
name: filename,
path: filePath,
extension: ext,
size: content.length,
content: content.substring(0, 5000), // Store first 5KB
embedding,
lastModified: new Date(),
tags: this.extractTags(content, ext)
};
// 4. Cache locally
this.documentCache.set(fileId, doc);
// 5. Ingest into Neo4j if available
if (neo4jService) {
await neo4jService.write(`
MERGE (f:File {id: $id})
SET f.name = $name,
f.path = $path,
f.extension = $ext,
f.size = $size,
f.lastModified = datetime(),
f.contentPreview = $preview
// Link to Directory
MERGE (d:Directory {path: $dirPath})
MERGE (f)-[:LOCATED_IN]->(d)
// Auto-Tagging based on extension
MERGE (t:Tag {name: $ext})
MERGE (f)-[:TAGGED]->(t)
`, {
id: fileId,
name: filename,
path: filePath,
dirPath: path.dirname(filePath),
ext: ext,
size: content.length,
preview: content.substring(0, 500)
});
}
console.log(`✨ [KNOWLEDGE] Assimilated: ${filename} (${eventType})`);
if (metricsService) {
metricsService.incrementCounter('knowledge_files_ingested');
}
} catch (error) {
console.error(`📚 [KNOWLEDGE] Error processing ${filePath}:`, error);
}
}
private extractTags(content: string, ext: string): string[] {
const tags: string[] = [ext];
// Extract hashtags
const hashtags = content.match(/#\w+/g) || [];
tags.push(...hashtags.map(t => t.toLowerCase()));
// Detect language/framework
if (content.includes('import React')) tags.push('react');
if (content.includes('from fastapi')) tags.push('fastapi');
if (content.includes('async function')) tags.push('async');
if (content.includes('class ')) tags.push('oop');
return [...new Set(tags)];
}
private generateFileId(filePath: string): string {
return crypto.createHash('md5').update(filePath).digest('hex');
}
// Public API for querying
public async searchSimilar(query: string, topK: number = 5): Promise<CompiledDocument[]> {
const items = Array.from(this.documentCache.values()).map(doc => ({
id: doc.id,
embedding: doc.embedding
}));
const results = await vectorService.findSimilar(query, items, topK);
return results
.map(r => this.documentCache.get(r.id))
.filter((d): d is CompiledDocument => d !== undefined);
}
public getDocument(id: string): CompiledDocument | undefined {
return this.documentCache.get(id);
}
public getAllDocuments(): CompiledDocument[] {
return Array.from(this.documentCache.values());
}
public getStats() {
return {
totalDocuments: this.documentCache.size,
queueLength: this.eventQueue.length,
isProcessing: this.isProcessing
};
}
public async stop(): Promise<void> {
if (this.watcher) {
await this.watcher.close();
this.watcher = null;
}
if (this.processingInterval) {
clearInterval(this.processingInterval);
this.processingInterval = null;
}
console.log('📚 [NEURAL] Compiler Stopped');
}
}
export const neuralCompiler = NeuralCompiler.getInstance();