guanghulab/knowledge-base/kb-manager.js

210 lines
5.0 KiB
JavaScript
Raw Normal View History

//知识库管理器·kb-manager.js
// HoloLake·M-DINGTALK Phase 3
// DEV-004 之之×秋秋
//
const fs = require('fs');
const path = require('path');
//知识库文档目录
const DOCS_DIR = path.join(__dirname, 'docs');
const INDEX_FILE = path.join(__dirname, 'kb-index.json');
// 知识库索引(内存缓存)
let kbIndex = [];
/**
* 扫描文档目录建立索引
* 索引结构[{ filename, title, sections: [{ heading, content, keywords }] }]
*/
function buildIndex() {
console.log('[KB] 开始建立知识库索引...');
kbIndex = [];
// 读取docs目录下所有.md文件
const docsDir = DOCS_DIR;
if (!fs.existsSync(docsDir)) {
console.log('[KB] 文档目录不存在,创建中...');
fs.mkdirSync(docsDir, { recursive: true });
return kbIndex;
}
const files = fs.readdirSync(docsDir).filter(f => f.endsWith('.md'));
console.log(`[KB] 发现 ${files.length} 个文档文件`);
for (const file of files) {
const filePath = path.join(docsDir, file);
const content = fs.readFileSync(filePath, 'utf-8');
// 解析文档结构
const doc = parseDocument(file, content);
kbIndex.push(doc);
}
// 保存索引到文件
fs.writeFileSync(INDEX_FILE, JSON.stringify(kbIndex, null, 2), 'utf-8');
console.log(`[KB] 索引建立完成,共 ${kbIndex.length} 个文档, ${kbIndex.reduce((sum, d) => sum + d.sections.length, 0)} 个章节`);
return kbIndex;
}
/**
* 解析单个Markdown文档
*/
function parseDocument(filename, content) {
const lines = content.split('\n');
const doc = {
filename,
title: '',
sections: []
};
let currentSection = null;
for (const line of lines) {
// 检测标题行
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
if (headingMatch) {
// 保存上一个section
if (currentSection) {
currentSection.keywords = extractKeywords(currentSection.content);
doc.sections.push(currentSection);
}
const level = headingMatch[1].length;
const heading = headingMatch[2].trim();
// 第一个h1作为文档标题
if (level === 1 && !doc.title) {
doc.title = heading;
}
currentSection = {
heading,
level,
content: ''
};
} else if (currentSection) {
currentSection.content += line + '\n';
}
}
// 保存最后一个section
if (currentSection) {
currentSection.keywords = extractKeywords(currentSection.content);
doc.sections.push(currentSection);
}
// 如果没有找到h1标题用文件名
if (!doc.title) {
doc.title = filename.replace('.md', '');
}
return doc;
}
/**
* 从文本中提取关键词简单版提取中英文词汇
*/
function extractKeywords(text) {
// 移除Markdown语法
const clean = text
.replace(/```[\s\S]*?```/g, '') // 移除代码块
.replace(/[`*_#\[\]()]/g, '') // 移除Markdown符号
.replace(/\s+/g, ' ');
// 提取关键词2个字以上的词
const words = clean.match(/[a-zA-Z]{3,}|[\u4e00-\u9fa5]{2,}/g) || [];
// 去重并返回
return [...new Set(words)].slice(0, 30);
}
/**
* 搜索知识库
* @param {string} query - 搜索关键词
* @returns {Array} - 匹配结果列表
*/
function search(query) {
if (kbIndex.length === 0) {
buildIndex();
}
const queryLower = query.toLowerCase();
const queryWords = queryLower.split(/\s+/).filter(w => w.length >= 1);
const results = [];
for (const doc of kbIndex) {
for (const section of doc.sections) {
// 计算匹配分数
let score = 0;
const sectionText = (section.heading + ' ' + section.content).toLowerCase();
for (const word of queryWords) {
// 标题匹配权重更高
if (section.heading.toLowerCase().includes(word)) {
score += 10;
}
// 内容匹配
if (sectionText.includes(word)) {
score += 5;
}
// 关键词匹配
if (section.keywords && section.keywords.some(k => k.toLowerCase().includes(word))) {
score += 3;
}
}
if (score > 0) {
results.push({
doc: doc.title,
filename: doc.filename,
heading: section.heading,
content: section.content.trim().substring(0, 500),
score
});
}
}
}
// 按分数排序返回前5个
results.sort((a, b) => b.score - a.score);
return results.slice(0, 5);
}
/**
* 获取知识库统计信息
*/
function getStats() {
if (kbIndex.length === 0) {
buildIndex();
}
return {
totalDocs: kbIndex.length,
totalSections: kbIndex.reduce((sum, d) => sum + d.sections.length, 0),
docs: kbIndex.map(d => ({
filename: d.filename,
title: d.title,
sections: d.sections.length
}))
};
}
/**
* 重新加载知识库用于热更新
*/
function reload() {
console.log('[KB] 热更新:重新加载知识库...');
return buildIndex();
}
module.exports = {
buildIndex,
search,
getStats,
reload
};