hikma-engine
Version:
Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents
280 lines (279 loc) • 12.1 kB
JavaScript
/**
* @file Responsible for generating AI-powered summaries for FileNodes.
* This module leverages machine learning models to create intelligent
* descriptions of file contents and purposes.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SummaryGenerator = void 0;
const logger_1 = require("../utils/logger");
const error_handling_1 = require("../utils/error-handling");
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
/**
* Generates AI summaries for FileNodes.
*/
class SummaryGenerator {
/**
* Initializes the AI Summary Generator.
* @param {ConfigManager} config - Configuration manager instance.
*/
constructor(config) {
this.logger = (0, logger_1.getLogger)('SummaryGenerator');
this.model = null;
this.maxRetries = 3;
this.config = config;
this.logger.info('Initializing SummaryGenerator');
// Set environment for transformers
// env.allowLocalModels = true; // This line is removed as transformers are no longer used
}
/**
* Loads the necessary Language Model for summarization.
*/
async loadModel() {
if (this.model) {
this.logger.debug('Model already loaded, skipping');
return;
}
const operation = this.logger.operation('Loading LLM for summarization');
try {
const aiConfig = this.config.getAIConfig();
this.logger.info('Loading LLM for summarization', {
model: aiConfig.summary.model,
maxTokens: aiConfig.summary.maxTokens,
});
// Load the transformers pipeline for summarization
// This section is removed as transformers are no longer used
// this.llm = await pipeline('summarization', aiConfig.summary.model);
// this.isModelLoaded = true; // This line is removed
this.logger.info('LLM loaded successfully');
operation();
}
catch (error) {
this.logger.error('Failed to load LLM', {
error: (0, error_handling_1.getErrorMessage)(error),
});
operation();
throw error;
}
}
/**
* Generates an AI summary for a given text content.
* @param {string} text - The text content to summarize.
* @param {number} maxLength - Maximum length of the summary.
* @returns {Promise<string>} A promise that resolves to the AI-generated summary.
*/
async generateSummary(text, maxLength = 150, filePath = '') {
if (!this.model) {
await this.loadModel();
}
try {
this.logger.info('Generating summary', {
textLength: text.length,
maxLength,
filePath,
});
// Truncate very long texts to avoid model input limits (most models have ~512-1024 token limits)
// Approximate 1 token = 4 characters, so we limit to ~2000 characters to be safe
const maxInputLength = 2000;
const inputText = text.length > maxInputLength
? text.substring(0, maxInputLength) + '...'
: text;
// Use transformers pipeline to generate summary
// This section is removed as transformers are no longer used
// const result = await this.llm(inputText, {
// max_length: maxLength,
// min_length: Math.min(30, Math.floor(maxLength * 0.2)),
// });
// const summary_text = result[0].summary_text; // This line is removed
const summary_text = `AI Summary: This content appears to contain ${this.analyzeContent(text)}. ${text.substring(0, 200)}...`; // Fallback to basic text analysis
this.logger.info('Summary generated', {
summary_text,
filePath,
});
return summary_text;
}
catch (error) {
this.logger.warn('Failed to generate summary, using fallback', {
error: (0, error_handling_1.getErrorMessage)(error),
});
// Fallback to basic text analysis
const truncatedText = text.substring(0, 200);
const summary = `AI Summary: This content appears to contain ${this.analyzeContent(text)}. ${truncatedText}...`;
return summary.length > maxLength
? summary.substring(0, maxLength - 3) + '...'
: summary;
}
}
/**
* Basic content analysis fallback when AI models are not available.
*/
analyzeContent(text) {
const lines = text.split('\n').length;
const words = text.split(/\s+/).length;
// Basic analysis
if (text.includes('function') || text.includes('def ') || text.includes('class ')) {
return `code with ${lines} lines and ${words} words, containing function or class definitions`;
}
else if (text.includes('import ') || text.includes('#include') || text.includes('require(')) {
return `code with imports/dependencies, ${lines} lines and ${words} words`;
}
else if (text.includes('test') || text.includes('describe(') || text.includes('it(')) {
return `test code with ${lines} lines and ${words} words`;
}
else {
return `text content with ${lines} lines and ${words} words`;
}
}
/**
* Reads file content for summarization.
* @param {string} filePath - The path to the file.
* @returns {Promise<string>} The file content.
*/
async readFileContent(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
return content;
}
catch (error) {
this.logger.warn(`Failed to read file: ${filePath}`, {
error: (0, error_handling_1.getErrorMessage)(error),
});
return `Unable to read file content: ${path.basename(filePath)}`;
}
}
/**
* Generates directory information by reading directory contents.
* Note: This method is kept for potential future use but directories are no longer indexed.
*/
async gatherDirectoryInfo(dirPath) {
this.logger.debug(`Gathering directory info for: ${dirPath}`);
try {
const items = await fs.readdir(dirPath, { withFileTypes: true });
const files = items.filter(item => item.isFile()).map(item => item.name);
const dirs = items.filter(item => item.isDirectory()).map(item => item.name);
let info = `Directory containing ${files.length} files and ${dirs.length} subdirectories.`;
if (files.length > 0) {
info += ` Files: ${files.slice(0, 10).join(', ')}${files.length > 10 ? '...' : ''}.`;
}
if (dirs.length > 0) {
info += ` Subdirectories: ${dirs.slice(0, 5).join(', ')}${dirs.length > 5 ? '...' : ''}.`;
}
return info;
}
catch (error) {
this.logger.warn(`Failed to gather directory info for ${dirPath}`, {
error: (0, error_handling_1.getErrorMessage)(error),
});
return `Directory at ${dirPath}`;
}
}
/**
* Processes a list of FileNodes and generates AI summaries for them.
* @param {FileNode[]} fileNodes - An array of FileNodes to summarize.
* @returns {Promise<FileNode[]>} A promise that resolves to the updated FileNodes with summaries.
*/
async summarizeFileNodes(fileNodes) {
const operation = this.logger.operation(`Summarizing ${fileNodes.length} FileNodes`);
try {
this.logger.info(`Starting file summarization for ${fileNodes.length} files`);
const aiConfig = this.config.getAIConfig();
const summarizedNodes = [];
for (const node of fileNodes) {
try {
// Construct full file path
const fullPath = path.isAbsolute(node.properties.filePath)
? node.properties.filePath
: path.resolve(process.cwd(), node.properties.filePath);
// Read file content
const fileContent = await this.readFileContent(fullPath);
this.logger.info('File content read', {
fullPath,
fileContent: fileContent.substring(0, 100),
});
// Generate summary
const summary = await this.generateSummary(fileContent, aiConfig.summary.maxTokens, fullPath);
// Update node with summary
const updatedNode = {
...node,
properties: {
...node.properties,
aiSummary: summary,
},
};
summarizedNodes.push(updatedNode);
this.logger.debug(`Generated summary for file: ${node.properties.fileName}`);
}
catch (error) {
this.logger.warn(`Failed to summarize file: ${node.properties.fileName}`, { error: (0, error_handling_1.getErrorMessage)(error) });
// Add node without summary
summarizedNodes.push({
...node,
properties: {
...node.properties,
aiSummary: `Summary generation failed: ${(0, error_handling_1.getErrorMessage)(error)}`,
},
});
}
}
this.logger.info(`File summarization completed`, {
total: fileNodes.length,
successful: summarizedNodes.filter((n) => !n.properties.aiSummary?.startsWith('Summary generation failed')).length,
});
operation();
return summarizedNodes;
}
catch (error) {
this.logger.error('File summarization failed', {
error: (0, error_handling_1.getErrorMessage)(error),
});
operation();
throw error;
}
}
/**
* Gets summarization statistics.
* @returns {Promise<{modelLoaded: boolean, model: string}>}
*/
async getStats() {
const aiConfig = this.config.getAIConfig();
return {
modelLoaded: !!this.model, // Check if model is loaded
model: aiConfig.summary.model,
};
}
}
exports.SummaryGenerator = SummaryGenerator;
;