guanghulab/persona-studio/backend/brain/profile-learner.js

288 lines
8.1 KiB
JavaScript
Raw Normal View History

/**
* persona-studio · 用户画像自动学习引擎
*
* 触发每轮对话结束后自动调用 updateProfile()
* 功能从对话内容中推断用户技术水平沟通风格设计偏好等
*/
const fs = require('fs');
const path = require('path');
const BRAIN_DIR = path.join(__dirname, '..', '..', 'brain');
const MEMORY_DIR = path.join(BRAIN_DIR, 'memory');
/**
* 确保用户目录存在
*/
function ensureDevDir(devId) {
const dir = path.join(MEMORY_DIR, devId);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
}
/**
* 加载用户画像
*/
function loadProfile(devId) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'profile.json');
try {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch {
return createInitialProfile(devId);
}
}
/**
* 保存用户画像
*/
function saveProfile(devId, profile) {
const dir = ensureDevDir(devId);
const file = path.join(dir, 'profile.json');
profile.updated_at = new Date().toISOString();
fs.writeFileSync(file, JSON.stringify(profile, null, 2), 'utf-8');
}
/**
* 创建初始画像
*/
function createInitialProfile(devId) {
return {
dev_id: devId,
version: 1,
created_at: new Date().toISOString(),
updated_at: null,
tech_assessment: {
level: null,
known_skills: [],
learning_skills: [],
weakness: [],
growth_velocity: null,
assessment_confidence: 0
},
communication_style: {
verbosity: null,
prefers_examples: null,
question_frequency: null,
patience_level: null
},
design_preferences: {
color_scheme: null,
style_keywords: [],
layout_preference: null
},
project_patterns: {
avg_requirements_rounds: 0,
decision_speed: null,
change_frequency: 0,
total_projects: 0
},
emotional_profile: {
current_mood: 'neutral',
satisfaction_trend: [],
frustration_triggers: [],
delight_triggers: []
}
};
}
/**
* 从用户消息中推断技术技能
*/
function inferSkills(message) {
const skills = [];
const skillMap = {
'javascript': ['javascript', 'js', 'node', 'nodejs', 'npm'],
'typescript': ['typescript', 'ts'],
'python': ['python', 'pip', 'django', 'flask', 'fastapi'],
'react': ['react', 'jsx', 'tsx', 'hooks', 'useState', 'useEffect'],
'vue': ['vue', 'vuex', 'pinia', 'nuxt'],
'html': ['html', 'dom', 'div', 'css'],
'css': ['css', 'scss', 'sass', 'tailwind', 'styled'],
'sql': ['sql', 'mysql', 'postgres', 'sqlite', '数据库', 'database'],
'git': ['git', 'github', 'gitlab', 'commit', 'branch'],
'docker': ['docker', 'container', '容器', 'k8s', 'kubernetes'],
'api': ['api', 'rest', 'graphql', 'grpc', '接口'],
'mobile': ['android', 'ios', 'flutter', 'react native', '移动端', '小程序']
};
const lowerMsg = message.toLowerCase();
for (const [skill, keywords] of Object.entries(skillMap)) {
if (keywords.some(function (kw) { return lowerMsg.includes(kw); })) {
skills.push(skill);
}
}
return skills;
}
/**
* 推断沟通风格
*/
function inferCommunicationStyle(message) {
const style = {};
const msgLen = message.length;
// 简洁度
if (msgLen < 20) {
style.verbosity = 'concise';
} else if (msgLen > 200) {
style.verbosity = 'detailed';
}
// 偏好示例
if (/给.*例子|示例|比如|example|举个/i.test(message)) {
style.prefers_examples = true;
}
// 提问频率
if ((message.match(/[?]/g) || []).length >= 2) {
style.question_frequency = 'high';
}
// 直接命令式 = patience_level low
if (/直接告诉|直接给|快速|尽快|赶紧/i.test(message)) {
style.patience_level = 'low';
style.verbosity = 'concise';
}
return style;
}
/**
* 推断技术水平
*/
function inferTechLevel(message) {
const advancedKeywords = ['架构', '微服务', '分布式', '高并发', 'CI/CD', 'kubernetes', '设计模式', 'design pattern', '重构', '性能优化', 'SSR', 'ISR', 'WebSocket', 'gRPC'];
const intermediateKeywords = ['组件', '路由', '中间件', 'middleware', '接口', 'API', '数据库', '前后端', '部署', '框架'];
const beginnerKeywords = ['怎么开始', '入门', '新手', '不太懂', '什么是', '帮我做', '教我'];
const lower = message.toLowerCase();
if (advancedKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
return 'advanced';
}
if (intermediateKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
return 'intermediate';
}
if (beginnerKeywords.some(function (kw) { return lower.includes(kw.toLowerCase()); })) {
return 'beginner';
}
return null;
}
/**
* 推断设计偏好
*/
function inferDesignPreferences(message) {
const prefs = { style_keywords: [] };
if (/暗色|深色|dark|黑色/i.test(message)) {
prefs.color_scheme = 'dark';
} else if (/亮色|浅色|light|白色/i.test(message)) {
prefs.color_scheme = 'light';
}
const styleKeywords = ['简约', '科技感', '可爱', '商务', '极简', 'minimal', '现代', 'modern', '复古', '扁平', 'flat', '渐变', '毛玻璃', '赛博朋克'];
styleKeywords.forEach(function (kw) {
if (message.toLowerCase().includes(kw.toLowerCase())) {
prefs.style_keywords.push(kw);
}
});
return prefs;
}
/**
* 推断情感信号
*/
function inferEmotionalSignals(message) {
const signals = {};
if (/太好了|完美|厉害|赞|棒|不错|满意|感谢|谢谢|开心|👍|🎉|✅/i.test(message)) {
signals.current_mood = 'positive';
} else if (/不行|不对|错了|差|不满|失望|难用|麻烦|头疼|崩溃|😤|😡|❌/i.test(message)) {
signals.current_mood = 'negative';
} else {
signals.current_mood = 'neutral';
}
return signals;
}
/**
* 核心方法更新用户画像
* @param {string} devId - 开发编号
* @param {string} userMessage - 用户消息
* @param {string} assistantReply - 助手回复
*/
function updateProfile(devId, userMessage, assistantReply) {
if (!devId || devId === 'GUEST') return null;
const profile = loadProfile(devId);
// 1. 推断技术技能
const skills = inferSkills(userMessage);
if (skills.length > 0) {
const known = new Set(profile.tech_assessment.known_skills || []);
skills.forEach(function (s) { known.add(s); });
profile.tech_assessment.known_skills = Array.from(known);
profile.tech_assessment.assessment_confidence = Math.min(
(profile.tech_assessment.assessment_confidence || 0) + 5, 100
);
}
// 2. 推断技术水平
const techLevel = inferTechLevel(userMessage);
if (techLevel) {
profile.tech_assessment.level = techLevel;
}
// 3. 推断沟通风格
const commStyle = inferCommunicationStyle(userMessage);
Object.keys(commStyle).forEach(function (key) {
if (commStyle[key] != null) {
profile.communication_style[key] = commStyle[key];
}
});
// 4. 推断设计偏好
const designPrefs = inferDesignPreferences(userMessage);
if (designPrefs.color_scheme) {
profile.design_preferences.color_scheme = designPrefs.color_scheme;
}
if (designPrefs.style_keywords.length > 0) {
const existing = new Set(profile.design_preferences.style_keywords || []);
designPrefs.style_keywords.forEach(function (kw) { existing.add(kw); });
profile.design_preferences.style_keywords = Array.from(existing);
}
// 5. 推断情感信号
const emotions = inferEmotionalSignals(userMessage);
profile.emotional_profile.current_mood = emotions.current_mood;
const trend = profile.emotional_profile.satisfaction_trend || [];
trend.push({ mood: emotions.current_mood, at: new Date().toISOString() });
if (trend.length > 50) trend.splice(0, trend.length - 50);
profile.emotional_profile.satisfaction_trend = trend;
// 6. 更新版本
profile.version = (profile.version || 0) + 1;
saveProfile(devId, profile);
return profile;
}
module.exports = {
updateProfile,
loadProfile,
saveProfile,
createInitialProfile,
inferSkills,
inferTechLevel,
inferCommunicationStyle,
inferDesignPreferences,
inferEmotionalSignals
};