226 lines
6.4 KiB
JavaScript
226 lines
6.4 KiB
JavaScript
/**
|
||
* persona-studio · 知识提取引擎
|
||
*
|
||
* 触发:代码生成完成时 / 用户确认方案时 / 解决技术问题时
|
||
* 功能:从对话中提取有价值的知识条目存入 knowledge-base.json
|
||
*/
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
|
||
const KB_PATH = path.join(BRAIN_DIR, 'knowledge-base.json');
|
||
|
||
/**
|
||
* 加载知识库
|
||
*/
|
||
function loadKnowledgeBase() {
|
||
try {
|
||
return JSON.parse(fs.readFileSync(KB_PATH, 'utf-8'));
|
||
} catch {
|
||
return {
|
||
schema_version: '1.0',
|
||
description: '系统知识库 · 自动积累',
|
||
last_updated: null,
|
||
total_entries: 0,
|
||
entries: []
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存知识库
|
||
*/
|
||
function saveKnowledgeBase(kb) {
|
||
kb.last_updated = new Date().toISOString();
|
||
kb.total_entries = kb.entries.length;
|
||
fs.writeFileSync(KB_PATH, JSON.stringify(kb, null, 2), 'utf-8');
|
||
}
|
||
|
||
/**
|
||
* 从对话中提取技术方案
|
||
*/
|
||
function extractSolutions(conversation, sourceExp, sourceProject) {
|
||
const entries = [];
|
||
const messages = conversation || [];
|
||
|
||
for (let i = 0; i < messages.length; i++) {
|
||
const msg = messages[i];
|
||
if (msg.role !== 'assistant') continue;
|
||
const content = msg.content || '';
|
||
|
||
// 检测方案类关键词
|
||
if (/技术方案|解决方案|实现方式|推荐.*方案|建议使用/i.test(content)) {
|
||
entries.push({
|
||
id: 'KB-' + Date.now() + '-' + i,
|
||
type: 'solution',
|
||
category: detectCategory(content),
|
||
tags: extractTags(content),
|
||
title: extractTitle(content),
|
||
content: content.substring(0, 1000),
|
||
source_exp: sourceExp,
|
||
source_project: sourceProject || null,
|
||
usage_count: 0,
|
||
created_at: new Date().toISOString()
|
||
});
|
||
}
|
||
|
||
// 检测最佳实践
|
||
if (/最佳实践|best practice|推荐做法|正确.*方式/i.test(content)) {
|
||
entries.push({
|
||
id: 'KB-' + Date.now() + '-bp-' + i,
|
||
type: 'best_practice',
|
||
category: detectCategory(content),
|
||
tags: extractTags(content),
|
||
title: extractTitle(content),
|
||
content: content.substring(0, 1000),
|
||
source_exp: sourceExp,
|
||
source_project: sourceProject || null,
|
||
usage_count: 0,
|
||
created_at: new Date().toISOString()
|
||
});
|
||
}
|
||
|
||
// 检测常见陷阱
|
||
if (/注意|陷阱|坑|避免|不要.*这样|常见错误|pitfall/i.test(content)) {
|
||
entries.push({
|
||
id: 'KB-' + Date.now() + '-pit-' + i,
|
||
type: 'pitfall',
|
||
category: detectCategory(content),
|
||
tags: extractTags(content),
|
||
title: extractTitle(content),
|
||
content: content.substring(0, 1000),
|
||
source_exp: sourceExp,
|
||
source_project: sourceProject || null,
|
||
usage_count: 0,
|
||
created_at: new Date().toISOString()
|
||
});
|
||
}
|
||
}
|
||
|
||
return entries;
|
||
}
|
||
|
||
/**
|
||
* 检测技术类别
|
||
*/
|
||
function detectCategory(content) {
|
||
const lower = content.toLowerCase();
|
||
if (/react|vue|angular|前端|frontend|html|css|ui/i.test(lower)) return 'frontend';
|
||
if (/node|express|api|后端|backend|server|数据库|sql/i.test(lower)) return 'backend';
|
||
if (/docker|部署|deploy|ci|cd|运维|devops|nginx/i.test(lower)) return 'devops';
|
||
if (/设计|design|ux|ui|交互|布局|layout/i.test(lower)) return 'design';
|
||
if (/安全|security|auth|鉴权|加密|token/i.test(lower)) return 'security';
|
||
if (/性能|performance|优化|缓存|cache/i.test(lower)) return 'performance';
|
||
return 'general';
|
||
}
|
||
|
||
/**
|
||
* 提取标签
|
||
*/
|
||
function extractTags(content) {
|
||
const tags = [];
|
||
const techKeywords = [
|
||
'javascript', 'typescript', 'python', 'react', 'vue', 'node',
|
||
'express', 'html', 'css', 'sql', 'mongodb', 'redis', 'docker',
|
||
'nginx', 'git', 'api', 'rest', 'graphql', 'websocket'
|
||
];
|
||
const lower = content.toLowerCase();
|
||
techKeywords.forEach(function (kw) {
|
||
if (lower.includes(kw)) tags.push(kw);
|
||
});
|
||
return tags.slice(0, 10);
|
||
}
|
||
|
||
/**
|
||
* 提取标题(取第一行非空文本的前50字符)
|
||
*/
|
||
function extractTitle(content) {
|
||
const lines = content.split('\n').filter(function (l) { return l.trim().length > 0; });
|
||
if (lines.length === 0) return '未命名知识条目';
|
||
const first = lines[0].replace(/^[#*\-\s]+/, '').trim();
|
||
return first.length > 50 ? first.substring(0, 50) + '…' : first;
|
||
}
|
||
|
||
/**
|
||
* 将提取的知识添加到知识库(自动去重)
|
||
*/
|
||
function addToKnowledgeBase(entries) {
|
||
if (!entries || entries.length === 0) return;
|
||
|
||
const kb = loadKnowledgeBase();
|
||
|
||
entries.forEach(function (entry) {
|
||
// 简单去重:同标题+同类型 = 已存在
|
||
const exists = kb.entries.some(function (existing) {
|
||
return existing.title === entry.title && existing.type === entry.type;
|
||
});
|
||
if (!exists) {
|
||
kb.entries.push(entry);
|
||
}
|
||
});
|
||
|
||
// 保留最近 500 条
|
||
if (kb.entries.length > 500) {
|
||
kb.entries = kb.entries.slice(-500);
|
||
}
|
||
|
||
saveKnowledgeBase(kb);
|
||
}
|
||
|
||
/**
|
||
* 查询知识库(供 persona-engine 调用)
|
||
* @param {string} query - 搜索关键词
|
||
* @param {number} limit - 最大返回数量
|
||
* @returns {Array} 匹配的知识条目
|
||
*/
|
||
function queryKnowledge(query, limit) {
|
||
const kb = loadKnowledgeBase();
|
||
if (!query || kb.entries.length === 0) return [];
|
||
|
||
const lowerQuery = query.toLowerCase();
|
||
const keywords = lowerQuery.split(/[\s,,。.!??!]+/).filter(Boolean);
|
||
|
||
const scored = kb.entries.map(function (entry) {
|
||
let score = 0;
|
||
const entryText = ((entry.title || '') + ' ' + (entry.content || '') + ' ' + (entry.tags || []).join(' ')).toLowerCase();
|
||
|
||
keywords.forEach(function (kw) {
|
||
if (entryText.includes(kw)) score += 1;
|
||
});
|
||
|
||
// 使用次数加权
|
||
score += (entry.usage_count || 0) * 0.1;
|
||
|
||
return { entry: entry, score: score };
|
||
});
|
||
|
||
return scored
|
||
.filter(function (s) { return s.score > 0; })
|
||
.sort(function (a, b) { return b.score - a.score; })
|
||
.slice(0, limit || 5)
|
||
.map(function (s) {
|
||
// 增加使用计数
|
||
s.entry.usage_count = (s.entry.usage_count || 0) + 1;
|
||
return s.entry;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 核心方法:自动提取知识
|
||
*/
|
||
function autoExtractKnowledge(devId, conversation, projectName) {
|
||
const entries = extractSolutions(conversation, devId, projectName);
|
||
if (entries.length > 0) {
|
||
addToKnowledgeBase(entries);
|
||
}
|
||
return entries.length;
|
||
}
|
||
|
||
module.exports = {
|
||
autoExtractKnowledge,
|
||
queryKnowledge,
|
||
loadKnowledgeBase,
|
||
addToKnowledgeBase,
|
||
extractSolutions
|
||
};
|