/** * 多源数据导入 MCP 工具集 — Phase 4 * * 工具: * import_linkedin_export — 解析 LinkedIn Connections.csv 批量导入人脉 * import_x_timeline — 解析 X (Twitter) tweets.js 导入动态 * import_meeting_notes — 将会议纪要导入图谱 */ const { getGraphStore } = require('../../../db/GraphStore'); const { LinkedInReader } = require('../../../services/linkedin/linkedin-reader'); const { XReader } = require('../../../services/x/x-reader'); const { MeetingImporter } = require('../../../services/meeting/meeting-importer'); // 解析 name_or_id(复用 index.js 逻辑的本地版本) function resolveNode(nameOrId, type) { const g = getGraphStore(); let node = g.getNode(nameOrId); if (!node) { const results = type ? g.findByName(nameOrId, type) : g.search(nameOrId); node = results[0] || null; } return node; } function createImportTools() { return [ // ── 4.1 LinkedIn 批量导入 ──────────────────────────────────── { name: 'import_linkedin_export', description: '解析 LinkedIn 导出的 Connections.csv 文件,将联系人批量导入知识图谱。filePath 为文件绝对路径。', parameters: { type: 'object', required: ['filePath'], properties: { filePath: { type: 'string', description: 'LinkedIn Connections.csv 文件的绝对路径', }, }, }, async execute(params) { try { const reader = new LinkedInReader(params.filePath); const contacts = reader.parse(); const g = getGraphStore(); let created = 0; let updated = 0; for (const c of contacts) { const fullName = [c.firstName, c.lastName].filter(Boolean).join(' '); if (!fullName) continue; const existing = g.findByName(fullName, 'Person'); const id = existing[0]?.id; g.createNode('Person', { ...(id ? { id } : {}), name: fullName, email: c.email || undefined, company: c.company || undefined, role: c.position || undefined, linkedin: c.connectedOn ? `connected:${c.connectedOn}` : undefined, source: 'linkedin', }); if (id) updated++; else created++; } return `✅ LinkedIn 导入完成:新建 ${created} 人,更新 ${updated} 人,共处理 ${contacts.length} 条记录。`; } catch (err) { return `❌ LinkedIn 导入失败:${err.message}`; } }, }, // ── 4.2 X (Twitter) 动态导入 ────────────────────────────────── { name: 'import_x_timeline', description: '解析 X (Twitter) 导出的 tweets.js 文件,将动态作为 Event:post 节点导入图谱,并关联到指定联系人。', parameters: { type: 'object', required: ['filePath', 'person_name_or_id'], properties: { filePath: { type: 'string', description: 'tweets.js 文件的绝对路径', }, person_name_or_id: { type: 'string', description: '关联到哪个联系人(姓名或节点 id)', }, }, }, async execute(params) { try { const person = resolveNode(params.person_name_or_id, 'Person'); if (!person) { return `❌ 未找到联系人:${params.person_name_or_id}`; } const reader = new XReader(params.filePath); const tweets = reader.parseTweets(); const g = getGraphStore(); let imported = 0; for (const tw of tweets) { const eventNode = g.createNode('Event', { type: 'post', summary: tw.text.slice(0, 140), rawText: tw.text, date: tw.createdAt ? new Date(tw.createdAt).toISOString().slice(0, 10) : undefined, tweetId: tw.id, hashtags: tw.hashtags, source: 'x_twitter', }); g.createEdge(person.id, eventNode.id, 'ASSOCIATED'); imported++; } return `✅ X 动态导入完成:共导入 ${imported} 条推文,已关联到「${person.name}」。`; } catch (err) { return `❌ X 动态导入失败:${err.message}`; } }, }, // ── 4.3 会议纪要导入 ────────────────────────────────────────── { name: 'import_meeting_notes', description: '将会议纪要文本导入图谱,创建 Event:meeting 节点并关联参与者。', parameters: { type: 'object', required: ['summary'], properties: { title: { type: 'string', description: '会议标题' }, date: { type: 'string', description: '会议日期(YYYY-MM-DD)' }, summary: { type: 'string', description: '会议摘要(必填)' }, rawText: { type: 'string', description: '完整会议纪要原文' }, participants: { type: 'array', items: { type: 'string' }, description: '参与者姓名或节点 id 列表', }, }, }, async execute(params) { try { const importer = new MeetingImporter(); const result = importer.importMeetingNotes(params); const lines = [ `✅ 会议纪要已导入,事件 id:${result.eventId}`, `关联参与者(${result.linkedParticipants.length}):${result.linkedParticipants.join('、') || '(无)'}`, ]; if (result.skippedParticipants.length > 0) { lines.push(`⚠️ 未找到以下参与者(已跳过):${result.skippedParticipants.join('、')}`); } return lines.join('\n'); } catch (err) { return `❌ 会议纪要导入失败:${err.message}`; } }, }, ]; } module.exports = { createImportTools };