import-tools.js
7.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/**
* 多源数据导入 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 };