288 lines
8.1 KiB
JavaScript
288 lines
8.1 KiB
JavaScript
/**
|
||
* 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
|
||
};
|